diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b0ad5b9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,14 @@ +Copyright (c) 2014 Harm Hanemaaijer + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c0689f0 --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +VERSION_MAJOR = 0 +VERSION_MINOR = 1 + +VERSION = $(VERSION_MAJOR).$(VERSION_MINOR) +CC = g++ +CFLAGS = -Ofast -DVERSION_MAJOR=$(VERSION_MAJOR) -DVERSION_MINOR=$(VERSION_MINOR) +EXECNAME = flash-bench + +MODULE_OBJECTS = flash-bench.o cpu-stat.o + +$(EXECNAME) : $(MODULE_OBJECTS) + $(CC) $(CFLAGS) $(MODULE_OBJECTS) -o $(EXECNAME) -lpthread -lm + +.cpp.o : + $(CC) -c $(CFLAGS) $< -o $@ + +clean : + rm -f $(MODULE_OBJECTS) $(EXECNAME) .depend + +dep : + rm -f .depend + make .depend + +.depend: Makefile + rm -f .depend + echo '# Module dependencies' >> .depend + g++ -MM $(patsubst %.o,%.cpp,$(MODULE_OBJECTS)) >> .depend + +include .depend diff --git a/README b/README new file mode 100644 index 0000000..d3cf8b0 --- /dev/null +++ b/README @@ -0,0 +1,119 @@ + +flash-bench + +Synopsis + +flash-bench [OPTIONS] [TEST]|[TESTSHORTHANDS] [TEST]... + +Description + +flash-bench is a disk and file system benchmark for testing various access patterns, such as sequential read or write access, random read or write access, and access patterns stored in a trace. Although specifically relevant for flash memory-based storage such as SSD drives, USB sticks, SD cards and other memory cards, it also works with traditional hard-disk drives, RAM disk or any kind of file system that allows read or write access to a single file, or is represented as a block device. + +It can be used as a storage device, file system or real-world disk access benchmark, with or without use traces, and with or without the effects of OS disk caching. It can be used a low-level disk access benchmark when instructed to minimize OS cache effects or use direct or synchronous access. + +The program is best run as superuser, mainly because emptying of the Linux buffer cache is a priviledged operation. Otherwise, cache effects will usually skew the results. + +Options: + +-b, --block-device=[PATHNAME] + +Use a block device, such as the block device representing a flash storage drive, as the test device using direct access. Note that when a block device is specified, any benchmark involving write access will corrupt and destroy the data present on the drive. + +-i, --direct + +By default, flash-bench does not use the O_DIRECT access mode flag to minimize cache effects, so that the benefits of the OS buffer cache exist as they would in a real-world scenario. However, for low-level testing, this option can be specified and the O_DIRECT flag will be used, minimizing OS cache effects. This option has no effect on trace file tests; use --trace-direct instead. + +-d, --duration=[DURATION] + +Set the target duration of each benchmark test. This is only a minimum duration and the test may take considerably longer if it is slow. Can be used in combination with --size. The default is 30 seconds. Has no effect for trace file tests. + +-f, --file=[PATHNAME] + +Set the filename of the test file used for benchmarking. The default filename is flashbench.tmp. If it does not exist, the file will be created. For safety, block devices are detected and not allowed, use the --block-device option instead. + +-h, --help + +Display help. + +-n, --no-duration + +Do not enforce a target maximum duration for each test. + +-o, --random-seed=[VALUE] + +Seed the C library random number generator with a specific value instead of using a seed of 0. VALUE should be an integer, however --random-seed=time will cause the random seed to be derived from system time so that it will be a different for each run. + +-r, --range=[SIZE] + +Set the size in bytes of the range, starting from the beginning of the test file, that will be used in the benchmark tests. When not specified, 512 MB (512 megabytes) is the default, unless the test file already exists and is already larger than 512 MB, in which case the entire range of the file will be used. + +-s, --size=[SIZE] + +Set the maximum total size in bytes of the transactions performed for each benchmark test. Has no effect for trace file tests. + +-y, --sync + +Use synchronous I/O for disk access. Corresponds to the C library O_SYNC access mode flag that will in principle block until the data has been physically written to the underlying hardware. See the man page for the open(2) C library function for details. + +-v, --trace-direct + +Use the O_DIRECT access mode flag for trace file benchmark tests. Equivalent to the --direct option, but only applies to trace file tests. + +-u, --trace-duration=[DURATION] + +Set the target maximum duration of trace benchmark tests. + +Units used with --range, --size, --duration and --trace-duration options: + +SIZE is an integer and optional unit (for example, 10M is 10 * 1024 * 1024 bytes). Units are K (kilobytes, 1024), M (megabytes, 1024 ^ 2), G (gigabytes, 1024 ^ 3) and T (terabytes, 1024 ^ 4). + +DURATION is an integer and mandatory unit representing a duration (for example, 30s is 30 seconds). Units are s (seconds), m (minutes, 60 seconds), and h (hours, 3600 seconds). + +Tests: + +Benchmark test names, including trace file tests, are optionally specified as space-delimited arguments at the end of the commmand line. When no test names are specified, and no trace file tests have been specified, the complete set of access benchmarks will be executed. Tests can also be specified as one or more arguments of shorthand character strings, for which each character must correspond to a test shorthand character. + +To reduce cache effects, except in the case of trace file tests, each block in the test file is only accessed once, so a large test file (e.g. 512 MB or larger) is required to achieve longer test time. + +seqrd (shorthand character: r) + +Sequential read access. The entire test file range is read in sequential order, although the test may terminate early if maximum test time is exceeded. + +seqwr (shorthand character: w) + +Sequential write access. The entire test file range is read in sequential order, although the test may terminate early if maximum test time is exceeded. + +rndrd (shorthand character: R) + +Random read access. Each block within the test file range is read in a completely random order, although the test may terminate early if maximum test time is exceeded. The random order is deterministic (it will be exactly the same between runs), unless the --random-seed option is specified. + +rndwr (shorthand character: W) + +Random write access. Each block within the test file range is read in a completely random order, although the test may terminate early when the maximum test time is exceeded. + +trace=[PATHNAME] + +Add a trace file benchmark test. A trace file is simple, possibly prerecorded, list of disk transactions consisting of operation type (read or write), location on the disk, and size. While location and size will often always be aligned on a 4K block boundary, this is not mandatory. Normally, the entire trace is tested, and --duration and --size have no effect; a target maximum duration for traces can be specified with --trace-duration. Multiple traces can be specified. The file format of the trace file is described below. + + +Examples: + +sudo flash-bench --size=128M --range=512M rndrd rndwr + +Run random access tests with 128MB worth of data, using 128MB of the default test file, creating it if required. The default maximum target duration of 30 seconds is enforced. + +sudo flash-bench --duration=15s --size=512M rwRW + +Run sequential and random access tests (total four tests) with 512MB worth of data, using 512MB (default range) of the default test file, with a maximum target duration of 15s. + + +Trace file format: + +The trace file format consists of a sequential array of transactions in three possible formats, which may be mixed: + +1. An 8-byte format using 4K block units. The first four bytes consist of a 32-bit unsigned integer (in LSB byte-order) of which the uppermost bit (bit 31) is zero. Bit 30 determines the transaction type (0 = read, 1 = write), while the lowest order 30 bits define the size of the transaction in units of 4K blocks (which limits the maximum size to less than 4096 GB or 4 TB). The last four bytes define a 32-bit unsigned integer representing the location of the transaction as an offset in units of 4K blocks from the start of the file or device (giving a range of 16 terabytes). + +2. An 8-byte format with byte-specific transaction size precision. The first four bytes consist of a 32-bit unsigned integer (LSB byte-order) of which the uppermost bit (bit 31) is one and bit 30 is zero. Bit 29 determines the transaction type (0 = read, 1 = write), while the lowest order 29 bits define the location of the transaction in 4K block units, giving a range of 4 terabytes. The last four bytes define the size of the transaction in bytes (which limits the maximum size to less than 4 GB). + +3. A 16-byte format with high precision and virtually unlimited range. The first eight bytes consist of a 64-bit unsigned integer (LSB byte-order) of which the uppermost bit (bit 63) is one and bit 62 is also one. Bit 61 determines the transaction type (0 = read, 1 = write), while the lowest order 61 bits define the size of the transaction in bytes. The last eight bytes consist of a 64-bit unsigned integer defining the location of the transaction in bytes. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..48aa1a2 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +flash-bench is a disk and file system benchmark for testing various access +patterns, such as sequential read or write access, random read or write +access, and access patterns stored in a trace. Although specifically +relevant for flash memory-based storage such as SSD drives, USB sticks, +SD cards and other memory cards, it also works with traditional hard-disk +drives, RAM disk or any kind of file system that allows read or write access +to a single file, or is represented as a block device. + +It can be used as a storage device, file system or real-world disk access +benchmark, with or without use traces, and with or without the effects of +OS disk caching. It can be used a low-level disk access benchmark when +instructed to minimize OS cache effects or use direct or synchronous access. + +The program is best run as superuser, mainly because emptying of the Linux +buffer cache is a priviledged operation. Otherwise, cache effects will +usually skew the results. + +It has been developed for Linux and uses the pthreads library. + diff --git a/cpu-stat.cpp b/cpu-stat.cpp new file mode 100644 index 0000000..a0394e6 --- /dev/null +++ b/cpu-stat.cpp @@ -0,0 +1,361 @@ +/* + +Copyright (c) 2014 Harm Hanemaaijer + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cpu-stat.h" + +/* + * CPU usage calculation module. + */ + +static const bool thread_info_enabled = true; +static uint64_t cached_sc_clk_tck = 0; + +struct thread_stats_t { +public : + int pid; + char name[32]; + uint64_t utime_ticks; + int64_t cutime_ticks; + uint64_t stime_ticks; + int64_t cstime_ticks; + uint64_t vsize; // virtual memory size in bytes + uint64_t rss; //Resident Set Size in bytes +}; + +class ProcessStat { +public : + struct thread_stats_t process_stats; + int num_threads; + int max_thread_stats; + struct thread_stats_t *thread_stats; + uint64_t cpu_total_time; +}; + +static void init_pstat(ProcessStat *p) { + p->num_threads = 0; + p->max_thread_stats = 0; + strcpy(p->process_stats.name, "Undefined"); +} + +static void clear_thread_stats(struct thread_stats_t *thread_stats) { + thread_stats->utime_ticks = 0; + thread_stats->cutime_ticks = 0; + thread_stats->stime_ticks = 0; + thread_stats->cstime_ticks = 0; + thread_stats->vsize = 0; + thread_stats->rss = 0; +} + +static void free_thread_stats(ProcessStat *p) { + if (p->max_thread_stats > 0) + delete [] p->thread_stats; +} + +/* + * read /proc data into the passed ProcessStat + * returns 0 on success, -1 on error + */ +static int get_usage(const pid_t pid, ProcessStat *result) +{ + //convert pid to string + char pid_s[20]; + snprintf(pid_s, sizeof(pid_s), "%d", pid); + char stat_filepath[30] = "/proc/"; + strncat(stat_filepath, pid_s, + sizeof(stat_filepath) - strlen(stat_filepath) - 1); + char tasks_filepath[64]; + strcpy(tasks_filepath, stat_filepath); + strncat(stat_filepath, "/stat", sizeof(stat_filepath) - + strlen(stat_filepath) - 1); + + FILE *fpstat = fopen(stat_filepath, "r"); + if (fpstat == NULL) { + printf("CPUStat: Couldn't open %s.", stat_filepath); + return -1; + } + + FILE *fstat = fopen("/proc/stat", "r"); + if (fstat == NULL) { + printf("CPUStat: Couldn't open /proc/stat.\n"); + fclose(fstat); + return -1; + } + // Read values from /proc/pid/stat. + clear_thread_stats(&result->process_stats); + result->process_stats.pid = pid; + int64_t rss; + if (fscanf + (fpstat, + "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu" + "%lu %ld %ld %*d %*d %d %*d %*u %lu %ld", + &result->process_stats.utime_ticks, + &result->process_stats.stime_ticks, + &result->process_stats.cutime_ticks, + &result->process_stats.cstime_ticks, &result->num_threads, + &result->process_stats.vsize, &rss) == EOF) { + fclose(fpstat); + return -1; + } + fclose(fpstat); + result->process_stats.rss = rss * getpagesize(); + + //read+calc cpu total time from /proc/stat + uint64_t cpu_time[10]; + bzero(cpu_time, sizeof(cpu_time)); + if (fscanf(fstat, "%*s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu", + &cpu_time[0], &cpu_time[1], &cpu_time[2], &cpu_time[3], + &cpu_time[4], &cpu_time[5], &cpu_time[6], &cpu_time[7], + &cpu_time[8], &cpu_time[9]) == EOF) { + fclose(fstat); + return -1; + } + fclose(fstat); + + result->cpu_total_time = 0; + for (int i = 0; i < 10; i++) + result->cpu_total_time += cpu_time[i]; + + if (!thread_info_enabled) { + result->num_threads = 0; + return 0; + } + + // Read thread info. + if (result->max_thread_stats < result->num_threads) { + if (result->max_thread_stats > 0) + free(result->thread_stats); + result->thread_stats = new struct thread_stats_t[result->num_threads]; + result->max_thread_stats = result->num_threads; + } + + strncat(tasks_filepath, "/task/", 64 - strlen(tasks_filepath) - 1); + DIR *tasks_dir = opendir(tasks_filepath); + + for (int i = - 2; i < result->num_threads; i++) { + struct dirent *dir_entry = readdir(tasks_dir); + if (dir_entry == NULL) + break; + if (i < 0) + continue; + + clear_thread_stats(&result->thread_stats[i]); + char pid_str[16]; + strncpy(pid_str, dir_entry->d_name, 16); + int j; + for (j = 0; pid_str[j] >= '0' && pid_str[j] <= '9'; j++); + pid_str[j] = '\0'; + result->thread_stats[i].pid = atoi(pid_str); + + char thread_stat_filepath[64]; + strcpy(thread_stat_filepath, tasks_filepath); + strncat(thread_stat_filepath, pid_str, + sizeof(thread_stat_filepath) + - strlen(thread_stat_filepath) - 1); + strcat(thread_stat_filepath, "/stat"); + FILE *ftstat = fopen(thread_stat_filepath, "rb"); + if (ftstat == NULL) { + printf("CPUStat: Couldn't open %s.", thread_stat_filepath); + return -1; + } + int64_t rss; + char s[1024]; + fread(s, 1024, 1, ftstat); + fflush(stdout); + int k = 0; + while (s[k] != '(') + k++; + char *name = &s[k]; + while (s[k] != ')') + k++; + s[k + 1] = '\0'; + strncpy(result->thread_stats[i].name, name, 32); + result->thread_stats[i].name[31] = '\0'; + if (sscanf(&s[k + 2], + "%*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu" + "%lu %ld %ld %*d %*d %d %*d %*u %lu %ld", + &result->thread_stats[i].utime_ticks, + &result->thread_stats[i].stime_ticks, + &result->thread_stats[i].cutime_ticks, + &result->thread_stats[i].cstime_ticks, + &result->num_threads, &result->thread_stats[i].vsize, + &rss) == EOF) { + printf("Error reading %s.\n", thread_stat_filepath); +// fclose(ftstat); +// return -1; + } + fclose(ftstat); + result->thread_stats[i].rss = rss * getpagesize(); + } + closedir(tasks_dir); + + return 0; +} + +/* + * Calculate the elapsed CPU usage between two measuring points, in percent. + */ +static void calc_cpu_usage_pct(const ProcessStat *cur_usage, + const ProcessStat *last_usage, + double *ucpu_usage, double *scpu_usage, + double *thread_ucpu_usage, double *thread_scpu_usage) +{ + const uint64_t total_time_diff = cur_usage->cpu_total_time - + last_usage->cpu_total_time; + + *ucpu_usage = 100 * (((cur_usage->process_stats.utime_ticks + + cur_usage->process_stats.cutime_ticks) + - (last_usage->process_stats.utime_ticks + + last_usage->process_stats.cutime_ticks)) + / (double)total_time_diff); + + *scpu_usage = + 100 * + ((((cur_usage->process_stats.stime_ticks + + cur_usage->process_stats.cstime_ticks) + - (last_usage->process_stats.stime_ticks + + last_usage->process_stats.cstime_ticks))) / + (double)total_time_diff); + + if (thread_info_enabled && (thread_ucpu_usage != NULL || thread_scpu_usage != NULL)) { + int j = 0; + for (int i = 0; i < cur_usage->num_threads; i++) { + fflush(stdout); + if (j >= last_usage->num_threads || + last_usage->thread_stats[j].pid > cur_usage->thread_stats[i].pid) { + thread_ucpu_usage[i] = - 1.0; + thread_scpu_usage[i] = - 1.0; + continue; + } + for (;;) { + fflush(stdout); + if (cur_usage->thread_stats[i].pid == last_usage->thread_stats[j].pid) + break; + j++; + if (j >= last_usage->num_threads || + last_usage->thread_stats[j].pid > cur_usage->thread_stats[i].pid) { + thread_ucpu_usage[i] = - 1.0; + thread_scpu_usage[i] = - 1.0; + goto next; + } + } + fflush(stdout); + if (thread_ucpu_usage != NULL) { + thread_ucpu_usage[i] = 100 * (( + (cur_usage->thread_stats[i].utime_ticks + + cur_usage->thread_stats[i].cutime_ticks) + - (last_usage->thread_stats[j].utime_ticks + + last_usage->thread_stats[j].cutime_ticks)) + / (double)total_time_diff); + } + if (thread_scpu_usage != NULL) { + thread_scpu_usage[i] = 100 * (( + (cur_usage->thread_stats[i].stime_ticks + + cur_usage->thread_stats[i].cstime_ticks) + - (last_usage->thread_stats[j].stime_ticks + + last_usage->thread_stats[j].cstime_ticks)) + / (double)total_time_diff); + } + j++; +next: ; + } + } +} + +// Return the total CPU usage for the process. + +static void get_total_usage(const ProcessStat *pstat_current, +double *ucpu_usage, double *scpu_usage, double *thread_ucpu_usage, double *thread_scpu_usage) { + ProcessStat process_stat_zero; + init_pstat(&process_stat_zero); + clear_thread_stats(&process_stat_zero.process_stats); + process_stat_zero.num_threads = 0; + if (thread_info_enabled && (thread_ucpu_usage != NULL || thread_scpu_usage != NULL)) { + process_stat_zero.thread_stats = new struct thread_stats_t[pstat_current->num_threads]; + process_stat_zero.num_threads = pstat_current->num_threads; + process_stat_zero.max_thread_stats = pstat_current->num_threads; + for (int i = 0; i < pstat_current->num_threads; i++) + clear_thread_stats(&process_stat_zero.thread_stats[i]); + } + calc_cpu_usage_pct(pstat_current, &process_stat_zero, + ucpu_usage, scpu_usage, thread_ucpu_usage, thread_scpu_usage); + if (thread_info_enabled && (thread_ucpu_usage != NULL || thread_scpu_usage != NULL)) + free_thread_stats(&process_stat_zero); +} + +static double calc_time_taken(ProcessStat *current, ProcessStat *previous) { + uint64_t total_time_diff = current->cpu_total_time - + previous->cpu_total_time; + if (cached_sc_clk_tck == 0) + cached_sc_clk_tck = sysconf(_SC_CLK_TCK); + return (double)total_time_diff / cached_sc_clk_tck; +} + +CPUStat::CPUStat(int _pid) { + pid = _pid; + process_stat = new ProcessStat; +} + +CPUStat::~CPUStat() { + free_thread_stats(process_stat); + delete process_stat; +} + +void CPUStat::Update() { + ProcessStat *usage = process_stat; + get_usage(pid, usage); +} + +CPUStat *AllocateCPUStat(int pid) { + CPUStat *st = new CPUStat(pid); + return st; +} + +void CPUStat::GetTotalUsage(const CPUStat *st_previous, double *ucpu_usage, double *scpu_usage, +double *thread_ucpu_usage, double *thread_scpu_usage) const { + get_total_usage(process_stat, ucpu_usage, scpu_usage, thread_ucpu_usage, thread_scpu_usage); +} + +void CPUStat::GetUsageFrom(const CPUStat *st_previous, double *ucpu_usage, double *scpu_usage, +double *thread_ucpu_usage, double *thread_scpu_usage) const { + calc_cpu_usage_pct(process_stat, st_previous->process_stat, + ucpu_usage, scpu_usage, thread_ucpu_usage, thread_scpu_usage); +} + +// Calculate the user and system CPU time spent by the process. If either thread_ucpu_usage or +// thread_scpu_usage is not NULL, also calculate CPU time stats for all threads of the process. +// The results are stored as doubles at the double pointers provided by the arguments. + +void CalculateCPUUsage(const CPUStat *st_current, const CPUStat *st_previous, +double *ucpu_usage, double *scpu_usage, double *thread_ucpu_usage, double *thread_scpu_usage) +{ + st_current->GetUsageFrom(st_previous, ucpu_usage, scpu_usage, + thread_ucpu_usage, thread_scpu_usage); +} + diff --git a/cpu-stat.h b/cpu-stat.h new file mode 100644 index 0000000..89145e8 --- /dev/null +++ b/cpu-stat.h @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2014 Harm Hanemaaijer + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +*/ + +class ProcessStat; + +class CPUStat { +public : + int pid; +private : + ProcessStat *process_stat; +public : + CPUStat(int pid); + ~CPUStat(); + void Update(); + void GetTotalUsage(const CPUStat *cpust_previous, double *ucpu_usage, double *scpu_usage, + double *thread_ucpu_usage, double *thread_scpu_usage) const; + void GetTotalUsage(const CPUStat *cpust_previous, double *ucpu_usage, double *scpu_usage) const { + GetTotalUsage(cpust_previous, ucpu_usage, scpu_usage, NULL, NULL); + } + void GetUsageFrom(const CPUStat *cpust_previous, double *ucpu_usage, double *scpu_usage, + double *thread_ucpu_usage, double *thread_scpu_usage) const; + void GetUsageFrom(const CPUStat *cpust_previous, double *ucpu_usage, double *scpu_usage) const { + GetUsageFrom(cpust_previous, ucpu_usage, scpu_usage, NULL, NULL); + } +}; + +CPUStat *AllocateCPUStat(int pid); + +void CalculateCPUUsage(const CPUStat *cpust_current, const CPUStat *cpust_previous, + double *ucpu_usage, double *scpu_usage, double *thread_ucpu_usage, double *thread_scpu_usage); + + + diff --git a/dynamic-array.h b/dynamic-array.h new file mode 100644 index 0000000..215bf72 --- /dev/null +++ b/dynamic-array.h @@ -0,0 +1,130 @@ +/* + +Copyright (c) 2014 Harm Hanemaaijer + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +*/ + +#include +#include + +// Dynamic array template class allows any type of data. + +template +class DynamicArray { +private : + int nu_elements; + int max_elements; + int expansion_hint; + T *data; + +public : + DynamicArray(int starting_capacity = 4) { + nu_elements = 0; + max_elements = 0; + expansion_hint = starting_capacity; + } + inline int Size() const { + return nu_elements; + } + inline T Get(int i) const { + return data[i]; + } + // By how much to expand the array the next time it is full. + inline int GetExpansionHint(int size) { + // Double the size each time. + return size; + } + inline void ExpandCapacity() { + max_elements += expansion_hint; + data = (T *)realloc(data, sizeof(T) * max_elements); + expansion_hint = GetExpansionHint(max_elements); + } + inline void TrimCapacity() { + data = (T *)realloc(data, sizeof(T) * nu_elements); + max_elements = nu_elements; + } + inline void Add(T v) { + if (nu_elements == max_elements) + ExpandCapacity(); + data[nu_elements] = v; + nu_elements++; + } +}; + +// Template class to cast DynamicArray class type to another, same-sized type. +// T1 is the new type, T2 is the same-sized type for which an existing class exists, +// and C2 is the name of the existing class. + +template +class CastDynamicArray : public C2 { +public : + CastDynamicArray(int starting_capacity = 4) { } + inline T1 Get(int i) const { + return (T1)((C2 *)this->Get(i)); + } + inline void Add(T1 s) { + ((C2 *)this)->Add((T2)s); + } +}; + +typedef DynamicArray IntArray; +typedef DynamicArray Int64Array; +#if UINTPTR_MAX == 0xFFFFFFFF +// 32-bit pointers. +typedef CastDynamicArray PointerArray; +#else +// 64-bit pointers +typedef CastDynamicArray PointerArray; +#endif +typedef CastDynamicArray CharPointerArray; + +template +class TightDynamicArray : public DynamicArray { +public : + TightDynamicArray(int starting_capacity = 4) { } + // By how much to expand the array the next time it is full. + inline int GetExpansionHint(int size) { + // Conservatively expand the size of the array, keeping it tight. + // Because processing speed is not likely to be critical for a tight array, + // use some more expensive math functions. + // A faster, integer log2 function could be used. + float log2_size = log2f((float)size); + int expansion = floorf(powf(1.5f, log2_size)); + // Size Expand by + // 1 1 + // 2 1 + // 4 2 + // 8 3 + // 32 7 + // 256 25 + // 4096 129 + // 65536 656 + // 1000000 3234 + return expansion; + } +}; + +typedef TightDynamicArray TightIntArray; +typedef TightDynamicArray TightInt64Array; +#if UINTPTR_MAX == 0xFFFFFFFF +// 32-bit pointers. +typedef CastDynamicArray TightPointerArray; +#else +// 64-bit pointers +typedef CastDynamicArray TightPointerArray; +#endif +typedef CastDynamicArray TightCharPointerArray; + + diff --git a/filelist b/filelist new file mode 100644 index 0000000..8725a30 --- /dev/null +++ b/filelist @@ -0,0 +1,10 @@ +flash-bench/cpu-stat.cpp +flash-bench/cpu-stat.h +flash-bench/cpu-time.cpp +flash-bench/dynamic-array.h +flash-bench/filelist +flash-bench/flash-bench.cpp +flash-bench/Makefile +flash-bench/README +flash-bench/timer.h + diff --git a/flash-bench.cpp b/flash-bench.cpp new file mode 100644 index 0000000..1674216 --- /dev/null +++ b/flash-bench.cpp @@ -0,0 +1,835 @@ +/* + +Copyright (c) 2014 Harm Hanemaaijer + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cpu-stat.h" +#include "dynamic-array.h" +#include "timer.h" + +static const struct option long_options[] { + // Option name, argument flag, NULL, equivalent short option character. + { "block-device", required_argument, NULL, 'b' }, + { "direct", no_argument, NULL, 'i' }, + { "duration", required_argument, NULL, 'd' }, + { "file", required_argument, NULL, 'f' }, + { "help", no_argument, NULL, 'h' }, + { "no-duration", no_argument, NULL, 'n' }, + { "random-seed", required_argument, NULL, 'o' }, + { "range", required_argument, NULL, 'r' }, + { "size", required_argument, NULL, 's' }, + { "sync", no_argument, NULL, 'y' }, + { "trace-direct", no_argument, NULL, 'v' }, + { "trace-duration", required_argument, NULL, 'u' }, + { NULL, 0, NULL, 0 } +}; + +#define NU_OPTIONS (sizeof(long_options) / sizeof(long_options[0])) + +enum { CMD_READ = 0, CMD_WRITE = 1, CMD_SEQUENTIAL = 0, CMD_RANDOM = 2, + CMD_READ_SEQUENTIAL = CMD_READ | CMD_SEQUENTIAL, + CMD_WRITE_SEQUENTIAL = CMD_WRITE | CMD_SEQUENTIAL, + CMD_READ_RANDOM = CMD_READ | CMD_RANDOM, + CMD_WRITE_RANDOM = CMD_WRITE | CMD_RANDOM, + CMD_TRACE = 4 +}; + +class Test { +public : + char command_ch; + const char *name; + const char *description; + char command_flags; +}; + +static const Test test[] = { + { 'r', "seqrd", "Sequential read", CMD_READ | CMD_SEQUENTIAL }, + { 'w', "seqwr", "Sequential write", CMD_WRITE | CMD_SEQUENTIAL }, + { 'R', "rndrd", "Random read", CMD_READ | CMD_RANDOM }, + { 'W', "rndwr", "Random write", CMD_WRITE | CMD_RANDOM }, + { ' ', "trace", "Trace", CMD_TRACE } +}; + +#define NU_TESTS (sizeof(test) / sizeof(test[0])) +#define NU_STANDARD_TESTS (NU_TESTS - 1) + +static const char *default_test_filename = "flash-bench.tmp"; + +#define DEFAULT_TEST_FILE_RANGE (512 * 1024 * 1024) + +enum { + FLAG_BLOCK_DEVICE = 0x1, + FLAG_ACCESS_MODE_DIRECT = 0x2, + FLAG_TRACE_DURATION = 0x4, + FLAG_NO_DURATION = 0x8, + FLAG_RANDOM_SEED = 0x10, + FLAG_RANDOM_SEED_TIME = 0x20, + FLAG_TEST_FILE_RANGE = 0x40, + FLAG_TOTAL_TRANSACTION_SIZE = 0x80, + FLAG_ACCESS_MODE_SYNC = 0x100, + FLAG_TRACE_ACCESS_MODE_DIRECT = 0x200 +}; + +static int operating_flags; + +enum { VALUE_TYPE_SIZE, VALUE_TYPE_DURATION, VALUE_TYPE_GENERIC }; + +static int length_type; +static const char *test_filename; +static int64_t test_file_range; +static int64_t total_transaction_size; +static int nu_blocks; // The maximum total number of 4K block transactions per test. +static uint32_t duration; +static uint32_t trace_duration; +static uint32_t random_seed; +static int extra_mode_access_flags; +static int extra_mode_access_flags_trace; + +static char *buffer; +static int *indices; + +class Trace { +public : + uint8_t *data; + uint64_t size; +}; + +TightIntArray commands(4); +CharPointerArray trace_filenames(4); +CastDynamicArray traces(4); + +static inline void SetFlag(int flag) { + operating_flags |= flag; +} + +static bool FlagIsSet(int flag) { + return (operating_flags & flag) != 0; +} + +static void Message(const char *format, ...) { + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); +} + +static int RoundToMB(int64_t nu_bytes) { + return (nu_bytes + 512 * 1024 - 1) >> 20; +} + +static void Usage() { + Message("flashbench v%d.%d\n", VERSION_MAJOR, VERSION_MINOR); + Message("Usage: flash-bench [OPTIONS] [TESTNAME]|[TESTSHORTHAND] [TESTNAME]...\n\n"); + + Message("Options:\n"); + for (int i = 0;; i++) { + if (long_options[i].name == NULL) + break; + const char *value_str; + if (long_options[i].has_arg) + value_str = "=[VALUE]"; + else + value_str = ""; + Message(" -%c, --%s%s\n", long_options[i].val, long_options[i].name, value_str); + } + + Message("\nAvailable benchmark tests:\n" + " Short Name Description\n"); + for (int i = 0; i < NU_TESTS; i++) + Message(" %-3c %-16s %s\n", test[i].command_ch, test[i].name, test[i].description); + Message(" trace=[FILENAME] Replay a trace file\n"); + + Message("\nExample: flash-bench --size=128M --range=512M rndrd rndwr\n" + " Run random access tests with 128MB worth of data, using\n" + " 128MB of the default test file, creating it if required.\n" + " The default maximum target duration of 30 seconds is enforced.\n"); + + Message("\nExample: flash-bench --duration=15s --size=512M rwRW\n" + " Run sequential and random access tests (total four tests)\n" + " with 512MB worth of data, using %dMB (default range) of the\n" + " default test file, with a maximum target duration of 15s.\n", + RoundToMB(DEFAULT_TEST_FILE_RANGE)); +} + +static void __attribute__((noreturn)) FatalError(const char *format, ...) { + va_list args; + va_start(args, format); + vprintf(format, args); + fflush(stdout); + va_end(args); + exit(1); +} + +static int64_t ParseValue(char *arg, int *type) { + int length = strlen(arg); + int unit = arg[length - 1]; + bool no_unit; + if (isdigit(unit)) + no_unit = true; + else + no_unit = false; + if (unit != 'K' && unit != 'M' && unit != 'G' && unit != 's' && unit != 'm') + FatalError("Expected unit K, M, G (transaction size) or s or m (duration) " + "for length argument.\n"); + if (length < 2) + FatalError("Size expected before unit for length argument.\n"); + if (no_unit && length < 1) + FatalError("No value specified.\n"); + char *s = strdup(arg); + if (!no_unit) + s[length - 1] = '\0'; + int64_t size = atoi(s); + free(s); + if (size < 1 || size > (1 << 28)) + FatalError("Invalid number specified for size before unit for length argument.\n"); + if (no_unit) { + *type = VALUE_TYPE_GENERIC; + return size; + } + int t = VALUE_TYPE_SIZE; + switch (unit) { + case 'K' : size *= 1024; break; + case 'M' : size *= 1024 * 1024; break; + case 'G' : size *= 1024 * 1024 * 1024; break; + case 'm' : size *= 60; + case 's' : t = VALUE_TYPE_DURATION; break; + } + *type = t; + return size; +} + +static void ParseOptions(int argc, char **argv) { + operating_flags = 0; + duration = 60; + test_filename = default_test_filename; + int value_type; + + while (true) { + int this_option_optind = optind ? optind : 1; + int option_index = 0; + int c = getopt_long(argc, argv, "b:id:f:hno:r:s:yvu:", long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'b' : // -b, --block-device + SetFlag(FLAG_BLOCK_DEVICE); + test_filename = strdup(optarg); + break; + case 'i' : // -i. --direct + SetFlag(FLAG_ACCESS_MODE_DIRECT); + break; + case 'd' : // -d, --duration + duration = ParseValue(optarg, &value_type); + break; + case 'f' : // -f, --file + test_filename = strdup(optarg); + break; + case 'h' : // -h, --help + Usage(); + exit(0); + case 'n' : // -n, --no-duration + SetFlag(FLAG_NO_DURATION); + break; + case 'o' : // -o, --random-seed + if (strcmp(optarg, "time") == 0) { + SetFlag(FLAG_RANDOM_SEED_TIME); + } + else { + SetFlag(FLAG_RANDOM_SEED); + random_seed = ParseValue(optarg, &value_type); + } + break; + case 'r' : // -r, --range + SetFlag(FLAG_TEST_FILE_RANGE); + test_file_range = ParseValue(optarg, &value_type); + break; + case 's' : // -s, --size + SetFlag(FLAG_TOTAL_TRANSACTION_SIZE); + total_transaction_size = ParseValue(optarg, &value_type); + break; + case 'y' : // -y, --sync + SetFlag(FLAG_ACCESS_MODE_SYNC); + break; + case 'v' : // -v, --trace-direct + SetFlag(FLAG_TRACE_ACCESS_MODE_DIRECT); + break; + case 'u' : // -u, --trace-duration + SetFlag(FLAG_TRACE_DURATION); + trace_duration = ParseValue(optarg, &value_type); + break; + default : + FatalError(""); + break; + } + } + + if (optind < argc) { + for (int i = optind; i < argc; i++) { + if (strcmp(argv[i], "trace=") == 0) { + trace_filenames.Add(strdup(&argv[i][6])); + commands.Add(CMD_TRACE); + break; + } + int t = - 1; + for (int j = 0; j < NU_STANDARD_TESTS; j++) + if (strcmp(argv[i], test[j].name) == 0) { + t = j; + break; + } + if (t >= 0) + commands.Add(t); + else { + // Check for shorthand argument. + int n = strlen(argv[i]); + int count = 0; + for (int k = 0; k < n; k++) { + int j; + for (j = 0; j < NU_STANDARD_TESTS; j++) + if (argv[i][k] == test[j].command_ch) { + commands.Add(j); + count++; + break; + } + } + // If not all characters were recognized as test shorthands, + // report an error. + if (count < n) + FatalError("Unrecognized benchmark test name %s.\n", argv[i]); + } + } + } + + if (commands.Size() == 0) { + // No test names or traces specified. Perform all standard tests. + for (int i = 0; i < NU_STANDARD_TESTS; i++) + commands.Add(i); + } +} + +static void CreateBuffer() { + buffer = new char[4096]; + for (int i = 0; i < 4096; i++) { + buffer[i] = i & 0xFF; + } +} + +static void CreateIndices() { + indices = new int[nu_blocks]; +} + +static void SetRandomIndices() { + for (int i = 0; i < nu_blocks; i++) + indices[i] = i; + // Traverse the array from start to end and swap indices randomly. + for (int i = 0; i < nu_blocks; i++) { + int j = rand() % nu_blocks; + int index_i = indices[i]; + indices[i] = indices[j]; + indices[j] = index_i; + } +} + +static void DestroyBuffer() { + delete [] buffer; +} + +static const char *empty_environment[] = { NULL }; + +static void ExecuteSync() { + // Create a child process + int child_pid = fork(); + if (child_pid == 0) + execle("/bin/sync", "/bin/sync", (char *)NULL, empty_environment); + int status; + waitpid(child_pid, &status, 0); +} + +static const char char_three = '3'; + +static void DropCaches() { + ExecuteSync(); + int fd = open("/proc/sys/vm/drop_caches", O_WRONLY | O_SYNC); + int r = write(fd, &char_three, 1); + close(fd); + if (r != 1) + Message("Warning: Cache flushing unsuccesful. Permission problem?\n" + "flash-bench should be run as superuser for best effects.\n"); +} + +static void Sync() { + ExecuteSync(); +} + +// File I/O wrappers. + +static void CheckFDError(int fd) { + if (fd < 0) + FatalError("Error opening file.\n"); +} + +static void read_with_check(int fd, void *buffer, size_t size) { + ssize_t size_read = read(fd, buffer, size); + if (size_read != size) + FatalError("Error during read operation.\n"); +} + +static void write_with_check(int fd, void *buffer, size_t size) { + ssize_t size_written = write(fd, buffer, size); + if (size_written != size) + FatalError("Error during write operation.\n"); +} + + +static void CreateTestFile() { + int fd = open(test_filename, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP); + if (fd < 0) + FatalError("Error - could not create test file %s (permission problem?).\n", + test_filename); + for (int i = 0; i < (test_file_range + 4095) / 4096; i++) + write_with_check(fd, buffer, 4096); + close(fd); +} + +static void CheckTestFile() { + if (!FlagIsSet(FLAG_TEST_FILE_RANGE)) + test_file_range = DEFAULT_TEST_FILE_RANGE; + struct stat sb; + int r = stat(test_filename, &sb); + if (FlagIsSet(FLAG_BLOCK_DEVICE)) { + if (!S_ISBLK(sb.st_mode)) + FatalError("Device file %s does not appear to be a block device.\n", + test_filename); + if (sb.st_size < test_file_range) + FatalError("Block device size is smaller than test file range.\n"); + return; + } + bool ready = false; + if (r != - 1) { + // Have to check whether the file is a regular filesystem file, not a device. + if (!S_ISREG(sb.st_mode)) + FatalError("Error: Specified test filename is not a regular file. " + "Use --block-device option to use a block device for disk access.\n"); + // Check whether the file is large enough. + if (sb.st_size >= test_file_range) { + ready = true; + if (!FlagIsSet(FLAG_TEST_FILE_RANGE)) { + // If no test file range was specified, and the test file is at least + // larger than the standard size of 512 MB, use a range covering the + // whole file. + test_file_range = sb.st_size; + Message("Using test file %s (using all %dMB).\n", test_filename, + RoundToMB(test_file_range)); + } + else + Message("Reusing test file %s (using %dMB of %dMB).\n", + test_filename, RoundToMB(test_file_range), RoundToMB(sb.st_size)); + } + else + Message("Existing test file smaller than %dMB, creating new file.\n", + RoundToMB(test_file_range)); + } + if (!ready) { + Message("Creating test file %s of size %dMB.\n", test_filename, RoundToMB(test_file_range)); + CreateTestFile(); + } +} + +static void PrepareTraces() { +// traces.Init(); + for (int i = 0; i < trace_filenames.Size(); i++) { + char *filename = trace_filenames.Get(i); + struct stat sb; + int r = stat(filename, &sb); + // Load the trace data exactly as it is stored in the trace file. + Message("Loading trace file %s (%dMB).\n", RoundToMB(sb.st_size)); + uint8_t *tracep = new uint8_t[sb.st_size]; + FILE *f = fopen(filename, "rb"); + if (f = NULL) + FatalError("Could not open trace file %s.\n", filename); + ssize_t size = fread(tracep, 1, sb.st_size, f); + if (size < sb.st_size) + FatalError("Error loading trace file %s.\n", filename); + fclose(f); + Trace *trace = new Trace; + trace->data = tracep; + trace->size = size; + traces.Add(trace); + } +} + +// Duration-limited tests. + +static int SequentialRead(ThreadedTimeout *tt) { + int fd = open(test_filename, O_RDONLY | extra_mode_access_flags); + CheckFDError(fd); + int blocks_processed = 0; + for (int i = 0; i < nu_blocks; i++) { + read_with_check(fd, buffer, 4096); + blocks_processed++; + if (tt->StopSignalled()) + break; + } + close(fd); + return blocks_processed; +} + +static int SequentialWrite(ThreadedTimeout *tt) { + int fd = open(test_filename, O_WRONLY | extra_mode_access_flags); + CheckFDError(fd); + int blocks_processed = 0; + for (int i = 0; i < nu_blocks; i++) { + write_with_check(fd, buffer, 4096); + blocks_processed++; + if (tt->StopSignalled()) + break; + } + close(fd); + return blocks_processed; +} + +static int RandomRead(ThreadedTimeout *tt) { + int fd = open(test_filename, O_RDONLY | extra_mode_access_flags); + CheckFDError(fd); + int blocks_processed = 0; + for (int i = 0; i < nu_blocks; i++) { + int block_index = indices[i]; + lseek(fd, block_index * 4096, SEEK_SET); + read_with_check(fd, buffer, 4096); + blocks_processed++; + if (tt->StopSignalled()) + break; + } + close(fd); + return blocks_processed; +} + +static int RandomWrite(ThreadedTimeout *tt) { + int fd = open(test_filename, O_WRONLY | extra_mode_access_flags); + CheckFDError(fd); + int blocks_processed = 0; + for (int i = 0; i < nu_blocks; i++) { + int block_index = indices[i]; + lseek(fd, block_index * 4096, SEEK_SET); + write_with_check(fd, buffer, 4096); + blocks_processed++; + if (tt->StopSignalled()) + break; + } + close(fd); + return blocks_processed; +} + +// Tests with a set number of 4K blocks. + +static int SequentialRead() { + int fd = open(test_filename, O_RDONLY | extra_mode_access_flags); + CheckFDError(fd); + for (int i = 0; i < nu_blocks; i++) { + read_with_check(fd, buffer, 4096); + } + close(fd); + return nu_blocks; +} + +static int SequentialWrite() { + int fd = open(test_filename, O_WRONLY | extra_mode_access_flags); + CheckFDError(fd); + for (int i = 0; i < nu_blocks; i++) + write_with_check(fd, buffer, 4096); + close(fd); + return nu_blocks; +} + +static int RandomRead() { + int fd = open(test_filename, O_RDONLY | extra_mode_access_flags); + CheckFDError(fd); + for (int i = 0; i < nu_blocks; i++) { + int block_index = indices[i]; + lseek(fd, (off_t)block_index * 4096, SEEK_SET); + read_with_check(fd, buffer, 4096); + } + close(fd); + return nu_blocks; +} + +static int RandomWrite() { + int fd = open(test_filename, O_WRONLY | extra_mode_access_flags); + CheckFDError(fd); + for (int i = 0; i < nu_blocks; i++) { + int block_index = indices[i]; + lseek(fd, (off_t)block_index * 4096, SEEK_SET); + write_with_check(fd, buffer, 4096); + } + close(fd); + return nu_blocks; +} + +static int ExecuteTrace(Trace *trace, ThreadedTimeout *tt) { + int trace_bindex = 0; // Index into trace data in bytes. + int fd = open(test_filename, O_RDWR | extra_mode_access_flags_trace); + CheckFDError(fd); + int nu_blocks_processed = 0; + int last_4MB_chunk = 0; + for (;;) { + if (trace_bindex >= trace->size) + break; + uint32_t first_word = *(uint32_t *)(&trace->data[trace_bindex]); + uint32_t second_word = *(uint32_t *)(&trace->data[trace_bindex + 4]); + // Optionally, the transaction may not be aligned at 4KB block boundaries. + int head_size = 0; + int tail_size = 0; + uint64_t size_in_blocks; + uint64_t location; + int write_transaction; + if ((first_word & 0x80000000) == 0) { + // Format 1: 8 bytes, 4K block units. + int write_transaction = (first_word & 0x40000000) >> 30; + int size_in_blocks = first_word & 0x3FFFFFFF; + location = (uint64_t)second_word * 4096; + trace_bindex += 8; + } + else if ((first_word & 0x40000000) == 0) { + // Format 2: 8 bytes, location in blocks, size in bytes. + int write_transaction = (first_word & 0x20000000) >> 29; + location = (uint64_t)(first_word & 0x1FFFFFFF) * 4096; + size_in_blocks = second_word / 4096; + tail_size = second_word - size_in_blocks * 4096; + trace_bindex += 8; + } + else { + // Format 3: 16 bytes, location and size in bytes. + uint64_t size = first_word | ((uint64_t)second_word << 32); + uint32_t third_word = *(uint32_t *)(&trace->data[trace_bindex + 8]); + uint32_t fourth_word = *(uint32_t *)(&trace->data[trace_bindex + 12]); + location = third_word | ((uint64_t)fourth_word << 32); + if ((location & 0xFFF) != 0) { + head_size = 4096 - (location & 0xFFF); + if (head_size > size) + head_size = size; + size -= head_size; + } + size_in_blocks = size / 4096; + tail_size = size & 0xFFF; + } + lseek(fd, (off_t)location, SEEK_SET); + // Handle head. + if (head_size > 0) { + if (write_transaction) + write_with_check(fd, buffer, head_size); + else + read_with_check(fd, buffer, head_size); + nu_blocks_processed++; + } + // Handle main part (block-aligned). + for (int i = 0; i < size_in_blocks; i++) + if (write_transaction) + write_with_check(fd, buffer, 4096); + else + read_with_check(fd, buffer, 4096); + nu_blocks_processed += size_in_blocks; + // Handle tail. + if (tail_size > 0) { + if (write_transaction) + write_with_check(fd, buffer, tail_size); + else + read_with_check(fd, buffer, tail_size); + nu_blocks_processed; + } + // When there is a set trace duration, check it. + if (FlagIsSet(FLAG_TRACE_DURATION)) + if (tt->StopSignalled()) + break; + } + close(fd); + return nu_blocks_processed; +} + +int main(int argc, char *argv[]) { +#if 0 + // Running with no arguments should invoke running the default tests + // with default parameters, not usage information. + if (argc == 1) { + Usage(); + exit(0); + } +#endif + ParseOptions(argc, argv); + + // Reset the random number generator + if (FlagIsSet(FLAG_RANDOM_SEED)) + srandom(random_seed); + else if (FlagIsSet(FLAG_RANDOM_SEED_TIME)) + srandom((uint32_t)(GetCurrentTime() * 1000.0)); + else + // By default, the random number patttern is deterministic and random access + // benchmarks are repeatable (same access pattern). + srandom(0); + + CreateBuffer(); + CheckTestFile(); + + // Validate tests (make sure the amount of transactions does not exceed + // the size of the test file). + if (FlagIsSet(FLAG_TOTAL_TRANSACTION_SIZE) && + total_transaction_size > test_file_range) { + Message("Adjusting transaction size downward to test file range.\n"); + total_transaction_size = test_file_range; + } + // For only duration-limited tests, also set a limit for the total transaction size + // at the total test file size. + if (!FlagIsSet(FLAG_TOTAL_TRANSACTION_SIZE) && !FlagIsSet(FLAG_NO_DURATION)) + total_transaction_size = test_file_range; + nu_blocks = total_transaction_size >> 12; + + // Set file access mode variables. + int extra_mode_access_flags = 0; + int extra_mode_access_flags_trace = 0; + if (FlagIsSet(FLAG_ACCESS_MODE_SYNC)) { + extra_mode_access_flags = O_SYNC; + extra_mode_access_flags_trace = O_SYNC; + } + if (FlagIsSet(FLAG_ACCESS_MODE_DIRECT)) { + extra_mode_access_flags |= O_DIRECT; + } + if (FlagIsSet(FLAG_TRACE_ACCESS_MODE_DIRECT)) { + extra_mode_access_flags_trace |= O_DIRECT; + } + + CreateIndices(); + SetRandomIndices(); + + // Prepare traces. + PrepareTraces(); + + int trace_index = 0; + int pid = getpid(); + CPUStat *cpustat_before = AllocateCPUStat(pid); + CPUStat *cpustat_after = AllocateCPUStat(pid); + for (int i = 0; i < commands.Size(); i++) { + DropCaches(); + int com = commands.Get(i); + Message("Benchmark: %s", test[com].description); + // Print limits determining how long the test will be run. + uint32_t timeout_secs = 0; + int64_t tr_size; + if (test[com].command_flags & CMD_TRACE) { + Message(" %s", trace_filenames.Get(trace_index)); + if (FlagIsSet(FLAG_TRACE_DURATION)) + timeout_secs = trace_duration; + tr_size = 0; + } + else { + if (!FlagIsSet(FLAG_NO_DURATION)) + timeout_secs = duration; + tr_size = total_transaction_size; + } + if (timeout_secs == 0 && tr_size == 0) + Message(" No limits"); + else { + Message(" Limits: "); + if (tr_size != 0) + Message("Total size: %dMB", RoundToMB(tr_size)); + if (timeout_secs != 0) + Message(" Duration: %ds", timeout_secs); + } + Message("\n"); + + ThreadedTimeout *tt; + if (timeout_secs > 0) { + tt = new ThreadedTimeout(); + tt->Start((uint64_t)timeout_secs * 1000000); + } + cpustat_before->Update(); + int blocks_processed; + Timer timer; + timer.Start(); + if (test[com].command_flags & CMD_TRACE) { + blocks_processed = ExecuteTrace(traces.Get(trace_index), tt); + trace_index++; + } + else if (FlagIsSet(FLAG_NO_DURATION)) { + blocks_processed = nu_blocks; + switch (test[com].command_flags) { + case CMD_READ_SEQUENTIAL : + SequentialRead(); + break; + case CMD_WRITE_SEQUENTIAL : + SequentialWrite(); + break; + case CMD_READ_RANDOM : + RandomRead(); + break; + case CMD_WRITE_RANDOM : + RandomWrite(); + break; + default : + Message("Benchmark test unimplemented.\n"); + blocks_processed = 0; + break; + } + } + else { + switch (test[com].command_flags) { + case CMD_READ_SEQUENTIAL : + blocks_processed = SequentialRead(tt); + break; + case CMD_WRITE_SEQUENTIAL : + blocks_processed = SequentialWrite(tt); + break; + case CMD_READ_RANDOM : + blocks_processed = RandomRead(tt); + break; + case CMD_WRITE_RANDOM : + blocks_processed = RandomWrite(tt); + break; + default : + blocks_processed = 0; + Message("Benchmark test unimplemented.\n"); + break; + } + } + Sync(); + double elapsed_time = timer.Elapsed(); + cpustat_after->Update(); + if (timeout_secs > 0) + delete tt; + double ucpu, scpu; + cpustat_after->GetUsageFrom(cpustat_before, &ucpu, &scpu, NULL, NULL); + double processed_MB = (double)((int64_t)blocks_processed * 4096) / (1024 * 1024); + double bandwidth_MB = processed_MB / elapsed_time; + Message("%.1lfMB processed in %.2lfs (%.2lfMB/s), CPU: user %.2lf%%, sys %.2lf%%\n", + processed_MB, elapsed_time, bandwidth_MB, ucpu, scpu); + } + + DestroyBuffer(); +} + diff --git a/timer.h b/timer.h new file mode 100644 index 0000000..023cae9 --- /dev/null +++ b/timer.h @@ -0,0 +1,93 @@ +/* + +Copyright (c) 2014 Harm Hanemaaijer + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +*/ + +// Return current system time/date in seconds (double floating point format) + +inline uint64_t GetCurrentTimeUSec() { + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * 1000000 + tv.tv_usec; +} + +// Return current system time/date in cumulative microseconds + +inline double GetCurrentTime() { + return (double)GetCurrentTimeUSec() * 0.000001d; +} + +class Timer { +private : + uint64_t start_time; +public : + void Start() { + start_time = GetCurrentTimeUSec(); + } + uint64_t ElapsedUSec() { + uint64_t end_time = GetCurrentTimeUSec(); + uint64_t diff_time = end_time - start_time; + start_time = end_time; + return diff_time; + } + double Elapsed() { + return (double)ElapsedUSec() * 0.000001d; + } +}; + +// Threaded time-out + +class ThreadedTimeout { +private : + bool *stop_signalled; + uint64_t timeout_period; + pthread_t thread; + + static void *Thread(void *p) { + ThreadedTimeout *tt = (ThreadedTimeout *)p; + int secs = tt->timeout_period / 1000000; + if (secs > 0) + sleep(secs); + int usecs = tt->timeout_period % 1000000; + if (usecs > 0) + usleep(usecs); + *tt->stop_signalled = true; + } +public : + ThreadedTimeout() { + stop_signalled = new bool[1]; + } + ~ThreadedTimeout() { + Stop(); + delete [] stop_signalled; + } + void Start(uint64_t timeout_in_usec) { + timeout_period = timeout_in_usec; + *stop_signalled = false; + void * (*thread_func)(void *); + thread_func = &ThreadedTimeout::Thread; + pthread_create(&thread, NULL, thread_func, this); + } + void Stop() { + if (!(*stop_signalled)) + pthread_cancel(thread); + pthread_join(thread, NULL); + } + bool StopSignalled() const { + return *stop_signalled; + } +}; +