diff --git a/perf-tester/main.c b/perf-tester/main.c index e8c4bb7df..3a69804c4 100644 --- a/perf-tester/main.c +++ b/perf-tester/main.c @@ -86,6 +86,18 @@ struct other_thread { struct stats stats; // temp storage, owned by output thread }; +struct freq_stats { + long min; + long max; + long sum; + long samples; +}; + +struct thread_freq_stats { + mutex_t lock; + struct freq_stats stats; +}; + typedef void render_fn(const struct stats *stats, int line, int x, int breadth, int width, int color, @@ -141,6 +153,11 @@ static int init_threads = 0; static bool bidirectional = false; static int max_cpu = 0; static bool system_cpu; +static int break_in = 200; +static int measure_time = 500; +static int repeats = 1; +static bool cpu_freq; +static int freq_granularity = 50; #define BLOCKED_COLOR 1 @@ -1512,6 +1529,36 @@ static void options(int *argc, char ***argv) { .arg_data = &system_cpu, .description = "Consider system CPU usage for automated tests", }, + { + .long_name = "break-in", + .arg = G_OPTION_ARG_INT, + .arg_data = &break_in, + .description = "Break-in time in ms before measuring for automated tests", + }, + { + .long_name = "measure-time", + .arg = G_OPTION_ARG_INT, + .arg_data = &measure_time, + .description = "Duration of automated tests in ms", + }, + { + .long_name = "repeats", + .arg = G_OPTION_ARG_INT, + .arg_data = &repeats, + .description = "Number of times to repeat automated test", + }, + { + .long_name = "cpu-freq", + .arg = G_OPTION_ARG_NONE, + .arg_data = &cpu_freq, + .description = "Monitor CPU frequencies during automated test", + }, + { + .long_name = "freq-granularity", + .arg = G_OPTION_ARG_INT, + .arg_data = &freq_granularity, + .description = "Granularity in ms for measuring CPU frequencies", + }, { NULL, } }; @@ -1525,6 +1572,8 @@ static void options(int *argc, char ***argv) { if (max_cpu > 100 || max_cpu < 0) die("Invalid `max-cpu` number given"); + if (freq_granularity <= 0) + die("Invalid `freq-granularity` number given"); } @@ -1582,6 +1631,63 @@ static void delay_measure_workers(uint milliseconds, struct stats *totals) { } +static void *cpu_freq_monitor(void *p) { + struct thread_freq_stats *freq_stats = p; + + while (true) { + struct freq_stats iter_stats = {0}; + + { + AUTO_CLEANUP(DIR *dp, closedir_p) = opendir("/sys/devices/system/cpu/cpufreq"); + if (!dp) + break; // bail out + + + struct dirent *ent; + while ((ent = readdir(dp))) { + if (strncmp(ent->d_name, "policy", 6) != 0) + continue; // skip + + AUTO_CLEANUP(char *fn, free_gbuf) + = g_strdup_printf("/sys/devices/system/cpu/cpufreq/%s/scaling_cur_freq", + ent->d_name); + AUTO_CLEANUP(FILE *fp, fclose_p) = fopen(fn, "r"); + if (!fp) + continue; // ignore + + long long freq; + int rets = fscanf(fp, "%lld", &freq); + if (rets != 1) + continue; // ignore + + iter_stats.max = iter_stats.max ? MAX(iter_stats.max, freq) : freq; + iter_stats.min = iter_stats.min ? MIN(iter_stats.min, freq) : freq; + iter_stats.sum += freq; + iter_stats.samples++; + } + } + + // done collecting, add to shared struct + + { + LOCK(&freq_stats->lock); + freq_stats->stats.max = freq_stats->stats.max + ? MAX(freq_stats->stats.max, iter_stats.max) : iter_stats.max; + freq_stats->stats.min = freq_stats->stats.min + ? MIN(freq_stats->stats.min, iter_stats.min) : iter_stats.min; + freq_stats->stats.sum += iter_stats.sum; + freq_stats->stats.samples += iter_stats.samples; + } + + thread_cancel_enable(); + usleep(freq_granularity * 1000); + thread_cancel_disable(); + } + + return NULL; +} + + static void max_cpu_test(void) { int max_cpu_scaled = max_cpu * 100000; @@ -1590,17 +1696,24 @@ static void max_cpu_test(void) { uint test_num = 1; set_streams(test_num); - while (true) { + pthread_t cpu_thread = 0; + struct thread_freq_stats freq_stats = {.lock = MUTEX_STATIC_INIT}; + if (cpu_freq) + cpu_thread = thread_new("CPU freq", cpu_freq_monitor, &freq_stats); + + int count = repeats; + + while (count > 0) { // initial break-in - delay_measure_workers(200, NULL); + delay_measure_workers(break_in, NULL); cpu_collect(NULL, NULL); // measure struct stats totals = {0}; if (!system_cpu) - delay_measure_workers(500, &totals); + delay_measure_workers(measure_time, &totals); else { - delay_measure_workers(500, NULL); + delay_measure_workers(measure_time, NULL); cpu_collect(NULL, &totals); } @@ -1632,7 +1745,28 @@ static void max_cpu_test(void) { ? "bidirectional calls" : "unidirectional streams", workers.length); - break; + + if (cpu_freq) { + // retrieve stats and reset + struct freq_stats stats; + { + LOCK(&freq_stats.lock); + stats = freq_stats.stats; + freq_stats.stats = (__typeof__(freq_stats.stats)) {0}; + } + if (!stats.samples) + printf(" (no CPU frequency stats collected)\n"); + else { + printf(" CPU frequencies: " + "%.2f <> %.2f GHz, avg %.2f GHz\n", + (float) stats.min / 1000000., + (float) stats.max / 1000000., + (float) stats.sum / (float) stats.samples + / 1000000.); + } + } + + count--; } // scale to 50..100 @@ -1643,6 +1777,11 @@ static void max_cpu_test(void) { set_streams(test_num); } + + if (cpu_thread) { + pthread_cancel(cpu_thread); + pthread_join(cpu_thread, NULL); + } }