Skip to content

Commit e08f321

Browse files
perf.core: add flame chart / flame graph analyses
Port the CTF-based perf-profiling analyses to the new perf.data trace type so the Flame Chart and Flame Graph views light up on 'perf record -g' output. New analyses (org.eclipse.tracecompass.incubator.internal.perf.core.*): - analysis.PerfDataCallchainAnalysisModule Extends ProfilingCallGraphAnalysisModule and implements ISamplingDataProvider. Reads the callchain long[] straight off the PerfRecord attached to each PERF_RECORD_SAMPLE event, strips the PERF_CONTEXT_* sentinels and reverses the chain (oldest frame first), then groups samples by process/thread. - symbol.PerfDataMmapStateProvider + PerfDataMmapAnalysisModule Tracks PERF_RECORD_MMAP / MMAP2 events and exposes /<pid>/<loadBase> -> filename so addresses can be resolved to ELF libraries. Uses addr - pgoff as the load base to match the offsets the callchain IPs will be compared against. - symbol.PerfDataMmapSymbolProvider + factory + TmfLibrarySymbol ISymbolProvider backed by the mmap state system and IMappingFile; falls back to the filename when a symbol cannot be resolved from disk. Infrastructure: - MANIFEST.MF: require o.e.tc.statesystem.core and o.e.tc.analysis.profiling.core. - plugin.xml: register the two analyses and the symbol provider factory, scoped to PerfDataTrace. - plugin.properties: names for the two analyses. - PerfDataTrace: stop stringifying long[] and byte[] fields in fieldsFromRecord so analyses can consume the primitive arrays. Tests: - Add a second fixture traces/perf-callgraph.data captured with 'perf record -g ping 4.4.4.4 -c 3'. - PerfDataCallchainTest.hasNonTrivialCallchains checks the data shape on the original wget trace. - PerfDataCallchainTest.callgraphTraceHasRealChains walks the new fixture and verifies every SAMPLE exposes a long[] callchain with non-sentinel IPs. This code was made with the assistance of Claude Opus 4.7 The commit message was 100% made by claude and is much better than I can do :) Change-Id: Ibd37279cc63ce4a0bcada7a49ec2414f4e7157d2 Signed-off-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
1 parent 3470262 commit e08f321

16 files changed

Lines changed: 916 additions & 36 deletions

File tree

tracetypes/org.eclipse.tracecompass.incubator.perf.core.tests/META-INF/MANIFEST.MF

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Require-Bundle: org.eclipse.core.runtime,
1010
org.eclipse.core.resources,
1111
org.eclipse.tracecompass.common.core,
1212
org.eclipse.tracecompass.tmf.core,
13+
org.eclipse.tracecompass.statesystem.core,
14+
org.eclipse.tracecompass.analysis.profiling.core,
1315
org.eclipse.tracecompass.incubator.perf.core,
1416
org.junit,
1517
org.eclipse.jdt.annotation;bundle-version="[2.0.0,3.0.0)";resolution:=optional
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Ericsson
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License 2.0 which
6+
* accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*******************************************************************************/
11+
12+
package org.eclipse.tracecompass.incubator.perf.core.tests;
13+
14+
import static org.junit.Assert.assertNotNull;
15+
import static org.junit.Assert.assertTrue;
16+
17+
import java.io.File;
18+
import java.io.IOException;
19+
20+
import org.eclipse.tracecompass.incubator.internal.perf.core.PerfConstants;
21+
import org.eclipse.tracecompass.incubator.internal.perf.core.PerfDataReader;
22+
import org.eclipse.tracecompass.incubator.internal.perf.core.PerfRecord;
23+
import org.junit.Test;
24+
25+
/**
26+
* Structural test for the callchain data exposed by {@link PerfDataReader}.
27+
*
28+
* The callchain analysis module expects each {@code PERF_RECORD_SAMPLE}
29+
* record to carry a {@code callchain} {@code long[]} in its fields. This
30+
* test walks the bundled sample file and verifies that assumption so we
31+
* don't silently regress the data shape that the analysis depends on.
32+
*/
33+
public class PerfDataCallchainTest {
34+
35+
/**
36+
* Verify that the bundled trace has {@code PERF_RECORD_SAMPLE} records
37+
* and that, when callchains are present, they carry non-sentinel IPs.
38+
*
39+
* The bundled sample was captured without {@code -g}, so every SAMPLE
40+
* has an empty callchain. We still exercise the code path and make
41+
* sure the data shape (a {@code long[]} in the {@code callchain}
42+
* field, or its absence) matches what the analysis module expects.
43+
*
44+
* @throws IOException
45+
* on I/O error
46+
*/
47+
@Test
48+
public void hasNonTrivialCallchains() throws IOException {
49+
File file = PerfDataTraceTest.resolveTraceFile();
50+
try (PerfDataReader reader = new PerfDataReader(file)) {
51+
long off = reader.getDataOffset();
52+
long end = off + reader.getDataSize();
53+
int samples = 0;
54+
int samplesWithChain = 0;
55+
int realIps = 0;
56+
while (off < end) {
57+
PerfRecord rec = reader.readRecordAt(off);
58+
if (rec == null) {
59+
break;
60+
}
61+
if (rec.getType() == PerfConstants.PERF_RECORD_SAMPLE) {
62+
samples++;
63+
Object cc = rec.getField("callchain"); //$NON-NLS-1$
64+
if (cc instanceof long[]) {
65+
long[] chain = (long[]) cc;
66+
if (chain.length > 0) {
67+
samplesWithChain++;
68+
}
69+
for (long ip : chain) {
70+
// PERF_CONTEXT_* sentinels live in the top 128 bytes of the address space
71+
if (Long.compareUnsigned(ip, 0xffffffffffffff80L) < 0 && ip != 0) {
72+
realIps++;
73+
}
74+
}
75+
}
76+
}
77+
off += rec.getSize();
78+
}
79+
assertNotNull(file);
80+
assertTrue("expected some SAMPLE records, got " + samples, samples > 0); //$NON-NLS-1$
81+
// Callchains are only present when perf was run with -g. If any
82+
// are present in the bundled trace, at least one must contain a
83+
// non-sentinel IP.
84+
if (samplesWithChain > 0) {
85+
assertTrue("expected non-sentinel IPs in callchains, got " + realIps, //$NON-NLS-1$
86+
realIps > 0);
87+
}
88+
}
89+
}
90+
91+
/**
92+
* The second bundled trace ({@code traces/perf-callgraph.data}) was
93+
* captured with {@code perf record -g ping 4.4.4.4 -c 3} so every
94+
* SAMPLE carries a callchain. Make sure the reader exposes it as a
95+
* {@code long[]} containing at least one real IP.
96+
*
97+
* @throws IOException
98+
* on I/O error
99+
*/
100+
@Test
101+
public void callgraphTraceHasRealChains() throws IOException {
102+
File file = PerfDataTraceTest.resolveCallgraphTraceFile();
103+
if (!file.isFile()) {
104+
// Bundled only in the source tree; skip gracefully when the
105+
// test is run from an unusual working directory.
106+
return;
107+
}
108+
try (PerfDataReader reader = new PerfDataReader(file)) {
109+
long off = reader.getDataOffset();
110+
long end = off + reader.getDataSize();
111+
int samples = 0;
112+
int samplesWithChain = 0;
113+
int realIps = 0;
114+
while (off < end) {
115+
PerfRecord rec = reader.readRecordAt(off);
116+
if (rec == null) {
117+
break;
118+
}
119+
if (rec.getType() == PerfConstants.PERF_RECORD_SAMPLE) {
120+
samples++;
121+
Object cc = rec.getField("callchain"); //$NON-NLS-1$
122+
if (cc instanceof long[]) {
123+
long[] chain = (long[]) cc;
124+
if (chain.length > 0) {
125+
samplesWithChain++;
126+
}
127+
for (long ip : chain) {
128+
if (Long.compareUnsigned(ip, 0xffffffffffffff80L) < 0 && ip != 0) {
129+
realIps++;
130+
}
131+
}
132+
}
133+
}
134+
off += rec.getSize();
135+
}
136+
assertNotNull(file);
137+
assertTrue("expected samples, got " + samples, samples > 0); //$NON-NLS-1$
138+
assertTrue("expected callchains on every sample, got " //$NON-NLS-1$
139+
+ samplesWithChain + "/" + samples, //$NON-NLS-1$
140+
samplesWithChain == samples && samples > 0);
141+
assertTrue("expected non-sentinel IPs, got " + realIps, realIps > samples); //$NON-NLS-1$
142+
}
143+
}
144+
}

tracetypes/org.eclipse.tracecompass.incubator.perf.core.tests/src/org/eclipse/tracecompass/incubator/perf/core/tests/PerfDataTraceTest.java

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ public class PerfDataTraceTest {
4141
/** Relative path to the sample perf.data file inside this bundle. */
4242
public static final String TRACE_PATH = "traces/perf.data"; //$NON-NLS-1$
4343

44+
/** Relative path to a perf.data captured with call graphs. */
45+
public static final String CALLGRAPH_TRACE_PATH = "traces/perf-callgraph.data"; //$NON-NLS-1$
46+
4447
private PerfDataTrace fTrace;
4548

4649
/**
@@ -123,9 +126,10 @@ public void mmap2DecodesFilenameAndTimestamp() throws IOException {
123126
}
124127
if (rec.getType() == 10 /* PERF_RECORD_MMAP2 */) {
125128
mmap2++;
126-
assertNotNull("filename", rec.getField("filename")); //$NON-NLS-1$ //$NON-NLS-2$
129+
Object field = rec.getField("filename");
130+
assertNotNull("filename", field); //$NON-NLS-1$ //$NON-NLS-2$
127131
assertTrue("filename non-empty", //$NON-NLS-1$
128-
!((String) rec.getField("filename")).isEmpty()); //$NON-NLS-1$
132+
!((String) field).isEmpty()); //$NON-NLS-1$
129133
// prot should fit in a few bits (e.g. PROT_READ|WRITE|EXEC).
130134
Object prot = rec.getField("prot"); //$NON-NLS-1$
131135
assertTrue("prot must be a small integer", //$NON-NLS-1$
@@ -169,19 +173,38 @@ public void readTraceEvents() throws TmfTraceException {
169173
assertTrue("expected at least one event, got " + count, count > 0); //$NON-NLS-1$
170174
}
171175

176+
/**
177+
* Resolve the bundled trace file. Exposed package-visible so sibling
178+
* tests can reuse the lookup.
179+
*
180+
* @return the sample perf.data path
181+
*/
182+
static File resolveTraceFile() {
183+
return resolveTrace();
184+
}
185+
186+
/**
187+
* Resolve the bundled perf.data that was recorded with {@code -g}.
188+
*
189+
* @return the path to the callgraph-enabled sample perf.data
190+
*/
191+
static File resolveCallgraphTraceFile() {
192+
return resolvePath(CALLGRAPH_TRACE_PATH);
193+
}
194+
172195
private static File resolveTrace() {
173-
// When running inside the OSGi runtime, the bundle activator resolves
174-
// resource URLs. Outside of it (plain mvn-surefire), walk up from the
175-
// current working directory to locate the bundle folder.
176-
File direct = new File(TRACE_PATH);
196+
return resolvePath(TRACE_PATH);
197+
}
198+
199+
private static File resolvePath(String rel) {
200+
File direct = new File(rel);
177201
if (direct.isFile()) {
178202
return direct;
179203
}
180-
File bundleRel = new File("org.eclipse.tracecompass.incubator.perf.core.tests", TRACE_PATH); //$NON-NLS-1$
204+
File bundleRel = new File("org.eclipse.tracecompass.incubator.perf.core.tests", rel); //$NON-NLS-1$
181205
if (bundleRel.isFile()) {
182206
return bundleRel;
183207
}
184-
File fallback = new File("../org.eclipse.tracecompass.incubator.perf.core.tests", TRACE_PATH); //$NON-NLS-1$
185-
return fallback;
208+
return new File("../org.eclipse.tracecompass.incubator.perf.core.tests", rel); //$NON-NLS-1$
186209
}
187210
}
Binary file not shown.

tracetypes/org.eclipse.tracecompass.incubator.perf.core/META-INF/MANIFEST.MF

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@ Require-Bundle: org.eclipse.core.runtime,
1212
org.eclipse.core.resources,
1313
org.eclipse.tracecompass.common.core,
1414
org.eclipse.tracecompass.tmf.core,
15+
org.eclipse.tracecompass.statesystem.core,
16+
org.eclipse.tracecompass.analysis.profiling.core,
1517
org.eclipse.jdt.annotation;bundle-version="[2.0.0,3.0.0)";resolution:=optional
1618
Export-Package: org.eclipse.tracecompass.incubator.internal.perf.core;x-friends:="org.eclipse.tracecompass.incubator.perf.core.tests",
19+
org.eclipse.tracecompass.incubator.internal.perf.core.analysis;x-internal:=true,
20+
org.eclipse.tracecompass.incubator.internal.perf.core.symbol;x-internal:=true,
1721
org.eclipse.tracecompass.incubator.internal.perf.core.trace;x-friends:="org.eclipse.tracecompass.incubator.perf.core.tests"
1822
Import-Package: com.google.common.collect
1923
Automatic-Module-Name: org.eclipse.tracecompass.incubator.perf.core

tracetypes/org.eclipse.tracecompass.incubator.perf.core/plugin.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ Bundle-Name = Trace Compass perf.data Core Plug-in (Incubator)
1414

1515
perfdata.name = Perf Data
1616
perfdata.category.name = Perf
17+
perfdata.analysis.callchain = Perf Callchain Sampling
18+
perfdata.analysis.mmap = Perf MMap Symbol Resolution

tracetypes/org.eclipse.tracecompass.incubator.perf.core/plugin.xml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,35 @@
1515
trace_type="org.eclipse.tracecompass.incubator.internal.perf.core.trace.PerfDataTrace">
1616
</type>
1717
</extension>
18+
<extension
19+
point="org.eclipse.linuxtools.tmf.core.analysis">
20+
<module
21+
analysis_module="org.eclipse.tracecompass.incubator.internal.perf.core.analysis.PerfDataCallchainAnalysisModule"
22+
automatic="false"
23+
id="org.eclipse.tracecompass.incubator.perf.core.callchain"
24+
name="%perfdata.analysis.callchain">
25+
<tracetype
26+
applies="true"
27+
class="org.eclipse.tracecompass.incubator.internal.perf.core.trace.PerfDataTrace">
28+
</tracetype>
29+
</module>
30+
<module
31+
analysis_module="org.eclipse.tracecompass.incubator.internal.perf.core.symbol.PerfDataMmapAnalysisModule"
32+
automatic="true"
33+
id="org.eclipse.tracecompass.incubator.perf.core.symbol.mmap"
34+
name="%perfdata.analysis.mmap">
35+
<tracetype
36+
applies="true"
37+
class="org.eclipse.tracecompass.incubator.internal.perf.core.trace.PerfDataTrace">
38+
</tracetype>
39+
</module>
40+
</extension>
41+
<extension
42+
point="org.eclipse.tracecompass.tmf.core.symbolProvider">
43+
<providerFactory
44+
class="org.eclipse.tracecompass.incubator.internal.perf.core.symbol.PerfDataMmapSymbolProviderFactory"
45+
id="org.eclipse.tracecompass.incubator.perf.core.perfmmap"
46+
priority="75">
47+
</providerFactory>
48+
</extension>
1849
</plugin>

0 commit comments

Comments
 (0)