Skip to content

Commit 3713fae

Browse files
authored
ebpf: per target configuration with labels (#2977)
1 parent 5f808d3 commit 3713fae

File tree

17 files changed

+458
-124
lines changed

17 files changed

+458
-124
lines changed

ebpf/cmd/playground/main.go

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"connectrpc.com/connect"
1616
"github.com/go-kit/log"
17+
"github.com/grafana/pyroscope/ebpf/cpp/demangle"
1718
ebpfmetrics "github.com/grafana/pyroscope/ebpf/metrics"
1819
"github.com/pkg/errors"
1920
"github.com/prometheus/common/model"
@@ -28,7 +29,6 @@ import (
2829
"github.com/grafana/pyroscope/ebpf/pprof"
2930
"github.com/grafana/pyroscope/ebpf/sd"
3031
"github.com/grafana/pyroscope/ebpf/symtab"
31-
"github.com/grafana/pyroscope/ebpf/symtab/elf"
3232
"github.com/prometheus/client_golang/prometheus"
3333
commonconfig "github.com/prometheus/common/config"
3434
)
@@ -43,11 +43,35 @@ var (
4343
session ebpfspy.Session
4444
)
4545

46+
type splitLog struct {
47+
err log.Logger
48+
rest log.Logger
49+
}
50+
51+
func (s splitLog) Log(keyvals ...interface{}) error {
52+
if len(keyvals)%2 != 0 {
53+
return s.err.Log(keyvals...)
54+
}
55+
for i := 0; i < len(keyvals); i += 2 {
56+
if keyvals[i] == "level" {
57+
vv := keyvals[i+1]
58+
vvs, ok := vv.(fmt.Stringer)
59+
if ok && vvs.String() == "error" {
60+
return s.err.Log(keyvals...)
61+
}
62+
}
63+
}
64+
return s.rest.Log(keyvals...)
65+
}
66+
4667
func main() {
4768
config = getConfig()
4869
metrics = ebpfmetrics.New(prometheus.DefaultRegisterer)
4970

50-
logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
71+
logger = &splitLog{
72+
err: log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)),
73+
rest: log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)),
74+
}
5175

5276
targetFinder, err := sd.NewTargetFinder(os.DirFS("/"), logger, convertTargetOptions())
5377
if err != nil {
@@ -76,16 +100,11 @@ func main() {
76100
}
77101

78102
func collectProfiles(profiles chan *pushv1.PushRequest) {
79-
builders := pprof.NewProfileBuilders(int64(config.SampleRate))
80-
err := session.CollectProfiles(func(target *sd.Target, stack []string, value uint64, pid uint32, aggregation ebpfspy.SampleAggregation) {
81-
labelsHash, labels := target.Labels()
82-
builder := builders.BuilderForTarget(labelsHash, labels)
83-
if aggregation == ebpfspy.SampleAggregated {
84-
builder.CreateSample(stack, value)
85-
} else {
86-
builder.CreateSampleOrAddValue(stack, value)
87-
}
103+
builders := pprof.NewProfileBuilders(pprof.BuildersOptions{
104+
SampleRate: int64(config.SampleRate),
105+
PerPIDProfile: true,
88106
})
107+
err := pprof.Collect(builders, session)
89108

90109
if err != nil {
91110
panic(err)
@@ -163,6 +182,7 @@ func convertSessionOptions() ebpfspy.SessionOptions {
163182
PythonEnabled: config.PythonEnabled,
164183
Metrics: metrics,
165184
CacheOptions: config.CacheOptions,
185+
VerifierLogSize: 1024 * 1024 * 20,
166186
}
167187
}
168188

@@ -191,12 +211,13 @@ var defaultConfig = Config{
191211
UnknownSymbolModuleOffset: true,
192212
UnknownSymbolAddress: true,
193213
PythonEnabled: true,
214+
SymbolOptions: symtab.SymbolOptions{
215+
GoTableFallback: true,
216+
PythonFullFilePath: false,
217+
DemangleOptions: demangle.DemangleFull,
218+
},
194219
CacheOptions: symtab.CacheOptions{
195-
SymbolOptions: symtab.SymbolOptions{
196-
GoTableFallback: true,
197-
PythonFullFilePath: false,
198-
DemangleOptions: elf.DemangleFull,
199-
},
220+
200221
PidCacheOptions: symtab.GCacheOptions{
201222
Size: 239,
202223
KeepRounds: 8,
@@ -223,6 +244,7 @@ type Config struct {
223244
UnknownSymbolModuleOffset bool
224245
UnknownSymbolAddress bool
225246
PythonEnabled bool
247+
SymbolOptions symtab.SymbolOptions
226248
CacheOptions symtab.CacheOptions
227249
SampleRate int
228250
TargetsOnly bool
@@ -296,7 +318,6 @@ func getProcessTargets() []sd.DiscoveryTarget {
296318
"__meta_process_comm": string(comm),
297319
"__meta_process_cgroup": string(cgroup),
298320
}
299-
_ = level.Debug(logger).Log("msg", "process target", "target", target.DebugString())
300321
res = append(res, target)
301322
}
302323
return res
@@ -326,7 +347,6 @@ func relabelProcessTargets(targets []sd.DiscoveryTarget, cfg []*RelabelConfig) [
326347
continue
327348
}
328349
tt := sd.DiscoveryTarget(lbls.Map())
329-
_ = level.Debug(logger).Log("msg", "relabelled process", "target", tt.DebugString())
330350
res = append(res, tt)
331351
}
332352
return res

ebpf/cpp/demangle/demangle.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package demangle
2+
3+
import "github.com/ianlancetaylor/demangle"
4+
5+
var DemangleUnspecified []demangle.Option = nil
6+
var DemangleNoneSpecified []demangle.Option = make([]demangle.Option, 0)
7+
var DemangleSimplified = []demangle.Option{demangle.NoParams, demangle.NoEnclosingParams, demangle.NoTemplateParams}
8+
var DemangleTemplates = []demangle.Option{demangle.NoParams, demangle.NoEnclosingParams}
9+
var DemangleFull = []demangle.Option{demangle.NoClones}
10+
11+
func ConvertDemangleOptions(o string) []demangle.Option {
12+
switch o {
13+
case "none":
14+
return DemangleNoneSpecified
15+
case "simplified":
16+
return DemangleSimplified
17+
case "templates":
18+
return DemangleTemplates
19+
case "full":
20+
return DemangleFull
21+
default:
22+
return DemangleUnspecified
23+
}
24+
}

ebpf/pprof/pprof.go

Lines changed: 115 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/cespare/xxhash/v2"
1212
"github.com/google/pprof/profile"
13+
"github.com/grafana/pyroscope/ebpf/sd"
1314
"github.com/klauspost/compress/gzip"
1415
"github.com/prometheus/prometheus/model/labels"
1516
)
@@ -26,21 +27,96 @@ var (
2627
}
2728
)
2829

30+
type SampleType uint32
31+
32+
var SampleTypeCpu = SampleType(0)
33+
var SampleTypeMem = SampleType(1)
34+
35+
type SampleAggregation bool
36+
37+
var (
38+
// SampleAggregated mean samples are accumulated in ebpf, no need to dedup these
39+
SampleAggregated = SampleAggregation(true)
40+
// SampleNotAggregated mean values are not accumulated in ebpf, but streamed to userspace with value=1
41+
// TODO make consider aggregating python in ebpf as well
42+
SampleNotAggregated = SampleAggregation(false)
43+
)
44+
45+
type CollectProfilesCallback func(sample ProfileSample)
46+
47+
type SamplesCollector interface {
48+
CollectProfiles(callback CollectProfilesCallback) error
49+
}
50+
51+
type ProfileSample struct {
52+
Target *sd.Target
53+
Pid uint32
54+
SampleType SampleType
55+
Aggregation SampleAggregation
56+
Stack []string
57+
Value uint64
58+
Value2 uint64
59+
}
60+
61+
type BuildersOptions struct {
62+
SampleRate int64
63+
PerPIDProfile bool
64+
}
65+
66+
type builderHashKey struct {
67+
labelsHash uint64
68+
pid uint32
69+
sampleType SampleType
70+
}
71+
2972
type ProfileBuilders struct {
30-
Builders map[uint64]*ProfileBuilder
31-
SampleRate int64
73+
Builders map[builderHashKey]*ProfileBuilder
74+
opt BuildersOptions
3275
}
3376

34-
func NewProfileBuilders(sampleRate int64) *ProfileBuilders {
35-
return &ProfileBuilders{Builders: make(map[uint64]*ProfileBuilder), SampleRate: sampleRate}
77+
func NewProfileBuilders(options BuildersOptions) *ProfileBuilders {
78+
return &ProfileBuilders{Builders: make(map[builderHashKey]*ProfileBuilder), opt: options}
3679
}
3780

38-
func (b ProfileBuilders) BuilderForTarget(hash uint64, labels labels.Labels) *ProfileBuilder {
39-
res := b.Builders[hash]
81+
func Collect(builders *ProfileBuilders, collector SamplesCollector) error {
82+
return collector.CollectProfiles(func(sample ProfileSample) {
83+
builders.AddSample(&sample)
84+
})
85+
}
86+
87+
func (b *ProfileBuilders) AddSample(sample *ProfileSample) {
88+
bb := b.BuilderForSample(sample)
89+
if sample.Aggregation == SampleAggregated {
90+
bb.CreateSample(sample)
91+
} else {
92+
bb.CreateSampleOrAddValue(sample)
93+
}
94+
}
95+
96+
func (b *ProfileBuilders) BuilderForSample(sample *ProfileSample) *ProfileBuilder {
97+
labelsHash, labels := sample.Target.Labels()
98+
99+
k := builderHashKey{labelsHash: labelsHash, sampleType: sample.SampleType}
100+
if b.opt.PerPIDProfile {
101+
k.pid = sample.Pid
102+
}
103+
res := b.Builders[k]
40104
if res != nil {
41105
return res
42106
}
43107

108+
var sampleType []*profile.ValueType
109+
var periodType *profile.ValueType
110+
var period int64
111+
if sample.SampleType == SampleTypeCpu {
112+
sampleType = []*profile.ValueType{{Type: "cpu", Unit: "nanoseconds"}}
113+
periodType = &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}
114+
period = time.Second.Nanoseconds() / b.opt.SampleRate
115+
} else {
116+
sampleType = []*profile.ValueType{{Type: "alloc_objects", Unit: "count"}, {Type: "alloc_space", Unit: "bytes"}}
117+
periodType = &profile.ValueType{Type: "space", Unit: "bytes"}
118+
period = 512 * 1024 // todo
119+
}
44120
builder := &ProfileBuilder{
45121
locations: make(map[string]*profile.Location),
46122
functions: make(map[string]*profile.Function),
@@ -52,16 +128,16 @@ func (b ProfileBuilders) BuilderForTarget(hash uint64, labels labels.Labels) *Pr
52128
ID: 1,
53129
},
54130
},
55-
SampleType: []*profile.ValueType{{Type: "cpu", Unit: "nanoseconds"}},
56-
Period: time.Second.Nanoseconds() / b.SampleRate,
57-
PeriodType: &profile.ValueType{Type: "cpu", Unit: "nanoseconds"},
131+
SampleType: sampleType,
132+
Period: period,
133+
PeriodType: periodType,
58134
TimeNanos: time.Now().UnixNano(),
59135
},
60136
tmpLocationIDs: make([]uint64, 0, 128),
61137
tmpLocations: make([]*profile.Location, 0, 128),
62138
}
63139
res = builder
64-
b.Builders[hash] = res
140+
b.Builders[k] = res
65141
return res
66142
}
67143

@@ -76,36 +152,31 @@ type ProfileBuilder struct {
76152
tmpLocationIDs []uint64
77153
}
78154

79-
func (p *ProfileBuilder) CreateSample(stacktrace []string, value uint64) {
80-
sample := &profile.Sample{
81-
Value: []int64{int64(value) * p.Profile.Period},
82-
}
83-
for _, s := range stacktrace {
84-
loc := p.addLocation(s)
85-
sample.Location = append(sample.Location, loc)
155+
func (p *ProfileBuilder) CreateSample(inputSample *ProfileSample) {
156+
sample := p.newSample(inputSample)
157+
p.addValue(inputSample, sample)
158+
for i, s := range inputSample.Stack {
159+
sample.Location[i] = p.addLocation(s)
86160
}
87161
p.Profile.Sample = append(p.Profile.Sample, sample)
88162
}
89163

90-
func (p *ProfileBuilder) CreateSampleOrAddValue(stacktrace []string, value uint64) {
91-
scaledValue := int64(value) * p.Profile.Period
164+
func (p *ProfileBuilder) CreateSampleOrAddValue(inputSample *ProfileSample) {
92165
p.tmpLocations = p.tmpLocations[:0]
93166
p.tmpLocationIDs = p.tmpLocationIDs[:0]
94-
for _, s := range stacktrace {
167+
for _, s := range inputSample.Stack {
95168
loc := p.addLocation(s)
96169
p.tmpLocations = append(p.tmpLocations, loc)
97170
p.tmpLocationIDs = append(p.tmpLocationIDs, loc.ID)
98171
}
99172
h := xxhash.Sum64(uint64Bytes(p.tmpLocationIDs))
100173
sample := p.sampleHashToSample[h]
101174
if sample != nil {
102-
sample.Value[0] += scaledValue
175+
p.addValue(inputSample, sample)
103176
return
104177
}
105-
sample = &profile.Sample{
106-
Location: make([]*profile.Location, len(p.tmpLocations)),
107-
Value: []int64{scaledValue},
108-
}
178+
sample = p.newSample(inputSample)
179+
p.addValue(inputSample, sample)
109180
copy(sample.Location, p.tmpLocations)
110181
p.sampleHashToSample[h] = sample
111182
p.Profile.Sample = append(p.Profile.Sample, sample)
@@ -177,3 +248,22 @@ func uint64Bytes(s []uint64) []byte {
177248
hdr.Data = uintptr(unsafe.Pointer(&s[0]))
178249
return bs
179250
}
251+
func (p *ProfileBuilder) newSample(inputSample *ProfileSample) *profile.Sample {
252+
sample := new(profile.Sample)
253+
if inputSample.SampleType == SampleTypeCpu {
254+
sample.Value = []int64{0}
255+
} else {
256+
sample.Value = []int64{0, 0}
257+
}
258+
sample.Location = make([]*profile.Location, len(inputSample.Stack))
259+
return sample
260+
}
261+
262+
func (p *ProfileBuilder) addValue(inputSample *ProfileSample, sample *profile.Sample) {
263+
if inputSample.SampleType == SampleTypeCpu {
264+
sample.Value[0] += int64(inputSample.Value) * p.Profile.Period
265+
} else {
266+
sample.Value[0] += int64(inputSample.Value)
267+
sample.Value[1] += int64(inputSample.Value2)
268+
}
269+
}

0 commit comments

Comments
 (0)