Skip to content

Commit ef28c78

Browse files
author
Walter Erquinigo
committed
[tests] [trace] Add a more comprehensive test for thread trace export ctf command
Follow up on https://reviews.llvm.org/D105741 - Add new test that exhaustively checks the output file's content - Fix typos in documentation and other minor fixes Reviewed By: wallace Original Author: jj10306 Differential Revision: https://reviews.llvm.org/D107674
1 parent 5d940b7 commit ef28c78

File tree

4 files changed

+129
-48
lines changed

4 files changed

+129
-48
lines changed

lldb/source/Plugins/TraceExporter/common/TraceHTR.cpp

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ HTRBlockMetadata HTRInstructionLayer::GetMetadataByIndex(size_t index) const {
112112
func_calls[*func_name] = 1;
113113
}
114114
}
115-
return {instruction_load_address, 1, func_calls};
115+
return {instruction_load_address, 1, std::move(func_calls)};
116116
}
117117

118118
size_t HTRInstructionLayer::GetNumUnits() const {
@@ -391,20 +391,35 @@ llvm::json::Value lldb_private::toJSON(const TraceHTR &htr) {
391391
std::string load_address_hex_string(stream.str());
392392
display_name.assign(load_address_hex_string);
393393

394-
layers_as_json.emplace_back(llvm::json::Object{
395-
{"name", display_name},
396-
{"ph", "B"},
397-
{"ts", (int64_t)i},
394+
// name: load address of the first instruction of the block and the name
395+
// of the most frequently called function from the block (if applicable)
398396

399-
{"pid", (int64_t)layer_id},
400-
{"tid", (int64_t)layer_id},
401-
});
397+
// ph: the event type - 'X' for Complete events (see link to documentation
398+
// below)
399+
400+
// Since trace timestamps aren't yet supported in HTR, the ts (timestamp) is
401+
// based on the instruction's offset in the trace and the dur (duration) is
402+
// 1 since this layer contains single instructions. Using the instruction
403+
// offset and a duration of 1 oversimplifies the true timing information of
404+
// the trace, nonetheless, these approximate timestamps/durations provide an
405+
// clear visualization of the trace.
406+
407+
// ts: offset from the beginning of the trace for the first instruction in
408+
// the block
409+
410+
// dur: 1 since this layer contains single instructions.
402411

412+
// pid: the ID of the HTR layer the blocks belong to
413+
414+
// See
415+
// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.j75x71ritcoy
416+
// for documentation on the Trace Event Format
403417
layers_as_json.emplace_back(llvm::json::Object{
404-
{"ph", "E"},
405-
{"ts", (int64_t)i + 1},
418+
{"name", display_name},
419+
{"ph", "X"},
420+
{"ts", (int64_t)i},
421+
{"dur", 1},
406422
{"pid", (int64_t)layer_id},
407-
{"tid", (int64_t)layer_id},
408423
});
409424
}
410425

@@ -420,8 +435,6 @@ llvm::json::Value lldb_private::toJSON(const TraceHTR &htr) {
420435

421436
HTRBlockMetadata metadata = block.GetMetadata();
422437

423-
size_t end_ts = start_ts + metadata.GetNumInstructions();
424-
425438
llvm::Optional<llvm::StringRef> most_freq_func =
426439
metadata.GetMostFrequentlyCalledFunction();
427440
std::stringstream stream;
@@ -431,22 +444,23 @@ llvm::json::Value lldb_private::toJSON(const TraceHTR &htr) {
431444
most_freq_func ? offset_hex_string + ": " + most_freq_func->str()
432445
: offset_hex_string;
433446

447+
// Since trace timestamps aren't yet supported in HTR, the ts (timestamp)
448+
// and dur (duration) are based on the block's offset in the trace and
449+
// number of instructions in the block, respectively. Using the block
450+
// offset and the number of instructions oversimplifies the true timing
451+
// information of the trace, nonetheless, these approximate
452+
// timestamps/durations provide an understandable visualization of the
453+
// trace.
454+
auto duration = metadata.GetNumInstructions();
434455
layers_as_json.emplace_back(llvm::json::Object{
435456
{"name", display_name},
436-
{"ph", "B"},
457+
{"ph", "X"},
437458
{"ts", (int64_t)start_ts},
459+
{"dur", (int64_t)duration},
438460
{"pid", (int64_t)layer_id},
439-
{"tid", (int64_t)layer_id},
440-
});
441-
442-
layers_as_json.emplace_back(llvm::json::Object{
443-
{"ph", "E"},
444-
{"ts", (int64_t)end_ts},
445-
{"pid", (int64_t)layer_id},
446-
{"tid", (int64_t)layer_id},
447461
{"args", block_json},
448462
});
449-
start_ts = end_ts;
463+
start_ts += duration;
450464
}
451465
}
452466
return layers_as_json;

lldb/source/Plugins/TraceExporter/common/TraceHTR.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class HTRBlockMetadata {
3535
/// the block.
3636
HTRBlockMetadata(lldb::addr_t first_instruction_load_address,
3737
size_t num_instructions,
38-
llvm::DenseMap<ConstString, size_t> &func_calls)
38+
llvm::DenseMap<ConstString, size_t> &&func_calls)
3939
: m_first_instruction_load_address(first_instruction_load_address),
4040
m_num_instructions(num_instructions), m_func_calls(func_calls) {}
4141

lldb/docs/htr.rst renamed to lldb/source/Plugins/TraceExporter/docs/htr.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Concepts
1010

1111
**Layer:** The representation of trace data between passes. For Intel PT there are two types of layers:
1212

13-
**Instruction Layer:** Composed of the oad addresses of the instructions in the trace. In an effort to save space,
13+
**Instruction Layer:** Composed of the load addresses of the instructions in the trace. In an effort to save space,
1414
metadata is only stored for instructions that are of interest, not every instruction in the trace. HTR contains a
1515
single instruction layer.
1616

@@ -19,12 +19,13 @@ Concepts
1919
a block layer. HTR contains one or more block layers.
2020

2121
**Pass:** A transformation applied to a *layer* that generates a new *layer* that is a more summarized, consolidated representation of the trace data.
22-
A pass merges instructions/blocks based on its specific purpose - for example, a pass designed to summarize a processor trace by function calls would merge all the blocks of a function into a single block representing the entire function.l
22+
A pass merges instructions/blocks based on its specific purpose - for example, a pass designed to summarize a processor trace by function calls would merge all the blocks of a function into a single block representing the entire function.
2323

2424
The image below illusrates the transformation of a trace's representation (HTR)
2525

2626
.. image:: media/htr-example.png
2727

28+
2829
Passes
2930
------
3031
A *pass* is applied to a *layer* to extract useful information (summarization) and compress the trace representation into a new *layer*. The idea is to have a series of passes where each pass specializes in extracting certain information about the trace. Some examples of potential passes include: identifying functions, identifying loops, or a more general purpose such as identifying long sequences of instructions that are repeated (i.e. Basic Super Block). Below you will find a description of each pass currently implemented in lldb.

lldb/test/API/commands/trace/TestTraceExport.py

Lines changed: 88 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,96 @@ def testErrorMessages(self):
3434
substrs=["error: Process is not being traced"],
3535
error=True)
3636

37-
def testExportCreatesFile(self):
37+
38+
def testHtrBasicSuperBlockPassFullCheck(self):
39+
'''
40+
Test the BasicSuperBlock pass of HTR.
41+
42+
This test uses a very small trace so that the expected output is digestible and
43+
it's possible to manually verify the behavior of the algorithm.
44+
45+
This test exhaustively checks that each entry
46+
in the output JSON is equal to the expected value.
47+
48+
'''
49+
3850
self.expect("trace load -v " +
3951
os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"),
4052
substrs=["intel-pt"])
4153

4254
ctf_test_file = self.getBuildArtifact("ctf-test.json")
4355

44-
if os.path.exists(ctf_test_file):
45-
remove_file(ctf_test_file)
4656
self.expect(f"thread trace export ctf --file {ctf_test_file}")
4757
self.assertTrue(os.path.exists(ctf_test_file))
4858

59+
with open(ctf_test_file) as f:
60+
data = json.load(f)
4961

50-
def testHtrBasicSuperBlockPass(self):
5162
'''
52-
Test the BasicSuperBlock pass of HTR
63+
The expected JSON contained by "ctf-test.json"
64+
65+
dur: number of instructions in the block
66+
67+
name: load address of the first instruction of the block and the
68+
name of the most frequently called function from the block (if applicable)
69+
70+
ph: 'X' for Complete events (see link to documentation below)
71+
72+
pid: the ID of the HTR layer the blocks belong to
73+
74+
ts: offset from the beginning of the trace for the first instruction in the block
75+
76+
See https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.j75x71ritcoy
77+
for documentation on the Trace Event Format
78+
'''
79+
# Comments on the right indicate if a block is a "head" and/or "tail"
80+
# See BasicSuperBlockMerge in TraceHTR.h for a description of the algorithm
81+
expected = [
82+
{"dur":1,"name":"0x400511","ph":"X","pid":0,"ts":0},
83+
{"dur":1,"name":"0x400518","ph":"X","pid":0,"ts":1},
84+
{"dur":1,"name":"0x40051f","ph":"X","pid":0,"ts":2},
85+
{"dur":1,"name":"0x400529","ph":"X","pid":0,"ts":3}, # head
86+
{"dur":1,"name":"0x40052d","ph":"X","pid":0,"ts":4}, # tail
87+
{"dur":1,"name":"0x400521","ph":"X","pid":0,"ts":5},
88+
{"dur":1,"name":"0x400525","ph":"X","pid":0,"ts":6},
89+
{"dur":1,"name":"0x400529","ph":"X","pid":0,"ts":7}, # head
90+
{"dur":1,"name":"0x40052d","ph":"X","pid":0,"ts":8}, # tail
91+
{"dur":1,"name":"0x400521","ph":"X","pid":0,"ts":9},
92+
{"dur":1,"name":"0x400525","ph":"X","pid":0,"ts":10},
93+
{"dur":1,"name":"0x400529","ph":"X","pid":0,"ts":11}, # head
94+
{"dur":1,"name":"0x40052d","ph":"X","pid":0,"ts":12}, # tail
95+
{"dur":1,"name":"0x400521","ph":"X","pid":0,"ts":13},
96+
{"dur":1,"name":"0x400525","ph":"X","pid":0,"ts":14},
97+
{"dur":1,"name":"0x400529","ph":"X","pid":0,"ts":15}, # head
98+
{"dur":1,"name":"0x40052d","ph":"X","pid":0,"ts":16}, # tail
99+
{"dur":1,"name":"0x400521","ph":"X","pid":0,"ts":17},
100+
{"dur":1,"name":"0x400525","ph":"X","pid":0,"ts":18},
101+
{"dur":1,"name":"0x400529","ph":"X","pid":0,"ts":19}, # head
102+
{"dur":1,"name":"0x40052d","ph":"X","pid":0,"ts":20}, # tail
103+
{"args":{"Metadata":{"Functions":[],"Number of Instructions":3}},"dur":3,"name":"0x400511","ph":"X","pid":1,"ts":0},
104+
{"args":{"Metadata":{"Functions":[],"Number of Instructions":2}},"dur":2,"name":"0x400529","ph":"X","pid":1,"ts":3}, # head, tail
105+
{"args":{"Metadata":{"Functions":[],"Number of Instructions":2}},"dur":2,"name":"0x400521","ph":"X","pid":1,"ts":5},
106+
{"args":{"Metadata":{"Functions":[],"Number of Instructions":2}},"dur":2,"name":"0x400529","ph":"X","pid":1,"ts":7}, # head, tail
107+
{"args":{"Metadata":{"Functions":[],"Number of Instructions":2}},"dur":2,"name":"0x400521","ph":"X","pid":1,"ts":9},
108+
{"args":{"Metadata":{"Functions":[],"Number of Instructions":2}},"dur":2,"name":"0x400529","ph":"X","pid":1,"ts":11}, # head, tail
109+
{"args":{"Metadata":{"Functions":[],"Number of Instructions":2}},"dur":2,"name":"0x400521","ph":"X","pid":1,"ts":13},
110+
{"args":{"Metadata":{"Functions":[],"Number of Instructions":2}},"dur":2,"name":"0x400529","ph":"X","pid":1,"ts":15}, # head, tail
111+
{"args":{"Metadata":{"Functions":[],"Number of Instructions":2}},"dur":2,"name":"0x400521","ph":"X","pid":1,"ts":17},
112+
{"args":{"Metadata":{"Functions":[],"Number of Instructions":2}},"dur":2,"name":"0x400529","ph":"X","pid":1,"ts":19} # head, tail
113+
]
114+
115+
# Check that the length of the expected JSON array is equal to the actual
116+
self.assertTrue(len(data) == len(expected))
117+
for i in range(len(data)):
118+
# Check each individual JSON object in "ctf-test.json" against the expected value above
119+
self.assertTrue(data[i] == expected[i])
120+
121+
def testHtrBasicSuperBlockPassSequenceCheck(self):
122+
'''
123+
Test the BasicSuperBlock pass of HTR.
124+
125+
This test exports a modest sized trace and only checks that a particular sequence of blocks are
126+
expected, see `testHtrBasicSuperBlockPassFullCheck` for a more "exhaustive" test.
53127
54128
TODO: Once the "trace save" command is implemented, gather Intel PT
55129
trace of this program and load it like the other tests instead of
@@ -64,8 +138,6 @@ def testHtrBasicSuperBlockPass(self):
64138

65139
ctf_test_file = self.getBuildArtifact("ctf-test.json")
66140

67-
if os.path.exists(ctf_test_file):
68-
remove_file(ctf_test_file)
69141
self.expect(f"thread trace export ctf --file {ctf_test_file}")
70142
self.assertTrue(os.path.exists(ctf_test_file))
71143

@@ -77,18 +149,16 @@ def testHtrBasicSuperBlockPass(self):
77149
index_of_first_layer_1_block = None
78150
for i, event in enumerate(data):
79151
layer_id = event.get('pid')
152+
self.assertTrue(layer_id is not None)
80153
if layer_id == 1 and index_of_first_layer_1_block is None:
81154
index_of_first_layer_1_block = i
82-
if layer_id is not None and event['ph'] == 'B':
83-
num_units_by_layer[layer_id] += 1
84-
85-
# Check that there are two layers
86-
self.assertTrue(0 in num_units_by_layer and 1 in num_units_by_layer)
87-
# Check that each layer has the correct total number of blocks
88-
self.assertTrue(num_units_by_layer[0] == 1630)
89-
self.assertTrue(num_units_by_layer[1] == 383)
155+
num_units_by_layer[layer_id] += 1
90156

157+
# Check that there are only two layers and that the layer IDs are correct
158+
# Check that layer IDs are correct
159+
self.assertTrue(len(num_units_by_layer) == 2 and 0 in num_units_by_layer and 1 in num_units_by_layer)
91160

161+
# The expected block names for the first 7 blocks of layer 1
92162
expected_block_names = [
93163
'0x4005f0',
94164
'0x4005fe',
@@ -98,12 +168,8 @@ def testHtrBasicSuperBlockPass(self):
98168
'0x4005b9: fast_handle_request(int)',
99169
'0x4005d5: log_response(int)',
100170
]
101-
# 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.
171+
102172
data_index = index_of_first_layer_1_block
103-
expected_index = 0
104-
while expected_index < len(expected_block_names):
105-
self.assertTrue(data[data_index]['name'] == expected_block_names[expected_index])
106-
self.assertTrue(data[data_index]['name'] == expected_block_names[expected_index])
107-
data_index += 2
108-
expected_index += 1
173+
for i in range(len(expected_block_names)):
174+
self.assertTrue(data[data_index + i]['name'] == expected_block_names[i])
109175

0 commit comments

Comments
 (0)