-
Notifications
You must be signed in to change notification settings - Fork 24
/
profilers.go
137 lines (116 loc) · 3.76 KB
/
profilers.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
// +kubebuilder:validation:Optional
package profilers
import (
"context"
httppprof "net/http/pprof"
"os"
"path"
"runtime/pprof"
"github.com/fluxninja/lumberjack"
"github.com/gorilla/mux"
"go.uber.org/fx"
"github.com/fluxninja/aperture/v2/pkg/config"
"github.com/fluxninja/aperture/v2/pkg/log"
)
const (
defaultKey = "profilers"
// HTTPPathPrefix is the path prefix to the profiler HTTP endpoints.
httpPathPrefix = "/debug/pprof"
// DefaultCPUFile is the default filename for the cpu profile.
defaultCPUFile = "cpu.prof"
)
// defaultPath is the default path that is used to store profiles.
var defaultPath = path.Join(config.DefaultLogDirectory, "profiles")
// Module is a fx module that provides the profilers.
func Module() fx.Option {
constructor := Constructor{ConfigKey: defaultKey}
return fx.Options(
fx.Invoke(constructor.setupProfilers),
)
}
// swagger:operation POST /profilers common-configuration Profilers
// ---
// x-fn-config-env: true
// parameters:
// - in: body
// schema:
// "$ref": "#/definitions/ProfilersConfig"
// ProfilersConfig holds configuration for profilers.
// swagger:model
// +kubebuilder:object:generate=true
type ProfilersConfig struct {
// Path to save performance profiles. "default" path is `/var/log/aperture/<service>/profiles`.
ProfilesPath string `json:"profiles_path" default:"default"`
// Register routes. Profile types `profile`, `symbol` and `cmdline` will be registered at `/debug/pprof/{profile,symbol,cmdline}`.
RegisterHTTPRoutes bool `json:"register_http_routes" default:"true"`
// Flag to enable CPU profiling on process start and save it to a file. The Browser (HTTP) interface won't work if this is enabled, as the CPU profile will always be running.
CPUProfile bool `json:"cpu_profiler" default:"false"`
}
// Constructor holds fields to create an instance of profilers.
type Constructor struct {
ConfigKey string
DefaultConfig ProfilersConfig
}
func (constructor Constructor) setupProfilers(unmarshaller config.Unmarshaller,
router *mux.Router,
lc fx.Lifecycle,
) error {
config := constructor.DefaultConfig
if err := unmarshaller.UnmarshalKey(constructor.ConfigKey, &config); err != nil {
log.Error().Err(err)
return err
}
if config.ProfilesPath == "default" {
config.ProfilesPath = defaultPath
}
var cpuProfileFile *lumberjack.Logger
var err error
if config.CPUProfile {
filename := path.Join(config.ProfilesPath, defaultCPUFile)
log.Debug().Str("filename", filename).Msg("opening cpu profile writer")
cpuProfileFile = newProfileWriter(filename)
}
if config.RegisterHTTPRoutes {
router.HandleFunc(httpPathPrefix, httppprof.Index)
router.HandleFunc(path.Join(httpPathPrefix, "cmdline"), httppprof.Cmdline)
router.HandleFunc(path.Join(httpPathPrefix, "profile"), httppprof.Profile)
router.HandleFunc(path.Join(httpPathPrefix, "symbol"), httppprof.Symbol)
}
lc.Append(fx.Hook{
OnStart: func(context.Context) error {
log.Info().Interface("config", config).Msg("profiler config")
if config.CPUProfile {
log.Debug().Msg("starting cpu profile")
err = pprof.StartCPUProfile(cpuProfileFile)
if err != nil {
log.Error().Err(err).Msg("Failed to start CPU profiling")
}
}
return nil
},
OnStop: func(context.Context) error {
if config.CPUProfile {
log.Debug().Msg("stopping cpu profile")
pprof.StopCPUProfile()
closeProfileWriter(cpuProfileFile)
}
return nil
},
})
return nil
}
func newProfileWriter(filename string) *lumberjack.Logger {
writer := &lumberjack.Logger{
Filename: filename,
MaxBackups: 10,
MaxAge: 7,
}
return writer
}
func closeProfileWriter(lg *lumberjack.Logger) {
filename := lg.Filename
_ = lg.Rotate()
_ = lg.Close()
// remove the new file created by lumberjack after rotate is called
_ = os.Remove(filename)
}