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 }