This diff introduces Hierarchical Trace Representation (HTR) and creates the `thread trace export ctf -f <filename> -t <thread_id>` command to export an Intel PT trace's HTR to Chrome Trace Format (CTF) for visualization.
See `lldb/docs/htr.rst` for context/documentation on HTR.
**Overview of Changes**
- Add HTR documentation (see `lldb/docs/htr.rst`)
- Add HTR structures (layer, block, block metadata)
- Implement "Basic Super Block" HTR pass
- Add 'thread trace export ctf' command to export the HTR of an Intel PT
trace to Chrome Trace Format (CTF)
As this diff is the first iteration of HTR and trace visualization, future diffs will build on this work by generalizing the internal design of HTR and implementing new HTR passes that provide better trace summarization/visualization.
See attached video for an example of Intel PT trace visualization:
{F17851042}
Original Author: jj10306
Submitted by: wallace
Reviewed By: wallace, clayborg
Differential Revision: https://reviews.llvm.org/D105741
110 lines
4.2 KiB
Python
110 lines
4.2 KiB
Python
from collections import defaultdict
|
|
import lldb
|
|
import json
|
|
from intelpt_testcase import *
|
|
from lldbsuite.test.lldbtest import *
|
|
from lldbsuite.test import lldbutil
|
|
from lldbsuite.test.decorators import *
|
|
import os
|
|
|
|
class TestTraceExport(TraceIntelPTTestCaseBase):
|
|
|
|
mydir = TestBase.compute_mydir(__file__)
|
|
|
|
def testErrorMessages(self):
|
|
ctf_test_file = self.getBuildArtifact("ctf-test.json")
|
|
# We first check the output when there are no targets
|
|
self.expect(f"thread trace export ctf --file {ctf_test_file}",
|
|
substrs=["error: invalid target, create a target using the 'target create' command"],
|
|
error=True)
|
|
|
|
# We now check the output when there's a non-running target
|
|
self.expect("target create " +
|
|
os.path.join(self.getSourceDir(), "intelpt-trace", "a.out"))
|
|
|
|
self.expect(f"thread trace export ctf --file {ctf_test_file}",
|
|
substrs=["error: invalid process"],
|
|
error=True)
|
|
|
|
# Now we check the output when there's a running target without a trace
|
|
self.expect("b main")
|
|
self.expect("run")
|
|
|
|
self.expect(f"thread trace export ctf --file {ctf_test_file}",
|
|
substrs=["error: Process is not being traced"],
|
|
error=True)
|
|
|
|
def testExportCreatesFile(self):
|
|
self.expect("trace load -v " +
|
|
os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"),
|
|
substrs=["intel-pt"])
|
|
|
|
ctf_test_file = self.getBuildArtifact("ctf-test.json")
|
|
|
|
if os.path.exists(ctf_test_file):
|
|
remove_file(ctf_test_file)
|
|
self.expect(f"thread trace export ctf --file {ctf_test_file}")
|
|
self.assertTrue(os.path.exists(ctf_test_file))
|
|
|
|
|
|
def testHtrBasicSuperBlockPass(self):
|
|
'''
|
|
Test the BasicSuperBlock pass of HTR
|
|
|
|
TODO: Once the "trace save" command is implemented, gather Intel PT
|
|
trace of this program and load it like the other tests instead of
|
|
manually executing the commands to trace the program.
|
|
'''
|
|
self.expect(f"target create {os.path.join(self.getSourceDir(), 'intelpt-trace', 'export_ctf_test_program.out')}")
|
|
self.expect("b main")
|
|
self.expect("r")
|
|
self.expect("b exit")
|
|
self.expect("thread trace start")
|
|
self.expect("c")
|
|
|
|
ctf_test_file = self.getBuildArtifact("ctf-test.json")
|
|
|
|
if os.path.exists(ctf_test_file):
|
|
remove_file(ctf_test_file)
|
|
self.expect(f"thread trace export ctf --file {ctf_test_file}")
|
|
self.assertTrue(os.path.exists(ctf_test_file))
|
|
|
|
|
|
with open(ctf_test_file) as f:
|
|
data = json.load(f)
|
|
|
|
num_units_by_layer = defaultdict(int)
|
|
index_of_first_layer_1_block = None
|
|
for i, event in enumerate(data):
|
|
layer_id = event.get('pid')
|
|
if layer_id == 1 and index_of_first_layer_1_block is None:
|
|
index_of_first_layer_1_block = i
|
|
if layer_id is not None and event['ph'] == 'B':
|
|
num_units_by_layer[layer_id] += 1
|
|
|
|
# Check that there are two layers
|
|
self.assertTrue(0 in num_units_by_layer and 1 in num_units_by_layer)
|
|
# Check that each layer has the correct total number of blocks
|
|
self.assertTrue(num_units_by_layer[0] == 1630)
|
|
self.assertTrue(num_units_by_layer[1] == 383)
|
|
|
|
|
|
expected_block_names = [
|
|
'0x4005f0',
|
|
'0x4005fe',
|
|
'0x400606: iterative_handle_request_by_id(int, int)',
|
|
'0x4005a7',
|
|
'0x4005af',
|
|
'0x4005b9: fast_handle_request(int)',
|
|
'0x4005d5: log_response(int)',
|
|
]
|
|
# There are two events per block, a beginning and an end. This means we must increment data_index by 2, so we only encounter the beginning event of each block.
|
|
data_index = index_of_first_layer_1_block
|
|
expected_index = 0
|
|
while expected_index < len(expected_block_names):
|
|
self.assertTrue(data[data_index]['name'] == expected_block_names[expected_index])
|
|
self.assertTrue(data[data_index]['name'] == expected_block_names[expected_index])
|
|
data_index += 2
|
|
expected_index += 1
|
|
|