27
27
ProcessStoppedException ,
28
28
StopEventSetException ,
29
29
)
30
- from gprofiler .gprofiler_types import (
31
- ProcessToProfileData ,
32
- ProcessToStackSampleCounters ,
33
- ProfileData ,
34
- StackToSampleCount ,
35
- nonnegative_integer ,
36
- )
30
+ from gprofiler .gprofiler_types import ProcessToStackSampleCounters , ProfileData , StackToSampleCount
37
31
from gprofiler .log import get_logger_adapter
38
32
from gprofiler .metadata import application_identifiers
39
33
from gprofiler .metadata .application_metadata import ApplicationMetadata
40
34
from gprofiler .metadata .py_module_version import get_modules_versions
41
- from gprofiler .metadata .system_metadata import get_arch
42
35
from gprofiler .platform import is_linux , is_windows
43
36
from gprofiler .profiler_state import ProfilerState
44
- from gprofiler .profilers .profiler_base import ProfilerInterface , SpawningProcessProfilerBase
45
- from gprofiler .profilers .registry import ProfilerArgument , register_profiler
46
- from gprofiler .utils .collapsed_format import parse_one_collapsed_file
47
-
48
- if is_linux ():
49
- from gprofiler .profilers .python_ebpf import PythonEbpfProfiler , PythonEbpfError
50
-
37
+ from gprofiler .profilers .profiler_base import SpawningProcessProfilerBase
38
+ from gprofiler .profilers .registry import register_profiler
51
39
from gprofiler .utils import pgrep_exe , pgrep_maps , random_prefix , removed_path , resource_path , run_process
40
+ from gprofiler .utils .collapsed_format import parse_one_collapsed_file
52
41
from gprofiler .utils .process import process_comm , search_proc_maps
53
42
54
43
logger = get_logger_adapter (__name__ )
@@ -163,6 +152,19 @@ def make_application_metadata(self, process: Process) -> Dict[str, Any]:
163
152
return metadata
164
153
165
154
155
+ @register_profiler (
156
+ "Python" ,
157
+ profiler_name = "PySpy" ,
158
+ # py-spy is like pyspy, it's confusing and I mix between them
159
+ possible_modes = ["auto" , "pyspy" , "py-spy" ],
160
+ default_mode = "auto" ,
161
+ # we build pyspy for both,.
162
+ supported_archs = ["x86_64" , "aarch64" ],
163
+ supported_windows_archs = ["AMD64" ],
164
+ # profiler arguments are defined by preferred profiler of the runtime, that is PythonEbpfProfiler
165
+ profiler_arguments = [],
166
+ supported_profiling_modes = ["cpu" ],
167
+ )
166
168
class PySpyProfiler (SpawningProcessProfilerBase ):
167
169
MAX_FREQUENCY = 50
168
170
_EXTRA_TIMEOUT = 10 # give py-spy some seconds to run (added to the duration)
@@ -173,10 +175,14 @@ def __init__(
173
175
duration : int ,
174
176
profiler_state : ProfilerState ,
175
177
* ,
176
- add_versions : bool ,
178
+ python_mode : str ,
179
+ python_add_versions : bool ,
177
180
):
178
181
super ().__init__ (frequency , duration , profiler_state )
179
- self .add_versions = add_versions
182
+ if python_mode == "py-spy" :
183
+ python_mode = "pyspy"
184
+ assert python_mode in ("auto" , "pyspy" ), f"unexpected mode: { python_mode } "
185
+ self .add_versions = python_add_versions
180
186
self ._metadata = PythonMetadata (self ._profiler_state .stop_event )
181
187
182
188
def _make_command (self , pid : int , output_path : str , duration : int ) -> List [str ]:
@@ -289,153 +295,5 @@ def _should_skip_process(self, process: Process) -> bool:
289
295
290
296
return False
291
297
292
-
293
- @register_profiler (
294
- "Python" ,
295
- # py-spy is like pyspy, it's confusing and I mix between them
296
- possible_modes = ["auto" , "pyperf" , "pyspy" , "py-spy" , "disabled" ],
297
- default_mode = "auto" ,
298
- # we build pyspy for both, pyperf only for x86_64.
299
- # TODO: this inconsistency shows that py-spy and pyperf should have different Profiler classes,
300
- # we should split them in the future.
301
- supported_archs = ["x86_64" , "aarch64" ],
302
- supported_windows_archs = ["AMD64" ],
303
- profiler_mode_argument_help = "Select the Python profiling mode: auto (try PyPerf, resort to py-spy if it fails), "
304
- "pyspy (always use py-spy), pyperf (always use PyPerf, and avoid py-spy even if it fails)"
305
- " or disabled (no runtime profilers for Python)." ,
306
- profiler_arguments = [
307
- # TODO should be prefixed with --python-
308
- ProfilerArgument (
309
- "--no-python-versions" ,
310
- dest = "python_add_versions" ,
311
- action = "store_false" ,
312
- default = True ,
313
- help = "Don't add version information to Python frames. If not set, frames from packages are displayed with "
314
- "the name of the package and its version, and frames from Python built-in modules are displayed with "
315
- "Python's full version." ,
316
- ),
317
- # TODO should be prefixed with --python-
318
- ProfilerArgument (
319
- "--pyperf-user-stacks-pages" ,
320
- dest = "python_pyperf_user_stacks_pages" ,
321
- default = None ,
322
- type = nonnegative_integer ,
323
- help = "Number of user stack-pages that PyPerf will collect, this controls the maximum stack depth of native "
324
- "user frames. Pass 0 to disable user native stacks altogether." ,
325
- ),
326
- ProfilerArgument (
327
- "--python-pyperf-verbose" ,
328
- dest = "python_pyperf_verbose" ,
329
- action = "store_true" ,
330
- help = "Enable PyPerf in verbose mode (max verbosity)" ,
331
- ),
332
- ],
333
- supported_profiling_modes = ["cpu" ],
334
- )
335
- class PythonProfiler (ProfilerInterface ):
336
- """
337
- Controls PySpyProfiler & PythonEbpfProfiler as needed, providing a clean interface
338
- to GProfiler.
339
- """
340
-
341
- def __init__ (
342
- self ,
343
- frequency : int ,
344
- duration : int ,
345
- profiler_state : ProfilerState ,
346
- python_mode : str ,
347
- python_add_versions : bool ,
348
- python_pyperf_user_stacks_pages : Optional [int ],
349
- python_pyperf_verbose : bool ,
350
- ):
351
- if python_mode == "py-spy" :
352
- python_mode = "pyspy"
353
-
354
- assert python_mode in ("auto" , "pyperf" , "pyspy" ), f"unexpected mode: { python_mode } "
355
-
356
- if get_arch () != "x86_64" or is_windows ():
357
- if python_mode == "pyperf" :
358
- raise Exception (f"PyPerf is supported only on x86_64 (and not on this arch { get_arch ()} )" )
359
- python_mode = "pyspy"
360
-
361
- if python_mode in ("auto" , "pyperf" ):
362
- self ._ebpf_profiler = self ._create_ebpf_profiler (
363
- frequency ,
364
- duration ,
365
- profiler_state ,
366
- python_add_versions ,
367
- python_pyperf_user_stacks_pages ,
368
- python_pyperf_verbose ,
369
- )
370
- else :
371
- self ._ebpf_profiler = None
372
-
373
- if python_mode == "pyspy" or (self ._ebpf_profiler is None and python_mode == "auto" ):
374
- self ._pyspy_profiler : Optional [PySpyProfiler ] = PySpyProfiler (
375
- frequency ,
376
- duration ,
377
- profiler_state ,
378
- add_versions = python_add_versions ,
379
- )
380
- else :
381
- self ._pyspy_profiler = None
382
-
383
- if is_linux ():
384
-
385
- def _create_ebpf_profiler (
386
- self ,
387
- frequency : int ,
388
- duration : int ,
389
- profiler_state : ProfilerState ,
390
- add_versions : bool ,
391
- user_stacks_pages : Optional [int ],
392
- verbose : bool ,
393
- ) -> Optional [PythonEbpfProfiler ]:
394
- try :
395
- profiler = PythonEbpfProfiler (
396
- frequency ,
397
- duration ,
398
- profiler_state ,
399
- add_versions = add_versions ,
400
- user_stacks_pages = user_stacks_pages ,
401
- verbose = verbose ,
402
- )
403
- profiler .test ()
404
- return profiler
405
- except Exception as e :
406
- logger .debug (f"eBPF profiler error: { str (e )} " )
407
- logger .info ("Python eBPF profiler initialization failed" )
408
- return None
409
-
410
298
def check_readiness (self ) -> bool :
411
299
return True
412
-
413
- def start (self ) -> None :
414
- if self ._ebpf_profiler is not None :
415
- self ._ebpf_profiler .start ()
416
- elif self ._pyspy_profiler is not None :
417
- self ._pyspy_profiler .start ()
418
-
419
- def snapshot (self ) -> ProcessToProfileData :
420
- if self ._ebpf_profiler is not None :
421
- try :
422
- return self ._ebpf_profiler .snapshot ()
423
- except PythonEbpfError as e :
424
- assert not self ._ebpf_profiler .is_running ()
425
- logger .warning (
426
- "Python eBPF profiler failed, restarting PyPerf..." ,
427
- pyperf_exit_code = e .returncode ,
428
- pyperf_stdout = e .stdout ,
429
- pyperf_stderr = e .stderr ,
430
- )
431
- self ._ebpf_profiler .start ()
432
- return {} # empty this round
433
- else :
434
- assert self ._pyspy_profiler is not None
435
- return self ._pyspy_profiler .snapshot ()
436
-
437
- def stop (self ) -> None :
438
- if self ._ebpf_profiler is not None :
439
- self ._ebpf_profiler .stop ()
440
- elif self ._pyspy_profiler is not None :
441
- self ._pyspy_profiler .stop ()
0 commit comments