./0000755000000000000000000000000013610120176007706 5ustar rootroot./.gitignore0000644000000000000000000000011413610120176011672 0ustar rootroot# SPDX-License-Identifier: MIT *.o *_builtin.c jitterdebugger jittersamples ./jitterplot0000755000000000000000000000533213610120176012037 0ustar rootroot#!/usr/bin/env python3 # SPDX-License-Identifier: MIT import os import sys import json import argparse import subprocess import matplotlib.pyplot as plt import numpy as np import pandas as pd __version__ = '0.3' # silence SettingWithCopyWarning globally # https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#evaluation-order-matters pd.options.mode.chained_assignment = None def plot_histogram(filename): with open(filename) as file: rawdata = json.load(file) cpu_id = 0 fig, ax = plt.subplots() while True: if str(cpu_id) not in rawdata['cpu']: break cid = str(cpu_id) data = rawdata['cpu'][cid] d = {int(k): int(v) for k, v in data['histogram'].items()} l = 'cpu{} min{:>3} avg{:>7} max{:>3}'.format( cid, rawdata['cpu'][cid]['min'], rawdata['cpu'][cid]['avg'], rawdata['cpu'][cid]['max']) ax.bar(list(d.keys()), list(d.values()), log=True, alpha=0.5, label=l) cpu_id = cpu_id + 1 L = ax.legend() plt.setp(L.texts, family='monospace') plt.show() def load_samples(filename): dt = np.dtype([('CPUID', 'u4'), ('Seconds', 'u8'), ('Nanoseconds', 'u8'), ('Value', 'u8')]) data = np.fromfile(filename, dtype=dt) df = pd.DataFrame(data) return df def plot_all_cpus(df): ids = df["CPUID"].unique() max_jitter = max(df["Value"]) fig = plt.figure() axes = fig.subplots(len(ids)) for ax, data in zip( iter(axes), (df[df["CPUID"] == id] for id in ids), ): data["Time"] = data["Seconds"] + data["Nanoseconds"] * 10**-9 ax.plot("Time", "Value", data=data) ax.set_xlabel("Time [s]") ax.set_ylabel("Latency [us]") ax.set_ylim(bottom=0, top=max_jitter) plt.show() def main(): ap = argparse.ArgumentParser(description='Plot statistics collected with jitterdebugger') ap.add_argument('--version', action='version', version='%(prog)s ' + __version__) sap = ap.add_subparsers(dest='cmd') hrs = sap.add_parser('hist', help='Print historgram') hrs.add_argument('HIST_FILE') srs = sap.add_parser('samples', help='Plot samples graph') srs.add_argument('SAMPLE_FILE') args = ap.parse_args(sys.argv[1:]) if args.cmd == 'hist': fname = args.HIST_FILE if os.path.isdir(fname): fname = fname + '/results.json' plot_histogram(fname) elif args.cmd == 'samples': fname = args.SAMPLE_FILE if os.path.isdir(fname): fname = fname + '/samples.raw' df = load_samples(fname) plot_all_cpus(df) if __name__ == '__main__': main() ./LICENSE0000644000000000000000000000205313610120176010713 0ustar rootrootMIT License Copyright (c) 2018 Siemens AG Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ./.travis.yml0000644000000000000000000000236413610120176012024 0ustar rootroot# SPDX-License-Identifier: MIT language: c compiler: - gcc matrix: include: - os: linux before_install: - docker pull debian:latest - docker run --name travis-ci -v $TRAVIS_BUILD_DIR:/travis -w /travis -td debian:latest /bin/bash install: - docker exec -ti travis-ci bash -c "apt-get update" - docker exec -ti travis-ci bash -c "apt-get install -y build-essential git libhdf5-dev" - os: linux before_install: - docker pull debian:testing - docker run --name travis-ci -v $TRAVIS_BUILD_DIR:/travis -w /travis -td debian:testing /bin/bash install: - docker exec -ti travis-ci bash -c "apt-get update" - docker exec -ti travis-ci bash -c "apt-get install -y build-essential git libhdf5-dev" - os: linux before_install: - docker pull fedora:latest - docker run --name travis-ci -v $TRAVIS_BUILD_DIR:/travis -w /travis -td fedora:latest /bin/bash install: - docker exec -ti travis-ci bash -c "dnf -y update" - docker exec -ti travis-ci bash -c "dnf -y install @development-tools git hdf5-devel redhat-rpm-config" script: - docker exec -ti travis-ci bash -c "CC=$CC CXX=$CXX make" after_script: - docker stop travis-ci ./jd_work.c0000644000000000000000000000164713610120176011521 0ustar rootroot// SPDX-License-Identifier: MIT #include #include #include #include #include #include #include "jitterdebugger.h" static pid_t bpid; int start_workload(const char *cmd) { int err; if (!cmd) return 0; bpid = fork(); if (bpid > 0) return 0; if (bpid < 0) return errno; err = setpgid(0,0); if (err) err_handler(errno, "setpgid()"); printf("start background workload: %s\n", cmd); err = execl("/bin/sh", "sh", "-c", cmd, (char *)0); if (err) { err_handler(err, "execl()"); exit(EXIT_FAILURE); } exit(0); } void stop_workload(void) { int status; int err; if (bpid == 0) return; err = killpg(bpid, SIGTERM); if (err) err_handler(errno, "kill()"); if (!waitpid(bpid, &status, 0)) err_handler(errno, "waitpid()"); err = WEXITSTATUS(status); if (WIFEXITED(status) && err) warn_handler("workload exited with %d", err); } ./README.rst0000644000000000000000000001115213610120176011375 0ustar rootroot.. SPDX-License-Identifier: MIT ============== jitterdebugger ============== jitterdebugger measures wake up latencies. jitterdebugger starts a thread on each CPU which programs a timer and measures the time it takes from the timer expiring until the thread which set the timer runs again. This tool is a re-implementation of cyclictest. It doesn't have all the command line options as cyclictest which results are easy to get wrong and therefore an invalid latency report. The default settings of jitterdebugger will produce a correct measurement out of the box. Furthermore, the tool supports storing all samples for post processing. ############ Build status ############ +------------+------------------+ | Branch | Build Status | +============+==================+ | ``master`` | |travis-master|_ | +------------+------------------+ | ``next`` | |travis-next|_ | +------------+------------------+ .. |travis-master| image:: https://travis-ci.org/igaw/jitterdebugger.svg?branch=master .. _travis-master: https://travis-ci.org/igaw/jitterdebugger/branches .. |travis-next| image:: https://travis-ci.org/igaw/jitterdebugger.svg?branch=next .. _travis-next: https://travis-ci.org/igaw/jitterdebugger/branches ################# Runtime Depenency ################# jitterdebugger has only dependency to glibc (incl pthread). - glibc >= 2.24 jitterplot and jittersamples have additional dependency to - Python3 - Matlibplot - Pandas - HDF5 >= 1.8.17 ##### Usage ##### When running jitterdebugger without any command line options, there wont be any output until terminated by sending CTRL-C. It will print out the wake up latency for each CPU including a histogram of all latencies as JSON. The output can direclty be saved into a file using the -f command line option. :: # jitterdebugger ^C { "cpu": { "0": { "histogram": { "3": 3678, "4": 220, "5": 1, "8": 1, "11": 1 }, "count": 3901, "min": 3, "max": 11, "avg": 3.06 }, "1": { "histogram": { "3": 3690, "4": 188, "5": 2, "8": 3, "9": 2, "18": 1 }, "count": 3886, "min": 3, "max": 18, "avg": 3.06 } } } When providing '-v', jitterdebugger will live update all counters: :: # jitterdebugger -v affinity: 0,1 = 2 [0x3] T: 0 ( 614) A: 0 C: 13476 Min: 3 Avg: 3.08 Max: 10 T: 1 ( 615) A: 1 C: 13513 Min: 3 Avg: 3.10 Max: 20 ^C { "cpu": { "0": { "histogram": { "3": 4070, "4": 269, "5": 26, "6": 5, "7": 1, "8": 1, "9": 2, "10": 1 }, "count": 4375, "min": 3, "max": 10, "avg": 3.08 }, "1": { "histogram": { "3": 4002, "4": 320, "5": 22, "6": 4, "7": 2, "8": 1, "10": 2, "11": 1, "16": 2, "20": 1 }, "count": 4357, "min": 3, "max": 20, "avg": 3.10 } } } Field explanation: - T: Thread id (PID) - A: CPU affinity - C: Number of measurement cycles - Min: Smallest wake up latency observed - Max: Biggest wake up latency observed - Avg: Arithmetic average of all observed wake up latencies. ################ Measurement loop ################ The tool will start a measurement thread on each available CPU. The measurement loop does following: :: next = clock_gettime(CLOCK_MONOTONIC) + 1000us while not terminated: next = next + 1000us clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, next) now = clock_gettime() diff = now - next store(diff) ############## Histogram plot ############## This project provides a very simple analisys tool to a histogram. First let jitterdebugger collect some data and store the output into a file. :: # jitterdebugger -f results.json ^C # jitterplot hist results.json ################# Exporting samples ################# jitterdebugger is able to store all samples to a binary file. For post processing use jittersamples to print data as normal ASCII output: :: # jitterdebugger -o samples.raw ^C # jittersamples samples.raw | head 0;1114.936950838;9 0;1114.937204763;3 0;1114.937458457;3 0;1114.937711970;3 0;1114.937965595;3 0;1114.938218986;3 0;1114.938472416;3 0;1114.938725788;3 0;1114.938979191;3 0;1114.939232594;3 The fields are: 1. CPUID 2. Timestamp in seconds 3. Wake up latency in micro seconds ./jitterdebugger.h0000644000000000000000000000663413610120176013076 0ustar rootroot// SPDX-License-Identifier: MIT #ifndef __JITTERDEBUGGER_H #define __JITTERDEBUGGER_H #include #include #include #include #include #define JD_VERSION "0.3" // Results in a 1400 bytes payload per UDP packet #define SAMPLES_PER_PACKET 50 #define READ_ONCE(x) \ ({ \ union { typeof(x) __v; char __t[1]; } __u = { .__t = { 0 } }; \ *(typeof(x) *) __u.__t = *(volatile typeof(x) *) &x; \ __u.__v; \ }) #define WRITE_ONCE(x, v) \ ({ \ union { typeof(x) __v; char __t[1]; } __u = { .__v = (v) } ; \ *(volatile typeof(x) *) &x = *(typeof(x) *) __u.__t; \ __u.__v; \ }) struct latency_sample { uint32_t cpuid; struct timespec ts; uint64_t val; } __attribute__((packed)); struct ringbuffer; struct ringbuffer *ringbuffer_create(unsigned int size); void ringbuffer_free(struct ringbuffer *rb); int ringbuffer_read(struct ringbuffer *rb, struct timespec *ts, uint64_t *val); int ringbuffer_write(struct ringbuffer *rb, struct timespec ts, uint64_t val); void _err_handler(int error, char *format, ...) __attribute__((format(printf, 2, 3))); void _warn_handler(char *format, ...) __attribute__((format(printf, 1, 2))); #define err_handler(error, fmt, arg...) do { \ _err_handler(error, "%s:%s(): " fmt, \ __FILE__, __func__, ## arg); \ } while (0) #define warn_handler(fmt, arg...) do { \ _warn_handler("%s:%s(): " fmt "\n", \ __FILE__, __func__, ## arg); \ } while (0) #define err_abort(fmt, arg...) do { \ fprintf(stderr, fmt "\n", ## arg); \ exit(1); \ } while (0) long int parse_num(const char *str, int base, size_t *len); long int parse_time(const char *str); static inline long int parse_dec(const char *str) { return parse_num(str, 10, NULL); } int sysfs_load_str(const char *path, char **buf); /* cpu_set_t helpers */ void cpuset_fprint(FILE *f, cpu_set_t *set); ssize_t cpuset_parse(cpu_set_t *set, const char *str); int start_workload(const char *cmd); void stop_workload(void); struct system_info { char *sysname; char *nodename; char *release; char *version; char *machine; int cpus_online; }; struct system_info *collect_system_info(void); void store_system_info(const char *path, struct system_info *sysinfo); void free_system_info(struct system_info *sysinfo); char *jd_strdup(const char *src); FILE *jd_fopen(const char *path, const char *filename, const char *mode); void jd_cp(const char *src, const char *path); struct jd_samples_info { const char *dir; unsigned int cpus_online; }; struct jd_samples_ops { const char *name; const char *format; int (*output)(struct jd_samples_info *info, FILE *input); }; int jd_samples_register(struct jd_samples_ops *ops); void jd_samples_unregister(struct jd_samples_ops *ops); struct jd_plugin_desc { const char *name; int (*init)(void); void (*cleanup)(void); }; #define JD_PLUGIN_ID(x) jd_plugin_desc __jd_builtin_ ## x #define JD_PLUGIN_DEFINE_1(name, init, cleanup) \ struct JD_PLUGIN_ID(name) = { \ #name, init, cleanup \ }; #define JD_PLUGIN_DEFINE(init, cleanup) \ JD_PLUGIN_DEFINE_1(_FILENAME, init, cleanup) void __jd_plugin_init(void); void __jd_plugin_cleanup(void); // XXX replace single list with some library implementation struct jd_slist { void *data; struct jd_slist *next; }; void jd_slist_append(struct jd_slist *jd_slist, void *data); void jd_slist_remove(struct jd_slist *jd_slist, void *data); #endif /* __JITTERDEBUGGER_H */ ./man/0000755000000000000000000000000013610120176010461 5ustar rootroot./man/jitterplot.10000644000000000000000000000146113610120176012745 0ustar rootroot.\" SPDX-License-Identifier: MIT .TH JITTERPLOT 1 .SH NAME jitterplot \- plot collected samples by jitterdebugger .SH SYNOPSIS .B jitterplot [OPTIONS] {hist,samples} .SH DESCRIPTION .B jittersamples procudes plots from the collected samples by jitterdebugger. hist produces a histogram plot using the default output (JSON encoded) from jitterdebugger. samples procudes a plot using the all the collected samples by jitterdebugger in CSV format. .SH OPTIONS .TP .BI "-h, --help" Show help text and exit. .TP .BI "--version" Show jitterplot version. .SH EXAMPLES .EX # jitterdebugger -f results.json ^C # jitterplot hist results.json # jitterdebugger -o samples.raw ^C # jittersamples samples.raw > samples.txt # jitterplot samples samples.txt .EE .SH SEE ALSO .ad l .nh .BR jitterdebugger (1) .BR jittersamples (1) ./man/jitterdebugger.10000644000000000000000000000626213610120176013557 0ustar rootroot.\" SPDX-License-Identifier: MIT .TH JITTERDEBUGGER 1 .SH NAME jitterdebugger \- measures wake up latencies .SH SYNOPSIS .B jitterdebugger [OPTIONS] .SH DESCRIPTION .B jitterdebugger measures wake up latencies. jitterdebugger starts a thread on each CPU which programs a timer and measures the time it takes from the timer expiring until the thread which set the timer runs again. jitterdebugger default settings will produce a correct meassurement. The program runs until CTRL-C or SIGTERM is received. The results are printed to STDOUT as JSON encoded string. .SH OPTIONS .TP .BI "-h, --help" Show help text and exit. .TP .BI "-v, --verbose" Show live updates of the measurments .TP .BI "-f, --file=" FILE Write the results into the FILE .TP .BI "-c, --command=" CMD Start workload CMD in background, e.g. -c "while true; do; hackbench; done". The CMD is executed in a full shell, that means VARIABLE expansion works as well. When jitterdebugger terminates itself (because of break value or loops), the background workload is also terminated. .TP .BI "-N " SERVER:PORT Send samples to SERVER:PORT as UDP packets. See also jittersamples --listen. .TP .BI "-t, --timeout=" N Run meassurments for N seconds. N is allowed to be postfix with 'd' (days), 'h' (hours), 'm' (minutes) or 's' seconds. .TP .BI "-l, --loops=" N Run meassurments N times and the terminate jitterdebugger. .TP .BI "-b, --break=" N Run jitterdebugger until the N or greater latency has been observed. jitterdebugger will also stop a running tracer by writing 0 to tracing/tracing_on. Furthermore, the value observed will be written to the trace buffers tracing/trace_marker, e.g "Hit latency 249". .TP .BI "-i, --interval=" N Set the sleep time between each measuring. The default value is 1000us .TP .BI "-o, --output=" DIR Write all samples measured into directory DIR. The file is called samples.raw and it is binary encoded and can be decoded using jittersamples. Additional meta data is stored into DIR. .TP .BI "-a, --affinity=" CPUSET Set the CPU affinity mask. jitterdebugger starts only meassuring threads on CPUSET,. e.g. 0,2,5-7 starts a thread on first, third and 5, 6 and 7 CPU. May also be set in hexadecimal with '0x' prefix .TP .BI "-p, --priority=" PRI Set the priority of the meassuring threads. The default value is 98. Note priority 99 is not available because 99 should only be used for kernel housekeeping tasks. .SH EXAMPLES .EX # jitterdebugger -v affinity: 0,1 = 2 [0x3] T: 0 ( 614) A: 0 C: 13476 Min: 3 Avg: 3.08 Max: 10 T: 1 ( 615) A: 1 C: 13513 Min: 3 Avg: 3.10 Max: 20 ^C { "cpu": { "0": { "histogram": { "3": 4070, "4": 269, "5": 26, "6": 5, "7": 1, "8": 1, "9": 2, "10": 1 }, "count": 4375, "min": 3, "max": 10, "avg": 3.08 }, "1": { "histogram": { "3": 4002, "4": 320, "5": 22, "6": 4, "7": 2, "8": 1, "10": 2, "11": 1, "16": 2, "20": 1 }, "count": 4357, "min": 3, "max": 20, "avg": 3.10 } } } .EE .SH SEE ALSO .ad l .nh .BR jittersamples (1) .BR jitterplot (1) ./man/jittersamples.10000644000000000000000000000232713610120176013435 0ustar rootroot.\" SPDX-License-Identifier: MIT .TH JITTERSAMPLES 1 .SH NAME jittersamples \- decodes collected samples by jitterdebugger .SH SYNOPSIS .B jittersamples hist [OPTIONS] .SH DESCRIPTION .B jittersamples prints all samples stored by jitterdebugger to standard output. The input file is expected to be in raw format. The default output is in CSV (Comma Separated Values) format. The fields are: .IP \[bu] CPUID .IP \[bu] Timestamp in seconds .IP \[bu] Wake up latency in micro seconds .SH OPTIONS .TP .BI "-h, --help" Show help text and exit. .TP .BI "-f, --format" FMT Set output format. Supported formats are CSV and HDF5. For HDF5 an output file has to be provided via --output command line. .TP .BI "-o, --output" FILE Write data to FILE instead to STDOUT. .TP .BI "-l, --listen" PORT Listen on PORT for incoming samples and store the data into FILE in raw format. .SH EXAMPLES .EX # jitterdebugger -o samples.raw ^C # jittersamples samples.raw | head 0;1114.936950838;9 0;1114.937204763;3 0;1114.937458457;3 0;1114.937711970;3 0;1114.937965595;3 0;1114.938218986;3 0;1114.938472416;3 0;1114.938725788;3 0;1114.938979191;3 0;1114.939232594;3 .EE .SH SEE ALSO .ad l .nh .BR jitterdebugger (1) .BR jitterplot (1) ./jittersamples.c0000644000000000000000000001021413610120176012736 0ustar rootroot// SPDX-License-Identifier: MIT #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include "jitterdebugger.h" static void read_online_cpus(struct jd_samples_info *info) { FILE *fd; int n; fd = jd_fopen(info->dir, "cpus_online", "r"); if (!fd) err_handler(errno, "Could not read %s/cpus_online\n", info->dir); n = fscanf(fd, "%d\n", &info->cpus_online); if (n == EOF) { if (ferror(fd)) { err_handler(errno, "fscanf()"); perror("fscanf"); } else { fprintf(stderr, "cpus_online: No matching characters, no matching failure\n"); exit(1); } } else if (n != 1) { fprintf(stderr, "fscan() read more then one element\n"); exit(1); } fclose(fd); if (info->cpus_online < 1) { fprintf(stderr, "invalid input from cpus_online\n"); exit(1); } } static void dump_samples(const char *port) { struct addrinfo hints, *res, *tmp; struct latency_sample sp[SAMPLES_PER_PACKET]; ssize_t len; size_t wlen; int err, sk; bzero(&hints, sizeof(struct addrinfo)); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; err = getaddrinfo(NULL, port, &hints, &res); if (err < 0) err_handler(errno, "getaddrinfo()"); tmp = res; do { sk = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sk < 0) continue; err = bind(sk, res->ai_addr, res->ai_addrlen); if (err == 0) break; close(sk); res = res->ai_next; } while(res); freeaddrinfo(tmp); while (1) { len = recvfrom(sk, sp, sizeof(sp), 0, NULL, NULL); if (len != sizeof(sp)) { warn_handler("UDP packet has wrong size\n"); continue; } wlen = fwrite(sp, sizeof(sp), 1, stdout); if (wlen != sizeof(sp)) err_handler(errno, "fwrite()"); } close(sk); } struct jd_slist jd_samples_plugins = { NULL, }; int jd_samples_register(struct jd_samples_ops *ops) { jd_slist_append(&jd_samples_plugins, ops); return 0; } void jd_samples_unregister(struct jd_samples_ops *ops) { jd_slist_remove(&jd_samples_plugins, ops); } static struct option long_options[] = { { "help", no_argument, 0, 'h' }, { "version", no_argument, 0, 0 }, { "format", required_argument, 0, 'f' }, { "listen", required_argument, 0, 'l' }, { 0, }, }; static void __attribute__((noreturn)) usage(int status) { printf("jittersamples [options] [DIR]\n"); printf(" DIR Directory generated by jitterdebugger --output\n"); printf("\n"); printf("Usage:\n"); printf(" -h, --help Print this help\n"); printf(" --version Print version of jittersamples\n"); printf(" -f, --format FMT Exporting samples in format [csv, hdf5]\n"); printf(" -l, --listen PORT Listen on PORT, dump samples to stdout\n"); exit(status); } int main(int argc, char *argv[]) { FILE *input; int c, long_idx; char *format = "csv"; char *port = NULL; struct jd_samples_info info; struct jd_slist *list; while (1) { c = getopt_long(argc, argv, "hf:l:", long_options, &long_idx); if (c < 0) break; switch (c) { case 0: if (!strcmp(long_options[long_idx].name, "version")) { printf("jittersamples %s\n", JD_VERSION); exit(0); } break; case 'h': usage(0); case 'f': format = optarg; break; case 'l': port = optarg; break; default: printf("unknown option\n"); usage(1); } } if (port) { dump_samples(port); exit(0); } if (optind == argc) { fprintf(stderr, "Missing input DIR\n"); usage(1); } info.dir = argv[optind]; read_online_cpus(&info); __jd_plugin_init(); input = jd_fopen(info.dir, "samples.raw", "r"); if (!input) err_handler(errno, "Could not open '%s/samples.raw' for reading", info.dir); for (list = jd_samples_plugins.next; list; list = list->next) { struct jd_samples_ops *plugin = list->data; if (strcmp(plugin->format, format)) continue; plugin->output(&info, input); break; } fclose(input); __jd_plugin_cleanup(); if (!list) { fprintf(stderr, "Unsupported file format \"%s\"\n", format); exit(1); } return 0; } ./jd_sysinfo.c0000644000000000000000000000426013610120176012223 0ustar rootroot// SPDX-License-Identifier: MIT #define _GNU_SOURCE #include #include #include #include #include #include #include #include "jitterdebugger.h" #define SYSLOG_ACTION_READ_ALL 3 #define SYSLOG_ACTION_SIZE_BUFFER 10 struct system_info *collect_system_info(void) { struct system_info *info; struct utsname buf; int err; info = malloc(sizeof(*info)); if (!info) err_handler(errno, "malloc()"); err = uname(&buf); if (err) err_handler(errno, "Could not retrieve name and information about current kernel"); info->sysname = jd_strdup(buf.sysname); info->nodename = jd_strdup(buf.nodename); info->release = jd_strdup(buf.release); info->version = jd_strdup(buf.version); info->machine = jd_strdup(buf.machine); info->cpus_online = get_nprocs(); return info; } void store_system_info(const char *path, struct system_info *sysinfo) { char *buf; FILE *fd; unsigned int len; jd_cp("/proc/cmdline", path); jd_cp("/proc/config.gz", path); jd_cp("/proc/cpuinfo", path); jd_cp("/proc/interrupts", path); jd_cp("/proc/sched_debug", path); // cpus_online fd = jd_fopen(path, "cpus_online", "w"); if (!fd) return; fprintf(fd, "%d\n", sysinfo->cpus_online); fclose(fd); // uname fd = jd_fopen(path, "uname", "w"); if (!fd) return; fprintf(fd, "%s %s %s %s %s\n", sysinfo->sysname, sysinfo->nodename, sysinfo->release, sysinfo->version, sysinfo->machine); fclose(fd); // dmesg len = klogctl(SYSLOG_ACTION_SIZE_BUFFER, NULL, 0); buf = malloc(len * sizeof(char)); if (!buf) err_handler(errno, "malloc()"); if (klogctl(SYSLOG_ACTION_READ_ALL, buf, len) < 0) return; fd = jd_fopen(path, "dmesg", "w"); if (!fd) { free(buf); return; } if (fwrite(buf, sizeof(char), len, fd) != len) warn_handler("writing dmesg failed\n"); fclose(fd); free(buf); } void free_system_info(struct system_info *sysinfo) { if (sysinfo->sysname) free(sysinfo->sysname); if (sysinfo->nodename) free(sysinfo->nodename); if (sysinfo->release) free(sysinfo->release); if (sysinfo->version) free(sysinfo->version); if (sysinfo->machine) free(sysinfo->machine); free(sysinfo); } ./jitterdebugger.c0000644000000000000000000004505213610120176013066 0ustar rootroot// SPDX-License-Identifier: MIT #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "jitterdebugger.h" #define VT100_ERASE_EOL "\033[K" #define VT100_CURSOR_UP "\033[%uA" #define NSEC_PER_SEC 1000000000 #define NSEC_PER_US 1000UL /* Default test interval in us */ #define DEFAULT_INTERVAL 1000 struct stats { pthread_t pid; pid_t tid; unsigned int affinity; uint64_t max; uint64_t min; uint64_t avg; unsigned int hist_size; uint64_t *hist; /* each slot is one us */ uint64_t total; uint64_t count; struct ringbuffer *rb; }; struct record_data { struct stats *stats; char *server; char *port; FILE *fd; }; static int jd_shutdown; static cpu_set_t affinity; static unsigned int num_threads; static unsigned int priority = 80; static uint64_t break_val = UINT64_MAX; static unsigned int sleep_interval_us = DEFAULT_INTERVAL; static unsigned int interval_resolution = NSEC_PER_US; static unsigned int max_loops = 0; static int trace_fd = -1; static int tracemark_fd = -1; static void sig_handler(int sig) { WRITE_ONCE(jd_shutdown, 1); } static inline int64_t ts_sub(struct timespec t1, struct timespec t2) { int64_t diff; diff = NSEC_PER_SEC * (int64_t)((int) t1.tv_sec - (int) t2.tv_sec); diff += ((int) t1.tv_nsec - (int) t2.tv_nsec); return diff / interval_resolution; } static inline struct timespec ts_add(struct timespec t1, struct timespec t2) { t1.tv_sec = t1.tv_sec + t2.tv_sec; t1.tv_nsec = t1.tv_nsec + t2.tv_nsec; while (t1.tv_nsec >= NSEC_PER_SEC) { t1.tv_nsec -= NSEC_PER_SEC; t1.tv_sec++; } return t1; } static int c_states_disable(void) { uint32_t latency = 0; int fd; /* Disable on all CPUs all C states. */ fd = TEMP_FAILURE_RETRY(open("/dev/cpu_dma_latency", O_RDWR | O_CLOEXEC)); if (fd < 0) { if (errno == EACCES) fprintf(stderr, "No permission to open /dev/cpu_dma_latency\n"); err_handler(errno, "open()"); } write(fd, &latency, sizeof(latency)); return fd; } static void c_states_enable(int fd) { /* By closing the fd, the PM settings are restored. */ close(fd); } static void open_trace_fds(void) { const char *tracing_on = "/sys/kernel/debug/tracing/tracing_on"; const char *trace_marker = "/sys/kernel/debug/tracing/trace_marker"; trace_fd = TEMP_FAILURE_RETRY(open(tracing_on, O_WRONLY)); if (trace_fd < 0) err_handler(errno, "open()"); tracemark_fd = TEMP_FAILURE_RETRY(open(trace_marker, O_WRONLY)); if (tracemark_fd < 0) err_handler(errno, "open()"); } static void stop_tracer(uint64_t diff) { char buf[128]; int len; len = snprintf(buf, 128, "Hit latency %" PRIu64, diff); write(tracemark_fd, buf, len); write(trace_fd, "0\n", 2); } static inline pid_t __gettid(void) { /* * glibc wrapper available since glibc-2.30, but use our own * implementation for compatibility with older versions. */ return syscall(SYS_gettid); } static void dump_stats(FILE *f, struct system_info *sysinfo, struct stats *s) { unsigned int i, j, comma; fprintf(f, "{\n"); fprintf(f, " \"version\": 3,\n"); fprintf(f, " \"sysinfo\": {\n"); fprintf(f, " \"sysname\": \"%s\",\n", sysinfo->sysname); fprintf(f, " \"nodename\": \"%s\",\n", sysinfo->nodename); fprintf(f, " \"release\": \"%s\",\n", sysinfo->release); fprintf(f, " \"version\": \"%s\",\n", sysinfo->version); fprintf(f, " \"machine\": \"%s\",\n", sysinfo->machine); fprintf(f, " \"cpus_online\": %d,\n", sysinfo->cpus_online); fprintf(f, " \"resolution_in_ns\": %u\n", interval_resolution); fprintf(f, " },\n"); fprintf(f, " \"cpu\": {\n"); for (i = 0; i < num_threads; i++) { fprintf(f, " \"%u\": {\n", i); fprintf(f, " \"histogram\": {"); for (j = 0, comma = 0; j < s[i].hist_size; j++) { if (!s[i].hist[j]) continue; fprintf(f, "%s", comma ? ",\n" : "\n"); fprintf(f, " \"%u\": %" PRIu64,j, s[i].hist[j]); comma = 1; } if (comma) fprintf(f, "\n"); fprintf(f, " },\n"); fprintf(f, " \"count\": %" PRIu64 ",\n", s[i].count); fprintf(f, " \"min\": %" PRIu64 ",\n", s[i].min); fprintf(f, " \"max\": %" PRIu64 ",\n", s[i].max); fprintf(f, " \"avg\": %.2f\n", (double)s[i].total / (double)s[i].count); fprintf(f, " }%s\n", i == num_threads - 1 ? "" : ","); } fprintf(f, " }\n"); fprintf(f, "}\n"); } static void __display_stats(struct stats *s) { unsigned int i; for (i = 0; i < num_threads; i++) { printf("T:%2u (%5lu) A:%2u C:%10" PRIu64 " Min:%10" PRIu64 " Avg:%8.2f Max:%10" PRIu64 " " VT100_ERASE_EOL "\n", i, (long)s[i].tid, s[i].affinity, s[i].count, s[i].min, (double) s[i].total / (double) s[i].count, s[i].max); } } static void *display_stats(void *arg) { struct stats *s = arg; unsigned int i; for (i = 0; i < num_threads; i++) printf("\n"); while (!READ_ONCE(jd_shutdown)) { printf(VT100_CURSOR_UP, num_threads); __display_stats(s); fflush(stdout); usleep(100 * 1000); /* 100 ms interval */ } return NULL; } static void store_file(struct record_data *rec) { struct stats *s = rec->stats; struct latency_sample sample; struct timespec ts; uint64_t val; unsigned int i; while (!READ_ONCE(jd_shutdown)) { for (i = 0; i < num_threads; i++) { sample.cpuid = i; while (!ringbuffer_read(s[i].rb, &ts, &val)) { memcpy(&sample.ts, &ts, sizeof(sample.ts)); memcpy(&sample.val, &val, sizeof(sample.val)); fwrite(&sample, sizeof(struct latency_sample), 1, rec->fd); } } usleep(DEFAULT_INTERVAL); } } static void store_network(struct record_data *rec) { struct latency_sample sp[SAMPLES_PER_PACKET]; struct addrinfo hints, *res, *tmp; struct sockaddr *sa; socklen_t salen; int err, sk, len; unsigned int i, c; struct stats *s = rec->stats; struct timespec ts; uint64_t val; bzero(&hints, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; err = getaddrinfo(rec->server, rec->port, &hints, &res); if (err < 0) err_handler(err, "getaddrinfo()"); tmp = res; do { sk = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sk >= 0) break; res = res->ai_next; } while (res); if (sk < 0) err_handler(ENOENT, "no server"); sa = malloc(res->ai_addrlen); memcpy(sa, res->ai_addr, res->ai_addrlen); salen = res->ai_addrlen; freeaddrinfo(tmp); err = fcntl(sk, F_SETFL, O_NONBLOCK, 1); if (err < 0) err_handler(errno, "fcntl"); c = 0; while (!READ_ONCE(jd_shutdown)) { for (i = 0; i < num_threads; i++) { while (!ringbuffer_read(s[i].rb, &ts, &val)) { sp[c].cpuid = i; memcpy(&sp[c].ts, &ts, sizeof(sp[c].ts)); memcpy(&sp[c].val, &val, sizeof(sp[c].val)); if (c == SAMPLES_PER_PACKET - 1) { len = sendto(sk, (const void *)sp, sizeof(sp), 0, sa, salen); if (len < 0) perror("sendto"); c = 0; } else c++; } } usleep(DEFAULT_INTERVAL); } close(sk); } static void *store_samples(void *arg) { struct record_data *rec = arg; if (rec->fd) store_file(rec); else store_network(rec); return NULL; } static void *worker(void *arg) { struct stats *s = arg; struct timespec now, next, interval; sigset_t mask; uint64_t diff; int err; /* Don't handle any signals */ sigfillset(&mask); if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) err_handler(errno, "sigprocmask()"); s->tid = __gettid(); interval.tv_sec = 0; interval.tv_nsec = sleep_interval_us * NSEC_PER_US; err = clock_gettime(CLOCK_MONOTONIC, &now); if (err) err_handler(err, "clock_gettime()"); next = ts_add(now, interval); while (!READ_ONCE(jd_shutdown)) { next = ts_add(next, interval); err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL); if (err) err_handler(err, "clock_nanosleep()"); err = clock_gettime(CLOCK_MONOTONIC, &now); if (err) err_handler(err, "clock_gettime()"); /* Update the statistics */ diff = ts_sub(now, next); if (diff > s->max) s->max = diff; if (diff < s->min) s->min = diff; s->count++; s->total += diff; if (diff < s->hist_size) s->hist[diff]++; if (s->rb) ringbuffer_write(s->rb, now, diff); if (diff > break_val) { stop_tracer(diff); WRITE_ONCE(jd_shutdown, 1); } if (max_loops > 0 && s->count >= max_loops) break; } return NULL; } static void start_measuring(struct stats *s, struct record_data *rec) { struct sched_param sched; pthread_attr_t attr; cpu_set_t mask; unsigned int i, t; int err; pthread_attr_init(&attr); for (i = 0, t = 0; i < num_threads; i++) { CPU_ZERO(&mask); /* * Skip unset cores. will not crash as long as * num_threads <= CPU_COUNT(&affinity) */ while (!CPU_ISSET(t, &affinity)) t++; CPU_SET(t, &mask); /* Don't stay on the same core in next loop */ s[i].affinity = t++; s[i].min = UINT64_MAX; s[i].hist_size = NSEC_PER_SEC / interval_resolution / 1000; s[i].hist = calloc(s[i].hist_size, sizeof(uint64_t)); if (!s[i].hist) err_handler(errno, "calloc()"); if (rec) { s[i].rb = ringbuffer_create(1024 * 1024); if (!s[i].rb) err_handler(ENOMEM, "ringbuffer_create()"); } err = pthread_attr_setaffinity_np(&attr, sizeof(mask), &mask); if (err) err_handler(err, "pthread_attr_setaffinity_np()"); err = pthread_attr_setschedpolicy(&attr, SCHED_FIFO); if (err) err_handler(err, "pthread_attr_setschedpolicy()"); sched.sched_priority = priority; err = pthread_attr_setschedparam(&attr, &sched); if (err) err_handler(err, "pthread_attr_setschedparam()"); err = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); if (err) err_handler(err, "pthread_attr_setinheritsched()"); err = pthread_create(&s[i].pid, &attr, &worker, &s[i]); if (err) { if (err == EPERM) fprintf(stderr, "No permission to set the " "scheduling policy and/or priority\n"); else if (err == EINVAL) fprintf(stderr, "Invalid settings in thread attributes. " "Check your affinity mask\n"); err_handler(err, "pthread_create()"); } } pthread_attr_destroy(&attr); } static struct option long_options[] = { { "help", no_argument, 0, 'h' }, { "verbose", no_argument, 0, 'v' }, { "version", no_argument, 0, 0 }, { "command", required_argument, 0, 'c' }, { "loops", required_argument, 0, 'l' }, { "duration", required_argument, 0, 'D' }, { "break", required_argument, 0, 'b' }, { "nsec", no_argument, 0, 'N' }, { "interval", required_argument, 0, 'i' }, { "output", required_argument, 0, 'o' }, { "affinity", required_argument, 0, 'a' }, { "priority", required_argument, 0, 'p' }, { 0, }, }; static void __attribute__((noreturn)) usage(int status) { printf("jitterdebugger [options]\n"); printf("\n"); printf("General usage:\n"); printf(" -h, --help Print this help\n"); printf(" -v, --verbose Print live statistics\n"); printf(" --version Print version of jitterdebugger\n"); printf(" -o, --output DIR Store collected data into DIR\n"); printf(" -c, --command CMD Execute CMD (workload) in background\n"); printf("\n"); printf("Sampling:\n"); printf(" -l, --loops VALUE Max number of measurements\n"); printf(" -D, --duration TIME Specify a length for the test run.\n"); printf(" Append 'm', 'h', or 'd' to specify minutes, hours or days.\n"); printf(" -b, --break VALUE Stop if max latency exceeds VALUE.\n"); printf(" Also the tracers\n"); printf(" -N, --nsec Meassurement in nano seconds\n"); printf(" -i, --interval TIME Sleep interval for sampling threads in microseconds\n"); printf(" or nano seconds (see -N/--nsec)\n"); printf(" -n Send samples to host:port\n"); printf(" -s Store samples into --output DIR\n"); printf("\n"); printf("Threads: \n"); printf(" -a, --affinity CPUSET Core affinity specification\n"); printf(" e.g. 0,2,5-7 starts a thread on first, third and last two\n"); printf(" cores on a 8-core system.\n"); printf(" May also be set in hexadecimal with '0x' prefix\n"); printf(" -p, --priority PRI Worker thread priority. [1..98]\n"); exit(status); } int main(int argc, char *argv[]) { struct sigaction sa; unsigned int i; int c, fd, err; struct stats *s; pthread_t pid, iopid; cpu_set_t affinity_available, affinity_set; int long_idx; long val; struct record_data *rec = NULL; FILE *rfd = NULL; struct system_info *sysinfo; /* Command line options */ unsigned int opt_duration = 0; char *opt_dir = NULL; char *opt_cmd = NULL; char *opt_net = NULL; int opt_samples = 0; int opt_verbose = 0; CPU_ZERO(&affinity_set); while (1) { c = getopt_long(argc, argv, "c:n:sp:vD:l:b:Ni:o:a:h", long_options, &long_idx); if (c < 0) break; switch (c) { case 0: if (!strcmp(long_options[long_idx].name, "version")) { printf("jitterdebugger %s\n", JD_VERSION); exit(0); } break; case 'o': opt_dir = optarg; break; case 'c': opt_cmd = optarg; break; case 'n': opt_net = optarg; break; case 's': opt_samples = 1; break; case 'p': val = parse_dec(optarg); if (val < 1 || val > 98) err_abort("Invalid value for priority. " "Valid range is [1..98]\n"); priority = val; break; case 'v': opt_verbose = 1; break; case 'D': val = parse_time(optarg); if (val < 0) err_abort("Invalid value for duration. " "Valid postfixes are 'd', 'h', 'm', 's'\n"); opt_duration = val; break; case 'l': val = parse_dec(optarg); if (val <= 0) err_abort("Invalid value for loops. " "Valid range is [1..]\n"); max_loops = val; break; case 'b': val = parse_dec(optarg); if (val <= 0) err_abort("Invalid value for break. " "Valid range is [1..]\n"); break_val = val; break; case 'N': interval_resolution = 1; break; case 'i': val = parse_dec(optarg); if (val < 1) err_abort("Invalid value for interval. " "Valid range is [1..]. " "Default: %u us.\n", sleep_interval_us); sleep_interval_us = val; break; case 'h': usage(0); case 'a': val = cpuset_parse(&affinity_set, optarg); if (val < 0) { fprintf(stderr, "Invalid value for affinity. Valid range is [0..]\n"); exit(1); } break; default: usage(1); } } if (geteuid() != 0) printf("jitterdebugger is not running with root rights.\n"); sysinfo = collect_system_info(); if (opt_dir) { err = mkdir(opt_dir, 0777); if (err) { if (errno != EEXIST) { err_handler(errno, "Creating directory '%s' failed\n", opt_dir); } warn_handler("Directory '%s' already exist: overwriting contents", opt_dir); } else { store_system_info(opt_dir, sysinfo); } } if (opt_net || opt_samples) { if (opt_net && opt_samples) { fprintf(stdout, "Can't use both options -s or -n together\n"); exit(1); } rec = malloc(sizeof(*rec)); if (!rec) err_handler(ENOMEM, "malloc()"); if (opt_net) { rec->server = strtok(opt_net, " :"); rec->port = strtok(NULL, " :"); if (!rec->server || !rec->port) { fprintf(stdout, "Invalid server name and/or port string\n"); exit(1); } } if (opt_samples) { if (!opt_dir) { fprintf(stdout, "-o/--output is needed with -s option\n"); exit(1); } rec->fd = jd_fopen(opt_dir, "samples.raw", "w"); if (!rec->fd) err_handler(errno, "Couldn't create samples.raw file"); } } sa.sa_flags = 0; sa.sa_handler = sig_handler; sigemptyset(&sa.sa_mask); if (sigaction(SIGINT, &sa, NULL) < 0) err_handler(errno, "sigaction()"); if (sigaction(SIGTERM, &sa, NULL) < 0) err_handler(errno, "sigaction()"); if (sigaction(SIGALRM, &sa, NULL) < 0) err_handler(errno, "sigaction()"); if (opt_duration > 0) alarm(opt_duration); if (mlockall(MCL_CURRENT|MCL_FUTURE) < 0) { if (errno == ENOMEM || errno == EPERM) fprintf(stderr, "Nonzero RTLIMIT_MEMLOCK soft resource " "limit or missing process privileges " "(CAP_IPC_LOCK)\n"); err_handler(errno, "mlockall()"); } fd = c_states_disable(); if (break_val != UINT64_MAX) open_trace_fds(); if (CPU_COUNT(&affinity_set)) { /* * The user is able to override the affinity mask with * a set which contains more CPUs than * sched_getaffinity() returns. */ affinity = affinity_set; } else { if (sched_getaffinity(0, sizeof(cpu_set_t), &affinity_available)) err_handler(errno, "sched_getaffinity()"); affinity = affinity_available; } if (opt_verbose) { printf("affinity: "); cpuset_fprint(stdout, &affinity); printf("\n"); } num_threads = CPU_COUNT(&affinity); s = calloc(num_threads, sizeof(struct stats)); if (!s) err_handler(errno, "calloc()"); err = start_workload(opt_cmd); if (err < 0) err_handler(errno, "starting workload failed"); start_measuring(s, rec); if (opt_net || opt_samples) { rec->stats = s; err = pthread_create(&iopid, NULL, store_samples, rec); if (err) err_handler(err, "pthread_create()"); } if (opt_verbose) { err = pthread_create(&pid, NULL, display_stats, s); if (err) err_handler(err, "pthread_create()"); } for (i = 0; i < num_threads; i++) { err = pthread_join(s[i].pid, NULL); if (err) err_handler(err, "pthread_join()"); } WRITE_ONCE(jd_shutdown, 1); stop_workload(); if (rec) { err = pthread_join(iopid, NULL); if (err) err_handler(err, "pthread_join()"); if (rec->fd) fclose(rec->fd); free(rec); } if (opt_verbose) { err = pthread_join(pid, NULL); if (err) err_handler(err, "pthread_join()"); } else { printf("\n"); __display_stats(s); } printf("\n"); if (opt_dir) { rfd = jd_fopen(opt_dir, "results.json", "w"); if (rfd) { dump_stats(rfd, sysinfo, s); fclose(rfd); } else { warn_handler("Couldn't create results.json"); } free_system_info(sysinfo); } if (opt_verbose && break_val != UINT_MAX) { for (i = 0; i < num_threads; i++) { if (s[i].max > break_val) { const char *unit = "us"; if (interval_resolution == 1) unit = "ns"; printf("Thread %lu on CPU %u hit %lu %s latency\n", (long)s[i].tid, i, s[i].max, unit); } } } for (i = 0; i < num_threads; i++) { free(s[i].hist); if (s[i].rb) ringbuffer_free(s[i].rb); } free(s); if (tracemark_fd > 0) close(trace_fd); if (trace_fd > 0) close(trace_fd); c_states_enable(fd); return 0; } ./jd_samples_hdf5.c0000644000000000000000000000737513610120176013115 0ustar rootroot// SPDX-License-Identifier: MIT #define _GNU_SOURCE #include #include #include #include #include "jitterdebugger.h" #define min(x,y) \ ({ \ typeof(x) __x = (x); \ typeof(x) __y = (y); \ __x < __y ? __x : __y; \ }) #define BLOCK_SIZE 10000 struct cpu_data { hid_t set; uint64_t count; struct latency_sample *data; }; static int output_hdf5(struct jd_samples_info *info, FILE *input) { struct cpu_data *cpudata; struct latency_sample *data, *s, *d; hid_t file, type; herr_t err; uint64_t nrs, bs; size_t nr; off_t sz; unsigned int i, cnt; char *sid; char *ofile; if (asprintf(&ofile, "%s/samples.hdf5", info->dir) < 0) err_handler(errno, "asprintf()"); if (fseeko(input, 0L, SEEK_END) < 0) err_handler(errno, "fseek()"); sz = ftello(input); if (fseeko(input, 0L, SEEK_SET) < 0) err_handler(errno, "fseek()"); nrs = sz / sizeof(struct latency_sample); bs = min(nrs, BLOCK_SIZE); data = malloc(sizeof(struct latency_sample) * bs); if (!data) err_handler(ENOMEM, "malloc()"); file = H5Fcreate(ofile, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT); if (file == H5I_INVALID_HID) err_handler(EIO, "failed to open file %s\n", ofile); type = H5Tcreate(H5T_COMPOUND, sizeof(struct latency_sample)); if (type == H5I_INVALID_HID) err_handler(EIO, "failed to create compound HDF5 type"); err = H5Tinsert(type, "CPUID", 0, H5T_NATIVE_UINT32); if (err < 0) err_handler(EIO, "failed to add type info to HDF5 compound type"); err = H5Tinsert(type, "Seconds", 4, H5T_NATIVE_UINT64); if (err < 0) err_handler(EIO, "failed to add type info to HDF5 compound type"); err = H5Tinsert(type, "Nanoseconds", 12, H5T_NATIVE_UINT64); if (err < 0) err_handler(EIO, "failed to add type info to HDF5 compound type"); err = H5Tinsert(type, "Value", 20, H5T_NATIVE_UINT64); if (err < 0) err_handler(EIO, "failed to add type info to HDF5 compound type"); cpudata = malloc(info->cpus_online * sizeof(struct cpu_data)); if (!cpudata) err_handler(errno, "failed to allocated memory for cpu sets\n"); for (i = 0; i < info->cpus_online; i++) { cpudata[i].count = 0; cpudata[i].data = malloc(BLOCK_SIZE * sizeof(struct latency_sample)); if (asprintf(&sid, "cpu%d\n", i) < 0) err_handler(errno, "failed to create label\n"); cpudata[i].set = H5PTcreate(file, sid, type, (hsize_t)bs, H5P_DEFAULT); free(sid); if (cpudata[i].set == H5I_INVALID_HID) err_handler(EIO, "failed to create HDF5 packet table"); } for (;;) { nr = fread(data, sizeof(struct latency_sample), bs, input); if (nr != bs) { if (feof(input)) break; if (ferror(input)) err_handler(errno, "fread()"); } for (i = 0; i < nr; i++) { s = &data[i]; if (s->cpuid >= info->cpus_online) { fprintf(stderr, "invalid sample found (cpuid %d)\n", s->cpuid); continue; } cnt = cpudata[s->cpuid].count; d = &(cpudata[s->cpuid].data[cnt]); memcpy(d, s, sizeof(struct latency_sample)); cpudata[s->cpuid].count++; } for (i = 0; i < info->cpus_online; i++) { H5PTappend(cpudata[i].set, cpudata[i].count, cpudata[i].data); cpudata[i].count = 0; } } free(data); for (i = 0; i < info->cpus_online; i++) { H5PTclose(cpudata[i].set); free(cpudata[i].data); } free(cpudata); H5Tclose(type); H5Fclose(file); free(ofile); return 0; } static struct jd_samples_ops hdf5_ops = { .name = "Hierarchical Data Format", .format = "hdf5", .output = output_hdf5, }; static int hdf5_plugin_init(void) { return jd_samples_register(&hdf5_ops); } static void hdf5_plugin_cleanup(void) { jd_samples_unregister(&hdf5_ops); } JD_PLUGIN_DEFINE(hdf5_plugin_init, hdf5_plugin_cleanup); ./jd_plugin.c0000644000000000000000000000217213610120176012027 0ustar rootroot// SPDX-License-Identifier: MIT #include #include "jitterdebugger.h" extern struct jd_plugin_desc *__jd_builtin[]; void jd_slist_append(struct jd_slist *jd_slist, void *data) { struct jd_slist *l; for (l = jd_slist; l && l->next; l = l->next) /* do nothing */ ; if (!l) err_abort("linked list is inconsistent"); l->next = malloc(sizeof(struct jd_slist)); if (!l->next) err_abort("allocation for link list failed"); bzero(l->next, sizeof(*l->next)); l->next->data = data; } void jd_slist_remove(struct jd_slist *jd_slist, void *data) { struct jd_slist *last, *l; last = jd_slist; l = last->next; while (l && l->data != data) { last = l; l = l->next; } if (!l) { warn_handler("Element not found to remove"); return; } } void __jd_plugin_init(void) { int i; for (i = 0; __jd_builtin[i]; i++) { struct jd_plugin_desc *desc = __jd_builtin[i]; if (desc->init()) { err_abort("plugin initialization failed: %s", desc->name); } } } void __jd_plugin_cleanup(void) { int i; for (i = 0; __jd_builtin[i]; i++) { struct jd_plugin_desc *desc = __jd_builtin[i]; desc->cleanup(); } } ./scripts/0000755000000000000000000000000013610120176011375 5ustar rootroot./scripts/genbuiltin0000755000000000000000000000044013610120176013461 0ustar rootroot#!/bin/sh # SPDX-License-Identifier: MIT echo "#include \"jitterdebugger.h\"" echo for i in $* do echo "extern struct jd_plugin_desc __jd_builtin_$i;" done echo echo "struct jd_plugin_desc *__jd_builtin[] = {" for i in $* do echo " &__jd_builtin_$i," done echo " NULL" echo "};" ./scripts/release.sh0000755000000000000000000000166113610120176013360 0ustar rootroot#!/bin/sh # SPDX-License-Identifier: MIT OLD_VERSION=${1:-} NEW_VERSION=${2:-} usage() { echo "$0: OLD_VERSION NEW_VERSION" echo "" echo "example:" echo " $0 0.1 0.2" } if [ -z "$OLD_VERSION" ] || [ -z "$NEW_VERSION" ] ; then usage exit 1 fi echo "$NEW_VERSION" > newchangelog git shortlog "$OLD_VERSION".. >> newchangelog cat CHANGELOG.md >> newchangelog emacs newchangelog --eval "(text-mode)" echo -n "All fine, ready to release? [y/N]" read a a=$(echo "$a" | tr '[:upper:]' '[:lower:]') if [ "$a" != "y" ]; then echo "no not happy, let's stop doing the release" exit 1 fi mv newchangelog CHANGELOG.md sed -i "s,\(__version__ =\).*,\1 \'$NEW_VERSION\'," jitterplot sed -i "s,\(#define JD_VERSION\).*,\1 \"$NEW_VERSION\"," jitterdebugger.h git add CHANGELOG.md jitterplot jitterdebugger.h git commit -m "Release $NEW_VERSION" git tag -s -m "Release $NEW_VERSION" "$NEW_VERSION" git push --follow-tags ./Makefile0000644000000000000000000000244313610120176011351 0ustar rootroot# SPDX-License-Identifier: MIT CFLAGS += -pthread -Wall -Wstrict-aliasing=1 -Wno-unused-result \ -Wsign-compare -Wtype-limits -Wmissing-prototypes \ -Wstrict-prototypes LDFLAGS += -pthread ifdef DEBUG CFLAGS += -O0 -g LDFLAGS += -g else CFLAGS += -O2 endif TARGETS=jitterdebugger jittersamples all: $(TARGETS) jitterdebugger: jd_utils.o jd_work.o jd_sysinfo.o jitterdebugger.o jittersamples_builtin_modules = jd_samples_csv # Determine if HDF5 support will be built into jittersamples JSCC=${CC} ifneq ("$(wildcard /usr/include/hdf5.h)", "") CFLAGS += -DCONFIG_HDF5 JSCC = h5cc -shlib jittersamples_builtin_modules += jd_samples_hdf5 endif jittersamples_builtin_sources = $(addsuffix .c,$(jittersamples_builtin_modules)) jittersamples_builtin_objs = $(addsuffix .o,$(jittersamples_builtin_modules)) jittersamples_objs = jd_utils.o $(jittersamples_builtin_objs) \ jd_samples_builtin.o jd_plugin.o jittersamples.o jd_samples_builtin.c: scripts/genbuiltin $(jittersamples_builtin_sources) scripts/genbuiltin $(jittersamples_builtin_modules) > $@ $(jittersamples_objs): %.o: %.c export HDF5_CC=${CC} $(JSCC) ${CFLAGS} -D_FILENAME=$(basename $<) -c $< -o $@ jittersamples: $(jittersamples_objs) $(JSCC) $^ -o $@ PHONY: .clean clean: rm -f *.o rm -f $(TARGETS) rm -f jd_samples_builtin.c ./jd_utils.c0000644000000000000000000001514313610120176011673 0ustar rootroot// SPDX-License-Identifier: MIT #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include "jitterdebugger.h" #define BUFSIZE 4096 struct ringbuffer_sample { struct timespec ts; uint64_t val; }; struct ringbuffer { uint32_t size; uint32_t overflow; uint32_t read; uint32_t write; struct ringbuffer_sample *data; }; static int ringbuffer_full(uint32_t size, uint32_t read, uint32_t write) { return write - read == size; } static int ringbuffer_empty(uint32_t read, uint32_t write) { return write == read; } static uint32_t ringbuffer_mask(uint32_t size, uint32_t idx) { return idx & (size - 1); } struct ringbuffer *ringbuffer_create(unsigned int size) { struct ringbuffer *rb; /* Size needs to be of power of 2 */ if ((size & (size - 1)) != 0) return NULL; rb = calloc(1, sizeof(*rb)); if (!rb) return NULL; rb->size = size; rb->data = calloc(rb->size, sizeof(struct ringbuffer_sample)); return rb; } void ringbuffer_free(struct ringbuffer *rb) { free(rb->data); free(rb); } int ringbuffer_write(struct ringbuffer *rb, struct timespec ts, uint64_t val) { uint32_t read, idx; read = READ_ONCE(rb->read); if (ringbuffer_full(rb->size, read, rb->write)) { rb->overflow++; return 1; } idx = ringbuffer_mask(rb->size, rb->write + 1); rb->data[idx].ts = ts; rb->data[idx].val = val; WRITE_ONCE(rb->write, rb->write + 1); return 0; } int ringbuffer_read(struct ringbuffer *rb, struct timespec *ts, uint64_t *val) { uint32_t write, idx; write = READ_ONCE(rb->write); if (ringbuffer_empty(rb->read, write)) return 1; idx = ringbuffer_mask(rb->size, rb->read + 1); *ts = rb->data[idx].ts; *val = rb->data[idx].val; WRITE_ONCE(rb->read, rb->read + 1); return 0; } void _err_handler(int error, char *fmt, ...) { va_list ap; char *msg; va_start(ap, fmt); vasprintf(&msg, fmt, ap); va_end(ap); fprintf(stderr, "%s: %s\n", msg, strerror(error)); free(msg); exit(1); } void _warn_handler(char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stdout, fmt, ap); va_end(ap); } /* * Returns parsed zero or positive number, or a negative error value. * len optionally stores the length of the parsed string, may be NULL. */ long int parse_num(const char *str, int base, size_t *len) { long int ret; char *endptr; errno = 0; ret = strtol(str, &endptr, base); if (errno) return -errno; if (ret < 0) return -ERANGE; if (len) *len = endptr - str; return ret; } long int parse_time(const char *str) { long int t; char *end; t = strtol(str, &end, 10); if (!(strlen(end) == 0 || strlen(end) == 1)) return -EINVAL; if (end) { switch (*end) { case 's': case 'S': break; case 'm': case 'M': t *= 60; break; case 'h': case 'H': t *= 60 * 60; break; case 'd': case 'D': t *= 24 * 60 * 60; break; default: return -EINVAL; } } return t; } static void cpuset_from_bits(cpu_set_t *set, unsigned long bits) { unsigned i; for (i = 0; i < sizeof(bits) * 8; i++) if ((1UL << i) & bits) CPU_SET(i, set); } static unsigned long cpuset_to_bits(cpu_set_t *set) { unsigned long bits = 0; unsigned int i, t, bit; for (i = 0, t = 0; t < (unsigned) CPU_COUNT(set); i++) { bit = CPU_ISSET(i, set); bits |= bit << i; t += bit; } return bits; } static inline void _cpuset_fprint_end(FILE *f, unsigned long i, unsigned long r) { if (r > 1) fprintf(f, "%c%lu", r > 2 ? '-' : ',', i - 1); } /* Prints cpu_set_t as an affinity specification. */ void cpuset_fprint(FILE *f, cpu_set_t *set) { unsigned long bit = 0, range = 0; unsigned long i, t, comma = 0; for (i = 0, t = 0; t < (unsigned) CPU_COUNT(set); t += bit, i++) { bit = CPU_ISSET(i, set); if (!range && bit) { if (comma) fputc(',', f); comma = 1; fprintf(f, "%lu", i); } else if (!bit) { _cpuset_fprint_end(f, i, range); range = 0; } range += bit; } _cpuset_fprint_end(f, i, range); fprintf(f, " = %u [0x%lX]", CPU_COUNT(set), cpuset_to_bits(set)); } static long int _cpuset_parse_num(const char *str, int base, size_t *len) { long int ret; ret = parse_num(str, base, len); if (ret < 0) err_abort("cpuset: unable to parse string %s", str); return ret; } /* * Parses affinity specification (i.e. 0,2-3,7) into a cpu_set_t. * Returns parsed string length or -errno. */ ssize_t cpuset_parse(cpu_set_t *set, const char *str) { unsigned int i, first, last; ssize_t len_next; long int num = 0; size_t len = 0; if (!strncmp(str, "0x", 2)) { num = _cpuset_parse_num(str, 16, &len); cpuset_from_bits(set, (unsigned long) num); return len; } num = _cpuset_parse_num(str, 10, &len); str += len; first = num; if (str[0] == '-') { num = _cpuset_parse_num(str + 1, 10, &len); str += 1 + len; last = len ? num + 1 : CPU_SETSIZE; /* "x-" means x-CPU_SIZE */ } else last = first + 1; if (CPU_SETSIZE < last) { warn_handler("cpu num %d bigger than CPU_SETSIZE(%u), reducing", last, CPU_SETSIZE); last = CPU_SETSIZE; } for (i = first; i < last; i++) CPU_SET(i, set); if (str[0] == ',') { len_next = cpuset_parse(set, str + 1); if (len_next < 0) return len_next; len += len_next; } return len; } int sysfs_load_str(const char *path, char **buf) { int fd, ret; size_t len; fd = TEMP_FAILURE_RETRY(open(path, O_RDONLY)); if (fd < 0) return -errno; len = sysconf(_SC_PAGESIZE); *buf = malloc(len); if (!*buf) { ret = -ENOMEM; goto out_fd; } ret = read(fd, *buf, len - 1); if (ret < 0) { ret = -errno; goto out_buf; } (*buf)[ret] = 0; out_buf: if (ret < 0) free(*buf); out_fd: close(fd); return ret; } char *jd_strdup(const char *src) { char *dst; dst = strdup(src); if (!dst) err_handler(errno, "strdup()"); return dst; } FILE *jd_fopen(const char *path, const char *filename, const char *mode) { FILE *fd; char *fn, *tmp; tmp = jd_strdup(filename); if (asprintf(&fn, "%s/%s", path, basename(tmp)) < 0) err_handler(errno, "asprintf()"); fd = fopen(fn, mode); free(tmp); free(fn); return fd; } void jd_cp(const char *src, const char *path) { char buf[BUFSIZ]; FILE *fds, *fdd; size_t n; fds = fopen(src, "r"); if (!fds) { warn_handler("Could not open '%s' for reading", src); return; } fdd = jd_fopen(path, src, "w"); if (!fdd) { fclose(fdd); warn_handler("Could not copy '%s'", src); return; } while ((n = fread(buf, sizeof(char), sizeof(buf), fds)) > 0) { if (fwrite(buf, sizeof(char), n, fdd) != n) { warn_handler("Could not copy '%s'", src); goto out; } } out: fclose(fdd); fclose(fds); } ./jd_samples_csv.c0000644000000000000000000000163613610120176013054 0ustar rootroot// SPDX-License-Identifier: MIT #include #include #include "jitterdebugger.h" static int output_csv(struct jd_samples_info *info, FILE *input) { struct latency_sample sample; FILE *output; output = jd_fopen(info->dir, "samples.csv", "w"); if (!output) err_handler(errno, "Could not open '%s/samples.csv' for writing", info->dir); while(fread(&sample, sizeof(struct latency_sample), 1, input)) { fprintf(output, "%d;%lld.%.9ld;%" PRIu64 "\n", sample.cpuid, (long long)sample.ts.tv_sec, sample.ts.tv_nsec, sample.val); } fclose(output); return 0; } struct jd_samples_ops csv_ops = { .name = "comma separate values", .format = "csv", .output = output_csv, }; static int csv_plugin_init(void) { return jd_samples_register(&csv_ops); } static void csv_plugin_cleanup(void) { jd_samples_unregister(&csv_ops); } JD_PLUGIN_DEFINE(csv_plugin_init, csv_plugin_cleanup); ./CHANGELOG.md0000644000000000000000000000036213610120176011520 0ustar rootroot0.3 - release 0.2 was a brown bag release, thus a new release 0.2 - man pages added - license fixes - version identifier added to JSON output - various typos documentation fixed 0.1 - initial release