diff options
author | Thomas Renninger <trenn@suse.de> | 2009-08-04 20:17:42 +0200 |
---|---|---|
committer | Dominik Brodowski <linux@dominikbrodowski.net> | 2009-08-04 20:17:42 +0200 |
commit | 5d2e3355391848b2552c1f97c856b1ca6fb1d22e (patch) | |
tree | 65d26280741b22537d46c71767e7ab1c38ff89c3 | |
parent | 659204b524bf4b4a5973f24f89bea94b8e514ffe (diff) | |
download | cpufrequtils-5d2e3355391848b2552c1f97c856b1ca6fb1d22e.tar.gz |
Introduce cpufreq micro benchmark
What is this benchmark for:
- Identify worst case performance loss when doing dynamic frequency
scaling using Linux kernel governors
- Identify average reaction time of a governor to CPU load changes
- (Stress) Testing whether a cpufreq low level driver or governor works
as expected
- Identify cpufreq related performance regressions between kernels
- Possibly Real time priority testing? -> what happens if there are
processes with a higher prio than the governor's kernel thread
- ...
What this benchmark does *not* cover:
- Power saving related regressions (In fact as better the performance
throughput is, the worse the power savings will be, but the first should
mostly count more...)
- Real world (workloads)
Details how to use it can be found in the readme.
The benchmark got written by Christian Kornacker <ckornacker@suse.de>
and is published under the GNU General Public License.
(compare with copyright remarks inside the code).
cpufrequtils Makefile adjustments and some minor compile warning cleanups
came from:
Signed-off-by: Thomas Renninger <trenn@suse.de>
Signed-off-by: Dominik Brodowski <linux@dominikbrodowski.net>
-rw-r--r-- | Makefile | 22 | ||||
-rw-r--r-- | bench/Makefile | 27 | ||||
-rw-r--r-- | bench/README | 49 | ||||
-rw-r--r-- | bench/benchmark.c | 160 | ||||
-rw-r--r-- | bench/benchmark.h | 28 | ||||
-rw-r--r-- | bench/config.h | 36 | ||||
-rw-r--r-- | bench/example.cfg | 11 | ||||
-rw-r--r-- | bench/main.c | 203 | ||||
-rw-r--r-- | bench/parse.c | 201 | ||||
-rw-r--r-- | bench/parse.h | 48 | ||||
-rw-r--r-- | bench/system.c | 195 | ||||
-rw-r--r-- | bench/system.h | 29 |
12 files changed, 1007 insertions, 2 deletions
@@ -33,6 +33,9 @@ V ?= false # Requires gettext. NLS ?= true +# cpufreq-bench benchmarking tool +CPUFRQ_BENCH ?= true + # Use the sysfs-based interface which is included in all 2.6 kernels # built with cpufreq support SYSFS ?= true @@ -62,10 +65,13 @@ LANGUAGES = de fr it cs pt # added in front of any of them bindir ?= /usr/bin +sbindir ?= /usr/sbin mandir ?= /usr/man includedir ?= /usr/include libdir ?= /usr/lib localedir ?= /usr/share/locale +docdir ?= /usr/share/doc/packages/cpufrequtils +confdir ?= /etc/ # Toolchain: what tools do we use, and what options do they need: @@ -142,6 +148,10 @@ ifeq ($(strip $(NLS)),true) COMPILE_NLS += update-gmo endif +ifeq ($(strip $(CPUFRQ_BENCH)),true) + INSTALL_BENCH += install-bench + COMPILE_BENCH += compile-bench +endif CFLAGS += $(WARNINGS) -I$(GCCINCDIR) @@ -169,7 +179,7 @@ endif # the actual make rules -all: ccdv libcpufreq utils $(COMPILE_NLS) +all: ccdv libcpufreq utils $(COMPILE_NLS) $(COMPILE_BENCH) ccdv: build/ccdv build/ccdv: build/ccdv.c @@ -215,6 +225,9 @@ update-gmo: po/$(PACKAGE).pot msgfmt --statistics -o po/$$HLANG.gmo po/$$HLANG.po; \ done; +compile-bench: libcpufreq + @V=$(V) make -C bench + clean: -find . \( -not -type d \) -and \( -name '*~' -o -name '*.[oas]' -o -name '*.l[oas]' \) -type f -print \ | xargs rm -f @@ -223,6 +236,7 @@ clean: -rm -f cpufreq-info cpufreq-set -rm -f build/ccdv -rm -rf po/*.gmo po/*.pot + make -C bench clean install-lib: @@ -247,7 +261,11 @@ install-gmo: $(INSTALL_DATA) -D po/$$HLANG.gmo $(DESTDIR)${localedir}/$$HLANG/LC_MESSAGES/cpufrequtils.mo; \ done; -install: install-lib install-tools install-man $(INSTALL_NLS) +install-bench: + @#DESTDIR must be set from outside to survive + @sbindir=$(sbindir) docdir=$(docdir) confdir=$(confdir) make -C bench install + +install: install-lib install-tools install-man $(INSTALL_NLS) $(INSTALL_BENCH) uninstall: - rm -f $(DESTDIR)${libdir}/libcpufreq.* diff --git a/bench/Makefile b/bench/Makefile new file mode 100644 index 0000000..327c4e6 --- /dev/null +++ b/bench/Makefile @@ -0,0 +1,27 @@ +LIBS = -L../.libs/ -lm -lcpufreq + +OBJS = main.o parse.o system.o benchmark.o +CFLAGS += -D_GNU_SOURCE + +ifeq ($(strip $(V)),false) + CC=@../build/ccdv gcc +else + CC=gcc +endif + +cpufreq-bench: $(OBJS) + $(CC) -o $@ $(CFLAGS) $(OBJS) $(LIBS) + +all: cpufreq-bench + +install: + mkdir -p $(DESTDIR)/$(sbindir) + mkdir -p $(DESTDIR)/$(docdir) + mkdir -p $(DESTDIR)/$(confdir) + install -m 755 cpufreq-bench $(DESTDIR)/$(sbindir)/cpufreq-bench + install -m 644 README $(DESTDIR)/$(docdir)/README + install -m 644 example.cfg $(DESTDIR)/$(confdir)/cpufreq-bench.conf + +clean: + rm -f *.o + rm -f cpufreq-bench diff --git a/bench/README b/bench/README new file mode 100644 index 0000000..4f6a225 --- /dev/null +++ b/bench/README @@ -0,0 +1,49 @@ +This is cpufreq-bench, a microbenchmark for the cpufreq framework. + +description: +cpufreq-bench helps to test the condition of a given cpufreq governor. +For that purpose, it compares the performance governor to a configured +powersave module. + +The functional principle is quite easy: we generate a load for a specific +time with the performance governor. The load is generated with some rounds +of calculation. Now, we idle for some time to let the CPU change to a lower +frequency. Then, We take that amount of rounds and do another test with +the powersave governor. But now, we don’t generate load for a specific time +but rather generate load with the amount of calculations we’ve got. +The resulting time is compared to the time we spent for the initial +performance calculation. +The powersave cycle should take 1-40% longer than the performance cycle due +to the time the CPU needs to change to a higher frequency. +nr. of calculations + +^ +|__________________ _ _ _ _ _ +| performance | powersave| +| | | +| | | +|-----------------------------------------------> time + +To get a more precise value, this sleep/load cycle is done several times. +We use the average values for the comparison. +After each round, a specific time is added to the load and sleep time to see +how good the sleep/load switch behaves with different timeframes. + + +usage: +-l, --load=<long int> initial load time in us +-s, --sleep=<long int> initial sleep time in us +-x, --load-step=<long int> time to be added to load time, in us +-y, --sleep-step=<long int> time to be added to sleep time, in us +-c, --cpu=<unsigned int> CPU Number to use, starting at 0 +-p, --prio=<priority> scheduler priority, HIGH, LOW or DEFAULT +-g, --governor=<governor> cpufreq governor to test +-n, --cycles=<int> load/sleep cycles to get an avarage value to compare +-r, --rounds<int> load/sleep rounds +-f, --file=<configfile> config file to use +-o, --output=<dir> output dir, must exist +-v, --verbose verbose output on/off + +Due to the high priority, the application my not be responsible for some time. +After the benchmark, the logfile is saved in OUTPUTDIR/benchmark_TIMESTAMP.log + diff --git a/bench/benchmark.c b/bench/benchmark.c new file mode 100644 index 0000000..d9c1d47 --- /dev/null +++ b/bench/benchmark.c @@ -0,0 +1,160 @@ +/* cpufreq-bench CPUFreq microbenchmark + * + * Copyright (C) 2008 Christian Kornacker <ckornacker@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <stdio.h> +#include <unistd.h> +#include <math.h> + +#include "config.h" +#include "system.h" +#include "benchmark.h" + +/** + * compute how many rounds of calculation we should do + * to get the given load time + * + * @param load aimed load time in µs + * + * @retval rounds of calculation + **/ + +unsigned int calculate_timespace(long load) +{ + int i; + long long now, then; + unsigned int estimated = GAUGECOUNT; + unsigned int rounds = 0; + unsigned int timed = 0; + + printf("calibrating load of %lius, please wait...\n", load); + + /* get the initial calculation time for a specific number of rounds */ + now = get_time(); + ROUNDS(estimated); + then = get_time(); + + timed = (unsigned int)(then - now); + + /* approximation of the wanted load time by comparing with the + * initial calculation time */ + for (i= 0; i < 4; i++) + { + rounds = (unsigned int)(load * estimated / timed); + dprintf("calibrating with %u rounds\n", rounds); + now = get_time(); + ROUNDS(rounds); + then = get_time(); + + timed = (unsigned int)(then - now); + estimated = rounds; + } + + printf("calibration done\n"); + + return estimated; +} + +/** + * benchmark + * generates a specific sleep an load time with the performance + * governor and compares the used time for same calculations done + * with the configured powersave governor + * + * @param config config values for the benchmark + * + **/ + +void start_benchmark(struct config *config) +{ + unsigned int _round, cycle; + long long now, then; + long sleep_time = 0, load_time = 0; + long performance_time = 0, powersave_time = 0; + unsigned int calculations; + + sleep_time = config->sleep; + load_time = config->load; + + for (_round=0; _round < config->rounds; _round++) { + performance_time = 0LL; + powersave_time = 0LL; + + /* set the cpufreq governor to "performance" which disables + * P-State switching. */ + if (set_cpufreq_governor("performance", config->cpu) != 0) + return; + + printf("_round %i: doing %u cycles with %u calculations for %lius\n", + _round + 1, config->cycles, calculations, load_time); + + /* calibrate the calculation time. the resulting calculation + * _rounds should produce a load which matches the configured + * load time */ + calculations = calculate_timespace(load_time); + + fprintf(config->output, "%li %li %li %u ", + load_time, sleep_time, load_time / calculations, calculations); + + if (config->verbose) { + printf("avarage: %lius, rps:%li\n", load_time / calculations, 1000000 * calculations / load_time); + } + + /* do some sleep/load cycles and determine the avarege time we need + * for one cycle */ + for (cycle = 0; cycle < config->cycles; cycle++) { + now = get_time(); + usleep(sleep_time); + ROUNDS(calculations); + then = get_time(); + performance_time += then - now - sleep_time; + if (config->verbose) + printf("performance cycle took %lius, sleep: %lius, load: %lius, rounds: %u\n", + (long)(then - now), sleep_time, load_time, calculations); + } + fprintf(config->output, "%li ", performance_time / config->cycles); + + /* set the powersave governor which activates P-State switching + * again */ + if (set_cpufreq_governor(config->governor, config->cpu) != 0) + return; + + /* again, do some sleep/load cycles with the powersave governor */ + for (cycle = 0; cycle < config->cycles; cycle++) { + now = get_time(); + usleep(sleep_time); + ROUNDS(calculations); + then = get_time(); + powersave_time += then - now - sleep_time; + if (config->verbose) + printf("powersave cycle took %lius, sleep: %lius, load: %lius, rounds: %u\n", + (long)(then - now), sleep_time, load_time, calculations); + } + + /* compare the avarage sleep/load cycles */ + fprintf(config->output, "%li ", powersave_time / config->cycles); + fprintf(config->output, "%.3f\n", performance_time * 100.0 / powersave_time); + + if (config->verbose) + printf("performance is at %.2f%%\n", performance_time * 100.0 / powersave_time); + + sleep_time += config->sleep_step; + load_time += config->load_step; + } +} + diff --git a/bench/benchmark.h b/bench/benchmark.h new file mode 100644 index 0000000..9def4fd --- /dev/null +++ b/bench/benchmark.h @@ -0,0 +1,28 @@ +/* cpufreq-bench CPUFreq microbenchmark + * + * Copyright (C) 2008 Christian Kornacker <ckornacker@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* load loop, this schould take about 1 to 2ms to complete */ +#define ROUNDS(x) {unsigned int rcnt; \ + for (rcnt = 0; rcnt< x*1000; rcnt++) { \ + (void)(((int)(pow(rcnt, rcnt) * sqrt(rcnt*7230970)) ^ 7230716) ^ (int)atan2(rcnt, rcnt)); \ + }} \ + + +unsigned int calculate_timespace(long load); +void start_benchmark(struct config *config); diff --git a/bench/config.h b/bench/config.h new file mode 100644 index 0000000..9690f1b --- /dev/null +++ b/bench/config.h @@ -0,0 +1,36 @@ +/* cpufreq-bench CPUFreq microbenchmark + * + * Copyright (C) 2008 Christian Kornacker <ckornacker@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* initial loop count for the load calibration */ +#define GAUGECOUNT 1500 + +/* default scheduling policy SCHED_OTHER */ +#define SCHEDULER SCHED_OTHER + +#define PRIORITY_DEFAULT 0 +#define PRIORITY_HIGH sched_get_priority_max(SCHEDULER) +#define PRIORITY_LOW sched_get_priority_min(SCHEDULER) + +/* enable further debug messages */ +#ifdef DEBUG +#define dprintf printf +#else +#define dprintf( ... ) while(0) { } +#endif + diff --git a/bench/example.cfg b/bench/example.cfg new file mode 100644 index 0000000..09d2765 --- /dev/null +++ b/bench/example.cfg @@ -0,0 +1,11 @@ +sleep = 25000 +load = 25000 +cpu = 0 +priority = LOW +output = /tmp +sleep_step = 25000 +load_step = 25000 +cycles = 10 +rounds = 60 +#verbose = 0 +governor = ondemand diff --git a/bench/main.c b/bench/main.c new file mode 100644 index 0000000..1ff1525 --- /dev/null +++ b/bench/main.c @@ -0,0 +1,203 @@ +/* cpufreq-bench CPUFreq microbenchmark + * + * Copyright (C) 2008 Christian Kornacker <ckornacker@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <getopt.h> +#include <errno.h> + +#include "config.h" +#include "system.h" +#include "benchmark.h" + +static struct option long_options[] = +{ + {"output", 1, 0, 'o'}, + {"sleep", 1, 0, 's'}, + {"load", 1, 0, 'l'}, + {"verbose", 0, 0, 'v'}, + {"cpu", 1, 0, 'c'}, + {"governor", 1, 0, 'g'}, + {"prio", 1, 0, 'p'}, + {"file", 1, 0, 'f'}, + {"cycles", 1, 0, 'n'}, + {"rounds", 1, 0, 'r'}, + {"load-step", 1, 0, 'x'}, + {"sleep-step", 1, 0, 'y'}, + {"help", 0, 0, 'h'}, + {0, 0, 0, 0} +}; + +/******************************************************************* + usage +*******************************************************************/ + +void usage() +{ + printf("usage: ./bench\n"); + printf("Options:\n"); + printf(" -l, --load=<long int>\t\tinitial load time in us\n"); + printf(" -s, --sleep=<long int>\t\tinitial sleep time in us\n"); + printf(" -x, --load-step=<long int>\ttime to be added to load time, in us\n"); + printf(" -y, --sleep-step=<long int>\ttime to be added to sleep time, in us\n"); + printf(" -c, --cpu=<cpu #>\t\t\tCPU Nr. to use, starting at 0\n"); + printf(" -p, --prio=<priority>\t\t\tscheduler priority, HIGH, LOW or DEFAULT\n"); + printf(" -g, --governor=<governor>\t\tcpufreq governor to test\n"); + printf(" -n, --cycles=<int>\t\t\tload/sleep cycles\n"); + printf(" -r, --rounds<int>\t\t\tload/sleep rounds\n"); + printf(" -f, --file=<configfile>\t\tconfig file to use\n"); + printf(" -o, --output=<dir>\t\t\toutput path. Filename will be OUTPUTPATH/benchmark_TIMESTAMP.log\n"); + printf(" -v, --verbose\t\t\t\tverbose output on/off\n"); + printf(" -h, --help\t\t\t\tPrint this help screen\n"); + exit (1); +} + +/******************************************************************* + main +*******************************************************************/ + +int main(int argc, char **argv) +{ + int c; + int option_index = 0; + struct config *config = NULL; + + if (argc == 1) + usage(); + + config = prepare_default_config(); + + if (config == NULL) + return EXIT_FAILURE; + + while (1) { + c = getopt_long (argc, argv, "hg:o:s:l:vc:p:f:n:r:x:y:", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'o': + if (config->output != NULL) + fclose(config->output); + + config->output = prepare_output(optarg); + + if (config->output == NULL) + return EXIT_FAILURE; + + dprintf("user output path -> %s\n", optarg); + break; + case 's': + sscanf(optarg, "%li", &config->sleep); + dprintf("user sleep time -> %s\n", optarg); + break; + case 'l': + sscanf(optarg, "%li", &config->load); + dprintf("user load time -> %s\n", optarg); + break; + case 'c': + sscanf(optarg, "%u", &config->cpu); + dprintf("user cpu -> %s\n", optarg); + break; + case 'g': + strncpy(config->governor, optarg, 14); + dprintf("user governor -> %s\n", optarg); + break; + case 'p': + if (string_to_prio(optarg) != SCHED_ERR) { + config->prio = string_to_prio(optarg); + dprintf("user prio -> %s\n", optarg); + } else { + if (config != NULL) { + if (config->output != NULL) + fclose(config->output); + free(config); + } + usage(); + } + break; + case 'n': + sscanf(optarg, "%u", &config->cycles); + dprintf("user cycles -> %s\n", optarg); + break; + case 'r': + sscanf(optarg, "%u", &config->rounds); + dprintf("user rounds -> %s\n", optarg); + break; + case 'x': + sscanf(optarg, "%li", &config->load_step); + dprintf("user load_step -> %s\n", optarg); + break; + case 'y': + sscanf(optarg, "%li", &config->sleep_step); + dprintf("user sleep_step -> %s\n", optarg); + break; + case 'f': + config = prepare_config(optarg); + if (config == NULL) + return EXIT_FAILURE; + break; + case 'v': + config->verbose = 1; + dprintf("verbose output enabled\n"); + break; + case 'h': + case '?': + default: + if (config != NULL) { + if (config->output != NULL) + fclose(config->output); + free(config); + } + usage(); + } + } + + printf("starting benchmark with parameters:\n"); + printf("config:\n\t" + "sleep=%li\n\t" + "load=%li\n\t" + "sleep_step=%li\n\t" + "load_step=%li\n\t" + "cpu=%u\n\t" + "cycles=%u\n\t" + "rounds=%u\n\t" + "governor=%s\n\n", + config->sleep, + config->load, + config->sleep_step, + config->load_step, + config->cpu, + config->cycles, + config->rounds, + config->governor); + + prepare_user(config); + prepare_system(config); + start_benchmark(config); + + fclose (config->output); + free (config); + + return EXIT_SUCCESS; +} + diff --git a/bench/parse.c b/bench/parse.c new file mode 100644 index 0000000..2b26279 --- /dev/null +++ b/bench/parse.c @@ -0,0 +1,201 @@ +/* cpufreq-bench CPUFreq microbenchmark + * + * Copyright (C) 2008 Christian Kornacker <ckornacker@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <time.h> +#include <sys/utsname.h> + +#include "parse.h" +#include "config.h" + +/** + * converts priority string to priority + * + * @param str string that represents a scheduler priority + * + * @retval priority + * @retval SCHED_ERR when the priority doesn't exit + **/ + +enum sched_prio string_to_prio(const char *str) +{ + if (strncasecmp("high", str, strlen(str)) == 0) + return SCHED_HIGH; + else if (strncasecmp("default", str, strlen(str)) == 0) + return SCHED_DEFAULT; + else if (strncasecmp("low", str, strlen(str)) == 0) + return SCHED_LOW; + else + return SCHED_ERR; +} + +/** + * create and open logfile + * + * @param dir directory in which the logfile should be created + * + * @retval logfile on success + * @retval NULL when the file can't be created + **/ + +FILE *prepare_output(const char *dir) +{ + FILE *output = NULL; + int len; + char *filename; + struct utsname sysdata; + + len = strlen(dir) + 30; + filename = malloc(sizeof(char) * len); + + if (uname(&sysdata) == 0) { + len += strlen(sysdata.nodename) + strlen(sysdata.release); + filename = realloc(filename, sizeof(char) * len); + + if(filename == NULL) { + perror("realloc"); + return NULL; + } + + snprintf(filename, len - 1, "%s/benchmark_%s_%s_%li.log", + dir, sysdata.nodename, sysdata.release, time(NULL)); + } else { + snprintf(filename, len -1, "%s/benchmark_%li.log", dir, time(NULL)); + } + + dprintf("logilename: %s\n", filename); + + if ((output = fopen(filename, "w+")) == NULL) { + perror("fopen"); + fprintf(stderr, "error: unable to open logfile\n"); + } + + free(filename); + fprintf(output, "#load sleep avarage rounds performance powersave percentage\n"); + return output; +} + +/** + * returns the default config + * + * @retval default config on success + * @retval NULL when the output file can't be created + **/ + +struct config *prepare_default_config() +{ + struct config *config = malloc(sizeof(struct config)); + + dprintf("loading defaults\n"); + + config->sleep = 500000; + config->load = 500000; + config->sleep_step = 500000; + config->load_step = 500000; + config->cycles = 5; + config->rounds = 50; + config->cpu = 0; + config->prio = SCHED_HIGH; + config->verbose = 0; + strncpy(config->governor, "ondemand", 8); + + if ((config->output = prepare_output("/tmp")) == NULL) { + free(config); + return NULL; + } + + return config; +} + +/** + * parses config file and returns the config to the caller + * + * @param path config file name + * + * @retval parsed benchmark config + **/ + +struct config *prepare_config(const char *path) +{ + size_t len = 0; + char *opt, *val, *line = NULL; + FILE *configfile = fopen(path, "r"); + struct config *config = malloc(sizeof(struct config)); + + if (configfile == NULL) { + perror("fopen"); + fprintf(stderr, "error: unable to read configfile\n"); + free(config); + return NULL; + } + + while (getline(&line, &len, configfile) != -1) + { + if (line[0] == '#' || line[0] == ' ') + continue; + + sscanf(line, "%as = %as", &opt, &val); + + dprintf("parsing: %s -> %s\n", opt, val); + + if (strncmp("sleep", opt, strlen(opt)) == 0) + sscanf(val, "%li", &config->sleep); + + else if (strncmp("load", opt, strlen(opt)) == 0) + sscanf(val, "%li", &config->load); + + else if (strncmp("load_step", opt, strlen(opt)) == 0) + sscanf(val, "%li", &config->load_step); + + else if (strncmp("sleep_step", opt, strlen(opt)) == 0) + sscanf(val, "%li", &config->sleep_step); + + else if (strncmp("cycles", opt, strlen(opt)) == 0) + sscanf(val, "%u", &config->cycles); + + else if (strncmp("rounds", opt, strlen(opt)) == 0) + sscanf(val, "%u", &config->rounds); + + else if (strncmp("verbose", opt, strlen(opt)) == 0) + sscanf(val, "%u", &config->verbose); + + else if (strncmp("output", opt, strlen(opt)) == 0) + config->output = prepare_output(val); + + else if (strncmp("cpu", opt, strlen(opt)) == 0) + sscanf(val, "%u", &config->cpu); + + else if (strncmp("governor", opt, 14) == 0) + strncpy(config->governor, val, 14); + + else if (strncmp("priority", opt, strlen(opt)) == 0) { + if (string_to_prio(val) != SCHED_ERR) + config->prio = string_to_prio(val); + } + } + + free(line); + free(opt); + free(val); + + return config; +} diff --git a/bench/parse.h b/bench/parse.h new file mode 100644 index 0000000..45ec3be --- /dev/null +++ b/bench/parse.h @@ -0,0 +1,48 @@ +/* cpufreq-bench CPUFreq microbenchmark + * + * Copyright (C) 2008 Christian Kornacker <ckornacker@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* struct that holds the required config parameters */ +struct config +{ + long sleep; /* sleep time in µs */ + long load; /* load time in µs */ + long sleep_step; /* time value which changes the + * sleep time after every round in µs */ + long load_step; /* time value which changes the + * load time after every round in µs */ + unsigned int cycles; /* calculation cycles with the same sleep/load time */ + unsigned int rounds; /* calculation rounds with iterated sleep/load time */ + unsigned int cpu; /* cpu for which the affinity is set */ + char governor[15]; /* cpufreq governor */ + enum sched_prio /* possible scheduler priorities */ + { + SCHED_ERR=-1,SCHED_HIGH, SCHED_DEFAULT, SCHED_LOW + } prio; + + unsigned int verbose; /* verbose output */ + FILE *output; /* logfile */ +}; + +enum sched_prio string_to_prio(const char *str); + +FILE *prepare_output(const char *dir); + +struct config *prepare_config(const char *path); +struct config *prepare_default_config(); + diff --git a/bench/system.c b/bench/system.c new file mode 100644 index 0000000..d089057 --- /dev/null +++ b/bench/system.c @@ -0,0 +1,195 @@ +/* cpufreq-bench CPUFreq microbenchmark + * + * Copyright (C) 2008 Christian Kornacker <ckornacker@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <stdio.h> +#include <time.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> + +#include <sched.h> + +#include <cpufreq.h> + +#include "config.h" +#include "system.h" + +/** + * returns time since epoch in µs + * + * @retval time + **/ + +long long int get_time() +{ + struct timeval now; + + gettimeofday(&now, NULL); + + return (long long int)(now.tv_sec * 1000000LL + now.tv_usec); +} + +/** + * sets the cpufreq governor + * + * @param governor cpufreq governor name + * @param cpu cpu for which the governor should be set + * + * @retval 0 on success + * @retval -1 when failed + **/ + +int set_cpufreq_governor(char *governor, unsigned int cpu) +{ + + dprintf("set %s as cpufreq governor\n", governor); + + if (cpufreq_cpu_exists(cpu) != 0) { + perror("cpufreq_cpu_exists"); + fprintf(stderr, "error: cpu %u does not exist\n", cpu); + return -1; + } + + if (cpufreq_modify_policy_governor(cpu, governor) != 0) { + perror("cpufreq_modify_policy_governor"); + fprintf(stderr, "error: unable to set %s governor\n", governor); + return -1; + } + + return 0; +} + +/** + * sets cpu affinity for the process + * + * @param cpu cpu# to which the affinity should be set + * + * @retval 0 on success + * @retval -1 when setting the affinity failed + **/ + +int set_cpu_affinity(unsigned int cpu) +{ + cpu_set_t cpuset; + + CPU_ZERO(&cpuset); + CPU_SET(cpu, &cpuset); + + dprintf("set affinity to cpu #%u\n", cpu); + + if (sched_setaffinity(getpid(), sizeof(cpu_set_t), &cpuset) < 0) { + perror("sched_setaffinity"); + fprintf(stderr, "warning: unable to set cpu affinity\n"); + return -1; + } + + return 0; +} + +/** + * sets the process priority parameter + * + * @param priority priority value + * + * @retval 0 on success + * @retval -1 when setting the priority failed + **/ + +int set_process_priority(int priority) +{ + struct sched_param param; + + dprintf("set scheduler priority to %i\n", priority); + + param.sched_priority = priority; + + if (sched_setscheduler(0, SCHEDULER, ¶m) < 0) { + perror("sched_setscheduler"); + fprintf(stderr, "warning: unable to set scheduler priority\n"); + return -1; + } + + return 0; +} + +/** + * notifys the user that the benchmark may run some time + * + * @param config benchmark config values + * + **/ + +void prepare_user(const struct config *config) +{ + unsigned long sleep_time = 0; + unsigned long load_time = 0; + unsigned int round; + int i; + + for (round = 0; round < config->rounds; round++) { + sleep_time += 2 * config->cycles * (config->sleep + config->sleep_step * round); + load_time += 2 * config->cycles * (config->load + config->load_step * round) + (config->load + config->load_step * round * 4); + } + + printf("approx. test duration: %im\n", (int)((sleep_time + load_time) / 60000000)); + printf("your terminal may hardly be responsible while the benchmark is running\n"); + + for (i = 5; i >= 0; i--) { + printf("\rbenchmark starts in %is", i); + fflush(stdout); + sleep(1); + } + printf("\n"); +} + +/** + * sets up the cpu affinity and scheduler priority + * + * @param config benchmark config values + * + **/ + +void prepare_system(const struct config *config) +{ + if (config->verbose) + printf("set cpu affinity to cpu #%u\n", config->cpu); + + set_cpu_affinity(config->cpu); + + switch (config->prio) { + case SCHED_HIGH: + if (config->verbose) + printf("high priority condition requested\n"); + + set_process_priority(PRIORITY_HIGH); + break; + case SCHED_LOW: + if (config->verbose) + printf("low priority condition requested\n"); + + set_process_priority(PRIORITY_LOW); + break; + default: + if (config->verbose) + printf("default priority condition requested\n"); + + set_process_priority(PRIORITY_DEFAULT); + } +} + diff --git a/bench/system.h b/bench/system.h new file mode 100644 index 0000000..3a8c858 --- /dev/null +++ b/bench/system.h @@ -0,0 +1,29 @@ +/* cpufreq-bench CPUFreq microbenchmark + * + * Copyright (C) 2008 Christian Kornacker <ckornacker@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "parse.h" + +long long get_time(); + +int set_cpufreq_governor(char *governor, unsigned int cpu); +int set_cpu_affinity(unsigned int cpu); +int set_process_priority(int priority); + +void prepare_user(const struct config *config); +void prepare_system(const struct config *config); |