xref: /llvm-project/lldb/test/API/commands/trace/TestTraceLoad.py (revision a50ea2f76f993f65c8756067f7ad5a21e560b0c9)
1import lldb
2from intelpt_testcase import *
3from lldbsuite.test.lldbtest import *
4from lldbsuite.test import lldbutil
5from lldbsuite.test.decorators import *
6
7
8class TestTraceLoad(TraceIntelPTTestCaseBase):
9    NO_DEBUG_INFO_TESTCASE = True
10
11    @testSBAPIAndCommands
12    def testLoadMultiCoreTrace(self):
13        src_dir = self.getSourceDir()
14        trace_description_file_path = os.path.join(
15            src_dir, "intelpt-multi-core-trace", "trace.json"
16        )
17        self.traceLoad(
18            traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"]
19        )
20        self.expect(
21            "thread trace dump instructions 2 -t",
22            substrs=[
23                "19526: [19691636.212 ns] (error) decoding truncated: TSC 40450075478109270 exceeds maximum TSC value 40450075477704372, will skip decoding the remaining data of the PSB (skipping 774 of 825 bytes)",
24                "m.out`foo() + 65 at multi_thread.cpp:12:21",
25                "9524: [19691632.221 ns] 0x0000000000400ba7    jg     0x400bb3",
26            ],
27        )
28        self.expect(
29            "thread trace dump instructions 3 -t",
30            substrs=[
31                "61831: [19736132.088 ns] 0x0000000000400bd7    addl   $0x1, -0x4(%rbp)",
32                "m.out`bar() + 26 at multi_thread.cpp:20:6",
33            ],
34        )
35
36        self.expect(
37            "thread trace dump info --json",
38            substrs=[
39                """{
40  "traceTechnology": "intel-pt",
41  "threadStats": {
42    "tid": 3497234,
43    "traceItemsCount": 0,
44    "memoryUsage": {
45      "totalInBytes": "0",
46      "avgPerItemInBytes": null
47    },
48    "timingInSeconds": {
49      "Decoding instructions": """,
50                """
51    },
52    "events": {
53      "totalCount": 0,
54      "individualCounts": {}
55    },
56    "errors": {
57      "totalCount": 0,
58      "libiptErrors": {},
59      "fatalErrors": 0,
60      "otherErrors": 0
61    },
62    "continuousExecutions": 0,
63    "PSBBlocks": 0
64  },
65  "globalStats": {
66    "timingInSeconds": {
67      "Context switch and Intel PT traces correlation": 0
68    },
69    "totalUnattributedPSBBlocks": 0,
70    "totalCountinuosExecutions": 153,
71    "totalPSBBlocks": 5,
72    "totalContinuousExecutions": 153
73  }
74}""",
75            ],
76        )
77
78        self.expect(
79            "thread trace dump info 2 --json",
80            substrs=[
81                """{
82  "traceTechnology": "intel-pt",
83  "threadStats": {
84    "tid": 3497496,
85    "traceItemsCount": 19527,""",
86                """},
87    "timingInSeconds": {
88      "Decoding instructions": """,
89                """
90    },
91    "events": {
92      "totalCount": 5,
93      "individualCounts": {
94        "software disabled tracing": 1,
95        "trace synchronization point": 1,
96        "CPU core changed": 1,
97        "HW clock tick": 2
98      }
99    },
100    "errors": {
101      "totalCount": 1,
102      "libiptErrors": {},
103      "fatalErrors": 0,
104      "otherErrors": 1
105    },
106    "continuousExecutions": 1,
107    "PSBBlocks": 1
108  },
109  "globalStats": {
110    "timingInSeconds": {
111      "Context switch and Intel PT traces correlation": 0""",
112                """},
113    "totalUnattributedPSBBlocks": 0,
114    "totalCountinuosExecutions": 153,
115    "totalPSBBlocks": 5,
116    "totalContinuousExecutions": 153
117  }
118}""",
119            ],
120        )
121
122    @testSBAPIAndCommands
123    def testLoadCompactMultiCoreTrace(self):
124        src_dir = self.getSourceDir()
125        trace_description_file_path = os.path.join(
126            src_dir, "intelpt-multi-core-trace", "trace.json"
127        )
128        self.traceLoad(
129            traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"]
130        )
131
132        self.expect(
133            "thread trace dump info 2",
134            substrs=["Total number of continuous executions found: 153"],
135        )
136
137        # we'll save the trace in compact format
138        compact_trace_bundle_dir = os.path.join(
139            self.getBuildDir(), "intelpt-multi-core-trace-compact"
140        )
141        self.traceSave(compact_trace_bundle_dir, compact=True)
142
143        # we'll delete the previous target and make sure it's trace object is deleted
144        self.dbg.DeleteTarget(self.dbg.GetTargetAtIndex(0))
145        self.expect(
146            "thread trace dump instructions 2 -t",
147            substrs=["error: invalid target"],
148            error=True,
149        )
150
151        # we'll load the compact trace and make sure it works
152        self.traceLoad(
153            os.path.join(compact_trace_bundle_dir, "trace.json"), substrs=["intel-pt"]
154        )
155        self.expect(
156            "thread trace dump instructions 2 -t",
157            substrs=[
158                "19526: [19691636.212 ns] (error)",
159                "m.out`foo() + 65 at multi_thread.cpp:12:21",
160                "19524: [19691632.221 ns] 0x0000000000400ba7    jg     0x400bb3",
161            ],
162        )
163        self.expect(
164            "thread trace dump instructions 3 -t",
165            substrs=[
166                "61833: [19736136.079 ns] (error)",
167                "61831: [19736132.088 ns] 0x0000000000400bd7    addl   $0x1, -0x4(%rbp)",
168                "m.out`bar() + 26 at multi_thread.cpp:20:6",
169            ],
170        )
171
172        # This reduced the number of continuous executions to look at
173        self.expect(
174            "thread trace dump info 2",
175            substrs=["Total number of continuous executions found: 3"],
176        )
177
178        # We clean up for the next run of this test
179        self.dbg.DeleteTarget(self.dbg.GetTargetAtIndex(0))
180
181    @testSBAPIAndCommands
182    def testLoadMultiCoreTraceWithStringNumbers(self):
183        src_dir = self.getSourceDir()
184        trace_description_file_path = os.path.join(
185            src_dir, "intelpt-multi-core-trace", "trace_with_string_numbers.json"
186        )
187        self.traceLoad(
188            traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"]
189        )
190        self.expect(
191            "thread trace dump instructions 2 -t",
192            substrs=[
193                "19526: [19691636.212 ns] (error)",
194                "m.out`foo() + 65 at multi_thread.cpp:12:21",
195                "19524: [19691632.221 ns] 0x0000000000400ba7    jg     0x400bb3",
196            ],
197        )
198        self.expect(
199            "thread trace dump instructions 3 -t",
200            substrs=[
201                "61831: [19736132.088 ns] 0x0000000000400bd7    addl   $0x1, -0x4(%rbp)",
202                "m.out`bar() + 26 at multi_thread.cpp:20:6",
203            ],
204        )
205
206    @testSBAPIAndCommands
207    def testLoadMultiCoreTraceWithMissingThreads(self):
208        src_dir = self.getSourceDir()
209        trace_description_file_path = os.path.join(
210            src_dir, "intelpt-multi-core-trace", "trace_missing_threads.json"
211        )
212        self.traceLoad(
213            traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"]
214        )
215        self.expect(
216            "thread trace dump instructions 3 -t",
217            substrs=[
218                "19526: [19691636.212 ns] (error)",
219                "m.out`foo() + 65 at multi_thread.cpp:12:21",
220                "19524: [19691632.221 ns] 0x0000000000400ba7    jg     0x400bb3",
221            ],
222        )
223        self.expect(
224            "thread trace dump instructions 2 -t",
225            substrs=[
226                "61831: [19736132.088 ns] 0x0000000000400bd7    addl   $0x1, -0x4(%rbp)",
227                "m.out`bar() + 26 at multi_thread.cpp:20:6",
228            ],
229        )
230
231    @testSBAPIAndCommands
232    def testLoadTrace(self):
233        src_dir = self.getSourceDir()
234        trace_description_file_path = os.path.join(
235            src_dir, "intelpt-trace", "trace.json"
236        )
237        self.traceLoad(
238            traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"]
239        )
240
241        target = self.dbg.GetSelectedTarget()
242        process = target.GetProcess()
243        self.assertEqual(process.GetProcessID(), 1234)
244
245        self.assertEqual(process.GetNumThreads(), 1)
246        self.assertEqual(process.GetThreadAtIndex(0).GetThreadID(), 3842849)
247
248        self.assertEqual(target.GetNumModules(), 1)
249        module = target.GetModuleAtIndex(0)
250        path = module.GetFileSpec()
251        self.assertEqual(path.fullpath, os.path.join(src_dir, "intelpt-trace", "a.out"))
252        self.assertGreater(module.GetNumSections(), 0)
253        self.assertEqual(module.GetSectionAtIndex(0).GetFileAddress(), 0x400000)
254
255        self.assertEqual(
256            "6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A", module.GetUUIDString()
257        )
258
259        # check that the Process and Thread objects were created correctly
260        self.expect("thread info", substrs=["tid = 3842849"])
261        self.expect("thread list", substrs=["Process 1234 stopped", "tid = 3842849"])
262        self.expect(
263            "thread trace dump info",
264            substrs=[
265                """thread #1: tid = 3842849
266
267  Trace technology: intel-pt
268
269  Total number of trace items: 28
270
271  Memory usage:
272    Raw trace size: 4 KiB""",
273                """
274
275  Events:
276    Number of individual events: 7
277      software disabled tracing: 2
278      hardware disabled tracing: 4
279      trace synchronization point: 1""",
280            ],
281        )
282
283    @testSBAPIAndCommands
284    def testLoadInvalidTraces(self):
285        src_dir = self.getSourceDir()
286
287        # We test first an invalid type
288        trace_description_file_path = os.path.join(
289            src_dir, "intelpt-trace", "trace_bad.json"
290        )
291        expected_substrs = [
292            """error: expected object at traceBundle.processes[0]
293
294Context:
295{
296  "cpuInfo": { ... },
297  "processes": [
298    /* error: expected object */
299    123
300  ],
301  "type": "intel-pt"
302}
303
304Schema:
305{
306  "type": "intel-pt",
307  "cpuInfo": {
308    // CPU information gotten from, for example, /proc/cpuinfo.
309
310    "vendor": "GenuineIntel" | "unknown",
311    "family": integer,
312    "model": integer,
313    "stepping": integer
314  },"""
315        ]
316        self.traceLoad(
317            traceDescriptionFilePath=trace_description_file_path,
318            error=True,
319            substrs=expected_substrs,
320        )
321
322        # Now we test a wrong cpu family field in the global bundle description file
323        trace_description_file_path = os.path.join(
324            src_dir, "intelpt-trace", "trace_bad2.json"
325        )
326        expected_substrs = [
327            "error: expected uint64_t at traceBundle.cpuInfo.family",
328            "Context",
329            "Schema",
330        ]
331        self.traceLoad(
332            traceDescriptionFilePath=trace_description_file_path,
333            error=True,
334            substrs=expected_substrs,
335        )
336
337        # Now we test a missing field in the intel-pt settings
338        trace_description_file_path = os.path.join(
339            src_dir, "intelpt-trace", "trace_bad4.json"
340        )
341        expected_substrs = [
342            """error: missing value at traceBundle.cpuInfo.family
343
344Context:
345{
346  "cpuInfo": /* error: missing value */ {
347    "model": 79,
348    "stepping": 1,
349    "vendor": "GenuineIntel"
350  },
351  "processes": [],
352  "type": "intel-pt"
353}""",
354            "Schema",
355        ]
356        self.traceLoad(
357            traceDescriptionFilePath=trace_description_file_path,
358            error=True,
359            substrs=expected_substrs,
360        )
361
362        # Now we test an incorrect load address in the intel-pt settings
363        trace_description_file_path = os.path.join(
364            src_dir, "intelpt-trace", "trace_bad5.json"
365        )
366        expected_substrs = [
367            "error: missing value at traceBundle.processes[1].pid",
368            "Schema",
369        ]
370        self.traceLoad(
371            traceDescriptionFilePath=trace_description_file_path,
372            error=True,
373            substrs=expected_substrs,
374        )
375
376        # The following wrong schema will have a valid target and an invalid one. In the case of failure,
377        # no targets should be created.
378        self.assertEqual(self.dbg.GetNumTargets(), 0)
379        trace_description_file_path = os.path.join(
380            src_dir, "intelpt-trace", "trace_bad3.json"
381        )
382        expected_substrs = ["error: missing value at traceBundle.processes[1].pid"]
383        self.traceLoad(
384            traceDescriptionFilePath=trace_description_file_path,
385            error=True,
386            substrs=expected_substrs,
387        )
388        self.assertEqual(self.dbg.GetNumTargets(), 0)
389
390    def testLoadTraceCursor(self):
391        src_dir = self.getSourceDir()
392        trace_description_file_path = os.path.join(
393            src_dir, "intelpt-multi-core-trace", "trace.json"
394        )
395        traceDescriptionFile = lldb.SBFileSpec(trace_description_file_path, True)
396
397        error = lldb.SBError()
398        trace = self.dbg.LoadTraceFromFile(error, traceDescriptionFile)
399        self.assertSBError(error)
400
401        target = self.dbg.GetSelectedTarget()
402        process = target.process
403
404        # 1. Test some expected items of thread 1's trace cursor.
405        thread1 = process.threads[1]
406        cursor = trace.CreateNewCursor(error, thread1)
407        self.assertTrue(cursor)
408        self.assertTrue(cursor.HasValue())
409        cursor.Seek(0, lldb.eTraceCursorSeekTypeBeginning)
410        cursor.SetForwards(True)
411
412        self.assertTrue(cursor.IsEvent())
413        self.assertEqual(cursor.GetEventTypeAsString(), "HW clock tick")
414        self.assertEqual(cursor.GetCPU(), lldb.LLDB_INVALID_CPU_ID)
415
416        cursor.Next()
417
418        self.assertTrue(cursor.IsEvent())
419        self.assertEqual(cursor.GetEventTypeAsString(), "CPU core changed")
420        self.assertEqual(cursor.GetCPU(), 51)
421
422        cursor.GoToId(19526)
423
424        self.assertTrue(cursor.IsError())
425        self.assertEqual(
426            cursor.GetError(),
427            "decoding truncated: TSC 40450075478109270 exceeds maximum TSC value 40450075477704372, will skip decoding the remaining data of the PSB (skipping 774 of 825 bytes)",
428        )
429
430        cursor.GoToId(19524)
431
432        self.assertTrue(cursor.IsInstruction())
433        self.assertEqual(cursor.GetLoadAddress(), 0x400BA7)
434
435        # Helper function to check equality of the current item of two trace cursors.
436        def assertCurrentTraceCursorItemEqual(lhs, rhs):
437            self.assertTrue(lhs.HasValue() and rhs.HasValue())
438
439            self.assertEqual(lhs.GetId(), rhs.GetId())
440            self.assertEqual(lhs.GetItemKind(), rhs.GetItemKind())
441            if lhs.IsError():
442                self.assertEqual(lhs.GetError(), rhs.GetError())
443            elif lhs.IsEvent():
444                self.assertEqual(lhs.GetEventType(), rhs.GetEventType())
445                self.assertEqual(lhs.GetEventTypeAsString(), rhs.GetEventTypeAsString())
446            elif lhs.IsInstruction():
447                self.assertEqual(lhs.GetLoadAddress(), rhs.GetLoadAddress())
448            else:
449                self.fail("Unknown trace item kind")
450
451        for thread in process.threads:
452            sequentialTraversalCursor = trace.CreateNewCursor(error, thread)
453            self.assertSBError(error)
454            # Skip threads with no trace items
455            if not sequentialTraversalCursor.HasValue():
456                continue
457
458            # 2. Test "End" boundary of the trace by advancing past the trace's last item.
459            sequentialTraversalCursor.Seek(0, lldb.eTraceCursorSeekTypeEnd)
460            self.assertTrue(sequentialTraversalCursor.HasValue())
461            sequentialTraversalCursor.SetForwards(True)
462            sequentialTraversalCursor.Next()
463            self.assertFalse(sequentialTraversalCursor.HasValue())
464
465            # 3. Test sequential traversal using sequential access API (ie Next())
466            # and random access API (ie GoToId()) simultaneously.
467            randomAccessCursor = trace.CreateNewCursor(error, thread)
468            self.assertSBError(error)
469            # Reset the sequential cursor
470            sequentialTraversalCursor.Seek(0, lldb.eTraceCursorSeekTypeBeginning)
471            sequentialTraversalCursor.SetForwards(True)
472            self.assertTrue(sequentialTraversalCursor.IsForwards())
473
474            while sequentialTraversalCursor.HasValue():
475                itemId = sequentialTraversalCursor.GetId()
476                randomAccessCursor.GoToId(itemId)
477                assertCurrentTraceCursorItemEqual(
478                    sequentialTraversalCursor, randomAccessCursor
479                )
480                sequentialTraversalCursor.Next()
481
482            # 4. Test a random access with random access API (ie Seek()) and
483            # sequential access API (ie consecutive calls to Next()).
484            TEST_SEEK_ID = 3
485            randomAccessCursor.GoToId(TEST_SEEK_ID)
486            # Reset the sequential cursor
487            sequentialTraversalCursor.Seek(0, lldb.eTraceCursorSeekTypeBeginning)
488            sequentialTraversalCursor.SetForwards(True)
489            for _ in range(TEST_SEEK_ID):
490                sequentialTraversalCursor.Next()
491            assertCurrentTraceCursorItemEqual(
492                sequentialTraversalCursor, randomAccessCursor
493            )
494
495    @testSBAPIAndCommands
496    def testLoadKernelTrace(self):
497        # kernel section without loadAddress (using default loadAddress).
498        src_dir = self.getSourceDir()
499        trace_description_file_path = os.path.join(
500            src_dir, "intelpt-kernel-trace", "trace.json"
501        )
502        self.traceLoad(
503            traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"]
504        )
505
506        self.expect("image list", substrs=["0xffffffff81000000", "modules/m.out"])
507
508        self.expect(
509            "thread list",
510            substrs=[
511                "Process 1 stopped",
512                "* thread #1: tid = 0x002d",
513                "  thread #2: tid = 0x0033",
514            ],
515        )
516
517        # kernel section with custom loadAddress.
518        trace_description_file_path = os.path.join(
519            src_dir, "intelpt-kernel-trace", "trace_with_loadAddress.json"
520        )
521        self.traceLoad(
522            traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"]
523        )
524
525        self.expect("image list", substrs=["0x400000", "modules/m.out"])
526
527    @testSBAPIAndCommands
528    def testLoadInvalidKernelTrace(self):
529        src_dir = self.getSourceDir()
530
531        # Test kernel section with non-empty processeses section.
532        trace_description_file_path = os.path.join(
533            src_dir, "intelpt-kernel-trace", "trace_kernel_with_process.json"
534        )
535        expected_substrs = [
536            'error: "processes" must be empty when "kernel" is provided when parsing traceBundle'
537        ]
538        self.traceLoad(
539            traceDescriptionFilePath=trace_description_file_path,
540            error=True,
541            substrs=expected_substrs,
542        )
543
544        # Test kernel section without cpus section.
545        trace_description_file_path = os.path.join(
546            src_dir, "intelpt-kernel-trace", "trace_kernel_wo_cpus.json"
547        )
548        expected_substrs = [
549            'error: "cpus" is required when "kernel" is provided when parsing traceBundle'
550        ]
551        self.traceLoad(
552            traceDescriptionFilePath=trace_description_file_path,
553            error=True,
554            substrs=expected_substrs,
555        )
556