Skip to content

Instantly share code, notes, and snippets.

@elazarl
Last active April 21, 2016 08:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save elazarl/140ccc8ebe8c98fc5050a4fdb7545df8 to your computer and use it in GitHub Desktop.
Save elazarl/140ccc8ebe8c98fc5050a4fdb7545df8 to your computer and use it in GitHub Desktop.
Compare rdtsc time passed before and after a call to nanosleep
#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