From: Marcin Bernatowicz <marcin.bernatowicz@linux.intel.com>
To: igt-dev@lists.freedesktop.org
Cc: adam.miszczak@linux.intel.com, jakub1.kolakowski@intel.com,
lukasz.laguna@intel.com,
Marcin Bernatowicz <marcin.bernatowicz@linux.intel.com>
Subject: [PATCH i-g-t 4/8] tests/intel/xe_sriov_scheduling: Add PMU-based verification helpers
Date: Wed, 27 May 2026 22:56:37 +0200 [thread overview]
Message-ID: <20260527205644.2801165-5-marcin.bernatowicz@linux.intel.com> (raw)
In-Reply-To: <20260527205644.2801165-1-marcin.bernatowicz@linux.intel.com>
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=y, Size: 14449 bytes --]
Add Xe PMU helper infrastructure to collect per-VF engine-active-ticks and
engine-total-ticks over the workload measurement window.
Use the collected counters to derive per-VF runtime shares and cross-check
them against throughput shares computed from workload completion
statistics, strengthening scheduling validation.
Keep this verification optional by enabling it only when the required
Xe perf events are available.
Signed-off-by: Marcin Bernatowicz <marcin.bernatowicz@linux.intel.com>
Cc: Adam Miszczak <adam.miszczak@linux.intel.com>
Cc: Jakub Kolakowski <jakub1.kolakowski@intel.com>
Cc: Lukasz Laguna <lukasz.laguna@intel.com>
---
tests/intel/xe_sriov_scheduling.c | 433 ++++++++++++++++++++++++++++++
1 file changed, 433 insertions(+)
diff --git a/tests/intel/xe_sriov_scheduling.c b/tests/intel/xe_sriov_scheduling.c
index f7cca95ae..6d8b7640d 100644
--- a/tests/intel/xe_sriov_scheduling.c
+++ b/tests/intel/xe_sriov_scheduling.c
@@ -3,6 +3,7 @@
* Copyright © 2025 Intel Corporation
*/
#include "igt.h"
+#include "igt_perf.h"
#include "igt_sriov_device.h"
#include "igt_syncobj.h"
#include "igt_sysfs.h"
@@ -660,6 +661,438 @@ prepare_job_sched_params(int num_threads, int job_timeout_ms, const struct subm_
return params;
}
+struct vf_config {
+ unsigned int vf_id;
+ struct vf_sched_params sched_params;
+ bool run_workload;
+};
+
+struct runtime_metrics {
+ unsigned int vf_id;
+ uint64_t engine_active_ticks;
+ uint64_t engine_total_ticks;
+ double measured_total_tick_share;
+ double active_to_total_ratio;
+ double measured_throughput_share;
+ double expected_throughput_share;
+ double expected_active_to_total_ratio;
+ double expected_total_tick_share;
+};
+
+struct perf_counters {
+ int pmu_fd[2];
+ uint64_t before[2];
+ uint64_t after[2];
+};
+
+static char perf_device[NAME_MAX];
+
+static bool has_perf_event(const char *device, const char *event)
+{
+ char path[512];
+
+ snprintf(path, sizeof(path),
+ "/sys/bus/event_source/devices/%s/events/%s",
+ device, event);
+
+ return access(path, F_OK) == 0;
+}
+
+static int perf_open_group(int xe, uint64_t config, int group)
+{
+ int fd;
+
+ fd = igt_perf_open_group(xe_perf_type_id(xe), config, group);
+ igt_skip_on(fd < 0 && errno == ENODEV);
+ igt_assert_fd(fd);
+
+ return fd;
+}
+
+static uint64_t perf_read_values(int fd, unsigned int num, uint64_t *val)
+{
+ uint64_t buf[2 + num];
+ unsigned int i;
+
+ igt_assert_eq(read(fd, buf, sizeof(buf)), sizeof(buf));
+
+ for (i = 0; i < num; i++)
+ val[i] = buf[2 + i];
+
+ return buf[1];
+}
+
+static uint64_t perf_add_format_config(const char *format, uint64_t val)
+{
+ uint64_t config;
+ uint32_t shift;
+ int ret;
+
+ ret = perf_event_format(perf_device, format, &shift);
+ igt_assert(ret >= 0);
+ config = val << shift;
+
+ return config;
+}
+
+static uint64_t perf_get_event_config(unsigned int gt,
+ const struct drm_xe_engine_class_instance *eci,
+ const char *event)
+{
+ uint64_t perf_config = 0;
+ int ret;
+
+ ret = perf_event_config(perf_device, event, &perf_config);
+ igt_assert(ret >= 0);
+ perf_config |= perf_add_format_config("gt", gt);
+
+ if (eci) {
+ perf_config |= perf_add_format_config("engine_class", eci->engine_class);
+ perf_config |= perf_add_format_config("engine_instance", eci->engine_instance);
+ }
+
+ return perf_config;
+}
+
+static uint64_t perf_get_event_config_vf(unsigned int gt, unsigned int vf_id,
+ const struct drm_xe_engine_class_instance *eci,
+ const char *event)
+{
+ return perf_get_event_config(gt, eci, event) |
+ perf_add_format_config("function", vf_id);
+}
+
+static double expected_total_tick_weight(const struct vf_config *config)
+{
+ return config->sched_params.exec_quantum_ms;
+}
+
+static double expected_throughput_weight(const struct vf_config *config)
+{
+ if (!config->run_workload)
+ return 0.0;
+
+ return config->sched_params.exec_quantum_ms;
+}
+
+static void init_perf_counters(int pf_fd,
+ const struct drm_xe_engine_class_instance *eci,
+ const struct vf_config *configs,
+ size_t num_configs,
+ struct perf_counters *counters)
+{
+ int leader = -1;
+
+ for (size_t i = 0; i < num_configs; i++) {
+ uint64_t config;
+
+ counters[i].pmu_fd[0] = -1;
+ counters[i].pmu_fd[1] = -1;
+
+ config = perf_get_event_config_vf(eci->gt_id, configs[i].vf_id, eci,
+ "engine-active-ticks");
+ counters[i].pmu_fd[0] = perf_open_group(pf_fd, config, leader);
+ if (leader < 0)
+ leader = counters[i].pmu_fd[0];
+
+ config = perf_get_event_config_vf(eci->gt_id, configs[i].vf_id, eci,
+ "engine-total-ticks");
+ counters[i].pmu_fd[1] = perf_open_group(pf_fd, config, leader);
+ }
+}
+
+static void fini_perf_counters(struct perf_counters *counters,
+ size_t num_configs)
+{
+ for (size_t i = 0; i < num_configs; i++) {
+ if (counters[i].pmu_fd[0] >= 0)
+ close(counters[i].pmu_fd[0]);
+ if (counters[i].pmu_fd[1] >= 0)
+ close(counters[i].pmu_fd[1]);
+ }
+}
+
+static uint64_t start_perf_counters(struct perf_counters *counters,
+ size_t num_configs)
+{
+ uint64_t values[2 * num_configs];
+
+ perf_read_values(counters[0].pmu_fd[0], 2 * num_configs, values);
+
+ for (size_t i = 0; i < num_configs; i++) {
+ counters[i].before[0] = values[2 * i];
+ counters[i].before[1] = values[2 * i + 1];
+ }
+
+ return current_timestamp_ns();
+}
+
+static void
+init_expected_runtime_metrics(const struct vf_config *configs,
+ size_t num_configs,
+ struct runtime_metrics *metrics)
+{
+ double total_expected_total_tick_weight = 0.0;
+ double total_expected_throughput_weight = 0.0;
+
+ for (size_t i = 0; i < num_configs; i++) {
+ total_expected_total_tick_weight += expected_total_tick_weight(&configs[i]);
+ total_expected_throughput_weight += expected_throughput_weight(&configs[i]);
+ }
+
+ for (size_t i = 0; i < num_configs; i++) {
+ metrics[i].vf_id = configs[i].vf_id;
+ metrics[i].expected_throughput_share = total_expected_throughput_weight ?
+ expected_throughput_weight(&configs[i]) /
+ total_expected_throughput_weight : 0.0;
+ metrics[i].expected_total_tick_share = total_expected_total_tick_weight ?
+ expected_total_tick_weight(&configs[i]) /
+ total_expected_total_tick_weight : 0.0;
+ metrics[i].expected_active_to_total_ratio =
+ configs[i].sched_params.priority == XE_SRIOV_SCHED_PRIORITY_NORMAL ?
+ (configs[i].run_workload ? 1.0 : 0.0) :
+ (metrics[i].expected_total_tick_share ?
+ metrics[i].expected_throughput_share /
+ metrics[i].expected_total_tick_share : 0.0);
+ }
+}
+
+static void compute_perf_metrics(const struct vf_config *configs,
+ const struct perf_counters *counters,
+ size_t num_configs,
+ struct runtime_metrics *metrics)
+{
+ uint64_t total_engine_total_ticks = 0;
+
+ init_expected_runtime_metrics(configs, num_configs, metrics);
+
+ for (size_t i = 0; i < num_configs; i++) {
+ metrics[i].engine_active_ticks = counters[i].after[0] - counters[i].before[0];
+ metrics[i].engine_total_ticks = counters[i].after[1] - counters[i].before[1];
+ total_engine_total_ticks += metrics[i].engine_total_ticks;
+ }
+
+ for (size_t i = 0; i < num_configs; i++) {
+ metrics[i].active_to_total_ratio = metrics[i].engine_total_ticks ?
+ (double)metrics[i].engine_active_ticks /
+ metrics[i].engine_total_ticks : 0.0;
+ metrics[i].measured_total_tick_share = total_engine_total_ticks ?
+ (double)metrics[i].engine_total_ticks /
+ total_engine_total_ticks : 0.0;
+
+ igt_info("%s actual={active_ticks=%" PRIu64 ",total_ticks=%" PRIu64
+ ",active/total=%.4f,total_share=%.4f} "
+ "expected={active/total=%.4f,total_share=%.4f,throughput_share=%.4f}\n",
+ igt_sriov_func_str(configs[i].vf_id),
+ metrics[i].engine_active_ticks, metrics[i].engine_total_ticks,
+ metrics[i].active_to_total_ratio,
+ metrics[i].measured_total_tick_share,
+ metrics[i].expected_active_to_total_ratio,
+ metrics[i].expected_total_tick_share,
+ metrics[i].expected_throughput_share);
+ }
+}
+
+static uint64_t stop_perf_counters(const struct vf_config *configs,
+ struct perf_counters *counters,
+ size_t num_configs,
+ struct runtime_metrics *metrics)
+{
+ uint64_t end_timestamp = current_timestamp_ns();
+ uint64_t values[2 * num_configs];
+
+ perf_read_values(counters[0].pmu_fd[0], 2 * num_configs, values);
+
+ for (size_t i = 0; i < num_configs; i++) {
+ counters[i].after[0] = values[2 * i];
+ counters[i].after[1] = values[2 * i + 1];
+ }
+
+ compute_perf_metrics(configs, counters, num_configs, metrics);
+
+ return end_timestamp;
+}
+
+static void init_vf_configs_from_set(int pf_fd,
+ const struct subm_set *set,
+ struct vf_config *configs)
+{
+ const struct drm_xe_engine_class_instance *eci = &set->data[0].subm.hwe;
+
+ for (int n = 0; n < set->ndata; ++n) {
+ const struct subm *subm = &set->data[n].subm;
+ const int vf_num = subm->vf_num;
+
+ igt_assert_eq(subm->hwe.gt_id, eci->gt_id);
+ igt_assert_eq(subm->hwe.engine_class, eci->engine_class);
+ igt_assert_eq(subm->hwe.engine_instance, eci->engine_instance);
+ configs[n] = (struct vf_config) {
+ .vf_id = vf_num,
+ .sched_params = {
+ .exec_quantum_ms = xe_sriov_admin_get_exec_quantum_ms(pf_fd,
+ vf_num),
+ .preempt_timeout_us = xe_sriov_admin_get_preempt_timeout_us(pf_fd,
+ vf_num),
+ .priority = xe_sriov_admin_get_sched_priority(pf_fd, vf_num, NULL),
+ },
+ .run_workload = true,
+ };
+ }
+}
+
+static unsigned int count_window_completions(const struct subm_stats *stats,
+ uint64_t window_start,
+ uint64_t window_end)
+{
+ unsigned int completions = 0;
+
+ for (int i = 0; i < stats->samples.n_values; i++) {
+ uint64_t cts = stats->complete_ts[i];
+
+ if (cts >= window_start && cts <= window_end)
+ completions++;
+ }
+
+ return completions;
+}
+
+static void compute_measured_throughput_share(const struct subm_set *set,
+ uint64_t window_start,
+ uint64_t window_end,
+ struct runtime_metrics *metrics)
+{
+ unsigned int completions[set->ndata];
+ unsigned int total_completions = 0;
+
+ for (int n = 0; n < set->ndata; ++n) {
+ completions[n] = count_window_completions(&set->data[n].stats,
+ window_start,
+ window_end);
+ total_completions += completions[n];
+ }
+
+ for (int n = 0; n < set->ndata; ++n) {
+ metrics[n].measured_throughput_share = total_completions ?
+ (double)completions[n] / total_completions : 0.0;
+ }
+}
+
+static void compute_concurrent_rate_share(const struct subm_set *set,
+ struct runtime_metrics *metrics)
+{
+ double total_rate = 0.0;
+
+ for (int n = 0; n < set->ndata; ++n)
+ total_rate += set->data[n].stats.concurrent_rate;
+
+ for (int n = 0; n < set->ndata; ++n) {
+ metrics[n].measured_throughput_share = total_rate ?
+ set->data[n].stats.concurrent_rate / total_rate : 0.0;
+ }
+}
+
+static void run_subm_set_and_collect_metrics(int pf_fd,
+ struct subm_set *set,
+ struct vf_config *vf_configs,
+ struct runtime_metrics *metrics,
+ bool verify_with_perf_counters,
+ uint64_t *measurement_start_ns,
+ uint64_t *measurement_end_ns)
+{
+ struct perf_counters *perf_counters = NULL;
+ const struct drm_xe_engine_class_instance *eci = &set->data[0].subm.hwe;
+
+ init_vf_configs_from_set(pf_fd, set, vf_configs);
+ init_expected_runtime_metrics(vf_configs, set->ndata, metrics);
+
+ if (verify_with_perf_counters) {
+ perf_counters = calloc(set->ndata, sizeof(*perf_counters));
+ igt_assert(perf_counters);
+ init_perf_counters(pf_fd, eci,
+ vf_configs, set->ndata, perf_counters);
+ *measurement_start_ns = start_perf_counters(perf_counters,
+ set->ndata);
+ }
+
+ subm_set_dispatch_and_wait_threads(set);
+
+ if (verify_with_perf_counters) {
+ *measurement_end_ns = stop_perf_counters(vf_configs,
+ perf_counters,
+ set->ndata,
+ metrics);
+ fini_perf_counters(perf_counters, set->ndata);
+ free(perf_counters);
+ }
+
+ subm_set_close_handles(set);
+ compute_common_time_frame_stats(set);
+ compute_concurrent_rate_share(set, metrics);
+
+ if (verify_with_perf_counters)
+ compute_measured_throughput_share(set,
+ *measurement_start_ns,
+ *measurement_end_ns,
+ metrics);
+}
+
+static void assert_share_matches_expected(const struct runtime_metrics *metrics,
+ size_t num_metrics,
+ double tolerance,
+ bool use_total_tick_share)
+{
+ for (size_t n = 0; n < num_metrics; ++n) {
+ double measured_share = use_total_tick_share ?
+ metrics[n].measured_total_tick_share :
+ metrics[n].measured_throughput_share;
+ double expected_share = use_total_tick_share ?
+ metrics[n].expected_total_tick_share :
+ metrics[n].expected_throughput_share;
+ const char *share_name = use_total_tick_share ?
+ "total tick share" : "throughput share";
+
+ igt_assert_f(check_within_epsilon(measured_share,
+ expected_share,
+ tolerance),
+ "%s=%0.4f not within +-%.0f%% of expected=%0.4f for %s\n",
+ share_name, measured_share, tolerance * 100,
+ expected_share,
+ igt_sriov_func_str(metrics[n].vf_id));
+ }
+}
+
+static void assert_throughput_share_matches_total_tick(const struct runtime_metrics *metrics,
+ size_t num_metrics,
+ double tolerance)
+{
+ for (size_t n = 0; n < num_metrics; ++n) {
+ double expected_ratio = metrics[n].expected_total_tick_share ?
+ metrics[n].expected_throughput_share /
+ metrics[n].expected_total_tick_share : 0.0;
+ double expected_throughput_share =
+ metrics[n].measured_total_tick_share * expected_ratio;
+
+ assert_within_epsilon(metrics[n].measured_throughput_share,
+ expected_throughput_share,
+ tolerance);
+ }
+}
+
+static void warn_active_ticks_mismatch(const struct runtime_metrics *metrics,
+ size_t num_metrics,
+ double tolerance)
+{
+ for (size_t n = 0; n < num_metrics; ++n) {
+ igt_warn_on_f(!check_within_epsilon(metrics[n].active_to_total_ratio,
+ metrics[n].expected_active_to_total_ratio,
+ tolerance),
+ "active/total=%0.4f not within +-%.0f%% of expected=%0.4f for %s\n",
+ metrics[n].active_to_total_ratio, tolerance * 100,
+ metrics[n].expected_active_to_total_ratio,
+ igt_sriov_func_str(metrics[n].vf_id));
+ }
+}
+
/**
* SUBTEST: equal-throughput
* Description:
--
2.43.0
next prev parent reply other threads:[~2026-05-27 20:58 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-27 20:56 [PATCH i-g-t 0/8] tests/intel/xe_sriov_scheduling: improve scheduling coverage Marcin Bernatowicz
2026-05-27 20:56 ` [PATCH i-g-t 1/8] tests/intel/xe_sriov_scheduling: Use timestamp helper Marcin Bernatowicz
2026-05-28 10:43 ` Kamil Konieczny
2026-06-09 14:48 ` Laguna, Lukasz
2026-05-27 20:56 ` [PATCH i-g-t 2/8] tests/intel/xe_sriov_scheduling: Add scheduling priority support Marcin Bernatowicz
2026-06-09 14:51 ` Laguna, Lukasz
2026-05-27 20:56 ` [PATCH i-g-t 3/8] tests/intel/xe_sriov_scheduling: Make sysfs_get_job_timeout_ms take const eci Marcin Bernatowicz
2026-06-09 14:52 ` Laguna, Lukasz
2026-05-27 20:56 ` Marcin Bernatowicz [this message]
2026-06-09 14:53 ` [PATCH i-g-t 4/8] tests/intel/xe_sriov_scheduling: Add PMU-based verification helpers Laguna, Lukasz
2026-05-27 20:56 ` [PATCH i-g-t 5/8] tests/intel/xe_sriov_scheduling: Raise min exec quantum to 2ms Marcin Bernatowicz
2026-06-09 14:54 ` Laguna, Lukasz
2026-05-27 20:56 ` [PATCH i-g-t 6/8] tests/intel/xe_sriov_scheduling: Refactor throughput_ratio and nonpreempt-engine-resets test Marcin Bernatowicz
2026-06-09 14:58 ` Laguna, Lukasz
2026-05-27 20:56 ` [PATCH i-g-t 7/8] tests/intel/xe_sriov_scheduling: Remove unused log_sample_values helper Marcin Bernatowicz
2026-06-09 14:58 ` Laguna, Lukasz
2026-05-27 20:56 ` [PATCH i-g-t 8/8] tests/intel/xe_sriov_scheduling: Add default fair scheduling test Marcin Bernatowicz
2026-06-09 14:58 ` Laguna, Lukasz
2026-05-28 0:01 ` ✓ Xe.CI.BAT: success for tests/intel/xe_sriov_scheduling: improve scheduling coverage Patchwork
2026-05-28 0:04 ` ✗ i915.CI.BAT: failure " Patchwork
2026-05-28 12:15 ` ✓ Xe.CI.FULL: success " Patchwork
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260527205644.2801165-5-marcin.bernatowicz@linux.intel.com \
--to=marcin.bernatowicz@linux.intel.com \
--cc=adam.miszczak@linux.intel.com \
--cc=igt-dev@lists.freedesktop.org \
--cc=jakub1.kolakowski@intel.com \
--cc=lukasz.laguna@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.