1import lldb 2import json 3from intelpt_testcase import * 4from lldbsuite.test.lldbtest import * 5from lldbsuite.test import lldbutil 6from lldbsuite.test.decorators import * 7 8 9def find(predicate, seq): 10 for item in seq: 11 if predicate(item): 12 return item 13 14 15class TestTraceSave(TraceIntelPTTestCaseBase): 16 def testErrorMessages(self): 17 # We first check the output when there are no targets 18 self.expect( 19 "trace save", 20 substrs=[ 21 "error: invalid target, create a target using the 'target create' command" 22 ], 23 error=True, 24 ) 25 26 # We now check the output when there's a non-running target 27 self.expect( 28 "target create " 29 + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out") 30 ) 31 32 self.expect( 33 "trace save", 34 substrs=["error: Command requires a current process."], 35 error=True, 36 ) 37 38 # Now we check the output when there's a running target without a trace 39 self.expect("b main") 40 self.expect("run") 41 42 self.expect( 43 "trace save", substrs=["error: Process is not being traced"], error=True 44 ) 45 46 def testSaveToInvalidDir(self): 47 self.expect( 48 "target create " 49 + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out") 50 ) 51 self.expect("b main") 52 self.expect("r") 53 self.expect("thread trace start") 54 self.expect("n") 55 56 # Check the output when saving without providing the directory argument 57 self.expect( 58 "trace save ", 59 substrs=[ 60 "error: a single path to a directory where the trace bundle will be created is required" 61 ], 62 error=True, 63 ) 64 65 # Check the output when saving to an invalid directory 66 self.expect( 67 "trace save /", substrs=["error: couldn't write to the file"], error=True 68 ) 69 70 def testSaveWhenNotLiveTrace(self): 71 self.expect( 72 "trace load -v " 73 + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"), 74 substrs=["intel-pt"], 75 ) 76 77 # Check the output when not doing live tracing 78 self.expect( 79 "trace save " 80 + os.path.join(self.getBuildDir(), "intelpt-trace", "trace_not_live_dir") 81 ) 82 83 def testSaveMultiCpuTrace(self): 84 """ 85 This test starts a per-cpu tracing session, then saves the session to disk, and 86 finally it loads it again. 87 """ 88 self.skipIfPerCpuTracingIsNotSupported() 89 90 self.expect( 91 "target create " 92 + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out") 93 ) 94 self.expect("b main") 95 self.expect("r") 96 self.expect("process trace start --per-cpu-tracing") 97 self.expect("b 7") 98 99 output_dir = os.path.join(self.getBuildDir(), "intelpt-trace", "trace_save") 100 self.expect("trace save " + output_dir) 101 102 def checkSessionBundle(session_file_path): 103 with open(session_file_path) as session_file: 104 session = json.load(session_file) 105 # We expect tsc conversion info 106 self.assertIn("tscPerfZeroConversion", session) 107 # We expect at least one cpu 108 self.assertGreater(len(session["cpus"]), 0) 109 110 # We expect the required trace files to be created 111 for cpu in session["cpus"]: 112 cpu_files_prefix = os.path.join(output_dir, "cpus", str(cpu["id"])) 113 self.assertTrue(os.path.exists(cpu_files_prefix + ".intelpt_trace")) 114 self.assertTrue( 115 os.path.exists(cpu_files_prefix + ".perf_context_switch_trace") 116 ) 117 118 # We expect at least one one process 119 self.assertGreater(len(session["processes"]), 0) 120 for process in session["processes"]: 121 # We expect at least one thread 122 self.assertGreater(len(process["threads"]), 0) 123 # We don't expect thread traces 124 for thread in process["threads"]: 125 self.assertTrue( 126 ("iptTrace" not in thread) or (thread["iptTrace"] is None) 127 ) 128 129 original_trace_session_file = os.path.join(output_dir, "trace.json") 130 checkSessionBundle(original_trace_session_file) 131 132 output_dir = os.path.join(self.getBuildDir(), "intelpt-trace", "trace_save") 133 self.expect("trace load " + os.path.join(output_dir, "trace.json")) 134 output_copy_dir = os.path.join( 135 self.getBuildDir(), "intelpt-trace", "copy_trace_save" 136 ) 137 self.expect("trace save " + output_copy_dir) 138 139 # We now check that the new bundle is correct on its own 140 copied_trace_session_file = os.path.join(output_copy_dir, "trace.json") 141 checkSessionBundle(copied_trace_session_file) 142 143 # We finally check that the new bundle has the same information as the original one 144 with open(original_trace_session_file) as original_file: 145 original = json.load(original_file) 146 with open(copied_trace_session_file) as copy_file: 147 copy = json.load(copy_file) 148 149 self.assertEqual(len(original["processes"]), len(copy["processes"])) 150 151 for process in original["processes"]: 152 copied_process = find( 153 lambda proc: proc["pid"] == process["pid"], copy["processes"] 154 ) 155 self.assertIsNotNone(copied_process) 156 157 for thread in process["threads"]: 158 copied_thread = find( 159 lambda thr: thr["tid"] == thread["tid"], 160 copied_process["threads"], 161 ) 162 self.assertIsNotNone(copied_thread) 163 164 for cpu in original["cpus"]: 165 copied_cpu = find(lambda cor: cor["id"] == cpu["id"], copy["cpus"]) 166 self.assertIsNotNone(copied_cpu) 167 168 def testSaveTrace(self): 169 self.expect( 170 "target create " 171 + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out") 172 ) 173 self.expect("b main") 174 self.expect("r") 175 self.expect("thread trace start") 176 self.expect("b 7") 177 178 ci = self.dbg.GetCommandInterpreter() 179 res = lldb.SBCommandReturnObject() 180 181 ci.HandleCommand("thread trace dump instructions -c 10 --forwards", res) 182 self.assertTrue(res.Succeeded()) 183 first_ten_instructions = res.GetOutput() 184 185 ci.HandleCommand("thread trace dump instructions -c 10", res) 186 self.assertTrue(res.Succeeded()) 187 last_ten_instructions = res.GetOutput() 188 189 # Now, save the trace to <trace_copy_dir> 190 self.expect( 191 "trace save " 192 + os.path.join(self.getBuildDir(), "intelpt-trace", "trace_copy_dir") 193 ) 194 195 # Load the trace just saved 196 self.expect( 197 "trace load -v " 198 + os.path.join( 199 self.getBuildDir(), "intelpt-trace", "trace_copy_dir", "trace.json" 200 ), 201 substrs=["intel-pt"], 202 ) 203 204 # Compare with instructions saved at the first time 205 ci.HandleCommand("thread trace dump instructions -c 10 --forwards", res) 206 self.assertTrue(res.Succeeded()) 207 self.assertEqual(res.GetOutput(), first_ten_instructions) 208 209 ci.HandleCommand("thread trace dump instructions -c 10", res) 210 self.assertTrue(res.Succeeded()) 211 self.assertEqual(res.GetOutput(), last_ten_instructions) 212 213 def testSaveKernelTrace(self): 214 original_trace_file = os.path.join( 215 self.getSourceDir(), "intelpt-kernel-trace", "trace.json" 216 ) 217 copied_trace_dir = os.path.join(self.getBuildDir(), "intelpt-kernel-trace") 218 copied_trace_file = os.path.join(copied_trace_dir, "trace.json") 219 220 self.expect("trace load -v " + original_trace_file, substrs=["intel-pt"]) 221 self.expect("trace save " + copied_trace_dir) 222 223 # We finally check that the new json has the same information as the original one 224 with open(original_trace_file) as original_file: 225 original_file = json.load(original_file) 226 with open(copied_trace_file) as copy_file: 227 copy_file = json.load(copy_file) 228 self.assertIn("kernel", copy_file) 229 230 self.assertEqual( 231 os.path.basename(original_file["kernel"]["file"]), 232 os.path.basename(copy_file["kernel"]["file"]), 233 ) 234