diff --git a/CHANGELOG.md b/CHANGELOG.md index b43eca95..25c755dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ - Add default non-root user in the Docker image. ([#593](https://github.com/getsentry/vroom/pull/593)) - Classify macOS frames from an application as application frames. ([#604](https://github.com/getsentry/vroom/pull/604)) - Return number of occurrences in flamegraph. ([#622](https://github.com/getsentry/vroom/pull/622), [#625](https://github.com/getsentry/vroom/pull/625)) -- Use function duration when computing metrics ([#627](https://github.com/getsentry/vroom/pull/627), [#628](https://github.com/getsentry/vroom/pull/628), [#629](https://github.com/getsentry/vroom/pull/629), [#630](https://github.com/getsentry/vroom/pull/630)) +- Use function duration when computing metrics ([#627](https://github.com/getsentry/vroom/pull/627), [#628](https://github.com/getsentry/vroom/pull/628), [#629](https://github.com/getsentry/vroom/pull/629), [#630](https://github.com/getsentry/vroom/pull/630), [#639](https://github.com/getsentry/vroom/pull/639)) - Remove Unused metrics endpoint ([#633](https://github.com/getsentry/vroom/pull/633)) **Bug Fixes**: diff --git a/internal/chunk/sample_test.go b/internal/chunk/sample_test.go index 18c6040c..9bdf5db6 100644 --- a/internal/chunk/sample_test.go +++ b/internal/chunk/sample_test.go @@ -40,6 +40,7 @@ func TestCallTrees(t *testing.T) { "1": { { DurationNS: 40_000_000, + DurationsNS: []uint64{40_000_000}, EndNS: 50_000_000, Fingerprint: 15444731332182868858, IsApplication: true, @@ -52,6 +53,7 @@ func TestCallTrees(t *testing.T) { Children: []*nodetree.Node{ { DurationNS: 40_000_000, + DurationsNS: []uint64{40_000_000}, EndNS: 50_000_000, StartNS: 10_000_000, Fingerprint: 14164357600995800812, @@ -64,6 +66,7 @@ func TestCallTrees(t *testing.T) { Children: []*nodetree.Node{ { DurationNS: 10_000_000, + DurationsNS: []uint64{10_000_000}, EndNS: 50_000_000, Fingerprint: 9531802423075301657, IsApplication: true, @@ -104,6 +107,7 @@ func TestCallTrees(t *testing.T) { "1": { { DurationNS: 30_000_000, + DurationsNS: []uint64{30_000_000}, EndNS: 40_000_000, Fingerprint: 15444731332182868858, IsApplication: true, @@ -116,6 +120,7 @@ func TestCallTrees(t *testing.T) { Children: []*nodetree.Node{ { DurationNS: 30_000_000, + DurationsNS: []uint64{30_000_000}, EndNS: 40_000_000, Fingerprint: 14164357600995800812, IsApplication: true, @@ -156,6 +161,7 @@ func TestCallTrees(t *testing.T) { "1": { { DurationNS: 10_000_000, + DurationsNS: []uint64{10_000_000}, EndNS: 20_000_000, Fingerprint: 15444731332182868858, IsApplication: true, @@ -168,6 +174,7 @@ func TestCallTrees(t *testing.T) { }, { DurationNS: 10_000_000, + DurationsNS: []uint64{10_000_000}, EndNS: 30_000_000, Fingerprint: 15444731332182868859, IsApplication: true, diff --git a/internal/flamegraph/flamegraph.go b/internal/flamegraph/flamegraph.go index 4e97440a..b848e29d 100644 --- a/internal/flamegraph/flamegraph.go +++ b/internal/flamegraph/flamegraph.go @@ -63,6 +63,8 @@ func addCallTreeToFlamegraph(flamegraphTree *[]*nodetree.Node, callTree []*nodet currentNode.Occurrence += node.Occurrence currentNode.SampleCount += node.SampleCount currentNode.DurationNS += node.DurationNS + currentNode.SelfTimeNS += node.SelfTimeNS + currentNode.DurationsNS = append(currentNode.DurationsNS, node.DurationsNS...) } else { currentNode = node.ShallowCopyWithoutChildren() *flamegraphTree = append(*flamegraphTree, currentNode) @@ -186,6 +188,16 @@ func toSpeedscope( fd.visitCalltree(tree, &stack) } + for i, frameInfo := range fd.frameInfos { + sort.Slice(frameInfo.DurationsNS, func(i, j int) bool { + return frameInfo.DurationsNS[i] < frameInfo.DurationsNS[j] + }) + frameInfo.P75Duration, _ = metrics.Quantile(frameInfo.DurationsNS, 0.75) + frameInfo.P95Duration, _ = metrics.Quantile(frameInfo.DurationsNS, 0.95) + frameInfo.P99Duration, _ = metrics.Quantile(frameInfo.DurationsNS, 0.99) + fd.frameInfos[i] = frameInfo + } + s.SetData("total_samples", fd.totalSamples) s.SetData("final_samples", fd.Len()) @@ -228,6 +240,9 @@ func (f *flamegraph) visitCalltree(node *nodetree.Node, currentStack *[]int) { *currentStack = append(*currentStack, i) f.frameInfos[i].Count += node.Occurrence f.frameInfos[i].Weight += node.DurationNS + f.frameInfos[i].SumDuration += node.DurationNS + f.frameInfos[i].SumSelfTime += node.SelfTimeNS + f.frameInfos[i].DurationsNS = append(f.frameInfos[i].DurationsNS, node.DurationsNS...) } else { frame := node.ToFrame() sfr := speedscope.Frame{ @@ -245,8 +260,11 @@ func (f *flamegraph) visitCalltree(node *nodetree.Node, currentStack *[]int) { *currentStack = append(*currentStack, len(f.frames)) f.frames = append(f.frames, sfr) f.frameInfos = append(f.frameInfos, speedscope.FrameInfo{ - Count: node.Occurrence, - Weight: node.DurationNS, + Count: node.Occurrence, + Weight: node.DurationNS, + SumDuration: node.DurationNS, + SumSelfTime: node.SelfTimeNS, + DurationsNS: node.DurationsNS, }) } diff --git a/internal/flamegraph/flamegraph_test.go b/internal/flamegraph/flamegraph_test.go index cc2a7d42..6ef42972 100644 --- a/internal/flamegraph/flamegraph_test.go +++ b/internal/flamegraph/flamegraph_test.go @@ -9,7 +9,6 @@ import ( "github.com/getsentry/vroom/internal/examples" "github.com/getsentry/vroom/internal/frame" - "github.com/getsentry/vroom/internal/metrics" "github.com/getsentry/vroom/internal/nodetree" "github.com/getsentry/vroom/internal/platform" "github.com/getsentry/vroom/internal/profile" @@ -23,7 +22,6 @@ func TestFlamegraphAggregation(t *testing.T) { name string profiles []sample.Profile output speedscope.Output - metrics []examples.FunctionMetrics }{ { name: "Basic profiles aggregation", @@ -169,10 +167,42 @@ func TestFlamegraphAggregation(t *testing.T) { {Image: "test.package", Name: "e", Fingerprint: 2430275448}, }, FrameInfos: []speedscope.FrameInfo{ - {Count: 3, Weight: 40}, - {Count: 2, Weight: 30}, - {Count: 2, Weight: 20}, - {Count: 1, Weight: 10}, + { + Count: 3, + Weight: 40, + SumDuration: 40, + SumSelfTime: 10, + P75Duration: 20, + P95Duration: 20, + P99Duration: 20, + }, + { + Count: 2, + Weight: 30, + SumDuration: 30, + SumSelfTime: 30, + P75Duration: 20, + P95Duration: 20, + P99Duration: 20, + }, + { + Count: 2, + Weight: 20, + SumDuration: 20, + SumSelfTime: 20, + P75Duration: 10, + P95Duration: 10, + P99Duration: 10, + }, + { + Count: 1, + Weight: 10, + SumDuration: 10, + SumSelfTime: 10, + P75Duration: 10, + P95Duration: 10, + P99Duration: 10, + }, }, Profiles: []examples.ExampleMetadata{ {ProfileID: "ab1"}, @@ -180,20 +210,6 @@ func TestFlamegraphAggregation(t *testing.T) { }, }, }, - metrics: []examples.FunctionMetrics{ - { - Name: "b", - Package: "test.package", - Fingerprint: 2430275455, - P75: 20, - P95: 20, - P99: 20, - Avg: 15, - Sum: 30, - SumSelfTime: 30, - Count: 3, - }, - }, }, { name: "Complex profiles aggregation", @@ -306,56 +322,39 @@ func TestFlamegraphAggregation(t *testing.T) { {Image: "test.package", Name: "c", Fingerprint: 2430275454, IsApplication: true}, }, FrameInfos: []speedscope.FrameInfo{ - {Count: 1, Weight: 90}, - {Count: 2, Weight: 70}, - {Count: 2, Weight: 50}, + { + Count: 1, + Weight: 90, + SumDuration: 90, + SumSelfTime: 20, + P75Duration: 90, + P95Duration: 90, + P99Duration: 90, + }, + { + Count: 2, + Weight: 70, + SumDuration: 70, + SumSelfTime: 20, + P75Duration: 40, + P95Duration: 40, + P99Duration: 40, + }, + { + Count: 2, + Weight: 50, + SumDuration: 50, + SumSelfTime: 50, + P75Duration: 30, + P95Duration: 30, + P99Duration: 30, + }, }, Profiles: []examples.ExampleMetadata{ {ProfileID: "ab1"}, }, }, }, - metrics: []examples.FunctionMetrics{ - { - Name: "c", - Package: "test.package", - Fingerprint: 2430275454, - InApp: true, - P75: 30, - P95: 30, - P99: 30, - Avg: 25, - Sum: 50, - SumSelfTime: 50, - Count: 5, - }, - { - Name: "a", - Package: "test.package", - Fingerprint: 2430275452, - InApp: true, - P75: 90, - P95: 90, - P99: 90, - Avg: 90, - Sum: 90, - SumSelfTime: 20, - Count: 9, - }, - { - Name: "b", - Package: "test.package", - Fingerprint: 2430275455, - InApp: true, - P75: 40, - P95: 40, - P99: 40, - Avg: 35, - Sum: 70, - SumSelfTime: 20, - Count: 7, - }, - }, }, { name: "zero self time", @@ -449,43 +448,39 @@ func TestFlamegraphAggregation(t *testing.T) { {Image: "test.package", Name: "c", Fingerprint: 2430275454, IsApplication: true}, }, FrameInfos: []speedscope.FrameInfo{ - {Count: 1, Weight: 30}, - {Count: 2, Weight: 50}, - {Count: 1, Weight: 30}, + { + Count: 1, + Weight: 30, + SumDuration: 30, + SumSelfTime: 0, + P75Duration: 30, + P95Duration: 30, + P99Duration: 30, + }, + { + Count: 2, + Weight: 50, + SumDuration: 50, + SumSelfTime: 20, + P75Duration: 30, + P95Duration: 30, + P99Duration: 30, + }, + { + Count: 1, + Weight: 30, + SumDuration: 30, + SumSelfTime: 30, + P75Duration: 30, + P95Duration: 30, + P99Duration: 30, + }, }, Profiles: []examples.ExampleMetadata{ {ProfileID: "ab1"}, }, }, }, - metrics: []examples.FunctionMetrics{ - { - Name: "c", - Package: "test.package", - Fingerprint: 2430275454, - InApp: true, - P75: 30, - P95: 30, - P99: 30, - Avg: 30, - Sum: 30, - SumSelfTime: 30, - Count: 3, - }, - { - Name: "b", - Package: "test.package", - Fingerprint: 2430275455, - InApp: true, - P75: 30, - P95: 30, - P99: 30, - Avg: 25, - Sum: 50, - SumSelfTime: 20, - Count: 5, - }, - }, }, } @@ -504,48 +499,12 @@ func TestFlamegraphAggregation(t *testing.T) { addCallTreeToFlamegraph(&ft, callTrees[0], annotateWithProfileExample(example)) } - if diff := testutil.Diff(toSpeedscope(context.TODO(), ft, 10, 99), test.output); diff != "" { - t.Fatalf("Result mismatch: got - want +\n%s", diff) - } - - ma := metrics.NewAggregator( - 100, - 5, - 0, - ) - - for _, sp := range test.profiles { - p := profile.New(&sp) - callTrees, err := p.CallTrees() - if err != nil { - t.Fatalf("error when generating calltrees: %v", err) - } - - start, end := p.StartAndEndEpoch() - example := examples.NewExampleFromProfileID( - p.ProjectID(), - p.ID(), - start, - end, - ) - - functions := metrics.CapAndFilterFunctions( - metrics.ExtractFunctionsFromCallTrees( - callTrees, - ma.MinDepth, - ), - int(ma.MaxUniqueFunctions), - false, - ) - ma.AddFunctions(functions, example) - } - m := ma.ToMetrics() - options := cmp.Options{ - cmpopts.IgnoreFields(examples.FunctionMetrics{}, "Worst"), - cmpopts.IgnoreFields(examples.FunctionMetrics{}, "Examples"), + cmpopts.IgnoreFields(speedscope.FrameInfo{}, "DurationsNS"), } - if diff := testutil.Diff(m, test.metrics, options); diff != "" { + + speedscope := toSpeedscope(context.TODO(), ft, 10, 99) + if diff := testutil.Diff(speedscope, test.output, options); diff != "" { t.Fatalf("Result mismatch: got - want +\n%s", diff) } }) @@ -623,8 +582,8 @@ func TestAnnotatingWithExamples(t *testing.T) { {Name: "function2", Fingerprint: 3932509229, IsApplication: true}, }, FrameInfos: []speedscope.FrameInfo{ - {Count: 4, Weight: 80_000_000}, - {Count: 2, Weight: 20_000_000}, + {Count: 4, Weight: 80_000_000, SumDuration: 80_000_000}, + {Count: 2, Weight: 20_000_000, SumDuration: 20_000_000}, }, Profiles: []examples.ExampleMetadata{ { diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index aa17955e..193938d4 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -73,9 +73,9 @@ func (ma *Aggregator) ToMetrics() []examples.FunctionMetrics { sort.Slice(f.DurationsNS, func(i, j int) bool { return f.DurationsNS[i] < f.DurationsNS[j] }) - p75, _ := quantile(f.DurationsNS, 0.75) - p95, _ := quantile(f.DurationsNS, 0.95) - p99, _ := quantile(f.DurationsNS, 0.99) + p75, _ := Quantile(f.DurationsNS, 0.75) + p95, _ := Quantile(f.DurationsNS, 0.95) + p99, _ := Quantile(f.DurationsNS, 0.99) metrics = append(metrics, examples.FunctionMetrics{ Name: f.Function, Package: f.Package, @@ -104,7 +104,7 @@ func (ma *Aggregator) ToMetrics() []examples.FunctionMetrics { return metrics } -func quantile(values []uint64, q float64) (uint64, error) { +func Quantile(values []uint64, q float64) (uint64, error) { if len(values) == 0 { return 0, errors.New("cannot compute percentile from empty list") } diff --git a/internal/nodetree/nodetree.go b/internal/nodetree/nodetree.go index f7184f98..54df2593 100644 --- a/internal/nodetree/nodetree.go +++ b/internal/nodetree/nodetree.go @@ -32,6 +32,7 @@ type ( Node struct { Children []*Node `json:"children,omitempty"` DurationNS uint64 `json:"duration_ns"` + SelfTimeNS uint64 `json:"self_time_ns"` Fingerprint uint64 `json:"fingerprint"` IsApplication bool `json:"is_application"` Line uint32 `json:"line,omitempty"` @@ -39,6 +40,7 @@ type ( Package string `json:"package"` Path string `json:"path,omitempty"` + DurationsNS []uint64 `json:"-"` EndNS uint64 `json:"-"` Frame frame.Frame `json:"-"` Occurrence uint32 `json:"-"` @@ -67,12 +69,45 @@ func NodeFromFrame(f frame.Frame, start, end, fingerprint uint64) *Node { StartNS: start, Profiles: map[examples.ExampleMetadata]struct{}{}, } - if end > 0 { + if n.EndNS > n.StartNS { n.DurationNS = n.EndNS - n.StartNS + n.DurationsNS = []uint64{n.DurationNS} } return &n } +func (n *Node) RecursiveComputeSelfTime() { + for _, c := range n.Children { + c.RecursiveComputeSelfTime() + } + + var childrenApplicationDurationNS uint64 + var childrenSystemDurationNS uint64 + + for _, c := range n.Children { + if c.IsApplication { + childrenApplicationDurationNS += c.DurationNS + } else { + childrenSystemDurationNS += c.DurationNS + } + } + + if n.IsApplication { + if n.DurationNS > childrenApplicationDurationNS { + n.SelfTimeNS = n.DurationNS - childrenApplicationDurationNS + } else { + n.SelfTimeNS = 0 + } + } else { + sum := childrenApplicationDurationNS + childrenSystemDurationNS + if n.DurationNS > sum { + n.SelfTimeNS = n.DurationNS - sum + } else { + n.SelfTimeNS = 0 + } + } +} + func (n *Node) ShallowCopyWithoutChildren() *Node { clone := Node{ EndNS: n.EndNS, @@ -88,6 +123,8 @@ func (n *Node) ShallowCopyWithoutChildren() *Node { StartNS: n.StartNS, Profiles: n.Profiles, DurationNS: n.DurationNS, + SelfTimeNS: n.SelfTimeNS, + DurationsNS: n.DurationsNS, } return &clone @@ -106,6 +143,7 @@ func (n *Node) ToFrame() frame.Frame { func (n *Node) SetDuration(t uint64) { n.EndNS = t n.DurationNS = n.EndNS - n.StartNS + n.DurationsNS = []uint64{n.DurationNS} } func (n *Node) WriteToHash(h hash.Hash) { diff --git a/internal/nodetree/nodetree_test.go b/internal/nodetree/nodetree_test.go index 41028952..a286c40c 100644 --- a/internal/nodetree/nodetree_test.go +++ b/internal/nodetree/nodetree_test.go @@ -500,3 +500,184 @@ func TestIsSymbolicated(t *testing.T) { }) } } + +func TestComputeSelfTime(t *testing.T) { + tests := []struct { + name string + node Node + selfTime uint64 + }{ + { + name: "single system frame", + node: Node{ + Children: []*Node{}, + DurationNS: 1000, + IsApplication: false, + }, + selfTime: 1000, + }, + { + name: "single application frame", + node: Node{ + Children: []*Node{}, + DurationNS: 1000, + IsApplication: true, + }, + selfTime: 1000, + }, + { + name: "system frame with single system frame child full duration", + node: Node{ + Children: []*Node{ + { + Children: []*Node{}, + DurationNS: 1000, + IsApplication: false, + }, + }, + DurationNS: 1000, + IsApplication: false, + }, + selfTime: 0, + }, + { + name: "system frame with single system frame child partial duration", + node: Node{ + Children: []*Node{ + { + Children: []*Node{}, + DurationNS: 500, + IsApplication: false, + }, + }, + DurationNS: 1000, + IsApplication: false, + }, + selfTime: 500, + }, + { + name: "system frame with single system frame child partial duration", + node: Node{ + Children: []*Node{ + { + Children: []*Node{}, + DurationNS: 500, + IsApplication: false, + }, + }, + DurationNS: 1000, + IsApplication: false, + }, + selfTime: 500, + }, + { + name: "system frame with single application frame child full duration", + node: Node{ + Children: []*Node{ + { + Children: []*Node{}, + DurationNS: 1000, + IsApplication: true, + }, + }, + DurationNS: 1000, + IsApplication: false, + }, + selfTime: 0, + }, + { + name: "system frame with single application frame child partial duration", + node: Node{ + Children: []*Node{ + { + Children: []*Node{}, + DurationNS: 500, + IsApplication: true, + }, + }, + DurationNS: 1000, + IsApplication: false, + }, + selfTime: 500, + }, + { + name: "system frame with multiple child frames", + node: Node{ + Children: []*Node{ + { + Children: []*Node{}, + DurationNS: 500, + IsApplication: true, + }, + { + Children: []*Node{}, + DurationNS: 250, + IsApplication: false, + }, + }, + DurationNS: 1000, + IsApplication: false, + }, + selfTime: 250, + }, + { + name: "application frame with single application frame child full duration", + node: Node{ + Children: []*Node{ + { + Children: []*Node{}, + DurationNS: 1000, + IsApplication: true, + }, + }, + DurationNS: 1000, + IsApplication: true, + }, + selfTime: 0, + }, + { + name: "application frame with single application frame child partial duration", + node: Node{ + Children: []*Node{ + { + Children: []*Node{}, + DurationNS: 500, + IsApplication: true, + }, + }, + DurationNS: 1000, + IsApplication: true, + }, + selfTime: 500, + }, + { + name: "application frame with multiple child frames", + node: Node{ + Children: []*Node{ + { + Children: []*Node{}, + DurationNS: 500, + IsApplication: true, + }, + { + Children: []*Node{}, + DurationNS: 250, + IsApplication: false, + }, + }, + DurationNS: 1000, + IsApplication: true, + }, + selfTime: 500, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(*testing.T) { + tt.node.RecursiveComputeSelfTime() + if diff := testutil.Diff(tt.node.SelfTimeNS, tt.selfTime); diff != "" { + t.Fatalf("Result mismatch: got - want +\n%s", diff) + } + }) + } +} diff --git a/internal/profile/android_test.go b/internal/profile/android_test.go index 0db21681..d724c80f 100644 --- a/internal/profile/android_test.go +++ b/internal/profile/android_test.go @@ -567,6 +567,7 @@ func TestCallTrees(t *testing.T) { 1: { { DurationNS: 1000, + DurationsNS: []uint64{1000}, IsApplication: true, EndNS: 2000, StartNS: 1000, @@ -583,6 +584,7 @@ func TestCallTrees(t *testing.T) { Children: []*nodetree.Node{ { DurationNS: 1000, + DurationsNS: []uint64{1000}, IsApplication: true, Name: "class2.method2()", Package: "class2", @@ -601,6 +603,7 @@ func TestCallTrees(t *testing.T) { }, { DurationNS: 0, + DurationsNS: []uint64{0}, IsApplication: true, Name: "class1.method1()", Package: "class1", @@ -626,6 +629,7 @@ func TestCallTrees(t *testing.T) { 1: { { DurationNS: 2000, + DurationsNS: []uint64{2000}, IsApplication: true, EndNS: 3000, Occurrence: 1, @@ -642,6 +646,7 @@ func TestCallTrees(t *testing.T) { Children: []*nodetree.Node{ { DurationNS: 1000, + DurationsNS: []uint64{1000}, IsApplication: true, EndNS: 2500, Occurrence: 1, @@ -658,6 +663,7 @@ func TestCallTrees(t *testing.T) { Children: []*nodetree.Node{ { DurationNS: 500, + DurationsNS: []uint64{500}, IsApplication: true, EndNS: 2250, Occurrence: 1, @@ -687,6 +693,7 @@ func TestCallTrees(t *testing.T) { 1: { { DurationNS: 1000, + DurationsNS: []uint64{1000}, IsApplication: true, EndNS: 2000, StartNS: 1000, diff --git a/internal/profile/profile.go b/internal/profile/profile.go index 1d177cba..38b3f3d7 100644 --- a/internal/profile/profile.go +++ b/internal/profile/profile.go @@ -81,7 +81,15 @@ func (p Profile) MarshalJSON() ([]byte, error) { } func (p *Profile) CallTrees() (map[uint64][]*nodetree.Node, error) { - return p.profile.CallTrees() + callTrees, err := p.profile.CallTrees() + + for _, callTreesForThread := range callTrees { + for _, callTree := range callTreesForThread { + callTree.RecursiveComputeSelfTime() + } + } + + return callTrees, err } func (p *Profile) DebugMeta() debugmeta.DebugMeta { diff --git a/internal/sample/sample_test.go b/internal/sample/sample_test.go index 80fc57c5..92820ed0 100644 --- a/internal/sample/sample_test.go +++ b/internal/sample/sample_test.go @@ -379,6 +379,7 @@ func TestCallTrees(t *testing.T) { 1: { { DurationNS: 40, + DurationsNS: []uint64{40}, EndNS: 50, Fingerprint: 15444731332182868858, IsApplication: true, @@ -391,6 +392,7 @@ func TestCallTrees(t *testing.T) { Children: []*nodetree.Node{ { DurationNS: 40, + DurationsNS: []uint64{40}, EndNS: 50, StartNS: 10, Fingerprint: 14164357600995800812, @@ -403,6 +405,7 @@ func TestCallTrees(t *testing.T) { Children: []*nodetree.Node{ { DurationNS: 10, + DurationsNS: []uint64{10}, EndNS: 50, Fingerprint: 9531802423075301657, IsApplication: true, @@ -446,6 +449,7 @@ func TestCallTrees(t *testing.T) { 1: { { DurationNS: 30, + DurationsNS: []uint64{30}, EndNS: 40, Fingerprint: 15444731332182868858, IsApplication: true, @@ -458,6 +462,7 @@ func TestCallTrees(t *testing.T) { Children: []*nodetree.Node{ { DurationNS: 30, + DurationsNS: []uint64{30}, EndNS: 40, Fingerprint: 14164357600995800812, IsApplication: true, @@ -501,6 +506,7 @@ func TestCallTrees(t *testing.T) { 1: { { DurationNS: 10, + DurationsNS: []uint64{10}, EndNS: 20, Fingerprint: 15444731332182868858, IsApplication: true, @@ -513,6 +519,7 @@ func TestCallTrees(t *testing.T) { }, { DurationNS: 10, + DurationsNS: []uint64{10}, EndNS: 30, Fingerprint: 15444731332182868859, IsApplication: true, @@ -1256,6 +1263,7 @@ func TestCallTreesFingerprintPerPlatform(t *testing.T) { 0: { { DurationNS: 10, + DurationsNS: []uint64{10}, EndNS: 10, Fingerprint: 1628006971372193492, IsApplication: false, @@ -1311,6 +1319,7 @@ func TestCallTreesFingerprintPerPlatform(t *testing.T) { 0: { { DurationNS: 10, + DurationsNS: []uint64{10}, EndNS: 10, Fingerprint: 1628006971372193492, IsApplication: true, @@ -1368,6 +1377,7 @@ func TestCallTreesFingerprintPerPlatform(t *testing.T) { 0: { { DurationNS: 10, + DurationsNS: []uint64{10}, EndNS: 10, Fingerprint: 12857020554704472368, IsApplication: false, diff --git a/internal/speedscope/speedscope.go b/internal/speedscope/speedscope.go index 28175d8f..9be5da5e 100644 --- a/internal/speedscope/speedscope.go +++ b/internal/speedscope/speedscope.go @@ -39,8 +39,14 @@ type ( } FrameInfo struct { - Count uint32 `json:"count"` - Weight uint64 `json:"weight"` + Count uint32 `json:"count"` + Weight uint64 `json:"weight"` + SumDuration uint64 `json:"sumDuration"` + SumSelfTime uint64 `json:"sumSelfTime"` + DurationsNS []uint64 `json:"-"` + P75Duration uint64 `json:"p75Duration"` + P95Duration uint64 `json:"p95Duration"` + P99Duration uint64 `json:"p99Duration"` } Event struct {