-
Notifications
You must be signed in to change notification settings - Fork 535
Expand file tree
/
Copy pathprofile.go
More file actions
158 lines (137 loc) · 5.09 KB
/
Copy pathprofile.go
File metadata and controls
158 lines (137 loc) · 5.09 KB
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
// 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 Datadog, Inc.
package profiler
import (
"context"
"fmt"
"io"
"runtime/trace"
"time"
v2 "github.com/DataDog/dd-trace-go/v2/profiler"
"github.com/DataDog/gostackparse"
pprofile "github.com/google/pprof/profile"
)
// ProfileType represents a type of profile that the profiler is able to run.
type ProfileType = v2.ProfileType
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 mutex and channel
// operations. The block profile is not enabled by default and may cause
// noticeable CPU overhead. We recommend against enabling it, see
// DefaultBlockRate for more information.
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
// GoroutineProfile reports stack traces of all current goroutines
GoroutineProfile
// MetricsProfile reports top-line metrics associated with user-specified profiles
MetricsProfile
)
// traceLogCPUProfileRate logs the cpuProfileRate to the execution tracer if
// its not 0. This gives us a better chance to correctly guess the CPU duration
// of traceEvCPUSample events. It will not work correctly if the user is
// calling runtime.SetCPUProfileRate() themselves, and there is no way to
// handle this scenario given the current APIs. See
// https://github.com/golang/go/issues/60701 for a proposal to improve the
// situation.
func traceLogCPUProfileRate(cpuProfileRate int) {
if cpuProfileRate != 0 {
trace.Log(context.Background(), "cpuProfileRate", fmt.Sprintf("%d", cpuProfileRate))
}
}
func goroutineDebug2ToPprof(r io.Reader, w io.Writer, t time.Time) (err error) {
// gostackparse.Parse() has been extensively tested and should not crash
// under any circumstances, but we really want to avoid crashing a customers
// applications, so this code will recover from any unexpected panics and
// return them as an error instead.
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
goroutines, errs := gostackparse.Parse(r)
functionID := uint64(1)
locationID := uint64(1)
p := &pprofile.Profile{
TimeNanos: t.UnixNano(),
}
m := &pprofile.Mapping{ID: 1, HasFunctions: true}
p.Mapping = []*pprofile.Mapping{m}
p.SampleType = []*pprofile.ValueType{
{
Type: "waitduration",
Unit: "nanoseconds",
},
}
for _, g := range goroutines {
sample := &pprofile.Sample{
Value: []int64{g.Wait.Nanoseconds()},
Label: map[string][]string{
"state": {g.State}, // TODO(fg) split into atomicstatus/waitreason?
"lockedm": {fmt.Sprintf("%t", g.LockedToThread)},
},
NumUnit: map[string][]string{"goid": {"id"}},
NumLabel: map[string][]int64{"goid": {int64(g.ID)}},
}
// Treat the frame that created this goroutine as part of the stack so it
// shows up in the stack trace / flame graph. Hopefully this will be more
// useful than confusing for people.
if g.CreatedBy != nil {
// TODO(fg) should we modify the function name to include "created by"?
g.Stack = append(g.Stack, g.CreatedBy)
}
// Based on internal discussion, the current strategy is to use virtual
// frames to indicate truncated stacks, see [1] for how python/jd does it.
// [1] https://github.com/DataDog/dd-trace-py/blob/e933d2485b9019a7afad7127f7c0eb541341cdb7/ddtrace/profiling/exporter/pprof.pyx#L117-L121
if g.FramesElided {
g.Stack = append(g.Stack, &gostackparse.Frame{
Func: "...additional frames elided...",
})
}
for _, call := range g.Stack {
function := &pprofile.Function{
ID: functionID,
Name: call.Func,
Filename: call.File,
}
p.Function = append(p.Function, function)
functionID++
location := &pprofile.Location{
ID: locationID,
Mapping: m,
Line: []pprofile.Line{{
Function: function,
Line: int64(call.Line),
}},
}
p.Location = append(p.Location, location)
locationID++
sample.Location = append(sample.Location, location)
}
p.Sample = append(p.Sample, sample)
}
// Put the error message in the pprof profiles as comments in case we need to
// debug issues at some point.
// TODO(fg) would be nice to also have a metric counter for this
for _, err := range errs {
p.Comments = append(p.Comments, "error: "+err.Error())
}
if err := p.CheckValid(); err != nil {
return fmt.Errorf("marshalGoroutineDebug2Profile: %s", err)
} else if err := p.Write(w); err != nil {
return fmt.Errorf("marshalGoroutineDebug2Profile: %s", err)
}
return nil
}
// now returns current time in UTC.
func now() time.Time {
return time.Now().UTC()
}