[PATCH i-g-t] tests/intel/xe_pmu: Add tests to validate engine activity accuracy
Soham Purkait
soham.purkait at intel.com
Thu Aug 7 13:34:19 UTC 2025
Add test to validate the engine activity is reported with expected
accuracy. Load the engine for 2%, 50% and 90%.
Signed-off-by: Soham Purkait <soham.purkait at intel.com>
---
tests/intel/xe_pmu.c | 152 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 152 insertions(+)
diff --git a/tests/intel/xe_pmu.c b/tests/intel/xe_pmu.c
index 0e5a7360f..f328933aa 100644
--- a/tests/intel/xe_pmu.c
+++ b/tests/intel/xe_pmu.c
@@ -72,6 +72,12 @@
* SUBTEST: engine-activity-render-node-load-idle
* Description: Test to validate engine activity on render node by running workload and trailing idle
*
+ * SUBTEST: engine-activity-accuracy-2
+ * SUBTEST: engine-activity-accuracy-50
+ * SUBTEST: engine-activity-accuracy-90
+ * Description: Test to validate accuracy of engine activity for the above percentage by
+ * running workload
+ *
* SUBTEST: all-fn-engine-activity-load
* Description: Test to validate engine activity by running load on all functions simultaneously
*
@@ -468,6 +474,140 @@ static void engine_activity_load_all(int fd, int num_engines, unsigned int flags
check_all_engines(num_engines, flag, before, after);
}
+#define assert_within(x, ref, tol) \
+ igt_assert_f((double)(x) <= ((double)(ref) + (tol)) && \
+ (double)(x) >= ((double)(ref) - (tol)), \
+ "%f not within +%f/-%f of %f! ('%s' vs '%s')\n", \
+ (double)(x), (double)(tol), (double)(tol), \
+ (double)(ref), #x, #ref)
+
+static void accuracy(int fd, struct drm_xe_engine_class_instance *eci,
+ unsigned long target_percentage, unsigned long target_iter)
+{
+ unsigned long active_us, cycle_us, calibration_us, idle_us, test_us;
+ const unsigned long min_test_us = 1e6;
+ uint64_t config, before[2], after[2];
+ int link[2], pmu_fd[2];
+ double engine_activity, expected;
+
+ cycle_us = min_test_us / target_iter;
+ active_us = cycle_us * target_percentage / 100;
+ idle_us = cycle_us - active_us;
+ test_us = cycle_us * target_iter;
+ calibration_us = test_us / 2;
+
+ while (idle_us < 2500 || active_us < 2500) {
+ active_us *= 2;
+ idle_us *= 2;
+ }
+
+ igt_info("calibration=%lums, test=%lums, cycle=%lums; ratio=%.2f%% (%luus/%luus)\n",
+ calibration_us / 1000, test_us / 1000, cycle_us / 1000,
+ ((double)active_us / cycle_us) * 100.0, active_us, idle_us);
+
+ igt_assert(pipe(link) == 0);
+
+ igt_fork(child, 1) {
+ const unsigned int timeout[] = { calibration_us * 1000, test_us * 1000 };
+ uint64_t total_active_ns = 0, total_ns = 0;
+ igt_spin_t *spin;
+ uint64_t vm, ahnd;
+
+ vm = xe_vm_create(fd, 0, 0);
+ intel_allocator_init();
+ ahnd = intel_allocator_open(fd, 0, INTEL_ALLOCATOR_RELOC);
+
+ for (int pass = 0; pass < ARRAY_SIZE(timeout); pass++) {
+ unsigned int target_idle_us = idle_us;
+ struct timespec start = { };
+ uint64_t pass_active_ns = 0;
+ unsigned long pass_ns = 0;
+ double avg = 0.0, var = 0.0;
+ int n = 0;
+
+ igt_nsec_elapsed(&start);
+
+ while (pass_ns < timeout[pass]) {
+ unsigned long loop_ns, loop_active_ns, loop_idle_ns, now;
+ double err, prev_avg, cur_val;
+
+ /* idle sleep */
+ igt_measured_usleep(target_idle_us);
+
+ /* start spinner */
+ spin = igt_spin_new(fd, .ahnd = ahnd, .vm = vm, .hwe = eci);
+ loop_idle_ns = igt_nsec_elapsed(&start);
+ igt_measured_usleep(active_us);
+ igt_spin_free(fd, spin);
+
+ now = igt_nsec_elapsed(&start);
+ loop_active_ns = now - loop_idle_ns;
+ loop_ns = now - pass_ns;
+ pass_ns = now;
+
+ pass_active_ns += loop_active_ns;
+ total_active_ns += loop_active_ns;
+ total_ns += loop_ns;
+
+ /* Re-calibrate according to err */
+ err = (double)total_active_ns / total_ns -
+ (double)target_percentage / 100.0;
+
+ target_idle_us = (double)target_idle_us * (1.0 + err);
+
+ /* Running average and variance for debug. */
+ cur_val = 100.0 * total_active_ns / total_ns;
+ prev_avg = avg;
+ avg += (cur_val - avg) / ++n;
+ var += (cur_val - avg) * (cur_val - prev_avg);
+ }
+
+ expected = (double)pass_active_ns / pass_ns;
+
+ igt_info("%u: %d cycles, busy %" PRIu64 " us, idle %" PRIu64 " us -> %.2f%% "
+ "(target: %lu%%; average = %.2f ± %.3f%%)\n",
+ pass, n, pass_active_ns / 1000, (pass_ns - pass_active_ns) / 1000,
+ 100 * expected, target_percentage, avg, sqrt(var / n));
+
+ igt_assert_eq(write(link[1], &expected, sizeof(expected)),
+ sizeof(expected));
+ }
+
+ xe_vm_destroy(fd, vm);
+ put_ahnd(ahnd);
+ }
+
+ config = get_event_config(eci->gt_id, eci, "engine-active-ticks");
+ pmu_fd[0] = open_group(fd, config, -1);
+
+ config = get_event_config(eci->gt_id, eci, "engine-total-ticks");
+ pmu_fd[1] = open_group(fd, config, pmu_fd[0]);
+
+ /* wait for calibration cycle to complete */
+ igt_assert_eq(read(link[0], &expected, sizeof(expected)),
+ sizeof(expected));
+
+ pmu_read_multi(pmu_fd[0], 2, before);
+ igt_assert_eq(read(link[0], &expected, sizeof(expected)),
+ sizeof(expected));
+ pmu_read_multi(pmu_fd[0], 2, after);
+
+ close(pmu_fd[0]);
+ close(pmu_fd[1]);
+
+ close(link[1]);
+ close(link[0]);
+
+ igt_waitchildren();
+
+ engine_activity = (double)(after[0] - before[0]) / (after[1] - before[1]);
+
+ igt_info("error=%.2f%% (%.2f%% vs %.2f%%)\n",
+ (engine_activity - expected) * 100, 100 * engine_activity, 100 * expected);
+
+ assert_within(100.0 * engine_activity, 100.0 * expected, 3);
+}
+
static void engine_activity_all_fn(int fd, struct drm_xe_engine_class_instance *eci, int num_fns)
{
uint64_t config, engine_active_ticks, engine_total_ticks;
@@ -971,6 +1111,18 @@ igt_main
engine_activity_load_all(fd, num_engines, TEST_LOAD);
}
+ igt_subtest_group {
+ const unsigned int percent[] = { 2, 50, 90 };
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(percent); i++) {
+ char test_name[NAME_MAX];
+
+ snprintf(test_name, NAME_MAX, "engine-activity-accuracy-%u", percent[i]);
+ test_each_engine(test_name, fd, eci)
+ accuracy(fd, eci, percent[i], 10);
+ }
+ }
+
igt_describe("Validate engine activity when PMU is opened after load");
test_each_engine("engine-activity-after-load-start", fd, eci)
engine_activity_load_start(fd, eci);
--
2.34.1
More information about the igt-dev
mailing list