Skip to content

Commit 27769c5

Browse files
authored
Merge pull request #3 from pyroscope-io/managed-code-filtering
Add sampling option to ignore time spent in native code
2 parents 680c360 + 012cd62 commit 27769c5

6 files changed

+102
-55
lines changed

examples/tracing/README.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# Collect Tracing
22

33
The example demonstrates how the package may be used to collect and process `NetTrace` stream data using Diagnostics IPC:
4-
the program processes events produced with **Microsoft-DotNETCore-SampleProfiler** provider and creates a sampled profile,
5-
rendered as a call tree.
4+
the program processes events produced with **Microsoft-DotNETCore-SampleProfiler** provider and creates a sampled profile.
65

76
1. Run dotnet application.
87
2. Find its PID, e.g.:

nettrace/nettrace_test.go

+29-11
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,38 @@ import (
1515
)
1616

1717
func TestNetTraceDecoding(t *testing.T) {
18-
t.Run(".Net 5.0 SampleProfiler Web app", func(t *testing.T) {
19-
requireEqual(t,
20-
"testdata/dotnet-5.0-SampleProfiler-webapp.golden.nettrace",
21-
"testdata/dotnet-5.0-SampleProfiler-webapp.txt")
22-
})
18+
t.Run(".Net 5.0 SampleProfiler", func(t *testing.T) {
19+
t.Run("Web app", func(t *testing.T) {
20+
t.Run("Managed code only", func(t *testing.T) {
21+
requireEqual(t,
22+
"testdata/dotnet-5.0-SampleProfiler-webapp.golden.nettrace",
23+
"testdata/dotnet-5.0-SampleProfiler-webapp-managed-only.txt",
24+
profiler.WithManagedCodeOnly())
25+
})
26+
t.Run("Managed and native code", func(t *testing.T) {
27+
requireEqual(t,
28+
"testdata/dotnet-5.0-SampleProfiler-webapp.golden.nettrace",
29+
"testdata/dotnet-5.0-SampleProfiler-webapp.txt")
30+
})
31+
})
2332

24-
t.Run(".Net 5.0 SampleProfiler Simple single thread app", func(t *testing.T) {
25-
requireEqual(t,
26-
"testdata/dotnet-5.0-SampleProfiler-single-thread.golden.nettrace",
27-
"testdata/dotnet-5.0-SampleProfiler-single-thread.txt")
33+
t.Run("Simple single thread app", func(t *testing.T) {
34+
t.Run("Managed code only", func(t *testing.T) {
35+
requireEqual(t,
36+
"testdata/dotnet-5.0-SampleProfiler-single-thread.golden.nettrace",
37+
"testdata/dotnet-5.0-SampleProfiler-single-thread-managed-only.txt",
38+
profiler.WithManagedCodeOnly())
39+
})
40+
t.Run("Managed and native code", func(t *testing.T) {
41+
requireEqual(t,
42+
"testdata/dotnet-5.0-SampleProfiler-single-thread.golden.nettrace",
43+
"testdata/dotnet-5.0-SampleProfiler-single-thread.txt")
44+
})
45+
})
2846
})
2947
}
3048

31-
func requireEqual(t *testing.T, sample, expected string) {
49+
func requireEqual(t *testing.T, sample, expected string, options ...profiler.Option) {
3250
t.Helper()
3351

3452
s, err := os.Open(sample)
@@ -40,7 +58,7 @@ func requireEqual(t *testing.T, sample, expected string) {
4058
trace, err := stream.Open()
4159
requireNoError(t, err)
4260

43-
p := profiler.NewSampleProfiler(trace)
61+
p := profiler.NewSampleProfiler(trace, options...)
4462
stream.EventHandler = p.EventHandler
4563
stream.MetadataHandler = p.MetadataHandler
4664
stream.StackBlockHandler = p.StackBlockHandler

nettrace/profiler/profiler.go

+29-13
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"github.com/pyroscope-io/dotnetdiag/nettrace"
1111
)
1212

13+
// SampleProfiler processes event stream from Microsoft-DotNETCore-SampleProfiler
14+
// provider and calculates time for every call stack.
1315
type SampleProfiler struct {
1416
trace *nettrace.Trace
1517
sym *symbols
@@ -20,16 +22,23 @@ type SampleProfiler struct {
2022

2123
events events
2224
samples []sample
25+
26+
managedOnly bool
2327
}
2428

25-
type sample struct {
26-
stack []uint64
27-
val int64
29+
type Option func(*SampleProfiler)
30+
31+
// WithManagedCodeOnly prescribes SampleProfiler to ignore the time that
32+
// was spent in native (unmanaged) code.
33+
func WithManagedCodeOnly() Option {
34+
return func(p *SampleProfiler) {
35+
p.managedOnly = true
36+
}
2837
}
2938

30-
type FrameInfo struct {
31-
SampledTime time.Duration
32-
Name string
39+
type sample struct {
40+
stack []uint64
41+
value int64
3342
}
3443

3544
type event struct {
@@ -74,14 +83,18 @@ const (
7483
sampleTypeManaged
7584
)
7685

77-
func NewSampleProfiler(trace *nettrace.Trace) *SampleProfiler {
78-
return &SampleProfiler{
86+
func NewSampleProfiler(trace *nettrace.Trace, options ...Option) *SampleProfiler {
87+
p := &SampleProfiler{
7988
trace: trace,
8089
sym: newSymbols(),
8190
md: make(map[int32]*nettrace.Metadata),
8291
threads: make(map[int64]*thread),
8392
stacks: make(map[int32][]uint64),
8493
}
94+
for _, option := range options {
95+
option(p)
96+
}
97+
return p
8598
}
8699

87100
func (s *SampleProfiler) Samples() map[string]time.Duration {
@@ -91,7 +104,7 @@ func (s *SampleProfiler) Samples() map[string]time.Duration {
91104
for i := range x.stack {
92105
name[i] = s.sym.resolve(x.stack[i])
93106
}
94-
samples[strings.Join(name, ";")] += time.Duration(x.val * -1)
107+
samples[strings.Join(name, ";")] += time.Duration(x.value * -1)
95108
}
96109
return samples
97110
}
@@ -137,10 +150,10 @@ func (s *SampleProfiler) SequencePointBlockHandler(*nettrace.SequencePointBlock)
137150
s.thread(x.threadID).addSample(x.typ, x.relativeTime, x.stackID)
138151
}
139152
for _, t := range s.threads {
140-
for k, v := range t.samples {
153+
for stackID, value := range t.samples {
141154
s.samples = append(s.samples, sample{
142-
stack: s.stacks[k],
143-
val: v,
155+
stack: s.stacks[stackID],
156+
value: value,
144157
})
145158
}
146159
t.samples = make(map[int32]int64)
@@ -169,7 +182,10 @@ func (s *SampleProfiler) thread(tid int64) *thread {
169182
if ok {
170183
return t
171184
}
172-
t = &thread{samples: make(map[int32]int64)}
185+
t = &thread{
186+
samples: make(map[int32]int64),
187+
managedOnly: s.managedOnly,
188+
}
173189
s.threads[tid] = t
174190
return t
175191
}

nettrace/profiler/thread.go

+29-29
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package profiler
22

33
type thread struct {
4-
lastBlockTime int64
5-
lastCPUTime int64
6-
// StackID -> sampled time
7-
samples map[int32]int64
4+
lastExternalTime int64
5+
lastManagedTime int64
6+
samples map[int32]int64
7+
managedOnly bool
88
}
99

1010
type threadState int
@@ -13,16 +13,16 @@ const (
1313
_ threadState = iota - 1
1414

1515
uninitialized
16-
running
17-
blocked
16+
managed
17+
external
1818
)
1919

2020
func (t *thread) state() threadState {
2121
switch {
22-
case t.lastBlockTime < 0:
23-
return running
24-
case t.lastBlockTime > 0:
25-
return blocked
22+
case t.lastExternalTime < 0:
23+
return managed
24+
case t.lastExternalTime > 0:
25+
return external
2626
default:
2727
return uninitialized
2828
}
@@ -36,35 +36,35 @@ func (t *thread) addSample(sampleType clrThreadSampleType, relativeTime int64, s
3636
case sampleTypeManaged:
3737
switch t.state() {
3838
case uninitialized:
39-
t.putCPUSample(stackID, relativeTime)
40-
t.lastBlockTime = -1
41-
case running:
42-
t.putCPUSample(stackID, relativeTime)
43-
case blocked:
44-
t.putBlockSample(stackID, relativeTime)
45-
t.lastBlockTime = -relativeTime
39+
t.managedSample(stackID, relativeTime)
40+
t.lastExternalTime = -1
41+
case managed:
42+
t.managedSample(stackID, relativeTime)
43+
case external:
44+
t.externalSample(stackID, relativeTime)
45+
t.lastExternalTime = -relativeTime
4646
}
47-
t.lastCPUTime = relativeTime
47+
t.lastManagedTime = relativeTime
4848

4949
case sampleTypeExternal:
5050
switch t.state() {
51-
case blocked, uninitialized:
52-
t.putBlockSample(stackID, relativeTime)
53-
case running:
54-
t.putCPUSample(stackID, relativeTime)
51+
case external, uninitialized:
52+
t.externalSample(stackID, relativeTime)
53+
case managed:
54+
t.managedSample(stackID, relativeTime)
5555
}
56-
t.lastBlockTime = relativeTime
56+
t.lastExternalTime = relativeTime
5757
}
5858
}
5959

60-
func (t *thread) putCPUSample(stackID int32, rt int64) {
61-
if t.lastCPUTime > 0 {
62-
t.samples[stackID] += t.lastCPUTime - rt
60+
func (t *thread) managedSample(stackID int32, rt int64) {
61+
if t.lastManagedTime > 0 {
62+
t.samples[stackID] += t.lastManagedTime - rt
6363
}
6464
}
6565

66-
func (t *thread) putBlockSample(stackID int32, rt int64) {
67-
if t.lastBlockTime > 0 {
68-
t.samples[stackID] += t.lastBlockTime - rt
66+
func (t *thread) externalSample(stackID int32, rt int64) {
67+
if t.lastExternalTime > 0 && !t.managedOnly {
68+
t.samples[stackID] += t.lastExternalTime - rt
6969
}
7070
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
mvc-hello-world!Example.Program.Main(class System.String[]);mvc-hello-world!Example.Program.Fast() 11217349
2+
mvc-hello-world!Example.Program.Main(class System.String[]);mvc-hello-world!Example.Program.Fast();mvc-hello-world!Example.Program.Work(int32) 1638356862
3+
mvc-hello-world!Example.Program.Main(class System.String[]);mvc-hello-world!Example.Program.Slow() 11253402
4+
mvc-hello-world!Example.Program.Main(class System.String[]);mvc-hello-world!Example.Program.Slow();mvc-hello-world!Example.Program.Work(int32) 6505198057

0 commit comments

Comments
 (0)