xref: /llvm-project/lldb/test/API/commands/trace/TestTraceExport.py (revision 9c2468821ec51defd09c246fea4a47886fff8c01)
1from collections import defaultdict
2import lldb
3import json
4from intelpt_testcase import *
5from lldbsuite.test.lldbtest import *
6from lldbsuite.test import lldbutil
7from lldbsuite.test.decorators import *
8import os
9
10
11class TestTraceExport(TraceIntelPTTestCaseBase):
12    def testErrorMessages(self):
13        ctf_test_file = self.getBuildArtifact("ctf-test.json")
14        # We first check the output when there are no targets
15        self.expect(
16            f"thread trace export ctf --file {ctf_test_file}",
17            substrs=[
18                "error: invalid target, create a target using the 'target create' command"
19            ],
20            error=True,
21        )
22
23        # We now check the output when there's a non-running target
24        self.expect(
25            "target create "
26            + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out")
27        )
28
29        self.expect(
30            f"thread trace export ctf --file {ctf_test_file}",
31            substrs=["error: Command requires a current process."],
32            error=True,
33        )
34
35        # Now we check the output when there's a running target without a trace
36        self.expect("b main")
37        self.expect("run")
38
39        self.expect(
40            f"thread trace export ctf --file {ctf_test_file}",
41            substrs=["error: Process is not being traced"],
42            error=True,
43        )
44
45    def _testHtrBasicSuperBlockPassFullCheck(self):
46        """
47        Test the BasicSuperBlock pass of HTR.
48
49        This test uses a very small trace so that the expected output is digestible and
50        it's possible to manually verify the behavior of the algorithm.
51
52        This test exhaustively checks that each entry
53        in the output JSON is equal to the expected value.
54
55        """
56
57        self.expect(
58            "trace load -v "
59            + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"),
60            substrs=["intel-pt"],
61        )
62
63        ctf_test_file = self.getBuildArtifact("ctf-test.json")
64
65        self.expect(f"thread trace export ctf --file {ctf_test_file}")
66        self.assertTrue(os.path.exists(ctf_test_file))
67
68        with open(ctf_test_file) as f:
69            data = json.load(f)
70
71        """
72        The expected JSON contained by "ctf-test.json"
73
74        dur: number of instructions in the block
75
76        name: load address of the first instruction of the block and the
77        name of the most frequently called function from the block (if applicable)
78
79        ph: 'X' for Complete events (see link to documentation below)
80
81        pid: the ID of the HTR layer the blocks belong to
82
83        ts: offset from the beginning of the trace for the first instruction in the block
84
85        See https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.j75x71ritcoy
86        for documentation on the Trace Event Format
87        """
88        # Comments on the right indicate if a block is a "head" and/or "tail"
89        # See BasicSuperBlockMerge in TraceHTR.h for a description of the algorithm
90        expected = [
91            {"dur": 1, "name": "0x400511", "ph": "X", "pid": 0, "ts": 0},
92            {"dur": 1, "name": "0x400518", "ph": "X", "pid": 0, "ts": 1},
93            {"dur": 1, "name": "0x40051f", "ph": "X", "pid": 0, "ts": 2},
94            {"dur": 1, "name": "0x400529", "ph": "X", "pid": 0, "ts": 3},  # head
95            {"dur": 1, "name": "0x40052d", "ph": "X", "pid": 0, "ts": 4},  # tail
96            {"dur": 1, "name": "0x400521", "ph": "X", "pid": 0, "ts": 5},
97            {"dur": 1, "name": "0x400525", "ph": "X", "pid": 0, "ts": 6},
98            {"dur": 1, "name": "0x400529", "ph": "X", "pid": 0, "ts": 7},  # head
99            {"dur": 1, "name": "0x40052d", "ph": "X", "pid": 0, "ts": 8},  # tail
100            {"dur": 1, "name": "0x400521", "ph": "X", "pid": 0, "ts": 9},
101            {"dur": 1, "name": "0x400525", "ph": "X", "pid": 0, "ts": 10},
102            {"dur": 1, "name": "0x400529", "ph": "X", "pid": 0, "ts": 11},  # head
103            {"dur": 1, "name": "0x40052d", "ph": "X", "pid": 0, "ts": 12},  # tail
104            {"dur": 1, "name": "0x400521", "ph": "X", "pid": 0, "ts": 13},
105            {"dur": 1, "name": "0x400525", "ph": "X", "pid": 0, "ts": 14},
106            {"dur": 1, "name": "0x400529", "ph": "X", "pid": 0, "ts": 15},  # head
107            {"dur": 1, "name": "0x40052d", "ph": "X", "pid": 0, "ts": 16},  # tail
108            {"dur": 1, "name": "0x400521", "ph": "X", "pid": 0, "ts": 17},
109            {"dur": 1, "name": "0x400525", "ph": "X", "pid": 0, "ts": 18},
110            {"dur": 1, "name": "0x400529", "ph": "X", "pid": 0, "ts": 19},  # head
111            {"dur": 1, "name": "0x40052d", "ph": "X", "pid": 0, "ts": 20},  # tail
112            {
113                "args": {"Metadata": {"Functions": [], "Number of Instructions": 3}},
114                "dur": 3,
115                "name": "0x400511",
116                "ph": "X",
117                "pid": 1,
118                "ts": 0,
119            },
120            {
121                "args": {"Metadata": {"Functions": [], "Number of Instructions": 2}},
122                "dur": 2,
123                "name": "0x400529",
124                "ph": "X",
125                "pid": 1,
126                "ts": 3,
127            },  # head, tail
128            {
129                "args": {"Metadata": {"Functions": [], "Number of Instructions": 2}},
130                "dur": 2,
131                "name": "0x400521",
132                "ph": "X",
133                "pid": 1,
134                "ts": 5,
135            },
136            {
137                "args": {"Metadata": {"Functions": [], "Number of Instructions": 2}},
138                "dur": 2,
139                "name": "0x400529",
140                "ph": "X",
141                "pid": 1,
142                "ts": 7,
143            },  # head, tail
144            {
145                "args": {"Metadata": {"Functions": [], "Number of Instructions": 2}},
146                "dur": 2,
147                "name": "0x400521",
148                "ph": "X",
149                "pid": 1,
150                "ts": 9,
151            },
152            {
153                "args": {"Metadata": {"Functions": [], "Number of Instructions": 2}},
154                "dur": 2,
155                "name": "0x400529",
156                "ph": "X",
157                "pid": 1,
158                "ts": 11,
159            },  # head, tail
160            {
161                "args": {"Metadata": {"Functions": [], "Number of Instructions": 2}},
162                "dur": 2,
163                "name": "0x400521",
164                "ph": "X",
165                "pid": 1,
166                "ts": 13,
167            },
168            {
169                "args": {"Metadata": {"Functions": [], "Number of Instructions": 2}},
170                "dur": 2,
171                "name": "0x400529",
172                "ph": "X",
173                "pid": 1,
174                "ts": 15,
175            },  # head, tail
176            {
177                "args": {"Metadata": {"Functions": [], "Number of Instructions": 2}},
178                "dur": 2,
179                "name": "0x400521",
180                "ph": "X",
181                "pid": 1,
182                "ts": 17,
183            },
184            {
185                "args": {"Metadata": {"Functions": [], "Number of Instructions": 2}},
186                "dur": 2,
187                "name": "0x400529",
188                "ph": "X",
189                "pid": 1,
190                "ts": 19,
191            },  # head, tail
192        ]
193
194        # Check that the length of the expected JSON array is equal to the actual
195        self.assertEqual(len(data), len(expected))
196        for i in range(len(data)):
197            # Check each individual JSON object in "ctf-test.json" against the expected value above
198            self.assertEqual(data[i], expected[i])
199
200    def _testHtrBasicSuperBlockPassSequenceCheck(self):
201        """
202        Test the BasicSuperBlock pass of HTR.
203
204        This test exports a modest sized trace and only checks that a particular sequence of blocks are
205        expected, see `testHtrBasicSuperBlockPassFullCheck` for a more "exhaustive" test.
206
207        TODO: Once the "trace save" command is implemented, gather Intel PT
208        trace of this program and load it like the other tests instead of
209        manually executing the commands to trace the program.
210        """
211        self.expect(
212            f"target create {os.path.join(self.getSourceDir(), 'intelpt-trace', 'export_ctf_test_program.out')}"
213        )
214        self.expect("b main")
215        self.expect("r")
216        self.expect("b exit")
217        self.expect("thread trace start")
218        self.expect("c")
219
220        ctf_test_file = self.getBuildArtifact("ctf-test.json")
221
222        self.expect(f"thread trace export ctf --file {ctf_test_file}")
223        self.assertTrue(os.path.exists(ctf_test_file))
224
225        with open(ctf_test_file) as f:
226            data = json.load(f)
227
228        num_units_by_layer = defaultdict(int)
229        index_of_first_layer_1_block = None
230        for i, event in enumerate(data):
231            layer_id = event.get("pid")
232            self.assertIsNotNone(layer_id)
233            if layer_id == 1 and index_of_first_layer_1_block is None:
234                index_of_first_layer_1_block = i
235            num_units_by_layer[layer_id] += 1
236
237        # Check that there are only two layers and that the layer IDs are correct
238        # Check that layer IDs are correct
239        self.assertTrue(
240            len(num_units_by_layer) == 2
241            and 0 in num_units_by_layer
242            and 1 in num_units_by_layer
243        )
244
245        # The expected block names for the first 7 blocks of layer 1
246        expected_block_names = [
247            "0x4005f0",
248            "0x4005fe",
249            "0x400606: iterative_handle_request_by_id(int, int)",
250            "0x4005a7",
251            "0x4005af",
252            "0x4005b9: fast_handle_request(int)",
253            "0x4005d5: log_response(int)",
254        ]
255
256        data_index = index_of_first_layer_1_block
257        for i in range(len(expected_block_names)):
258            self.assertEqual(data[data_index + i]["name"], expected_block_names[i])
259