-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathprofiler.go
173 lines (135 loc) · 4.05 KB
/
profiler.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
165
166
167
168
169
170
171
172
173
// Package profiler implements functions to start a handler
package profiler
import (
"context"
"errors"
"expvar"
"net/http"
"net/http/pprof"
"os"
"os/signal"
"sync"
"time"
)
const (
defaultTimeout = 30 * time.Minute
defaultListen = ":6666"
)
// Hooker represents the interface for Profiler hooks
type Hooker interface {
// PreStart will be executed after the signal was received but before the debug endpoint starts
PreStart()
// PostShutdown will be executed after the debug endpoint is shutdown or the start has failed
PostShutdown()
}
// =============================================================================
// Profiler represents the Profiler
type Profiler struct {
signal os.Signal
address string
timeout time.Duration
hooks []Hooker
running sync.Mutex
evt EventHandler
}
// Address returns the listen address for the debug endpoint
func (p *Profiler) Address() string {
return p.address
}
// =============================================================================
func (p *Profiler) Start(ctx context.Context) {
if !p.running.TryLock() {
return
}
go func() {
defer p.running.Unlock()
p.evt(InfoEvent, "start profiler signal handler", "signal", p.signal)
sigC := make(chan os.Signal, 1)
wg := new(sync.WaitGroup)
ctx, cancel := context.WithCancel(ctx)
for {
signal.Notify(sigC, p.signal)
select {
case <-sigC: // receive signal to start the debug endpoint
disableSignals(sigC)
wg.Add(1)
p.startEndpoint(ctx)
wg.Done()
case <-ctx.Done(): // stop the signal handler
p.evt(InfoEvent, "stop profiler signal handler", "signal", p.signal)
disableSignals(sigC)
// stop the endpoint (if running) and
// wait until the endpoint is stopped
cancel()
wg.Wait()
p.evt(InfoEvent, "profiler signal handler stopped")
return
}
}
}()
}
// startEndpoint starts the debug http endpoint
func (p *Profiler) startEndpoint(ctx context.Context) {
shutdown := make(chan struct{})
srv := &http.Server{
Addr: p.address,
Handler: standardLibraryMux(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
go func() {
p.evt(InfoEvent, "start debug endpoint", "address", p.address)
// execute the PreStart hooks
for _, h := range p.hooks {
h.PreStart()
}
if err := srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
p.evt(ErrorEvent, "start debug endpoint", "err", err)
}
// execute the PostShutdown hooks ... even after a failed startup
for _, h := range p.hooks {
h.PostShutdown()
}
close(shutdown)
}()
timer := time.NewTimer(p.timeout)
select {
case <-timer.C: // timer expired
case <-ctx.Done(): // context canceled
timer.Stop()
}
p.evt(InfoEvent, "stop debug endpoint", "address", p.address, "timeout", p.timeout)
sCtx, cancel := context.WithTimeout(context.Background(), p.timeout)
defer cancel()
if err := srv.Shutdown(sCtx); err != nil {
p.evt(ErrorEvent, "shutdown debug endpoint", "err", err)
}
<-shutdown
p.evt(InfoEvent, "debug endpoint stopped")
}
// =============================================================================
// standardLibraryMux registers all the debug routes from the standard library
// into a new mux bypassing the use of the DefaultServerMux. Using the
// DefaultServerMux would be a security risk since a dependency could inject a
// handler into our service without us knowing it.
//
// Source: https://github.com/ardanlabs/service4.1-video/blob/main/business/web/v1/debug/debug.go
func standardLibraryMux() *http.ServeMux {
mux := http.NewServeMux()
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
mux.Handle("/debug/vars", expvar.Handler())
return mux
}
// disableSignals stop receiving of signals and drain the signal channel
func disableSignals(sigC chan os.Signal) {
signal.Stop(sigC)
// drain signal channel
select {
case <-sigC:
default:
}
}