github.com/aavshr/aws-sdk-go@v1.41.3/awstesting/integration/performance/s3DownloadManager/main.go (about)

     1  //go:build go1.13 && integration && perftest
     2  // +build go1.13,integration,perftest
     3  
     4  package main
     5  
     6  import (
     7  	"flag"
     8  	"fmt"
     9  	"io"
    10  	"log"
    11  	"os"
    12  	"path/filepath"
    13  	"runtime"
    14  	"runtime/pprof"
    15  	"runtime/trace"
    16  	"time"
    17  
    18  	"github.com/aavshr/aws-sdk-go/aws"
    19  	"github.com/aavshr/aws-sdk-go/aws/request"
    20  	"github.com/aavshr/aws-sdk-go/aws/session"
    21  	"github.com/aavshr/aws-sdk-go/awstesting"
    22  	"github.com/aavshr/aws-sdk-go/awstesting/integration"
    23  	"github.com/aavshr/aws-sdk-go/service/s3"
    24  	"github.com/aavshr/aws-sdk-go/service/s3/s3manager"
    25  )
    26  
    27  var config Config
    28  
    29  func main() {
    30  	parseCommandLine()
    31  
    32  	log.SetOutput(os.Stderr)
    33  
    34  	config.Profiler.Start()
    35  	defer config.Profiler.Stop()
    36  
    37  	var err error
    38  	key := config.Key
    39  	size := config.Size
    40  	if len(key) == 0 {
    41  		uploadPartSize := getUploadPartSize(size, config.UploadPartSize, config.SDK.PartSize)
    42  		log.Printf("uploading %s file to s3://%s\n", integration.SizeToName(int(config.Size)), config.Bucket)
    43  		key, err = setupDownloadTest(config.Bucket, config.Size, uploadPartSize)
    44  		if err != nil {
    45  			log.Fatalf("failed to setup download testing: %v", err)
    46  		}
    47  
    48  		defer func() {
    49  			log.Printf("cleaning up s3://%s/%s\n", config.Bucket, key)
    50  			if err = teardownDownloadTest(config.Bucket, key); err != nil {
    51  				log.Fatalf("failed to teardwn test artifacts: %v", err)
    52  			}
    53  		}()
    54  	} else {
    55  		size, err = getObjectSize(config.Bucket, key)
    56  		if err != nil {
    57  			log.Fatalf("failed to get object size: %v", err)
    58  		}
    59  	}
    60  
    61  	traces := make(chan *RequestTrace, config.SDK.Concurrency)
    62  	requestTracer := downloadRequestTracer(traces)
    63  	downloader := newDownloader(config.Client, config.SDK, requestTracer)
    64  
    65  	metricReportDone := startTraceReceiver(traces)
    66  
    67  	log.Println("starting download...")
    68  	start := time.Now()
    69  	_, err = downloader.Download(&awstesting.DiscardAt{}, &s3.GetObjectInput{
    70  		Bucket: &config.Bucket,
    71  		Key:    &key,
    72  	})
    73  	if err != nil {
    74  		log.Fatalf("failed to download object, %v", err)
    75  	}
    76  	close(traces)
    77  
    78  	dur := time.Since(start)
    79  	log.Printf("Download finished, Size: %d, Dur: %s, Throughput: %.5f GB/s",
    80  		size, dur, (float64(size)/(float64(dur)/float64(time.Second)))/float64(1e9),
    81  	)
    82  
    83  	<-metricReportDone
    84  }
    85  
    86  func parseCommandLine() {
    87  	config.SetupFlags("", flag.CommandLine)
    88  
    89  	if err := flag.CommandLine.Parse(os.Args[1:]); err != nil {
    90  		flag.CommandLine.PrintDefaults()
    91  		log.Fatalf("failed to parse CLI commands")
    92  	}
    93  	if err := config.Validate(); err != nil {
    94  		flag.CommandLine.PrintDefaults()
    95  		log.Fatalf("invalid arguments: %v", err)
    96  	}
    97  }
    98  
    99  func setupDownloadTest(bucket string, fileSize, partSize int64) (key string, err error) {
   100  	er := &awstesting.EndlessReader{}
   101  	lr := io.LimitReader(er, fileSize)
   102  
   103  	key = integration.UniqueID()
   104  
   105  	sess := session.Must(session.NewSession(&aws.Config{
   106  		S3DisableContentMD5Validation: aws.Bool(true),
   107  		S3Disable100Continue:          aws.Bool(true),
   108  	}))
   109  
   110  	uploader := s3manager.NewUploader(sess, func(u *s3manager.Uploader) {
   111  		u.PartSize = partSize
   112  		u.Concurrency = runtime.NumCPU() * 2
   113  		u.RequestOptions = append(u.RequestOptions, func(r *request.Request) {
   114  			if r.Operation.Name != "UploadPart" && r.Operation.Name != "PutObject" {
   115  				return
   116  			}
   117  
   118  			r.HTTPRequest.Header.Set("X-Amz-Content-Sha256", "UNSIGNED-PAYLOAD")
   119  		})
   120  	})
   121  
   122  	_, err = uploader.Upload(&s3manager.UploadInput{
   123  		Bucket: &bucket,
   124  		Body:   lr,
   125  		Key:    &key,
   126  	})
   127  	if err != nil {
   128  		err = fmt.Errorf("failed to upload test object to s3: %v", err)
   129  	}
   130  
   131  	return
   132  }
   133  
   134  func teardownDownloadTest(bucket, key string) error {
   135  	sess := session.Must(session.NewSession())
   136  
   137  	svc := s3.New(sess)
   138  
   139  	_, err := svc.DeleteObject(&s3.DeleteObjectInput{Bucket: &bucket, Key: &key})
   140  	return err
   141  }
   142  
   143  func startTraceReceiver(traces <-chan *RequestTrace) <-chan struct{} {
   144  	metricReportDone := make(chan struct{})
   145  
   146  	go func() {
   147  		defer close(metricReportDone)
   148  		metrics := map[string]*RequestTrace{}
   149  		for trace := range traces {
   150  			curTrace, ok := metrics[trace.Operation]
   151  			if !ok {
   152  				curTrace = trace
   153  			} else {
   154  				curTrace.attempts = append(curTrace.attempts, trace.attempts...)
   155  				if len(trace.errs) != 0 {
   156  					curTrace.errs = append(curTrace.errs, trace.errs...)
   157  				}
   158  				curTrace.finish = trace.finish
   159  			}
   160  
   161  			metrics[trace.Operation] = curTrace
   162  		}
   163  
   164  		for _, name := range []string{
   165  			"GetObject",
   166  		} {
   167  			if trace, ok := metrics[name]; ok {
   168  				printAttempts(name, trace, config.LogVerbose)
   169  			}
   170  		}
   171  	}()
   172  
   173  	return metricReportDone
   174  }
   175  
   176  func printAttempts(op string, trace *RequestTrace, verbose bool) {
   177  	if !verbose {
   178  		return
   179  	}
   180  
   181  	log.Printf("%s: latency:%s requests:%d errors:%d",
   182  		op,
   183  		trace.finish.Sub(trace.start),
   184  		len(trace.attempts),
   185  		len(trace.errs),
   186  	)
   187  
   188  	for _, a := range trace.attempts {
   189  		log.Printf("  * %s", a)
   190  	}
   191  	if err := trace.Err(); err != nil {
   192  		log.Printf("Operation Errors: %v", err)
   193  	}
   194  	log.Println()
   195  }
   196  
   197  func downloadRequestTracer(traces chan<- *RequestTrace) request.Option {
   198  	tracerOption := func(r *request.Request) {
   199  		id := "op"
   200  		if v, ok := r.Params.(*s3.GetObjectInput); ok {
   201  			if v.Range != nil {
   202  				id = *v.Range
   203  			}
   204  		}
   205  		tracer := NewRequestTrace(r.Context(), r.Operation.Name, id)
   206  		r.SetContext(tracer)
   207  
   208  		r.Handlers.Send.PushFront(tracer.OnSendAttempt)
   209  		r.Handlers.CompleteAttempt.PushBack(tracer.OnCompleteAttempt)
   210  		r.Handlers.Complete.PushBack(tracer.OnComplete)
   211  		r.Handlers.Complete.PushBack(func(rr *request.Request) {
   212  			traces <- tracer
   213  		})
   214  	}
   215  
   216  	return tracerOption
   217  }
   218  
   219  func newDownloader(clientConfig ClientConfig, sdkConfig SDKConfig, options ...request.Option) *s3manager.Downloader {
   220  	client := NewClient(clientConfig)
   221  
   222  	sess, err := session.NewSessionWithOptions(session.Options{
   223  		Config:            aws.Config{HTTPClient: client},
   224  		SharedConfigState: session.SharedConfigEnable,
   225  	})
   226  	if err != nil {
   227  		log.Fatalf("failed to load session, %v", err)
   228  	}
   229  
   230  	downloader := s3manager.NewDownloader(sess, func(d *s3manager.Downloader) {
   231  		d.PartSize = sdkConfig.PartSize
   232  		d.Concurrency = sdkConfig.Concurrency
   233  		d.BufferProvider = sdkConfig.BufferProvider
   234  
   235  		d.RequestOptions = append(d.RequestOptions, options...)
   236  	})
   237  
   238  	return downloader
   239  }
   240  
   241  func getObjectSize(bucket, key string) (int64, error) {
   242  	sess := session.Must(session.NewSession())
   243  	svc := s3.New(sess)
   244  	resp, err := svc.HeadObject(&s3.HeadObjectInput{
   245  		Bucket: &bucket,
   246  		Key:    &key,
   247  	})
   248  	if err != nil {
   249  		return 0, err
   250  	}
   251  
   252  	return *resp.ContentLength, nil
   253  }
   254  
   255  type Profiler struct {
   256  	outputDir string
   257  
   258  	enableCPU   bool
   259  	enableTrace bool
   260  
   261  	cpuFile   *os.File
   262  	traceFile *os.File
   263  }
   264  
   265  func (p *Profiler) SetupFlags(prefix string, flagSet *flag.FlagSet) {
   266  	prefix += "profiler."
   267  
   268  	flagSet.StringVar(&p.outputDir, prefix+"output-dir", os.TempDir(), "output directory to write profiling data")
   269  	flagSet.BoolVar(&p.enableCPU, prefix+"cpu", false, "enable CPU profiling")
   270  	flagSet.BoolVar(&p.enableTrace, prefix+"trace", false, "enable tracing")
   271  }
   272  
   273  func (p *Profiler) Start() {
   274  	var err error
   275  
   276  	uuid := integration.UniqueID()
   277  	if p.enableCPU {
   278  		p.cpuFile, err = p.createFile(uuid, "cpu")
   279  		if err != nil {
   280  			panic(fmt.Sprintf("failed to create cpu profile file: %v", err))
   281  		}
   282  		err = pprof.StartCPUProfile(p.cpuFile)
   283  		if err != nil {
   284  			panic(fmt.Sprintf("failed to start cpu profile: %v", err))
   285  		}
   286  	}
   287  	if p.enableTrace {
   288  		p.traceFile, err = p.createFile(uuid, "trace")
   289  		if err != nil {
   290  			panic(fmt.Sprintf("failed to create trace file: %v", err))
   291  		}
   292  		err = trace.Start(p.traceFile)
   293  		if err != nil {
   294  			panic(fmt.Sprintf("failed to tracing: %v", err))
   295  		}
   296  	}
   297  }
   298  
   299  func (p *Profiler) logAndCloseFile(profile string, file *os.File) {
   300  	info, err := file.Stat()
   301  	if err != nil {
   302  		log.Printf("failed to stat %s profile: %v", profile, err)
   303  	} else {
   304  		log.Printf("writing %s profile to: %v", profile, filepath.Join(p.outputDir, info.Name()))
   305  	}
   306  	file.Close()
   307  }
   308  
   309  func (p *Profiler) Stop() {
   310  	if p.enableCPU {
   311  		pprof.StopCPUProfile()
   312  		p.logAndCloseFile("cpu", p.cpuFile)
   313  	}
   314  	if p.enableTrace {
   315  		trace.Stop()
   316  		p.logAndCloseFile("trace", p.traceFile)
   317  	}
   318  }
   319  
   320  func (p *Profiler) createFile(prefix, name string) (*os.File, error) {
   321  	return os.OpenFile(filepath.Join(p.outputDir, prefix+"."+name+".profile"), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
   322  }
   323  
   324  func getUploadPartSize(fileSize, uploadPartSize, downloadPartSize int64) int64 {
   325  	partSize := uploadPartSize
   326  
   327  	if partSize == 0 {
   328  		partSize = downloadPartSize
   329  	}
   330  	if fileSize/partSize > s3manager.MaxUploadParts {
   331  		partSize = (fileSize / s3manager.MaxUploadParts) + 1
   332  	}
   333  
   334  	return partSize
   335  }