llvm-project/lldb/test/API/commands/trace/TestTraceSave.py
Walter Erquinigo fc5ef57c7d [trace][intelpt] Support system-wide tracing [12] - Support multi-core trace load and save
:q!
This diff is massive, but it's because it connects the client with lldb-server
and also ensures that the postmortem case works.

- Flatten the postmortem trace schema. The reason is that the schema has become quite complex due to the new multicore case, which defeats the original purpose of having a schema that could work for every trace plug-in. At this point, it's better that each trace plug-in defines it's own full schema. This means that the only common field is "type".
-- Because of this new approach, I merged the "common" trace load and saving functionalities into the IntelPT one. This simplified the code quite a bit. If we eventually implement another trace plug-in, we can see then what we could reuse.
-- The new schema, which is flattened, has now better comments and is parsed better. A change I did was to disallow hex addresses, because they are a bit error prone. I'm asking now to print the address in decimal.
-- Renamed "intel" to "GenuineIntel" in the schema because that's what you see in /proc/cpuinfo.
- Implemented reading the context switch trace data buffer. I had to do
some refactors to do that cleanly.
-- A major change that I did here was to simplify the perf_event circular buffer reading logic. It was too complex. Maybe the original Intel author had something different in mind.
- Implemented all the necessary bits to read trace.json files with per-core data.
- Implemented all the necessary bits to save to disk per-core trace session.
- Added a test that ensures that parsing and saving to disk works.

Differential Revision: https://reviews.llvm.org/D126015
2022-06-15 13:28:36 -07:00

173 lines
7.3 KiB
Python

import lldb
import json
from intelpt_testcase import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
from lldbsuite.test.decorators import *
def find(predicate, seq):
for item in seq:
if predicate(item):
return item
class TestTraceSave(TraceIntelPTTestCaseBase):
mydir = TestBase.compute_mydir(__file__)
def testErrorMessages(self):
# We first check the output when there are no targets
self.expect("process trace save",
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("process trace save",
substrs=["error: Command requires a current 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("process trace save",
substrs=["error: Process is not being traced"],
error=True)
def testSaveToInvalidDir(self):
self.expect("target create " +
os.path.join(self.getSourceDir(), "intelpt-trace", "a.out"))
self.expect("b main")
self.expect("r")
self.expect("thread trace start")
self.expect("n")
# Check the output when saving without providing the directory argument
self.expect("process trace save -d",
substrs=["error: last option requires an argument"],
error=True)
# Check the output when saving to an invalid directory
self.expect("process trace save -d /",
substrs=["error: couldn't write to the file"],
error=True)
def testSaveWhenNotLiveTrace(self):
self.expect("trace load -v " +
os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"),
substrs=["intel-pt"])
# Check the output when not doing live tracing
self.expect("process trace save -d " +
os.path.join(self.getBuildDir(), "intelpt-trace", "trace_not_live_dir"))
def testSaveMultiCoreTrace(self):
'''
This test starts a per-core tracing session, then saves the session to disk, and
finally it loads it again.
'''
self.skipIfPerCoreTracingIsNotSupported()
self.expect("target create " +
os.path.join(self.getSourceDir(), "intelpt-trace", "a.out"))
self.expect("b main")
self.expect("r")
self.expect("process trace start --per-core-tracing")
self.expect("b 7")
output_dir = os.path.join(self.getBuildDir(), "intelpt-trace", "trace_save")
self.expect("process trace save -d " + output_dir)
def checkSessionBundle(session_file_path):
with open(session_file_path) as session_file:
session = json.load(session_file)
# We expect tsc conversion info
self.assertTrue("tscPerfZeroConversion" in session)
# We expect at least one core
self.assertGreater(len(session["cores"]), 0)
# We expect the required trace files to be created
for core in session["cores"]:
core_files_prefix = os.path.join(output_dir, "cores", str(core["coreId"]))
self.assertTrue(os.path.exists(core_files_prefix + ".intelpt_trace"))
self.assertTrue(os.path.exists(core_files_prefix + ".perf_context_switch_trace"))
# We expect at least one one process
self.assertGreater(len(session["processes"]), 0)
for process in session["processes"]:
# We expect at least one thread
self.assertGreater(len(process["threads"]), 0)
# We don't expect thread traces
for thread in process["threads"]:
self.assertTrue(("traceBuffer" not in thread) or (thread["traceBuffer"] is None))
original_trace_session_file = os.path.join(output_dir, "trace.json")
checkSessionBundle(original_trace_session_file)
output_dir = os.path.join(self.getBuildDir(), "intelpt-trace", "trace_save")
self.expect("trace load " + os.path.join(output_dir, "trace.json"))
output_copy_dir = os.path.join(self.getBuildDir(), "intelpt-trace", "copy_trace_save")
self.expect("process trace save -d " + output_copy_dir)
# We now check that the new bundle is correct on its own
copied_trace_session_file = os.path.join(output_copy_dir, "trace.json")
checkSessionBundle(copied_trace_session_file)
# We finally check that the new bundle has the same information as the original one
with open(original_trace_session_file) as original_file:
original = json.load(original_file)
with open(copied_trace_session_file) as copy_file:
copy = json.load(copy_file)
self.assertEqual(len(original["processes"]), len(copy["processes"]))
for process in original["processes"]:
copied_process = find(lambda proc : proc["pid"] == process["pid"], copy["processes"])
self.assertTrue(copied_process is not None)
for thread in process["threads"]:
copied_thread = find(lambda thr : thr["tid"] == thread["tid"], copied_process["threads"])
self.assertTrue(copied_thread is not None)
for core in original["cores"]:
copied_core = find(lambda cor : cor["coreId"] == core["coreId"], copy["cores"])
self.assertTrue(copied_core is not None)
def testSaveTrace(self):
self.expect("target create " +
os.path.join(self.getSourceDir(), "intelpt-trace", "a.out"))
self.expect("b main")
self.expect("r")
self.expect("thread trace start")
self.expect("b 7")
ci = self.dbg.GetCommandInterpreter()
res = lldb.SBCommandReturnObject()
ci.HandleCommand("thread trace dump instructions -c 10 --forwards", res)
self.assertEqual(res.Succeeded(), True)
first_ten_instructions = res.GetOutput()
ci.HandleCommand("thread trace dump instructions -c 10", res)
self.assertEqual(res.Succeeded(), True)
last_ten_instructions = res.GetOutput()
# Now, save the trace to <trace_copy_dir>
self.expect("process trace save -d " +
os.path.join(self.getBuildDir(), "intelpt-trace", "trace_copy_dir"))
# Load the trace just saved
self.expect("trace load -v " +
os.path.join(self.getBuildDir(), "intelpt-trace", "trace_copy_dir", "trace.json"),
substrs=["intel-pt"])
# Compare with instructions saved at the first time
ci.HandleCommand("thread trace dump instructions -c 10 --forwards", res)
self.assertEqual(res.Succeeded(), True)
self.assertEqual(res.GetOutput(), first_ten_instructions)
ci.HandleCommand("thread trace dump instructions -c 10", res)
self.assertEqual(res.Succeeded(), True)
self.assertEqual(res.GetOutput(), last_ten_instructions)