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  }