/
profile.go
164 lines (151 loc) · 4.71 KB
/
profile.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// Copyright 2020 cloudeng llc. All rights reserved.
// Use of this source code is governed by the Apache-2.0
// license that can be found in the LICENSE file.
// Package profiling provides support for enabling profiling of
// command line tools via flags.
package profiling
import (
"fmt"
"os"
"runtime"
"runtime/pprof"
"strconv"
"strings"
"cloudeng.io/errors"
)
// ProfileSpec represents a named profile and the name of the file to
// write its contents to. CPU profiling can be requested using the
// name 'cpu' rather than the CPUProfiling API calls in runtime/pprof
// that predate the named profiles.
type ProfileSpec struct {
Name string
Filename string
}
// ProfileFlag can be used to represent flags to request arbritrary profiles.
type ProfileFlag struct {
Profiles []ProfileSpec
}
// PredefinedProfiles returns the list of predefined profiles, ie. those
// documented as 'predefined' by the runtime/pprof package, such as
// "goroutine", "heap", "allocs", "threadcreate", "block", "mutex".
func PredefinedProfiles() []string {
return []string{"goroutine", "heap", "allocs", "threadcreate", "block", "mutex"}
}
// IsPredefined returns true if the specified name is one of the pprof predefined
// profiles, or 'cpu' which is recognised by this package as requesting
// a cpu profile.
func IsPredefined(name string) bool {
if name == "cpu" {
return true
}
return pprof.Lookup(name) != nil
}
// Set implements flag.Value.
func (pf *ProfileFlag) Set(v string) error {
parts := strings.Split(v, ":")
if len(parts) != 2 {
return fmt.Errorf("%v not in <profile>:<filename> format", v)
}
pf.Profiles = append(pf.Profiles, ProfileSpec{Name: parts[0], Filename: parts[1]})
return nil
}
// String implements flag.Value.
func (pf *ProfileFlag) String() string {
out := &strings.Builder{}
for i, p := range pf.Profiles {
fmt.Fprintf(out, "%s:%s", p.Name, p.Filename)
if i < len(pf.Profiles)-1 {
out.WriteByte(',')
}
}
return out.String()
}
// Get implements flag.Getter.
func (pf *ProfileFlag) Get() interface{} {
return pf.Profiles
}
func enableCPUProfiling(filename string) (func() error, error) {
if len(filename) == 0 {
return func() error { return nil }, nil
}
output, err := os.Create(filename)
if err != nil {
nerr := fmt.Errorf("could not create CPU profile: %v: %v", filename, err)
return func() error { return nerr }, nerr
}
if err := pprof.StartCPUProfile(output); err != nil {
nerr := fmt.Errorf("could not start CPU profile: %v", err)
return func() error { return nerr }, nerr
}
return func() error {
pprof.StopCPUProfile()
return output.Close()
}, nil
}
// Start enables the named profile and returns a function that
// can be used to save its contents to the specified file.
// Typical usage is as follows:
//
// save, err := profiling.Start("cpu", "cpu.out")
// if err != nil {
// panic(err)
// }
// defer save()
//
// For a heap profile simply use Start("heap", "heap.out"). Note that the
// returned save function cannot be used more than once and that Start must
// be called multiple times to create multiple heap output files for example.
// All of the predefined named profiles from runtime/pprof are supported. If
// a new, custom profile is requested, then the caller must obtain a reference
// to it via pprof.Lookup and the create profiling records appropriately.
func Start(name, filename string) (func() error, error) {
if len(name) == 0 || len(filename) == 0 {
err := fmt.Errorf("missing profile or filename: %q:%q", name, filename)
return func() error { return err }, err
}
if !IsPredefined(name) {
return func() error { return nil }, fmt.Errorf("unsupported profile: %v", name)
}
if name == "cpu" {
save, err := enableCPUProfiling(filename)
return save, err
}
if name == "block" {
rate, _ := strconv.Atoi(os.Getenv("GO_BLOCK_PROFILE_RATE"))
if rate == 0 {
rate = 1000
}
runtime.SetBlockProfileRate(rate)
}
output, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0760)
if err != nil {
return func() error { return err }, err
}
// Must be a predefined profile
p := pprof.Lookup(name)
return func() error {
errs := errors.M{}
errs.Append(p.WriteTo(output, 0))
errs.Append(output.Close())
return errs.Err()
}, nil
}
// StartFromSpecs starts all of the specified profiles.
func StartFromSpecs(specs ...ProfileSpec) (func(), error) {
deferedSaves := []func() error{}
for _, profile := range specs {
save, err := Start(profile.Name, profile.Filename)
if err != nil {
return nil, err
}
fmt.Printf("profiling: %v %v\n", profile.Name, profile.Filename)
deferedSaves = append(deferedSaves, save)
}
return func() {
for _, save := range deferedSaves {
if err := save(); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
}
}
}, nil
}