-
Notifications
You must be signed in to change notification settings - Fork 74
Initial design and implementation #1014
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
a159c66
ee0ff9e
0f718a5
f1bb9b8
4cee7bb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -60,6 +60,7 @@ | |||||||||||||
| from gprofiler.platform import is_aarch64, is_linux, is_windows | ||||||||||||||
| from gprofiler.profiler_state import ProfilerState | ||||||||||||||
| from gprofiler.profilers.factory import get_profilers | ||||||||||||||
| from gprofiler.profilers.perf import SystemProfiler | ||||||||||||||
| from gprofiler.profilers.profiler_base import NoopProfiler, ProcessProfilerBase, ProfilerInterface | ||||||||||||||
| from gprofiler.profilers.registry import get_profilers_registry | ||||||||||||||
| from gprofiler.state import State, init_state | ||||||||||||||
|
|
@@ -335,6 +336,51 @@ def _snapshot(self) -> None: | |||||||||||||
| else {"hostname": get_hostname()} | ||||||||||||||
| ) | ||||||||||||||
| metadata.update({"profiling_mode": self._profiler_state.profiling_mode}) | ||||||||||||||
|
|
||||||||||||||
| # Add sampling event information if custom event is being used | ||||||||||||||
| if isinstance(self.system_profiler, SystemProfiler) and self.system_profiler._custom_event_name: | ||||||||||||||
| from gprofiler.platform import get_hypervisor_vendor | ||||||||||||||
| from gprofiler.utils.hw_events import get_event_type, get_perf_available_events, get_precise_modifier | ||||||||||||||
|
|
||||||||||||||
| event_name = self.system_profiler._custom_event_name | ||||||||||||||
| hypervisor_vendor = get_hypervisor_vendor() | ||||||||||||||
| perf_events = get_perf_available_events() | ||||||||||||||
| event_type = get_event_type(event_name, perf_events) | ||||||||||||||
|
|
||||||||||||||
| # Use "custom" as fallback if event_type is None or empty | ||||||||||||||
| effective_type = event_type if event_type else "custom" | ||||||||||||||
| modifier = get_precise_modifier(event_name, effective_type, hypervisor_vendor) | ||||||||||||||
|
|
||||||||||||||
| metadata.update( | ||||||||||||||
| { | ||||||||||||||
| "sampling_event": event_name, | ||||||||||||||
| "sampling_mode": "period" if self.system_profiler._perf_period else "frequency", | ||||||||||||||
| "precise_modifier": modifier, | ||||||||||||||
| } | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| if self.system_profiler._perf_period: | ||||||||||||||
| metadata.update({"sampling_period": self.system_profiler._perf_period}) | ||||||||||||||
| else: | ||||||||||||||
| metadata.update({"sampling_frequency": self.system_profiler._frequency}) | ||||||||||||||
|
Comment on lines
+340
to
+365
|
||||||||||||||
| elif isinstance(self.system_profiler, SystemProfiler): | ||||||||||||||
| # Default CPU time-based profiling | ||||||||||||||
| metadata.update( | ||||||||||||||
| { | ||||||||||||||
| "sampling_event": "cpu-time", | ||||||||||||||
| "sampling_mode": "frequency", | ||||||||||||||
| "sampling_frequency": self.system_profiler._frequency, | ||||||||||||||
| } | ||||||||||||||
| ) | ||||||||||||||
| else: | ||||||||||||||
| # NoopProfiler - use default values | ||||||||||||||
| metadata.update( | ||||||||||||||
| { | ||||||||||||||
| "sampling_event": "cpu-time", | ||||||||||||||
| "sampling_mode": "frequency", | ||||||||||||||
| "sampling_frequency": 11, | ||||||||||||||
| } | ||||||||||||||
| ) | ||||||||||||||
| metrics = self._system_metrics_monitor.get_metrics() | ||||||||||||||
| hwmetrics = self._hw_metrics_monitor.get_hw_metrics() | ||||||||||||||
| if hwmetrics is None: | ||||||||||||||
|
|
@@ -606,6 +652,32 @@ def parse_cmd_args() -> configargparse.Namespace: | |||||||||||||
|
|
||||||||||||||
| _add_profilers_arguments(parser) | ||||||||||||||
|
|
||||||||||||||
| # Custom perf event arguments | ||||||||||||||
| perf_event_options = parser.add_argument_group("Perf Event") | ||||||||||||||
| perf_event_options.add_argument( | ||||||||||||||
| "--perf-event", | ||||||||||||||
| type=str, | ||||||||||||||
| dest="perf_event", | ||||||||||||||
| help="Specify a perf event for flamegraph generation (e.g., cache-misses, page-faults, sched:sched_switch). " | ||||||||||||||
| "When specified, only perf profiler will be active and all language-specific profilers will be disabled. " | ||||||||||||||
| "Event can be from 'perf list' or a custom event defined in hw_events.json.", | ||||||||||||||
| ) | ||||||||||||||
| perf_event_options.add_argument( | ||||||||||||||
| "--perf-event-period", | ||||||||||||||
| type=int, | ||||||||||||||
| dest="perf_event_period", | ||||||||||||||
| help="Use period-based sampling instead of frequency (-c instead of -F). " | ||||||||||||||
| "Specify the number of events between samples (e.g., 10000 for sampling every 10000 events). " | ||||||||||||||
| "Only valid with --perf-event.", | ||||||||||||||
| ) | ||||||||||||||
|
Comment on lines
+665
to
+672
|
||||||||||||||
| perf_event_options.add_argument( | ||||||||||||||
| "--hw-events-file", | ||||||||||||||
| type=str, | ||||||||||||||
| dest="hw_events_file", | ||||||||||||||
| help="Path to a JSON file containing custom PMU event definitions. " | ||||||||||||||
| "Only valid with --perf-event. If not specified, only built-in perf events are available.", | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| spark_options = parser.add_argument_group("Spark") | ||||||||||||||
|
|
||||||||||||||
| spark_options.add_argument( | ||||||||||||||
|
|
@@ -892,6 +964,14 @@ def parse_cmd_args() -> configargparse.Namespace: | |||||||||||||
| args.perf_inject = args.nodejs_mode == "perf" | ||||||||||||||
| args.perf_node_attach = args.nodejs_mode == "attach-maps" | ||||||||||||||
|
|
||||||||||||||
| # Validate --perf-event-period and -f/--frequency are mutually exclusive | ||||||||||||||
| # Must check before defaults are applied (args.frequency is None if not explicitly provided) | ||||||||||||||
| if args.perf_event_period and args.frequency is not None: | ||||||||||||||
| parser.error( | ||||||||||||||
| "--perf-event-period and -f/--frequency are mutually exclusive. " | ||||||||||||||
| "Use --perf-event-period for period-based sampling or -f for frequency-based sampling." | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| if args.profiling_mode == CPU_PROFILING_MODE: | ||||||||||||||
| if args.alloc_interval: | ||||||||||||||
| parser.error("--alloc-interval is only allowed in allocation profiling (--mode=allocation)") | ||||||||||||||
|
|
@@ -936,6 +1016,40 @@ def parse_cmd_args() -> configargparse.Namespace: | |||||||||||||
| if args.profile_spawned_processes and args.pids_to_profile is not None: | ||||||||||||||
| parser.error("--pids is not allowed when profiling spawned processes") | ||||||||||||||
|
|
||||||||||||||
| # Validate --perf-event-period only works with --perf-event | ||||||||||||||
| if args.perf_event_period and not args.perf_event: | ||||||||||||||
| parser.error("--perf-event-period requires --perf-event to be specified") | ||||||||||||||
|
|
||||||||||||||
| # Validate --hw-events-file only works with --perf-event | ||||||||||||||
| if getattr(args, "hw_events_file", None) and not args.perf_event: | ||||||||||||||
| parser.error("--hw-events-file requires --perf-event to be specified") | ||||||||||||||
|
|
||||||||||||||
| # Validate --perf-event only works with cpu profiling mode | ||||||||||||||
| if args.perf_event and args.profiling_mode != CPU_PROFILING_MODE: | ||||||||||||||
| parser.error("--perf-event is only supported in cpu profiling mode (--mode=cpu)") | ||||||||||||||
|
|
||||||||||||||
| # Validate and resolve perf event arguments | ||||||||||||||
| if args.perf_event: | ||||||||||||||
|
||||||||||||||
| if args.perf_event: | |
| if args.perf_event: | |
| # Perf events are only supported in CPU profiling mode. In other modes (e.g. allocation, none), | |
| # the perf profiler may be disabled or process profilers skipped, which can result in no profiling. | |
| if args.profiling_mode != CPU_PROFILING_MODE: | |
| parser.error("--perf-event is only supported with --mode cpu") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -193,8 +193,10 @@ class SystemInfo: | |
| kernel_release: str | ||
| kernel_version: str | ||
| system_name: str | ||
| hypervisor: str | ||
| processors: int | ||
| cpu_model_name: str | ||
| cpu_arch_codename: str | ||
| cpu_flags: str | ||
| memory_capacity_mb: int | ||
| hostname: str | ||
|
|
@@ -254,15 +256,24 @@ def get_static_system_info() -> SystemInfo: | |
| run_mode = get_run_mode() | ||
| deployment_type = get_deployment_type(run_mode) | ||
| cpu_model_name, cpu_flags = get_cpu_info() | ||
|
|
||
| # Import here to avoid circular dependency | ||
| from gprofiler.platform import get_cpu_model, get_hypervisor_vendor | ||
|
|
||
| hypervisor = get_hypervisor_vendor() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this is only needed for custom event, can we lazy load this up
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How late do you mean by? I think doing this earlier than later is better. As this is done during initialization and only one time, it shouldn't affect overhead at all. It would be better than doing during dynamic profiling by custom event requested. Not sure if I understood your comment. Let me know if I misunderstood. |
||
| cpu_arch_codename = get_cpu_model() | ||
|
|
||
| return SystemInfo( | ||
| python_version=sys.version, | ||
| run_mode=run_mode, | ||
| deployment_type=deployment_type, | ||
| kernel_release=uname.release, | ||
| kernel_version=uname.version, | ||
| system_name=uname.system, | ||
| hypervisor=hypervisor, | ||
| processors=cpu_count, | ||
| cpu_model_name=cpu_model_name, | ||
| cpu_arch_codename=cpu_arch_codename, | ||
| cpu_flags=cpu_flags, | ||
| memory_capacity_mb=round(psutil.virtual_memory().total / 1024 / 1024), | ||
| hostname=hostname, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
README points users to
gprofiler/resources/hw_events_template.json, but that file isn’t covered by the currentMANIFEST.inpatterns (onlyresources/burn,resources/perf, and a few subdirectories are included). In packaged installs (sdist/wheel), the template may be missing.Ensure the new JSON template is included as package data (e.g., update
MANIFEST.in/ packaging configuration accordingly).