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 }