Merge branch 'feature-usage-graphs'
Adds CPU telemetry recording (temperature and clock speed) during benchmark runs, uploaded alongside results. Workload phase recording was tried and then removed after it was found to distort scores; telemetry now samples at 2/sec.
This commit is contained in:
+222
-1
@@ -173,6 +173,8 @@ extern uint64_t fb_c_chase(void **ptrs, uint64_t steps);
|
||||
# define REPEATS 3 /* Number of tries. */
|
||||
#endif
|
||||
|
||||
#define TELEMETRY_REFRESH_INTERVAL 0.5
|
||||
|
||||
/* Simple repeatable random numbers. */
|
||||
|
||||
static uint64_t rng_state = 0x853c49e6748fea9bULL;
|
||||
@@ -220,6 +222,220 @@ struct background_metrics {
|
||||
int samples, available;
|
||||
};
|
||||
|
||||
|
||||
#define MAX_TELEMETRY_SAMPLES 1800
|
||||
|
||||
struct telemetry_sample {
|
||||
uint64_t elapsed_ms;
|
||||
double temperature_c, clock_mhz;
|
||||
};
|
||||
|
||||
struct telemetry_monitor {
|
||||
struct telemetry_sample samples[MAX_TELEMETRY_SAMPLES];
|
||||
size_t count;
|
||||
double started;
|
||||
volatile int stop;
|
||||
#if defined(_WIN32)
|
||||
HANDLE thread;
|
||||
#else
|
||||
pthread_t thread;
|
||||
#endif
|
||||
};
|
||||
|
||||
static int read_number_file(const char *path, double *value)
|
||||
{
|
||||
FILE *f = fopen(path, "r");
|
||||
int ok;
|
||||
if (!f) return 0;
|
||||
ok = fscanf(f, "%lf", value) == 1;
|
||||
fclose(f);
|
||||
return ok;
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
static int read_text_file(const char *path, char *value, size_t cap)
|
||||
{
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f || !fgets(value, (int)cap, f)) { if (f) fclose(f); return 0; }
|
||||
fclose(f);
|
||||
value[strcspn(value, "\r\n")] = '\0';
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int cpu_sensor_name(const char *name)
|
||||
{
|
||||
return strstr(name, "cpu") || strstr(name, "CPU") || strstr(name, "package") ||
|
||||
strstr(name, "Package") || strstr(name, "coretemp") || strstr(name, "k10temp") ||
|
||||
strstr(name, "zenpower") || strstr(name, "x86_pkg_temp") || strstr(name, "soc_thermal");
|
||||
}
|
||||
#endif
|
||||
|
||||
static double sample_cpu_temperature(void)
|
||||
{
|
||||
#if defined(__linux__)
|
||||
DIR *dir = opendir("/sys/class/thermal");
|
||||
struct dirent *entry;
|
||||
double hottest = -1.0;
|
||||
if (!dir) return -1.0;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
char path[512], type[128];
|
||||
double value;
|
||||
if (strncmp(entry->d_name, "thermal_zone", 12) != 0) continue;
|
||||
snprintf(path, sizeof(path), "/sys/class/thermal/%s/type", entry->d_name);
|
||||
if (!read_text_file(path, type, sizeof(type)) || !cpu_sensor_name(type)) continue;
|
||||
snprintf(path, sizeof(path), "/sys/class/thermal/%s/temp", entry->d_name);
|
||||
if (read_number_file(path, &value)) {
|
||||
if (value > 1000.0) value /= 1000.0;
|
||||
if (value >= 0.0 && value <= 150.0 && value > hottest) hottest = value;
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
if (hottest < 0.0) {
|
||||
dir = opendir("/sys/class/hwmon");
|
||||
if (!dir) return -1.0;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
char base[512], path[512], name[128];
|
||||
DIR *sensor_dir;
|
||||
struct dirent *sensor;
|
||||
if (strncmp(entry->d_name, "hwmon", 5) != 0) continue;
|
||||
snprintf(base, sizeof(base), "/sys/class/hwmon/%s", entry->d_name);
|
||||
snprintf(path, sizeof(path), "%s/name", base);
|
||||
if (!read_text_file(path, name, sizeof(name)) || !cpu_sensor_name(name)) continue;
|
||||
sensor_dir = opendir(base);
|
||||
if (!sensor_dir) continue;
|
||||
while ((sensor = readdir(sensor_dir)) != NULL) {
|
||||
size_t length = strlen(sensor->d_name);
|
||||
double value;
|
||||
if (strncmp(sensor->d_name, "temp", 4) != 0 || length < 7 || strcmp(sensor->d_name + length - 6, "_input") != 0) continue;
|
||||
snprintf(path, sizeof(path), "%s/%s", base, sensor->d_name);
|
||||
if (read_number_file(path, &value)) {
|
||||
if (value > 1000.0) value /= 1000.0;
|
||||
if (value >= 0.0 && value <= 150.0 && value > hottest) hottest = value;
|
||||
}
|
||||
}
|
||||
closedir(sensor_dir);
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
return hottest;
|
||||
#else
|
||||
return -1.0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static double sample_cpu_clock_mhz(void)
|
||||
{
|
||||
#if defined(__linux__)
|
||||
DIR *dir = opendir("/sys/devices/system/cpu");
|
||||
struct dirent *entry;
|
||||
double total = 0.0;
|
||||
long count = 0;
|
||||
if (dir) {
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
char path[512], *end;
|
||||
double value;
|
||||
if (strncmp(entry->d_name, "cpu", 3) != 0 || !isdigit((unsigned char)entry->d_name[3])) continue;
|
||||
strtol(entry->d_name + 3, &end, 10);
|
||||
if (*end) continue;
|
||||
snprintf(path, sizeof(path), "/sys/devices/system/cpu/%s/cpufreq/scaling_cur_freq", entry->d_name);
|
||||
if (read_number_file(path, &value) && value > 0.0) { total += value / 1000.0; count++; }
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
if (count) return total / (double)count;
|
||||
{
|
||||
FILE *f = fopen("/proc/cpuinfo", "r");
|
||||
char line[256];
|
||||
if (!f) return -1.0;
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
double value;
|
||||
if (sscanf(line, "cpu MHz%*[^:]: %lf", &value) == 1 && value > 0.0) { total += value; count++; }
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
return count ? total / (double)count : -1.0;
|
||||
#elif defined(__APPLE__)
|
||||
{
|
||||
uint64_t hz = 0; size_t size = sizeof(hz);
|
||||
return sysctlbyname("hw.cpufrequency", &hz, &size, NULL, 0) == 0 && hz ? (double)hz / 1000000.0 : -1.0;
|
||||
}
|
||||
#elif defined(_WIN32)
|
||||
{
|
||||
HKEY key; DWORD mhz = 0, size = sizeof(mhz), type = 0;
|
||||
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &key) != ERROR_SUCCESS) return -1.0;
|
||||
if (RegQueryValueExA(key, "~MHz", NULL, &type, (LPBYTE)&mhz, &size) != ERROR_SUCCESS || type != REG_DWORD) mhz = 0;
|
||||
RegCloseKey(key);
|
||||
return mhz ? (double)mhz : -1.0;
|
||||
}
|
||||
#else
|
||||
return -1.0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void record_telemetry_sample(struct telemetry_monitor *monitor)
|
||||
{
|
||||
struct telemetry_sample *sample;
|
||||
if (monitor->count >= MAX_TELEMETRY_SAMPLES) return;
|
||||
sample = &monitor->samples[monitor->count++];
|
||||
sample->elapsed_ms = (uint64_t)((now_seconds() - monitor->started) * 1000.0);
|
||||
sample->temperature_c = sample_cpu_temperature();
|
||||
sample->clock_mhz = sample_cpu_clock_mhz();
|
||||
}
|
||||
|
||||
static void telemetry_sleep(void)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
Sleep(25);
|
||||
#else
|
||||
usleep(25000);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
static DWORD WINAPI telemetry_entry(LPVOID arg)
|
||||
#else
|
||||
static void *telemetry_entry(void *arg)
|
||||
#endif
|
||||
{
|
||||
struct telemetry_monitor *monitor = (struct telemetry_monitor *)arg;
|
||||
double next = monitor->started;
|
||||
while (!monitor->stop) {
|
||||
double current = now_seconds();
|
||||
if (current >= next) { record_telemetry_sample(monitor); next += TELEMETRY_REFRESH_INTERVAL; }
|
||||
telemetry_sleep();
|
||||
}
|
||||
record_telemetry_sample(monitor);
|
||||
#if defined(_WIN32)
|
||||
return 0;
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int start_telemetry_monitor(struct telemetry_monitor *monitor, double started)
|
||||
{
|
||||
memset(monitor, 0, sizeof(*monitor));
|
||||
monitor->started = started;
|
||||
#if defined(_WIN32)
|
||||
monitor->thread = CreateThread(NULL, 0, telemetry_entry, monitor, 0, NULL);
|
||||
return monitor->thread != NULL;
|
||||
#else
|
||||
return pthread_create(&monitor->thread, NULL, telemetry_entry, monitor) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void stop_telemetry_monitor(struct telemetry_monitor *monitor, int started)
|
||||
{
|
||||
if (!started) return;
|
||||
monitor->stop = 1;
|
||||
#if defined(_WIN32)
|
||||
WaitForSingleObject(monitor->thread, INFINITE);
|
||||
CloseHandle(monitor->thread);
|
||||
#else
|
||||
pthread_join(monitor->thread, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
struct cpu_snapshot { uint64_t total, idle; };
|
||||
|
||||
static int take_cpu_snapshot(struct cpu_snapshot *s)
|
||||
@@ -789,7 +1005,9 @@ int fossbench_run(int verbose, int upload_mode, int system_check)
|
||||
struct result real_multi[NTESTS], real_single[NTESTS];
|
||||
struct system_info system_info;
|
||||
struct background_metrics background;
|
||||
struct telemetry_monitor telemetry;
|
||||
double benchmark_started;
|
||||
int telemetry_started;
|
||||
uint64_t duration_ms;
|
||||
size_t i;
|
||||
|
||||
@@ -810,6 +1028,7 @@ int fossbench_run(int verbose, int upload_mode, int system_check)
|
||||
}
|
||||
}
|
||||
benchmark_started = now_seconds();
|
||||
telemetry_started = start_telemetry_monitor(&telemetry, benchmark_started);
|
||||
|
||||
printf("\n preparing workloads...");
|
||||
fflush(stdout);
|
||||
@@ -860,6 +1079,7 @@ int fossbench_run(int verbose, int upload_mode, int system_check)
|
||||
}
|
||||
|
||||
printf(" --------------------------------------------------------------------------\n");
|
||||
stop_telemetry_monitor(&telemetry, telemetry_started);
|
||||
|
||||
duration_ms = (uint64_t)((now_seconds() - benchmark_started) * 1000.0);
|
||||
printf(" %-24s %41.2fs\n", "TOTAL DURATION", (double)duration_ms / 1000.0);
|
||||
@@ -890,7 +1110,8 @@ int fossbench_run(int verbose, int upload_mode, int system_check)
|
||||
fprintf(stderr, " Upload support is disabled in this build.\n");
|
||||
#else
|
||||
upload_results(&system_info, raw_multi, raw_single,
|
||||
real_multi, real_single, duration_ms, &background);
|
||||
real_multi, real_single, duration_ms, &background,
|
||||
&telemetry);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
+25
-3
@@ -157,15 +157,16 @@ static int upload_results(const struct system_info *info,
|
||||
const struct result *raw_single,
|
||||
const struct result *real_multi,
|
||||
const struct result *real_single, uint64_t duration_ms,
|
||||
const struct background_metrics *background)
|
||||
const struct background_metrics *background,
|
||||
const struct telemetry_monitor *telemetry)
|
||||
{
|
||||
char host[256], port[16], path[512], payload[32768];
|
||||
char host[256], port[16], path[512], payload[131072];
|
||||
char auth_header[600], response_body[2048], claim_url[1024], result_url[1024];
|
||||
char cpu[512], model[512], os[512], compiler[256], kernel[256];
|
||||
const char *base = FB_API_BASE_URL, *p, *slash, *colon;
|
||||
int status = 0, payload_len;
|
||||
#if !defined(_WIN32)
|
||||
char request[40000], response[4096];
|
||||
char request[140000], response[4096];
|
||||
struct addrinfo hints, *addresses = NULL, *a;
|
||||
int fd = -1, request_len;
|
||||
#endif
|
||||
@@ -225,6 +226,27 @@ static int upload_results(const struct system_info *info,
|
||||
used += (size_t)n;
|
||||
if (!append_result_tests(payload, sizeof payload, &used, real_multi, real_single)) return 0;
|
||||
if (used + 3 >= sizeof payload) return 0;
|
||||
n = snprintf(payload + used, sizeof payload - used, "],\"telemetry\":[");
|
||||
if (n < 0 || (size_t)n >= sizeof payload - used) return 0;
|
||||
used += (size_t)n;
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < telemetry->count; i++) {
|
||||
const struct telemetry_sample *sample = &telemetry->samples[i];
|
||||
char temperature[32], clock[32];
|
||||
if (sample->temperature_c < 0.0) strcpy(temperature, "null");
|
||||
else snprintf(temperature, sizeof(temperature), "%.2f", sample->temperature_c);
|
||||
if (sample->clock_mhz < 0.0) strcpy(clock, "null");
|
||||
else snprintf(clock, sizeof(clock), "%.2f", sample->clock_mhz);
|
||||
n = snprintf(payload + used, sizeof payload - used,
|
||||
"%s{\"elapsed_ms\":%llu,\"temperature_c\":%s,\"clock_mhz\":%s}",
|
||||
i ? "," : "", (unsigned long long)sample->elapsed_ms,
|
||||
temperature, clock);
|
||||
if (n < 0 || (size_t)n >= sizeof payload - used) return 0;
|
||||
used += (size_t)n;
|
||||
}
|
||||
}
|
||||
if (used + 4 >= sizeof payload) return 0;
|
||||
memcpy(payload + used, "]}}", 4);
|
||||
payload_len = (int)(used + 3);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user