github.com/mweagle/Sparta@v1.15.0/profile_loop_awsbinary.go (about) 1 // +build lambdabinary 2 3 package sparta 4 5 import ( 6 "fmt" 7 "os" 8 "path" 9 "path/filepath" 10 "runtime/pprof" 11 "time" 12 13 "github.com/aws/aws-sdk-go/aws" 14 "github.com/aws/aws-sdk-go/service/s3/s3manager" 15 spartaAWS "github.com/mweagle/Sparta/aws" 16 "github.com/sirupsen/logrus" 17 ) 18 19 var currentSlot int 20 var stackName string 21 var profileBucket string 22 23 const snapshotCount = 3 24 25 func nextUploadSlot() int { 26 uploadSlot := currentSlot 27 currentSlot = (currentSlot + 1) % snapshotCount 28 return uploadSlot 29 } 30 31 func init() { 32 currentSlot = 0 33 // These correspond to the environment variables that were published 34 // into the Lambda environment by the profile decorator 35 stackName = os.Getenv(envVarStackName) 36 profileBucket = os.Getenv(envVarProfileBucketName) 37 } 38 39 func profileOutputFile(basename string) (*os.File, error) { 40 fileName := fmt.Sprintf("%s.%s.profile", basename, InstanceID()) 41 // http://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html 42 if os.Getenv("_LAMBDA_SERVER_PORT") != "" { 43 fileName = filepath.Join("/tmp", fileName) 44 } 45 return os.Create(fileName) 46 } 47 48 //////////////////////////////////////////////////////////////////////////////// 49 // Type returned from worker pool uploading profiles to S3 50 type uploadResult struct { 51 err error 52 uploaded bool 53 } 54 55 func (ur *uploadResult) Error() error { 56 return ur.err 57 } 58 func (ur *uploadResult) Result() interface{} { 59 return ur.uploaded 60 } 61 62 func uploadFileTask(uploader *s3manager.Uploader, 63 profileType string, 64 uploadSlot int, 65 localFilePath string, 66 logger *logrus.Logger) taskFunc { 67 return func() workResult { 68 fileReader, fileReaderErr := os.Open(localFilePath) 69 if fileReaderErr != nil { 70 return &uploadResult{err: fileReaderErr} 71 } 72 defer fileReader.Close() 73 defer os.Remove(localFilePath) 74 75 uploadFileName := fmt.Sprintf("%d-%s", uploadSlot, path.Base(localFilePath)) 76 keyPath := path.Join(profileSnapshotRootKeypathForType(profileType, stackName), uploadFileName) 77 uploadInput := &s3manager.UploadInput{ 78 Bucket: aws.String(profileBucket), 79 Key: aws.String(keyPath), 80 Body: fileReader, 81 } 82 uploadOutput, uploadErr := uploader.Upload(uploadInput) 83 return &uploadResult{ 84 err: uploadErr, 85 uploaded: uploadOutput != nil, 86 } 87 } 88 } 89 90 func snapshotProfiles(s3BucketArchive interface{}, 91 snapshotInterval time.Duration, 92 cpuProfileDuration time.Duration, 93 profileTypes ...string) { 94 95 // The session the S3 Uploader will use 96 profileLogger, _ := NewLogger("") 97 98 publishProfiles := func(cpuProfilePath string) { 99 100 profileLogger.WithFields(logrus.Fields{ 101 "CPUProfilePath": cpuProfilePath, 102 "Types": profileTypes, 103 }).Info("Publishing CPU profile") 104 105 uploadSlot := nextUploadSlot() 106 sess := spartaAWS.NewSession(profileLogger) 107 uploader := s3manager.NewUploader(sess) 108 uploadTasks := make([]*workTask, 0) 109 110 if cpuProfilePath != "" { 111 uploadTasks = append(uploadTasks, 112 newWorkTask(uploadFileTask(uploader, 113 "cpu", 114 uploadSlot, 115 cpuProfilePath, 116 profileLogger))) 117 } 118 for _, eachProfileType := range profileTypes { 119 namedProfile := pprof.Lookup(eachProfileType) 120 if namedProfile != nil { 121 outputProfile, outputFileErr := profileOutputFile(eachProfileType) 122 if outputFileErr != nil { 123 profileLogger.WithFields(logrus.Fields{ 124 "Error": outputFileErr, 125 }).Error("Failed to CPU profile file") 126 } else { 127 namedProfile.WriteTo(outputProfile, 0) 128 outputProfile.Close() 129 uploadTasks = append(uploadTasks, 130 newWorkTask(uploadFileTask(uploader, 131 eachProfileType, 132 uploadSlot, 133 outputProfile.Name(), 134 profileLogger))) 135 } 136 } 137 } 138 workerPool := newWorkerPool(uploadTasks, 32) 139 workerPool.Run() 140 ScheduleProfileLoop(s3BucketArchive, 141 snapshotInterval, 142 cpuProfileDuration, 143 profileTypes...) 144 } 145 146 if cpuProfileDuration != 0 { 147 outputFile, outputFileErr := profileOutputFile("cpu") 148 if outputFileErr != nil { 149 profileLogger.Warn("Failed to create cpu profile path: %s\n", 150 outputFileErr.Error()) 151 return 152 } 153 startErr := pprof.StartCPUProfile(outputFile) 154 if startErr != nil { 155 profileLogger.Warn("Failed to start CPU profile: %s\n", startErr.Error()) 156 } 157 profileLogger.Info("Opened CPU profile") 158 time.AfterFunc(cpuProfileDuration, func() { 159 pprof.StopCPUProfile() 160 profileLogger.Info("Opened CPU profile") 161 closeErr := outputFile.Close() 162 if closeErr != nil { 163 profileLogger.Warn("Failed to close CPU profile output: %s\n", closeErr.Error()) 164 } else { 165 publishProfiles(outputFile.Name()) 166 } 167 }) 168 } else { 169 publishProfiles("") 170 } 171 } 172 173 // ScheduleProfileLoop installs a profiling loop that pushes profile information 174 // to S3 for local consumption using a `profile` command that wraps 175 // pprof 176 func ScheduleProfileLoop(s3BucketArchive interface{}, 177 snapshotInterval time.Duration, 178 cpuProfileDuration time.Duration, 179 profileTypes ...string) { 180 181 time.AfterFunc(snapshotInterval, func() { 182 snapshotProfiles(s3BucketArchive, snapshotInterval, cpuProfileDuration, profileTypes...) 183 }) 184 }