187 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			187 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| /*
 | |
|  * Benchmark scanning sysfs files for PMU information.
 | |
|  *
 | |
|  * Copyright 2023 Google LLC.
 | |
|  */
 | |
| #include <stdio.h>
 | |
| #include "bench.h"
 | |
| #include "util/debug.h"
 | |
| #include "util/pmu.h"
 | |
| #include "util/pmus.h"
 | |
| #include "util/stat.h"
 | |
| #include <linux/atomic.h>
 | |
| #include <linux/err.h>
 | |
| #include <linux/time64.h>
 | |
| #include <subcmd/parse-options.h>
 | |
| 
 | |
| static unsigned int iterations = 100;
 | |
| 
 | |
| struct pmu_scan_result {
 | |
| 	char *name;
 | |
| 	int nr_aliases;
 | |
| 	int nr_formats;
 | |
| 	int nr_caps;
 | |
| 	bool is_core;
 | |
| };
 | |
| 
 | |
| static const struct option options[] = {
 | |
| 	OPT_UINTEGER('i', "iterations", &iterations,
 | |
| 		"Number of iterations used to compute average"),
 | |
| 	OPT_END()
 | |
| };
 | |
| 
 | |
| static const char *const bench_usage[] = {
 | |
| 	"perf bench internals pmu-scan <options>",
 | |
| 	NULL
 | |
| };
 | |
| 
 | |
| static int nr_pmus;
 | |
| static struct pmu_scan_result *results;
 | |
| 
 | |
| static int save_result(void)
 | |
| {
 | |
| 	struct perf_pmu *pmu = NULL;
 | |
| 	struct list_head *list;
 | |
| 	struct pmu_scan_result *r;
 | |
| 
 | |
| 	while ((pmu = perf_pmus__scan(pmu)) != NULL) {
 | |
| 		r = realloc(results, (nr_pmus + 1) * sizeof(*r));
 | |
| 		if (r == NULL)
 | |
| 			return -ENOMEM;
 | |
| 
 | |
| 		results = r;
 | |
| 		r = results + nr_pmus;
 | |
| 
 | |
| 		r->name = strdup(pmu->name);
 | |
| 		r->is_core = pmu->is_core;
 | |
| 		r->nr_caps = pmu->nr_caps;
 | |
| 
 | |
| 		r->nr_aliases = perf_pmu__num_events(pmu);
 | |
| 
 | |
| 		r->nr_formats = 0;
 | |
| 		list_for_each(list, &pmu->format)
 | |
| 			r->nr_formats++;
 | |
| 
 | |
| 		pr_debug("pmu[%d] name=%s, nr_caps=%d, nr_aliases=%d, nr_formats=%d\n",
 | |
| 			nr_pmus, r->name, r->nr_caps, r->nr_aliases, r->nr_formats);
 | |
| 		nr_pmus++;
 | |
| 	}
 | |
| 
 | |
| 	perf_pmus__destroy();
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int check_result(bool core_only)
 | |
| {
 | |
| 	struct pmu_scan_result *r;
 | |
| 	struct perf_pmu *pmu;
 | |
| 	struct list_head *list;
 | |
| 	int nr;
 | |
| 
 | |
| 	for (int i = 0; i < nr_pmus; i++) {
 | |
| 		r = &results[i];
 | |
| 		if (core_only && !r->is_core)
 | |
| 			continue;
 | |
| 
 | |
| 		pmu = perf_pmus__find(r->name);
 | |
| 		if (pmu == NULL) {
 | |
| 			pr_err("Cannot find PMU %s\n", r->name);
 | |
| 			return -1;
 | |
| 		}
 | |
| 
 | |
| 		if (pmu->nr_caps != (u32)r->nr_caps) {
 | |
| 			pr_err("Unmatched number of event caps in %s: expect %d vs got %d\n",
 | |
| 				pmu->name, r->nr_caps, pmu->nr_caps);
 | |
| 			return -1;
 | |
| 		}
 | |
| 
 | |
| 		nr = perf_pmu__num_events(pmu);
 | |
| 		if (nr != r->nr_aliases) {
 | |
| 			pr_err("Unmatched number of event aliases in %s: expect %d vs got %d\n",
 | |
| 				pmu->name, r->nr_aliases, nr);
 | |
| 			return -1;
 | |
| 		}
 | |
| 
 | |
| 		nr = 0;
 | |
| 		list_for_each(list, &pmu->format)
 | |
| 			nr++;
 | |
| 		if (nr != r->nr_formats) {
 | |
| 			pr_err("Unmatched number of event formats in %s: expect %d vs got %d\n",
 | |
| 				pmu->name, r->nr_formats, nr);
 | |
| 			return -1;
 | |
| 		}
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void delete_result(void)
 | |
| {
 | |
| 	for (int i = 0; i < nr_pmus; i++)
 | |
| 		free(results[i].name);
 | |
| 	free(results);
 | |
| 
 | |
| 	results = NULL;
 | |
| 	nr_pmus = 0;
 | |
| }
 | |
| 
 | |
| static int run_pmu_scan(void)
 | |
| {
 | |
| 	struct stats stats;
 | |
| 	struct timeval start, end, diff;
 | |
| 	double time_average, time_stddev;
 | |
| 	u64 runtime_us;
 | |
| 	int ret;
 | |
| 
 | |
| 	init_stats(&stats);
 | |
| 	pr_info("Computing performance of sysfs PMU event scan for %u times\n",
 | |
| 		iterations);
 | |
| 
 | |
| 	if (save_result() < 0) {
 | |
| 		pr_err("Failed to initialize PMU scan result\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	for (int j = 0; j < 2; j++) {
 | |
| 		bool core_only = (j == 0);
 | |
| 
 | |
| 		for (unsigned int i = 0; i < iterations; i++) {
 | |
| 			gettimeofday(&start, NULL);
 | |
| 			if (core_only)
 | |
| 				perf_pmus__scan_core(NULL);
 | |
| 			else
 | |
| 				perf_pmus__scan(NULL);
 | |
| 			gettimeofday(&end, NULL);
 | |
| 			timersub(&end, &start, &diff);
 | |
| 			runtime_us = diff.tv_sec * USEC_PER_SEC + diff.tv_usec;
 | |
| 			update_stats(&stats, runtime_us);
 | |
| 
 | |
| 			ret = check_result(core_only);
 | |
| 			perf_pmus__destroy();
 | |
| 			if (ret < 0)
 | |
| 				break;
 | |
| 		}
 | |
| 		time_average = avg_stats(&stats);
 | |
| 		time_stddev = stddev_stats(&stats);
 | |
| 		pr_info("  Average%s PMU scanning took: %.3f usec (+- %.3f usec)\n",
 | |
| 			core_only ? " core" : "", time_average, time_stddev);
 | |
| 	}
 | |
| 	delete_result();
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int bench_pmu_scan(int argc, const char **argv)
 | |
| {
 | |
| 	int err = 0;
 | |
| 
 | |
| 	argc = parse_options(argc, argv, options, bench_usage, 0);
 | |
| 	if (argc) {
 | |
| 		usage_with_options(bench_usage, options);
 | |
| 		exit(EXIT_FAILURE);
 | |
| 	}
 | |
| 
 | |
| 	err = run_pmu_scan();
 | |
| 
 | |
| 	return err;
 | |
| }
 |