github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/profiles.go (about)

     1  /*
     2   * Copyright (c) 2018, Psiphon Inc.
     3   * All rights reserved.
     4   *
     5   * This program is free software: you can redistribute it and/or modify
     6   * it under the terms of the GNU General Public License as published by
     7   * the Free Software Foundation, either version 3 of the License, or
     8   * (at your option) any later version.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package common
    21  
    22  import (
    23  	"os"
    24  	"path/filepath"
    25  	"runtime"
    26  	"runtime/pprof"
    27  	"time"
    28  )
    29  
    30  // WriteRuntimeProfiles writes Go runtime profile information to a set of
    31  // files in the specified output directory. The profiles include "heap",
    32  // "goroutine", and other selected profiles from:
    33  // https://golang.org/pkg/runtime/pprof/#Profile.
    34  //
    35  // The SampleDurationSeconds inputs determine how long to wait and sample
    36  // profiles that require active sampling. When set to 0, these profiles are
    37  // skipped.
    38  func WriteRuntimeProfiles(
    39  	logger Logger,
    40  	outputDirectory string,
    41  	filenameSuffix string,
    42  	blockSampleDurationSeconds int,
    43  	cpuSampleDurationSeconds int) {
    44  
    45  	openProfileFile := func(profileName string) *os.File {
    46  		filename := filepath.Join(outputDirectory, profileName+".profile")
    47  		if filenameSuffix != "" {
    48  			filename += "." + filenameSuffix
    49  		}
    50  		file, err := os.OpenFile(
    51  			filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
    52  		if err != nil {
    53  			logger.WithTraceFields(
    54  				LogFields{
    55  					"error":    err,
    56  					"fileName": filename}).Error("open profile file failed")
    57  			return nil
    58  		}
    59  		return file
    60  	}
    61  
    62  	writeProfile := func(profileName string) {
    63  
    64  		file := openProfileFile(profileName)
    65  		if file == nil {
    66  			return
    67  		}
    68  		err := pprof.Lookup(profileName).WriteTo(file, 1)
    69  		file.Close()
    70  		if err != nil {
    71  			logger.WithTraceFields(
    72  				LogFields{
    73  					"error":       err,
    74  					"profileName": profileName}).Error("write profile failed")
    75  		}
    76  	}
    77  
    78  	// TODO: capture https://golang.org/pkg/runtime/debug/#WriteHeapDump?
    79  	// May not be useful in its current state, as per:
    80  	// https://groups.google.com/forum/#!topic/golang-dev/cYAkuU45Qyw
    81  
    82  	// Write goroutine, heap, and threadcreate profiles
    83  	// https://golang.org/pkg/runtime/pprof/#Profile
    84  	writeProfile("goroutine")
    85  	writeProfile("heap")
    86  	writeProfile("threadcreate")
    87  
    88  	// Write CPU profile (after sampling)
    89  	// https://golang.org/pkg/runtime/pprof/#StartCPUProfile
    90  
    91  	if cpuSampleDurationSeconds > 0 {
    92  		file := openProfileFile("cpu")
    93  		if file != nil {
    94  			logger.WithTrace().Info("start cpu profiling")
    95  			err := pprof.StartCPUProfile(file)
    96  			if err != nil {
    97  				logger.WithTraceFields(
    98  					LogFields{"error": err}).Error("StartCPUProfile failed")
    99  			} else {
   100  				time.Sleep(time.Duration(cpuSampleDurationSeconds) * time.Second)
   101  				pprof.StopCPUProfile()
   102  				logger.WithTrace().Info("end cpu profiling")
   103  			}
   104  			file.Close()
   105  		}
   106  	}
   107  
   108  	// Write block profile (after sampling)
   109  	// https://golang.org/pkg/runtime/pprof/#Profile
   110  
   111  	if blockSampleDurationSeconds > 0 {
   112  		logger.WithTrace().Info("start block/mutex profiling")
   113  		runtime.SetBlockProfileRate(1)
   114  		runtime.SetMutexProfileFraction(1)
   115  		time.Sleep(time.Duration(blockSampleDurationSeconds) * time.Second)
   116  		runtime.SetBlockProfileRate(0)
   117  		runtime.SetMutexProfileFraction(0)
   118  		logger.WithTrace().Info("end block/mutex profiling")
   119  		writeProfile("block")
   120  		writeProfile("mutex")
   121  	}
   122  }