90 lines
4.0 KiB
C
90 lines
4.0 KiB
C
/* Read-only Linux timing evidence, NOT a PlatformQualification generator.
|
|
* stdout JSONL; no clock/power/sysctl writes. Run across an externally authorized
|
|
* suspend to observe BOOTTIME-MONOTONIC change. RAW/REALTIME are not independent
|
|
* UTC references. The sample triple matches platform_clock.rs read order.
|
|
*/
|
|
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/timex.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
static uint64_t clock_ns(clockid_t id) {
|
|
struct timespec t;
|
|
if (clock_gettime(id, &t) || t.tv_sec < 0) { perror("clock_gettime"); exit(1); }
|
|
return (uint64_t)t.tv_sec * 1000000000 + t.tv_nsec;
|
|
}
|
|
|
|
static void metadata(const char *phase) {
|
|
const char *paths[] = {
|
|
"/proc/sys/kernel/osrelease", "/proc/version", "/proc/cmdline",
|
|
"/proc/sys/kernel/random/boot_id", "/proc/cpuinfo",
|
|
"/sys/devices/system/clocksource/clocksource0/current_clocksource",
|
|
"/sys/devices/system/clocksource/clocksource0/available_clocksource",
|
|
"/sys/class/dmi/id/sys_vendor", "/sys/class/dmi/id/product_name",
|
|
"/sys/class/dmi/id/product_uuid", "/sys/class/dmi/id/bios_version",
|
|
"/sys/class/dmi/id/bios_date", "/sys/power/state", "/sys/power/mem_sleep",
|
|
"/sys/power/suspend_stats/success", "/sys/power/suspend_stats/fail",
|
|
"/sys/class/rtc/rtc0/name", "/proc/self/cgroup",
|
|
"/proc/self/timens_offsets"
|
|
};
|
|
for (size_t i = 0; i < sizeof(paths)/sizeof(*paths); i++) {
|
|
FILE *f = fopen(paths[i], "r");
|
|
printf("TIMING {\"kind\":\"metadata\",\"phase\":\"%s\",\"path\":\"%s\",\"errno\":%d,\"value\":\"",
|
|
phase, paths[i], f ? 0 : errno);
|
|
if (f) {
|
|
int c; size_t n = 0;
|
|
while ((c = fgetc(f)) != EOF && n++ < 65536) {
|
|
if (c == '"' || c == '\\') printf("\\%c", c);
|
|
else if (c < 32 || c >= 127) printf("\\u%04x", c);
|
|
else putchar(c);
|
|
}
|
|
if (c != EOF || ferror(f)) { fprintf(stderr, "metadata incomplete: %s\n", paths[i]); exit(1); }
|
|
fclose(f);
|
|
}
|
|
puts("\"}");
|
|
}
|
|
}
|
|
|
|
static unsigned long argument(const char *s, unsigned long maximum) {
|
|
char *end; errno = 0;
|
|
unsigned long n = strtoul(s, &end, 10);
|
|
if (errno || !*s || *end || n < 1 || n > maximum) {
|
|
fprintf(stderr, "invalid positive bounded argument: %s\n", s); exit(2);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
if (argc != 3) { fprintf(stderr, "usage: %s COUNT(1..100000) INTERVAL_MS(1..60000)\n", argv[0]); return 2; }
|
|
unsigned long count = argument(argv[1], 100000), ms = argument(argv[2], 60000);
|
|
setvbuf(stdout, NULL, _IOLBF, 0);
|
|
metadata("start");
|
|
for (unsigned long i = 0; i < count; i++) {
|
|
uint64_t before = clock_ns(CLOCK_BOOTTIME);
|
|
uint64_t wall = clock_ns(CLOCK_REALTIME);
|
|
uint64_t after = clock_ns(CLOCK_BOOTTIME);
|
|
uint64_t mono = clock_ns(CLOCK_MONOTONIC);
|
|
uint64_t raw = clock_ns(CLOCK_MONOTONIC_RAW);
|
|
uint64_t end = clock_ns(CLOCK_BOOTTIME);
|
|
struct timex tx = {0}; /* modes=0: query only, never discipline the clock. */
|
|
int state = adjtimex(&tx);
|
|
if (state < 0) { perror("adjtimex query"); return 1; }
|
|
printf("TIMING {\"kind\":\"sample\",\"index\":%lu,\"before_ns\":%" PRIu64
|
|
",\"wall_ns\":%" PRIu64 ",\"after_ns\":%" PRIu64
|
|
",\"mono_ns\":%" PRIu64 ",\"raw_ns\":%" PRIu64
|
|
",\"end_ns\":%" PRIu64 ",\"timex_state\":%d,\"timex_status\":%d,\"freq_scaled_ppm\":%ld,\"tolerance_scaled_ppm\":%ld}\n",
|
|
i, before, wall, after, mono, raw, end, state, tx.status, tx.freq, tx.tolerance);
|
|
if (i + 1 < count) {
|
|
struct timespec delay = {ms / 1000, (ms % 1000) * 1000000};
|
|
while (nanosleep(&delay, &delay)) if (errno != EINTR) { perror("nanosleep"); return 1; }
|
|
}
|
|
}
|
|
metadata("end");
|
|
puts("TIMING {\"kind\":\"complete\",\"qualified\":false}");
|
|
return ferror(stdout) ? 1 : 0;
|
|
}
|