-
Notifications
You must be signed in to change notification settings - Fork 2.5k
/
Copy pathpprofextension.go
106 lines (91 loc) · 2.86 KB
/
pprofextension.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
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofextension // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/pprofextension"
import (
"context"
"errors"
"net"
"net/http"
_ "net/http/pprof" // #nosec Needed to enable the performance profiler
"os"
"runtime"
"runtime/pprof"
"sync/atomic"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componentstatus"
"go.uber.org/zap"
)
var running = &atomic.Bool{}
type pprofExtension struct {
config Config
file *os.File
server http.Server
stopCh chan struct{}
telemetrySettings component.TelemetrySettings
}
func (p *pprofExtension) Start(_ context.Context, host component.Host) error {
// The runtime settings are global to the application, so while in principle it
// is possible to have more than one instance, running multiple will mean that
// the settings of the last started instance will prevail. In order to avoid
// this issue we will allow the start of a single instance once per process
// Summary: only a single instance can be running in the same process.
if !running.CompareAndSwap(false, true) {
return errors.New("only a single pprof extension instance can be running per process")
}
// Take care that if any error happen when starting the active instance is cleaned.
var startErr error
defer func() {
if startErr != nil {
running.Store(false)
}
}()
// Start the listener here so we can have earlier failure if port is
// already in use.
var ln net.Listener
ln, startErr = p.config.TCPAddr.Listen(context.Background())
if startErr != nil {
return startErr
}
runtime.SetBlockProfileRate(p.config.BlockProfileFraction)
runtime.SetMutexProfileFraction(p.config.MutexProfileFraction)
p.telemetrySettings.Logger.Info("Starting net/http/pprof server", zap.Any("config", p.config))
p.stopCh = make(chan struct{})
go func() {
defer func() {
running.Store(false)
close(p.stopCh)
}()
// The listener ownership goes to the server.
if errHTTP := p.server.Serve(ln); !errors.Is(errHTTP, http.ErrServerClosed) && errHTTP != nil {
componentstatus.ReportStatus(host, componentstatus.NewFatalErrorEvent(errHTTP))
}
}()
if p.config.SaveToFile != "" {
var f *os.File
f, startErr = os.Create(p.config.SaveToFile)
if startErr != nil {
return startErr
}
p.file = f
startErr = pprof.StartCPUProfile(f)
}
return startErr
}
func (p *pprofExtension) Shutdown(context.Context) error {
defer running.Store(false)
if p.file != nil {
pprof.StopCPUProfile()
_ = p.file.Close() // ignore the error
}
err := p.server.Close()
if p.stopCh != nil {
<-p.stopCh
}
return err
}
func newServer(config Config, params component.TelemetrySettings) *pprofExtension {
return &pprofExtension{
config: config,
telemetrySettings: params,
}
}