forked from DataDog/dd-trace-go
/
profile.go
168 lines (151 loc) · 4.44 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
165
166
167
168
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-2020 Datadog, Inc.
package profiler
import (
"bytes"
"errors"
"io"
"runtime/pprof"
"time"
)
// ProfileType represents a type of profile that the profiler is able to run.
type ProfileType int
const (
// HeapProfile reports memory allocation samples; used to monitor current
// and historical memory usage, and to check for memory leaks.
HeapProfile ProfileType = iota
// CPUProfile determines where a program spends its time while actively consuming
// CPU cycles (as opposed to while sleeping or waiting for I/O).
CPUProfile
// BlockProfile shows where goroutines block waiting on synchronization primitives
// (including timer channels). Block profile is not enabled by default.
BlockProfile
// MutexProfile reports the lock contentions. When you think your CPU is not fully utilized due
// to a mutex contention, use this profile. Mutex profile is not enabled by default.
MutexProfile
)
func (t ProfileType) String() string {
switch t {
case HeapProfile:
return "heap"
case CPUProfile:
return "cpu"
case MutexProfile:
return "mutex"
case BlockProfile:
return "block"
default:
return "unknown"
}
}
// profile specifies a pprof's data (gzipped protobuf), and the types contained
// within it.
type profile struct {
types []string
data []byte
}
// batch is a collection of profiles of different types, collected at roughly the same time. It maps
// to what the Datadog UI calls a profile.
type batch struct {
start, end time.Time
host string
profiles []*profile
}
func (b *batch) addProfile(p *profile) {
b.profiles = append(b.profiles, p)
}
func (p *profiler) runProfile(t ProfileType) (*profile, error) {
switch t {
case HeapProfile:
return heapProfile(p.cfg)
case CPUProfile:
return cpuProfile(p.cfg)
case MutexProfile:
return mutexProfile(p.cfg)
case BlockProfile:
return blockProfile(p.cfg)
default:
return nil, errors.New("profile type not implemented")
}
}
// writeHeapProfile writes the heap profile; replaced in tests
var writeHeapProfile = pprof.WriteHeapProfile
func heapProfile(cfg *config) (*profile, error) {
var buf bytes.Buffer
start := now()
if err := writeHeapProfile(&buf); err != nil {
return nil, err
}
end := now()
tags := append(cfg.tags, "profile_type:heap")
cfg.statsd.Timing("datadog.profiler.go.collect_time", end.Sub(start), tags, 1)
return &profile{
types: []string{"alloc_objects", "alloc_space", "inuse_objects", "inuse_space"},
data: buf.Bytes(),
}, nil
}
var (
// startCPUProfile starts the CPU profile; replaced in tests
startCPUProfile = pprof.StartCPUProfile
// stopCPUProfile stops the CPU profile; replaced in tests
stopCPUProfile = pprof.StopCPUProfile
)
func cpuProfile(cfg *config) (*profile, error) {
var buf bytes.Buffer
start := now()
if err := startCPUProfile(&buf); err != nil {
return nil, err
}
time.Sleep(cfg.cpuDuration)
stopCPUProfile()
end := now()
tags := append(cfg.tags, "profile_type:cpu")
cfg.statsd.Timing("datadog.profiler.go.collect_time", end.Sub(start), tags, 1)
return &profile{
types: []string{"samples", "cpu"},
data: buf.Bytes(),
}, nil
}
// lookpupProfile looks up the profile with the given name and writes it to w. It returns
// any errors encountered in the process. It is replaced in tests.
var lookupProfile = func(name string, w io.Writer) error {
prof := pprof.Lookup(name)
if prof == nil {
return errors.New("profile not found")
}
return prof.WriteTo(w, 0)
}
func blockProfile(cfg *config) (*profile, error) {
var buf bytes.Buffer
start := now()
if err := lookupProfile("block", &buf); err != nil {
return nil, err
}
end := now()
tags := append(cfg.tags, "profile_type:block")
cfg.statsd.Timing("datadog.profiler.go.collect_time", end.Sub(start), tags, 1)
return &profile{
types: []string{"delay"},
data: buf.Bytes(),
}, nil
}
func mutexProfile(cfg *config) (*profile, error) {
var buf bytes.Buffer
start := now()
if err := lookupProfile("mutex", &buf); err != nil {
return nil, err
}
end := now()
tags := append(cfg.tags, "profile_type:mutex")
cfg.statsd.Timing("datadog.profiler.go.collect_time", end.Sub(start), tags, 1)
return &profile{
types: []string{"contentions"},
data: buf.Bytes(),
}, nil
}
// now returns current time in UTC.
func now() time.Time {
return time.Now().UTC()
}