Skip to content

Commit f71252e

Browse files
committed
BUG? confusing... two ways to set skip_idle?
1 parent 3eddae8 commit f71252e

File tree

1 file changed

+155
-2
lines changed

1 file changed

+155
-2
lines changed

Lib/test/test_profiling/test_sampling_profiler/test_modes.py

Lines changed: 155 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import profiling.sampling
1010
import profiling.sampling.sample
1111
from profiling.sampling.pstats_collector import PstatsCollector
12+
from profiling.sampling.jsonl_collector import JsonlCollector
1213
from profiling.sampling.cli import main, _parse_mode
1314
from profiling.sampling.constants import PROFILING_MODE_EXCEPTION
1415
from _remote_debugging import (
@@ -20,9 +21,13 @@
2021
"Test only runs when _remote_debugging is available"
2122
)
2223

23-
from test.support import requires_remote_subprocess_debugging
24+
from test.support import (
25+
captured_stdout,
26+
captured_stderr,
27+
requires_remote_subprocess_debugging,
28+
)
2429

25-
from .helpers import test_subprocess
30+
from .helpers import close_and_unlink, test_subprocess
2631
from .mocks import MockFrameInfo, MockInterpreterInfo
2732

2833

@@ -228,6 +233,154 @@ def test_cpu_mode_with_no_samples(self):
228233
self.assertIn("No samples were collected", output)
229234
self.assertIn("CPU mode", output)
230235

236+
def test_jsonl_collector_rspects_skip_idle(self):
237+
"""Test that frames are actually filtered when skip_idle=True."""
238+
import tempfile
239+
import json
240+
241+
collapsed_out = tempfile.NamedTemporaryFile(delete=False)
242+
self.addCleanup(close_and_unlink, collapsed_out)
243+
244+
# Create mock frames with different thread statuses
245+
class MockThreadInfoWithStatus:
246+
def __init__(self, thread_id, frame_info, status):
247+
self.thread_id = thread_id
248+
self.frame_info = frame_info
249+
self.status = status
250+
251+
# Create test data: active thread (HAS_GIL | ON_CPU), idle thread (neither), and another active thread
252+
ACTIVE_STATUS = (
253+
THREAD_STATUS_HAS_GIL | THREAD_STATUS_ON_CPU
254+
) # Has GIL and on CPU
255+
IDLE_STATUS = 0 # Neither has GIL nor on CPU
256+
257+
test_frames = [
258+
MockInterpreterInfo(
259+
0,
260+
[
261+
MockThreadInfoWithStatus(
262+
1,
263+
[MockFrameInfo("active1.py", 10, "active_func1")],
264+
ACTIVE_STATUS,
265+
),
266+
MockThreadInfoWithStatus(
267+
2,
268+
[MockFrameInfo("idle.py", 20, "idle_func")],
269+
IDLE_STATUS,
270+
),
271+
MockThreadInfoWithStatus(
272+
3,
273+
[MockFrameInfo("active2.py", 30, "active_func2")],
274+
ACTIVE_STATUS,
275+
),
276+
],
277+
)
278+
]
279+
280+
# Test with skip_idle=True - should only process running threads
281+
collector_skip = JsonlCollector(
282+
sample_interval_usec=1000, skip_idle=True
283+
)
284+
collector_skip.collect(test_frames)
285+
286+
run_id = collector_skip.run_id
287+
288+
# Should only have functions from running threads (status 0)
289+
with captured_stdout(), captured_stderr():
290+
collector_skip.export(collapsed_out.name)
291+
292+
# Check file contents
293+
with open(collapsed_out.name, "r") as f:
294+
content = f.read()
295+
296+
lines = content.strip().split("\n")
297+
self.assertEqual(len(lines), 5)
298+
299+
def jsonl(obj):
300+
return json.dumps(obj, separators=(",", ":"))
301+
302+
expected = [
303+
jsonl({"type": "meta", "v": 1, "run_id": run_id,
304+
"sample_interval_usec": 1000}),
305+
jsonl({"type": "str_def", "v": 1, "run_id": run_id,
306+
"defs": [{"str_id": 1, "value": "active_func1"},
307+
{"str_id": 2, "value": "active1.py"},
308+
{"str_id": 3, "value": "idle_func"},
309+
{"str_id": 4, "value": "idle.py"},
310+
{"str_id": 5, "value": "active_func2"},
311+
{"str_id": 6, "value": "active2.py"}]}),
312+
jsonl({"type": "frame_def", "v": 1, "run_id": run_id,
313+
"defs": [{"frame_id": 1, "path_str_id": 2, "func_str_id": 1,
314+
"line": 10, "end_line": 10},
315+
{"frame_id": 2, "path_str_id": 4, "func_str_id": 3,
316+
"line": 20, "end_line": 20},
317+
{"frame_id": 3, "path_str_id": 6, "func_str_id": 5,
318+
"line": 30, "end_line": 30}]}),
319+
jsonl({"type": "agg", "v": 1, "run_id": run_id,
320+
"kind": "frame", "scope": "final", "samples_total": 3,
321+
"entries": [{"frame_id": 1, "self": 1, "cumulative": 1},
322+
{"frame_id": 2, "self": 1, "cumulative": 1},
323+
{"frame_id": 3, "self": 1, "cumulative": 1}]}),
324+
jsonl({"type": "end", "v": 1, "run_id": run_id,
325+
"samples_total": 3}),
326+
]
327+
328+
for exp in expected:
329+
self.assertIn(exp, lines)
330+
331+
# Test with skip_idle=False - should process all threads
332+
collector_no_skip = JsonlCollector(
333+
sample_interval_usec=1000, skip_idle=False
334+
)
335+
collector_no_skip.collect(test_frames)
336+
337+
run_id = collector_no_skip.run_id
338+
339+
# Should have functions from all threads
340+
with captured_stdout(), captured_stderr():
341+
collector_no_skip.export(collapsed_out.name)
342+
343+
# Check file contents
344+
with open(collapsed_out.name, "r") as f:
345+
content = f.read()
346+
347+
lines = content.strip().split("\n")
348+
self.assertEqual(len(lines), 5)
349+
350+
expected = [
351+
jsonl({"type": "meta", "v": 1, "run_id": run_id,
352+
"sample_interval_usec": 1000}),
353+
jsonl({"type": "str_def", "v": 1, "run_id": run_id,
354+
"defs": [{"str_id": 1, "value": "active_func1"},
355+
{"str_id": 2, "value": "active1.py"},
356+
{"str_id": 3, "value": "idle_func"},
357+
{"str_id": 4, "value": "idle.py"},
358+
{"str_id": 5, "value": "active_func2"},
359+
{"str_id": 6, "value": "active2.py"}]}),
360+
jsonl({"type": "frame_def", "v": 1, "run_id": run_id,
361+
"defs": [{"frame_id": 1, "path_str_id": 2, "func_str_id": 1,
362+
"line": 10, "end_line": 10},
363+
{"frame_id": 2, "path_str_id": 4, "func_str_id": 3,
364+
"line": 20, "end_line": 20},
365+
{"frame_id": 3, "path_str_id": 6, "func_str_id": 5,
366+
"line": 30, "end_line": 30}]}),
367+
jsonl({"type": "agg", "v": 1, "run_id": run_id,
368+
"kind": "frame", "scope": "final", "samples_total": 3,
369+
"entries": [{"frame_id": 1, "self": 1, "cumulative": 1},
370+
{"frame_id": 2, "self": 1, "cumulative": 1},
371+
{"frame_id": 3, "self": 1, "cumulative": 1}]}),
372+
jsonl({"type": "end", "v": 1, "run_id": run_id,
373+
"samples_total": 3}),
374+
]
375+
376+
for exp in expected:
377+
self.assertIn(exp, lines)
378+
379+
# self.assertIn(active1_key, collector_no_skip.result)
380+
# self.assertIn(active2_key, collector_no_skip.result)
381+
# self.assertIn(
382+
# idle_key, collector_no_skip.result
383+
# ) # Idle thread should be included
231384

232385
@requires_remote_subprocess_debugging()
233386
class TestGilModeFiltering(unittest.TestCase):

0 commit comments

Comments
 (0)