@@ -58,6 +58,25 @@ def find_child_by_name(children, strings, substr):
5858 return None
5959
6060
61+ def _jsonl_tables (records ):
62+ meta = next (record for record in records if record ["type" ] == "meta" )
63+ end = next (record for record in records if record ["type" ] == "end" )
64+ agg = next (record for record in records if record ["type" ] == "agg" )
65+ str_defs = {
66+ item ["str_id" ]: item ["value" ]
67+ for record in records
68+ if record ["type" ] == "str_def"
69+ for item in record ["defs" ]
70+ }
71+ frame_defs = [
72+ item
73+ for record in records
74+ if record ["type" ] == "frame_def"
75+ for item in record ["defs" ]
76+ ]
77+ return meta , str_defs , frame_defs , agg , end
78+
79+
6180class TestSampleProfilerComponents (unittest .TestCase ):
6281 """Unit tests for individual profiler components."""
6382
@@ -1666,14 +1685,12 @@ def test_diff_flamegraph_load_baseline(self):
16661685 self .assertAlmostEqual (cold_node ["diff" ], - 1.0 )
16671686 self .assertAlmostEqual (cold_node ["diff_pct" ], - 50.0 )
16681687
1669- def test_jsonl_collector_export (self ):
1688+ def test_jsonl_collector_export_exact_output (self ):
16701689 jsonl_out = tempfile .NamedTemporaryFile (delete = False )
16711690 self .addCleanup (close_and_unlink , jsonl_out )
16721691
16731692 collector = JsonlCollector (1000 )
1674- run_id = collector .run_id
1675-
1676- self .assertIsNotNone (run_id )
1693+ collector .run_id = "run-123"
16771694
16781695 test_frames1 = [
16791696 MockInterpreterInfo (
@@ -1705,46 +1722,74 @@ def test_jsonl_collector_export(self):
17051722 collector .collect (test_frames2 )
17061723 collector .collect (test_frames3 )
17071724
1708- with captured_stdout (), captured_stderr ():
1709- collector .export (jsonl_out .name )
1725+ collector .export (jsonl_out .name )
17101726
1711- # Check file contents
1712- with open (jsonl_out .name , "r" ) as f :
1727+ with open (jsonl_out .name , "r" , encoding = "utf-8" ) as f :
17131728 content = f .read ()
17141729
1715- lines = content .strip ().split ("\n " )
1716- self .assertEqual (len (lines ), 5 )
1717-
1718- def jsonl (obj ):
1719- return json .dumps (obj , separators = ("," , ":" ))
1720-
1721- expected = [
1722- jsonl ({"type" : "meta" , "v" : 1 , "run_id" : run_id ,
1723- "sample_interval_usec" : 1000 }),
1724- jsonl ({"type" : "str_def" , "v" : 1 , "run_id" : run_id ,
1725- "defs" : [{"str_id" : 1 , "value" : "func1" },
1726- {"str_id" : 2 , "value" : "file.py" },
1727- {"str_id" : 3 , "value" : "func2" },
1728- {"str_id" : 4 , "value" : "other_func" },
1729- {"str_id" : 5 , "value" : "other.py" }]}),
1730- jsonl ({"type" : "frame_def" , "v" : 1 , "run_id" : run_id ,
1731- "defs" : [{"frame_id" : 1 , "path_str_id" : 2 , "func_str_id" : 1 ,
1732- "line" : 10 , "end_line" : 10 },
1733- {"frame_id" : 2 , "path_str_id" : 2 , "func_str_id" : 3 ,
1734- "line" : 20 , "end_line" : 20 },
1735- {"frame_id" : 3 , "path_str_id" : 5 , "func_str_id" : 4 ,
1736- "line" : 5 , "end_line" : 5 }]}),
1737- jsonl ({"type" : "agg" , "v" : 1 , "run_id" : run_id ,
1738- "kind" : "frame" , "scope" : "final" , "samples_total" : 3 ,
1739- "entries" : [{"frame_id" : 1 , "self" : 2 , "cumulative" : 2 },
1740- {"frame_id" : 2 , "self" : 0 , "cumulative" : 2 },
1741- {"frame_id" : 3 , "self" : 1 , "cumulative" : 1 }]}),
1742- jsonl ({"type" : "end" , "v" : 1 , "run_id" : run_id ,
1743- "samples_total" : 3 }),
1744- ]
1745-
1746- for exp in expected :
1747- self .assertIn (exp , lines )
1730+ self .assertEqual (
1731+ content ,
1732+ (
1733+ '{"type":"meta","v":1,"run_id":"run-123","sample_interval_usec":1000}\n '
1734+ '{"type":"str_def","v":1,"run_id":"run-123","defs":[{"str_id":1,"value":"func1"},{"str_id":2,"value":"file.py"},{"str_id":3,"value":"func2"},{"str_id":4,"value":"other_func"},{"str_id":5,"value":"other.py"}]}\n '
1735+ '{"type":"frame_def","v":1,"run_id":"run-123","defs":[{"frame_id":1,"path_str_id":2,"func_str_id":1,"line":10,"end_line":10},{"frame_id":2,"path_str_id":2,"func_str_id":3,"line":20,"end_line":20},{"frame_id":3,"path_str_id":5,"func_str_id":4,"line":5,"end_line":5}]}\n '
1736+ '{"type":"agg","v":1,"run_id":"run-123","kind":"frame","scope":"final","samples_total":3,"entries":[{"frame_id":1,"self":2,"cumulative":2},{"frame_id":2,"self":0,"cumulative":2},{"frame_id":3,"self":1,"cumulative":1}]}\n '
1737+ '{"type":"end","v":1,"run_id":"run-123","samples_total":3}\n '
1738+ ),
1739+ )
1740+
1741+ def test_jsonl_collector_skip_idle_filters_threads (self ):
1742+ jsonl_out = tempfile .NamedTemporaryFile (delete = False )
1743+ self .addCleanup (close_and_unlink , jsonl_out )
1744+
1745+ active_status = THREAD_STATUS_HAS_GIL | THREAD_STATUS_ON_CPU
1746+ frames = [
1747+ MockInterpreterInfo (
1748+ 0 ,
1749+ [
1750+ MockThreadInfo (
1751+ 1 ,
1752+ [MockFrameInfo ("active1.py" , 10 , "active_func1" )],
1753+ status = active_status ,
1754+ ),
1755+ MockThreadInfo (
1756+ 2 ,
1757+ [MockFrameInfo ("idle.py" , 20 , "idle_func" )],
1758+ status = 0 ,
1759+ ),
1760+ MockThreadInfo (
1761+ 3 ,
1762+ [MockFrameInfo ("active2.py" , 30 , "active_func2" )],
1763+ status = active_status ,
1764+ ),
1765+ ],
1766+ )
1767+ ]
1768+
1769+ def export_summary (skip_idle ):
1770+ collector = JsonlCollector (1000 , skip_idle = skip_idle )
1771+ collector .collect (frames )
1772+ collector .export (jsonl_out .name )
1773+
1774+ with open (jsonl_out .name , "r" , encoding = "utf-8" ) as f :
1775+ records = [json .loads (line ) for line in f ]
1776+
1777+ _ , str_defs , frame_defs , agg_record , _ = _jsonl_tables (records )
1778+ paths = {str_defs [item ["path_str_id" ]] for item in frame_defs }
1779+ funcs = {str_defs [item ["func_str_id" ]] for item in frame_defs }
1780+ return paths , funcs , agg_record ["samples_total" ]
1781+
1782+ paths , funcs , samples_total = export_summary (skip_idle = True )
1783+ self .assertEqual (paths , {"active1.py" , "active2.py" })
1784+ self .assertEqual (funcs , {"active_func1" , "active_func2" })
1785+ self .assertEqual (samples_total , 2 )
1786+
1787+ paths , funcs , samples_total = export_summary (skip_idle = False )
1788+ self .assertEqual (paths , {"active1.py" , "idle.py" , "active2.py" })
1789+ self .assertEqual (
1790+ funcs , {"active_func1" , "idle_func" , "active_func2" }
1791+ )
1792+ self .assertEqual (samples_total , 3 )
17481793
17491794
17501795class TestRecursiveFunctionHandling (unittest .TestCase ):
@@ -2156,7 +2201,6 @@ def test_jsonl_collector_with_location_info(self):
21562201 self .addCleanup (close_and_unlink , jsonl_out )
21572202
21582203 collector = JsonlCollector (sample_interval_usec = 1000 )
2159- run_id = collector .run_id
21602204
21612205 # Frame with LocationInfo
21622206 frame = MockFrameInfo ("test.py" , 42 , "my_function" )
@@ -2167,46 +2211,35 @@ def test_jsonl_collector_with_location_info(self):
21672211 ]
21682212 collector .collect (frames )
21692213
2170- # Should extract lineno from location
2171- with captured_stdout (), captured_stderr ():
2172- collector .export (jsonl_out .name )
2214+ collector .export (jsonl_out .name )
21732215
2174- # Check file contents
2175- with open (jsonl_out .name , "r" ) as f :
2176- content = f .read ()
2216+ with open (jsonl_out .name , "r" , encoding = "utf-8" ) as f :
2217+ records = [json .loads (line ) for line in f ]
21772218
2178- lines = content .strip ().split ("\n " )
2179- self .assertEqual (len (lines ), 5 )
2180-
2181- def jsonl (obj ):
2182- return json .dumps (obj , separators = ("," , ":" ))
2183-
2184- expected = [
2185- jsonl ({"type" : "meta" , "v" : 1 , "run_id" : run_id ,
2186- "sample_interval_usec" : 1000 }),
2187- jsonl ({"type" : "str_def" , "v" : 1 , "run_id" : run_id ,
2188- "defs" : [{"str_id" : 1 , "value" : "my_function" },
2189- {"str_id" : 2 , "value" : "test.py" }]}),
2190- jsonl ({"type" : "frame_def" , "v" : 1 , "run_id" : run_id ,
2191- "defs" : [{"frame_id" : 1 , "path_str_id" : 2 , "func_str_id" : 1 ,
2192- "line" : 42 , "end_line" : 42 }]}),
2193- jsonl ({"type" : "agg" , "v" : 1 , "run_id" : run_id ,
2194- "kind" : "frame" , "scope" : "final" , "samples_total" : 1 ,
2195- "entries" : [{"frame_id" : 1 , "self" : 1 , "cumulative" : 1 }]}),
2196- jsonl ({"type" : "end" , "v" : 1 , "run_id" : run_id ,
2197- "samples_total" : 1 }),
2198- ]
2199-
2200- for exp in expected :
2201- self .assertIn (exp , lines )
2219+ meta , str_defs , frame_defs , agg , end = _jsonl_tables (records )
2220+ self .assertEqual (meta ["sample_interval_usec" ], 1000 )
2221+ self .assertEqual (agg ["samples_total" ], 1 )
2222+ self .assertEqual (end ["samples_total" ], 1 )
2223+ self .assertEqual (len (frame_defs ), 1 )
2224+ self .assertEqual (str_defs [frame_defs [0 ]["path_str_id" ]], "test.py" )
2225+ self .assertEqual (str_defs [frame_defs [0 ]["func_str_id" ]], "my_function" )
2226+ self .assertEqual (
2227+ frame_defs [0 ],
2228+ {
2229+ "frame_id" : 1 ,
2230+ "path_str_id" : frame_defs [0 ]["path_str_id" ],
2231+ "func_str_id" : frame_defs [0 ]["func_str_id" ],
2232+ "line" : 42 ,
2233+ "end_line" : 42 ,
2234+ },
2235+ )
22022236
22032237 def test_jsonl_collector_with_none_location (self ):
22042238 """Test JsonlCollector handles None location (synthetic frames)."""
22052239 jsonl_out = tempfile .NamedTemporaryFile (delete = False )
22062240 self .addCleanup (close_and_unlink , jsonl_out )
22072241
22082242 collector = JsonlCollector (sample_interval_usec = 1000 )
2209- run_id = collector .run_id
22102243
22112244 # Create frame with None location (like GC frame)
22122245 frame = MockFrameInfo ("~" , 0 , "<GC>" )
@@ -2219,38 +2252,28 @@ def test_jsonl_collector_with_none_location(self):
22192252 ]
22202253 collector .collect (frames )
22212254
2222- # Should handle None location as synthetic frame
2223- with captured_stdout (), captured_stderr ():
2224- collector .export (jsonl_out .name )
2225-
2226- # Check file contents
2227- with open (jsonl_out .name , "r" ) as f :
2228- content = f .read ()
2229-
2230- lines = content .strip ().split ("\n " )
2231- self .assertEqual (len (lines ), 5 )
2232-
2233- def jsonl (obj ):
2234- return json .dumps (obj , separators = ("," , ":" ))
2255+ collector .export (jsonl_out .name )
22352256
2236- expected = [
2237- jsonl ({"type" : "meta" , "v" : 1 , "run_id" : run_id ,
2238- "sample_interval_usec" : 1000 }),
2239- jsonl ({"type" : "str_def" , "v" : 1 , "run_id" : run_id ,
2240- "defs" : [{"str_id" : 1 , "value" : "<GC>" },
2241- {"str_id" : 2 , "value" : "~" }]}),
2242- jsonl ({"type" : "frame_def" , "v" : 1 , "run_id" : run_id ,
2243- "defs" : [{"frame_id" : 1 , "path_str_id" : 2 , "func_str_id" : 1 ,
2244- "line" : 0 , "synthetic" : True }]}),
2245- jsonl ({"type" : "agg" , "v" : 1 , "run_id" : run_id ,
2246- "kind" : "frame" , "scope" : "final" , "samples_total" : 1 ,
2247- "entries" : [{"frame_id" : 1 , "self" : 1 , "cumulative" : 1 }]}),
2248- jsonl ({"type" : "end" , "v" : 1 , "run_id" : run_id ,
2249- "samples_total" : 1 }),
2250- ]
2257+ with open (jsonl_out .name , "r" , encoding = "utf-8" ) as f :
2258+ records = [json .loads (line ) for line in f ]
22512259
2252- for exp in expected :
2253- self .assertIn (exp , lines )
2260+ meta , str_defs , frame_defs , agg , end = _jsonl_tables (records )
2261+ self .assertEqual (meta ["sample_interval_usec" ], 1000 )
2262+ self .assertEqual (agg ["samples_total" ], 1 )
2263+ self .assertEqual (end ["samples_total" ], 1 )
2264+ self .assertEqual (len (frame_defs ), 1 )
2265+ self .assertEqual (str_defs [frame_defs [0 ]["path_str_id" ]], "~" )
2266+ self .assertEqual (str_defs [frame_defs [0 ]["func_str_id" ]], "<GC>" )
2267+ self .assertEqual (
2268+ frame_defs [0 ],
2269+ {
2270+ "frame_id" : 1 ,
2271+ "path_str_id" : frame_defs [0 ]["path_str_id" ],
2272+ "func_str_id" : frame_defs [0 ]["func_str_id" ],
2273+ "line" : 0 ,
2274+ "synthetic" : True ,
2275+ },
2276+ )
22542277
22552278
22562279class TestOpcodeHandling (unittest .TestCase ):
@@ -2484,18 +2507,7 @@ def test_jsonl_collector_frame_format(self):
24842507 with open (f .name , "r" , encoding = "utf-8" ) as fp :
24852508 records = [json .loads (line ) for line in fp ]
24862509
2487- str_defs = {
2488- item ["str_id" ]: item ["value" ]
2489- for record in records
2490- if record ["type" ] == "str_def"
2491- for item in record ["defs" ]
2492- }
2493- frame_defs = [
2494- item
2495- for record in records
2496- if record ["type" ] == "frame_def"
2497- for item in record ["defs" ]
2498- ]
2510+ _ , str_defs , frame_defs , _ , _ = _jsonl_tables (records )
24992511
25002512 self .assertEqual (len (frame_defs ), 3 )
25012513
@@ -2662,18 +2674,7 @@ def test_jsonl_collector_filters_internal_frames(self):
26622674 with open (jsonl_out .name , "r" , encoding = "utf-8" ) as f :
26632675 records = [json .loads (line ) for line in f ]
26642676
2665- str_defs = {
2666- item ["str_id" ]: item ["value" ]
2667- for record in records
2668- if record ["type" ] == "str_def"
2669- for item in record ["defs" ]
2670- }
2671- frame_defs = [
2672- item
2673- for record in records
2674- if record ["type" ] == "frame_def"
2675- for item in record ["defs" ]
2676- ]
2677+ _ , str_defs , frame_defs , _ , _ = _jsonl_tables (records )
26772678
26782679 paths = {str_defs [item ["path_str_id" ]] for item in frame_defs }
26792680
0 commit comments