Compare rdtsc time passed before and after a call to nanosleep
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <errno.h> | |
#include <inttypes.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <stdint.h> | |
#include <string.h> | |
#include <regex.h> | |
#include <time.h> | |
#include <stdbool.h> | |
#include <argp.h> | |
#include <locale.h> | |
static inline __attribute__((always_inline)) uint64_t __real_tsc(void); | |
const int NANO_IN_MS = 1000 * 1000; | |
const int NANO_IN_US = 1000; | |
uint64_t get_cycles_in_milli(); | |
uint64_t nanosleep_jitter(struct timespec req); | |
static error_t parse_opt(int key, char *arg, struct argp_state *state); | |
static struct argp_option options[] = { | |
{ "sleep_time", 's', | |
"nanos", 0, | |
"how much time should we sleep, use ms or us for milli and micro", 0 }, | |
{ "times", 'n', "times", 0, "How many times should we test for jitter", 0 }, | |
{ "tsc_khz", 't', | |
"killohertz", 0, | |
"how many rdtsc cycles do we have in one millisecond", 0 }, | |
{ "raw", 'r', NULL, | |
OPTION_ARG_OPTIONAL, "should we print number of raw cycles counted", 0 }, | |
{ .name = NULL }, | |
}; | |
struct arguments { | |
struct timespec sleep_time; | |
int times; | |
int tsc_khz; | |
bool print_raw; | |
}; | |
/* | |
* Intro: | |
* This program measures jitter in the system. | |
* It would nanosleep for 10ms, and measure the actual | |
* time passed with rdtsc. | |
* It would output the difference between expected time | |
* and actual time passed. | |
* It would verify constant_tsc is set in /proc/cpuinfo, | |
* and would get the cpu rdtsc ratio by querying | |
* cpuinfo_max_freq in sysfs, as described in | |
* https://sourceware.org/ml/libc-alpha/2009-08/msg00001.html | |
* Usage: | |
* # gcc jitter.c -o jitter | |
* # ./jitter 10 | |
*/ | |
int main(int argc, char **argv) { | |
setlocale(LC_NUMERIC, ""); | |
int i; | |
const struct timespec _10ms = { .tv_nsec = NANO_IN_MS * 10 }; | |
struct arguments args = { .sleep_time = _10ms, .times = 5 }; | |
struct argp argp = { | |
.options = options, | |
.parser = parse_opt, | |
.doc = "This program nanosleep(3) for a certain amount of time\n" | |
"Measures how much time it took by rdtsc, and print the diff\n" | |
"Usage:\n" | |
"\t./jitter -s 10ms -n 2\n" | |
"\t-3.5615ms jitter actual 6.4385ms expected 10.0000ms\n" | |
"\t-3.5617ms jitter actual 6.4383ms expected 10.0000ms\n" | |
}; | |
argp_parse(&argp, argc, argv, 0, 0, &args); | |
uint64_t cycles_in_milli = args.tsc_khz; | |
if (!cycles_in_milli) { | |
cycles_in_milli = get_cycles_in_milli(); | |
if (cycles_in_milli == 0) { | |
fprintf(stderr, "Cannot find CPU cycles rate\n"); | |
return 1; | |
} | |
if (system("grep -qs constant_tsc /proc/cpuinfo") != 0) { | |
fprintf(stderr, "Cannot find constant_tsc in /proc/cpuinfo, " | |
"test is meaningless without it\n"); | |
return 1; | |
} | |
} | |
for (i = 0; i < args.times; i++) { | |
uint64_t actual_cycles = nanosleep_jitter(args.sleep_time); | |
uint64_t cycles_in_sec = cycles_in_milli * 1000; | |
/* we assume we would only handle millisec granuality */ | |
double expected_cycles = | |
args.sleep_time.tv_sec * cycles_in_sec + | |
(args.sleep_time.tv_nsec / 1000.) * (cycles_in_milli / 1000.); | |
double jitter = actual_cycles - expected_cycles; | |
if (args.print_raw) | |
printf("RAW: cycles %'" PRId64 " expected %'f diff %'f\n", | |
actual_cycles, expected_cycles, | |
actual_cycles - expected_cycles); | |
printf("%'.4fms jitter actual %'.4fms expected %'.4fms\n", | |
(double)jitter / cycles_in_milli, | |
(double)actual_cycles / cycles_in_milli, | |
(double)expected_cycles / cycles_in_milli); | |
} | |
return 0; | |
} | |
uint64_t get_cycles_in_milli() { | |
int khz; | |
FILE *fp = | |
fopen("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", "r"); | |
if (!fp) { | |
fclose(fp); | |
perror("cannot open cpu max frequency file"); | |
return 0; | |
} | |
fscanf(fp, "%d\n", &khz); | |
fprintf(stderr, "CPU max fequency is %'dkhz\n", khz); | |
fclose(fp); | |
return khz; | |
} | |
static inline __attribute__((always_inline)) uint64_t __real_tsc(void) { | |
uint32_t volatile hi, lo; | |
__asm__ __volatile__("rdtsc" : "=a"(lo), "=d"(hi)); | |
return (((uint64_t)hi) << 32) | lo; | |
} | |
void nanosleep_must(struct timespec req) { | |
struct timespec rem; | |
while (nanosleep(&req, &rem) == EINTR) | |
req = rem; | |
} | |
uint64_t nanosleep_jitter(struct timespec req) { | |
uint64_t now = __real_tsc(); | |
nanosleep_must(req); | |
return __real_tsc() - now; | |
} | |
bool parse_int(struct argp_state *state, int64_t *result, char *arg) { | |
char *endp; | |
uint64_t rv = strtoll(arg, &endp, 10); | |
if (*arg == '\0' || *endp != '\0') { | |
argp_failure(state, 2, errno, "a number is expected, instead '%s'", | |
arg); | |
return false; | |
} | |
*result = rv; | |
return true; | |
} | |
bool removepostfix(char *str, const char *postfix) { | |
int np = strlen(postfix); | |
int n = strlen(str); | |
if (n < np) | |
return false; | |
if (strcmp(str + n - np, postfix) != 0) | |
return false; | |
str[n - np] = '\0'; | |
return true; | |
} | |
static error_t parse_opt(int key, char *arg, struct argp_state *state) { | |
int64_t tmp; | |
struct arguments *args = state->input; | |
const int64_t MS_IN_SEC = 1000; | |
const int64_t US_IN_SEC = 1000 * 1000; | |
const int64_t NS_IN_SEC = 1000 * 1000 * 1000; | |
switch (key) { | |
case 's': | |
if (removepostfix(arg, "ms")) { | |
if (!parse_int(state, &tmp, arg)) | |
return ARGP_ERR_UNKNOWN; | |
args->sleep_time.tv_sec = tmp / MS_IN_SEC; | |
args->sleep_time.tv_nsec = (tmp % 1000) * NANO_IN_MS; | |
} | |
if (removepostfix(arg, "us")) { | |
if (!parse_int(state, &tmp, arg)) | |
return ARGP_ERR_UNKNOWN; | |
args->sleep_time.tv_sec = tmp / US_IN_SEC; | |
args->sleep_time.tv_nsec = (tmp % US_IN_SEC) * NANO_IN_US; | |
} | |
if (removepostfix(arg, "ns")) { | |
if (!parse_int(state, &tmp, arg)) | |
return ARGP_ERR_UNKNOWN; | |
args->sleep_time.tv_sec = tmp / NS_IN_SEC; | |
args->sleep_time.tv_nsec = tmp % NS_IN_SEC; | |
} | |
break; | |
case 'n': | |
if (!parse_int(state, &tmp, arg)) | |
return ARGP_ERR_UNKNOWN; | |
args->times = tmp; | |
break; | |
case 't': | |
if (!parse_int(state, &tmp, arg)) | |
return ARGP_ERR_UNKNOWN; | |
args->tsc_khz = tmp; | |
break; | |
case 'r': | |
args->print_raw = true; | |
break; | |
case ARGP_KEY_NO_ARGS: | |
break; | |
default: | |
return ARGP_ERR_UNKNOWN; | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment