Skip to content

Commit fd5cd63

Browse files
authored
[2/2] Add differential flamegraph script (#2044)
Summary: Add differential flamegraph script This depends on #2043. Relevant Issues: N/A Type of change: /kind new-pxl-script Test Plan: Ran the local UI and verified that the non negated version works as expected ![differential](https://github.com/user-attachments/assets/e021d5f6-e3e2-4d5c-a3a6-17612695e429) Changelog Message: Add `px/differential_flamegraph` script for comparing flamegraphs profiles between pods. See [this post](https://www.brendangregg.com/blog/2014-11-09/differential-flame-graphs.html) for more background on how to use a differential flamegraph. --------- Signed-off-by: Dom Del Nano <ddelnano@gmail.com>
1 parent 4778ba1 commit fd5cd63

File tree

3 files changed

+148
-0
lines changed

3 files changed

+148
-0
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Copyright 2018- The Pixie Authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
# SPDX-License-Identifier: Apache-2.0
16+
17+
import px
18+
19+
negate = False
20+
# TODO(ddelnano): negation requires switching the type of join from right to left
21+
# or returning a different DataFrame. This might not be possible with pxl's current
22+
# functionality, but this should be implemented once it's possible.
23+
24+
def merge_and_compute_delta(pod1, pod2, negate: bool):
25+
26+
diff = pod1.merge(
27+
pod2,
28+
how='right',
29+
left_on='stack_trace',
30+
right_on='stack_trace'
31+
suffixes=['_1', '_2'],
32+
)
33+
# TODO(ddelnano): This needs to be switched with pod1 if the flamegraph should
34+
# be negated.
35+
percentage_agg = pod2.groupby(['pod']).agg(
36+
count=('count', px.sum),
37+
)
38+
diff.pod = px.select(negate, diff.pod_1, diff.pod_2)
39+
diff.stack_trace = px.select(negate, diff.stack_trace_1, diff.stack_trace_2)
40+
diff.stack_trace = px.replace(' ', diff.stack_trace, '')
41+
diff.count = px.select(negate, diff.count_1, diff.count_2)
42+
diff.delta = diff.count_2 - diff.count_1
43+
diff.delta = px.select(negate, px.negate(diff.delta), diff.delta)
44+
45+
merged = diff.merge(
46+
percentage_agg,
47+
how='inner',
48+
left_on='pod',
49+
right_on='pod',
50+
suffixes=['', '_x']
51+
)
52+
merged.percent = 100 * merged.count / merged.count_x
53+
return merged
54+
55+
def differential_flamegraph(start_time: str, namespace: str, pod: str, baseline_pod: str):
56+
stack_traces = px.DataFrame(table='stack_traces.beta', start_time=start_time)
57+
stack_traces.namespace = stack_traces.ctx['namespace']
58+
stack_traces = stack_traces[stack_traces.namespace == namespace]
59+
stack_traces.node = px.Node(px._exec_hostname())
60+
stack_traces.pod = stack_traces.ctx['pod']
61+
stack_traces.keep_row = stack_traces.pod == baseline_pod
62+
stack_traces.keep_row = px.select(stack_traces.keep_row or stack_traces.pod == pod, True, False)
63+
stack_traces = stack_traces[stack_traces.keep_row]
64+
65+
stack_traces = stack_traces.groupby(['node', 'namespace', 'pod', 'stack_trace_id']).agg(
66+
stack_trace=('stack_trace', px.any),
67+
count=('count', px.sum)
68+
)
69+
70+
pod1 = stack_traces[stack_traces.pod == baseline_pod]
71+
pod1 = pod1.drop(['node', 'namespace', 'stack_trace_id'])
72+
73+
pod2 = stack_traces[stack_traces.pod == pod]
74+
pod2 = pod2.drop(['node', 'namespace', 'stack_trace_id'])
75+
76+
merged = merge_and_compute_delta(pod1, pod2, negate)
77+
return merged[['stack_trace', 'count', 'delta', 'percent', 'pod']]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
short: Differential Flame Graph
3+
long: >
4+
This live view shows a differential CPU flame graph. This is helpful in identifying what code
5+
paths have changed between deployments, different container instances, etc.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{
2+
"variables": [
3+
{
4+
"name": "start_time",
5+
"type": "PX_STRING",
6+
"description": "The relative start time of the window. Current time is assumed to be now",
7+
"defaultValue": "-5m"
8+
},
9+
{
10+
"name": "namespace",
11+
"type": "PX_NAMESPACE",
12+
"description": "The namespace to filter on."
13+
},
14+
{
15+
"name": "pod",
16+
"type": "PX_POD",
17+
"description": "The pod that will have its flamegraph analyzed compared to the baseline_pod"
18+
},
19+
{
20+
"name": "baseline_pod",
21+
"type": "PX_POD",
22+
"description": "The pod to serve as the baseline. The resulting flamegraph will show the difference from this pod's profile."
23+
}
24+
],
25+
"globalFuncs": [],
26+
"widgets": [
27+
{
28+
"name": "Flamegraph",
29+
"position": {
30+
"x": 0,
31+
"y": 0,
32+
"w": 12,
33+
"h": 6
34+
},
35+
"func": {
36+
"name": "differential_flamegraph",
37+
"args": [
38+
{
39+
"name": "start_time",
40+
"variable": "start_time"
41+
},
42+
{
43+
"name": "namespace",
44+
"variable": "namespace"
45+
},
46+
{
47+
"name": "pod",
48+
"variable": "pod"
49+
},
50+
{
51+
"name": "baseline_pod",
52+
"variable": "baseline_pod"
53+
}
54+
]
55+
},
56+
"displaySpec": {
57+
"@type": "types.px.dev/px.vispb.StackTraceFlameGraph",
58+
"stacktraceColumn": "stack_trace",
59+
"countColumn": "count",
60+
"percentageColumn": "percent",
61+
"podColumn": "pod",
62+
"differenceColumn": "delta"
63+
}
64+
}
65+
]
66+
}

0 commit comments

Comments
 (0)