diff --git a/backends/ze/ze_sampling_plugin.c b/backends/ze/ze_sampling_plugin.c index 79e6a2f4..bdb6bcaa 100644 --- a/backends/ze/ze_sampling_plugin.c +++ b/backends/ze/ze_sampling_plugin.c @@ -755,7 +755,10 @@ void thapi_initialize_sampling_plugin(void) { // register L0 sampler exactly once { - struct timespec interval = {.tv_sec = 0, .tv_nsec = 50000000}; /* 50 ms */ + const char *s = getenv("LTTNG_UST_ZE_SAMPLING_ENERGY_PERIOD_MS"); + long milliseconds = s ? atol(s) : 50; + struct timespec interval = {.tv_sec = milliseconds / 1000, + .tv_nsec = (milliseconds % 1000) * 1000000L}; plugin_handle = thapi_register_sampling(&thapi_sampling_energy, &interval); } return; diff --git a/integration_tests/sampling.bats b/integration_tests/sampling.bats index eaeb8ef9..ae81bba5 100644 --- a/integration_tests/sampling.bats +++ b/integration_tests/sampling.bats @@ -1,5 +1,49 @@ bats_require_minimum_version 1.5.0 +@test "sampling_interval_help" { + run iprof --help + + [ "$status" -eq 0 ] + [[ "$output" == *"-i, --sample-interval MS"* ]] + [[ "$output" == *"Default: 50"* ]] + [[ "$output" == *"frequency, energy, engine, fabric-port, and memory"* ]] +} + +@test "sampling_interval_is_passed_to_ze_sampler" { + for option in -i --sample-interval; do + trace="sampling_interval_trace_${option#-}" + rm -rf "$trace" + + LTTNG_UST_ZE_LIBZE_LOADER=/dev/null \ + iprof --no-analysis --sample --backends ze "$option" 125 \ + --trace-output "$trace" -- \ + bash -c 'test "$LTTNG_UST_ZE_SAMPLING_ENERGY_PERIOD_MS" = 125' + done +} + +@test "sampling_interval_rejects_invalid_values" { + for interval in 0 -1 nope; do + run iprof --sample --sample-interval "$interval" -- true + + [ "$status" -ne 0 ] + [[ "$output" == *"ERROR:"* ]] + done +} + +@test "sampling_interval_requires_sampling" { + run iprof --sample-interval 125 -- true + + [ "$status" -ne 0 ] + [[ "$output" == *"--sample-interval requires --sample"* ]] +} + +@test "sampling_interval_requires_ze_backend" { + run iprof --sample --sample-interval 125 --backends cxi -- true + + [ "$status" -ne 0 ] + [[ "$output" == *"--sample-interval requires the ze backend"* ]] +} + @test "sampling_heartbeat" { rm -rf heartbeat_trace diff --git a/xprof/xprof.rb.in b/xprof/xprof.rb.in index 95af63b0..6cf53b7b 100755 --- a/xprof/xprof.rb.in +++ b/xprof/xprof.rb.in @@ -928,6 +928,7 @@ def all_env_tracers(usr_binary) # is to call zesInit and set ZES_ENABLE_SYSMAN to 0 h['ZES_ENABLE_SYSMAN'] = 0 h['LTTNG_UST_ZE_SAMPLING_ENERGY'] = 1 + h['LTTNG_UST_ZE_SAMPLING_ENERGY_PERIOD_MS'] = OPTIONS[:'sample-interval'] if OPTIONS.include?(:'sample-interval') h['THAPI_SAMPLING_LIBRARIES'] << File.join(PKGLIBDIR, 'ze', 'libZESampling.so') end end @@ -1067,6 +1068,14 @@ if $thapi_launch || __FILE__ == $PROGRAM_NAME 'Use -1 for no limit.', default: 80) parser.on('-s', '--sample', 'Enable counters sampling.') + parser.on('-i', '--sample-interval MS', OptionParser::DecimalInteger, + 'Set the Level Zero telemetry sampling interval in milliseconds.', + 'Controls frequency, energy, engine, fabric-port, and memory samples.', + 'Default: 50 ms.') do |interval| + raise(OptionParser::ParseError, 'sample interval must be greater than zero') unless interval.positive? + + interval + end parser.on('--metadata', 'Display trace metadata.') parser.on('-v', '--version', 'Print the Version String.') do @@ -1098,6 +1107,13 @@ if $thapi_launch || __FILE__ == $PROGRAM_NAME options = {} begin parser.parse!(into: options) + options[:'backend-names'] = options[:backends].map { |name_level| name_level.split(':').first } + if options.include?(:'sample-interval') + raise OptionParser::ParseError, '--sample-interval requires --sample' unless options[:sample] + unless options[:'backend-names'].include?('ze') + raise OptionParser::ParseError, '--sample-interval requires the ze backend' + end + end rescue OptionParser::InvalidOption => e puts("ERROR: #{e}. Maybe missing --?") print_help_and_exit(parser) @@ -1106,7 +1122,6 @@ if $thapi_launch || __FILE__ == $PROGRAM_NAME print_help_and_exit(parser) end - options[:'backend-names'] = options[:backends].map { |name_level| name_level.split(':').first } OPTIONS = options.freeze if (launcher = %w[mpirun mpiexec].find { |b| ARGV.include?(b) })