added chunked Pi calculations test for fork vs thread
This commit is contained in:
@@ -0,0 +1,765 @@
|
||||
// pi_digits_benchmark.c
|
||||
// Benchmark pthreads vs fork() for computing digits of pi in independent chunks.
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <pthread.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <sys/wait.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <signal.h>
|
||||
|
||||
typedef enum {
|
||||
MODE_THREAD = 0,
|
||||
MODE_PROCESS = 1
|
||||
} run_mode_t;
|
||||
|
||||
typedef struct {
|
||||
long thread_count;
|
||||
long process_count;
|
||||
long pi_digits;
|
||||
long chunk_size;
|
||||
} bench_config_t;
|
||||
|
||||
typedef struct {
|
||||
int worker_id;
|
||||
long chunk_start;
|
||||
long chunk_count;
|
||||
double start_time;
|
||||
double end_time;
|
||||
int exit_code;
|
||||
char output_path[PATH_MAX];
|
||||
char stats_path[PATH_MAX];
|
||||
pid_t pid;
|
||||
} worker_info_t;
|
||||
|
||||
typedef struct {
|
||||
double wall_s;
|
||||
double cpu_user_s;
|
||||
double cpu_sys_s;
|
||||
double cpu_total_s;
|
||||
double cpu_pct;
|
||||
long max_rss_kb;
|
||||
long max_vsz_kb;
|
||||
long agg_rss_kb;
|
||||
long agg_vsz_kb;
|
||||
double mem_snapshot_s;
|
||||
size_t output_bytes;
|
||||
int exit_code;
|
||||
} run_metrics_t;
|
||||
|
||||
static double now_wall(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return ts.tv_sec + ts.tv_nsec / 1e9;
|
||||
}
|
||||
|
||||
static void iso_timestamp(char *out, size_t size) {
|
||||
time_t now = time(NULL);
|
||||
struct tm tm_now;
|
||||
localtime_r(&now, &tm_now);
|
||||
strftime(out, size, "%Y-%m-%dT%H:%M:%S%z", &tm_now);
|
||||
}
|
||||
|
||||
static void log_event(const char *fmt, ...) {
|
||||
char ts[64];
|
||||
iso_timestamp(ts, sizeof(ts));
|
||||
fprintf(stdout, "[%s] ", ts);
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vfprintf(stdout, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
fputc('\n', stdout);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static long read_env_long(const char *name, long def_value) {
|
||||
const char *val = getenv(name);
|
||||
if (!val || !*val) {
|
||||
return def_value;
|
||||
}
|
||||
char *end = NULL;
|
||||
long v = strtol(val, &end, 10);
|
||||
if (end == val || v <= 0) {
|
||||
return def_value;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
static int ensure_dir(const char *path) {
|
||||
if (mkdir(path, 0755) == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (errno == EEXIST) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int ensure_dir_recursive(const char *path) {
|
||||
char tmp[PATH_MAX];
|
||||
size_t len = strlen(path);
|
||||
if (len == 0 || len >= sizeof(tmp)) {
|
||||
return -1;
|
||||
}
|
||||
strncpy(tmp, path, sizeof(tmp));
|
||||
tmp[sizeof(tmp) - 1] = '\0';
|
||||
if (tmp[len - 1] == '/') {
|
||||
tmp[len - 1] = '\0';
|
||||
}
|
||||
|
||||
for (char *p = tmp + 1; *p; ++p) {
|
||||
if (*p == '/') {
|
||||
*p = '\0';
|
||||
if (ensure_dir(tmp) != 0) {
|
||||
return -1;
|
||||
}
|
||||
*p = '/';
|
||||
}
|
||||
}
|
||||
return ensure_dir(tmp);
|
||||
}
|
||||
|
||||
static int remove_dir_tree(const char *path) {
|
||||
DIR *dir = opendir(path);
|
||||
if (!dir) {
|
||||
return 0;
|
||||
}
|
||||
struct dirent *ent;
|
||||
while ((ent = readdir(dir)) != NULL) {
|
||||
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
char child[PATH_MAX];
|
||||
snprintf(child, sizeof(child), "%s/%s", path, ent->d_name);
|
||||
unlink(child);
|
||||
}
|
||||
closedir(dir);
|
||||
rmdir(path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void reap_zombies(void) {
|
||||
int status = 0;
|
||||
while (waitpid(-1, &status, WNOHANG) > 0) {
|
||||
}
|
||||
}
|
||||
|
||||
static void kill_orphan_pids(const char *pid_file) {
|
||||
FILE *f = fopen(pid_file, "r");
|
||||
if (!f) {
|
||||
return;
|
||||
}
|
||||
pid_t pid = 0;
|
||||
while (fscanf(f, "%d", &pid) == 1) {
|
||||
if (pid > 1) {
|
||||
kill(pid, SIGTERM);
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
usleep(200000);
|
||||
f = fopen(pid_file, "r");
|
||||
if (!f) {
|
||||
return;
|
||||
}
|
||||
while (fscanf(f, "%d", &pid) == 1) {
|
||||
if (pid > 1) {
|
||||
kill(pid, SIGKILL);
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
static void cleanup_outputs(const char *out_dir, const char *tmp_dir, const char *pid_file, const char *final_path, const char *workers_csv, const char *results_csv) {
|
||||
(void)out_dir;
|
||||
log_event("Cleanup start");
|
||||
kill_orphan_pids(pid_file);
|
||||
reap_zombies();
|
||||
remove_dir_tree(tmp_dir);
|
||||
unlink(final_path);
|
||||
unlink(workers_csv);
|
||||
unlink(results_csv);
|
||||
unlink(pid_file);
|
||||
log_event("Cleanup complete");
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
long start;
|
||||
long end;
|
||||
long index;
|
||||
FILE *out;
|
||||
char buffer[4096];
|
||||
size_t buf_len;
|
||||
} emit_ctx_t;
|
||||
|
||||
static int emit_digit(emit_ctx_t *ctx, int digit) {
|
||||
ctx->index++;
|
||||
if (ctx->index == 0) {
|
||||
return 0;
|
||||
}
|
||||
long pos = ctx->index - 1;
|
||||
if (pos < ctx->start) {
|
||||
return 0;
|
||||
}
|
||||
if (pos >= ctx->end) {
|
||||
return 1;
|
||||
}
|
||||
if (digit < 0 || digit > 9) {
|
||||
return 1;
|
||||
}
|
||||
ctx->buffer[ctx->buf_len++] = (char)('0' + digit);
|
||||
if (ctx->buf_len == sizeof(ctx->buffer)) {
|
||||
fwrite(ctx->buffer, 1, ctx->buf_len, ctx->out);
|
||||
ctx->buf_len = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int flush_emit(emit_ctx_t *ctx) {
|
||||
if (ctx->buf_len > 0) {
|
||||
if (fwrite(ctx->buffer, 1, ctx->buf_len, ctx->out) != ctx->buf_len) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int write_pi_chunk(FILE *out, long start, long count) {
|
||||
if (count <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
long end = start + count;
|
||||
if (end <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
long total = end;
|
||||
long len_calc = (total * 10) / 3 + 1;
|
||||
if (len_calc <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t len = (size_t)len_calc;
|
||||
int *a = calloc(len, sizeof(int));
|
||||
if (!a) {
|
||||
return -1;
|
||||
}
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
a[i] = 2;
|
||||
}
|
||||
|
||||
int nines = 0;
|
||||
int predigit = 0;
|
||||
emit_ctx_t ctx = {start, end, -1, out, {0}, 0};
|
||||
|
||||
for (long i = 0; i < total; ++i) {
|
||||
int q = 0;
|
||||
for (long j = (long)len - 1; j >= 0; --j) {
|
||||
int x = 10 * a[j] + q * (int)(j + 1);
|
||||
a[j] = x % (int)(2 * j + 1);
|
||||
q = x / (int)(2 * j + 1);
|
||||
}
|
||||
a[0] = q % 10;
|
||||
q = q / 10;
|
||||
|
||||
if (q == 9) {
|
||||
nines++;
|
||||
continue;
|
||||
}
|
||||
if (q == 10) {
|
||||
if (emit_digit(&ctx, predigit + 1)) {
|
||||
break;
|
||||
}
|
||||
for (int k = 0; k < nines; ++k) {
|
||||
if (emit_digit(&ctx, 0)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
predigit = 0;
|
||||
nines = 0;
|
||||
} else {
|
||||
if (emit_digit(&ctx, predigit)) {
|
||||
break;
|
||||
}
|
||||
for (int k = 0; k < nines; ++k) {
|
||||
if (emit_digit(&ctx, 9)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
predigit = q;
|
||||
nines = 0;
|
||||
}
|
||||
|
||||
if (ctx.index - 1 >= end) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.index - 1 < end) {
|
||||
emit_digit(&ctx, predigit);
|
||||
}
|
||||
|
||||
int rc = flush_emit(&ctx);
|
||||
free(a);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int compute_chunk_to_file(const worker_info_t *info) {
|
||||
FILE *out = fopen(info->output_path, "w");
|
||||
if (!out) {
|
||||
return -1;
|
||||
}
|
||||
int rc = write_pi_chunk(out, info->chunk_start, info->chunk_count);
|
||||
fclose(out);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int read_self_status_kb(long *rss_kb, long *vsz_kb) {
|
||||
*rss_kb = 0;
|
||||
*vsz_kb = 0;
|
||||
FILE *f = fopen("/proc/self/status", "r");
|
||||
if (!f) {
|
||||
return -1;
|
||||
}
|
||||
char line[256];
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
if (sscanf(line, "VmRSS: %ld", rss_kb) == 1) {
|
||||
continue;
|
||||
}
|
||||
if (sscanf(line, "VmSize: %ld", vsz_kb) == 1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int write_worker_stats(const char *path) {
|
||||
long rss_kb = 0;
|
||||
long vsz_kb = 0;
|
||||
if (read_self_status_kb(&rss_kb, &vsz_kb) != 0) {
|
||||
return -1;
|
||||
}
|
||||
FILE *f = fopen(path, "w");
|
||||
if (!f) {
|
||||
return -1;
|
||||
}
|
||||
fprintf(f, "%ld,%ld\n", rss_kb, vsz_kb);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void *thread_worker(void *arg) {
|
||||
worker_info_t *info = (worker_info_t *)arg;
|
||||
int rc = compute_chunk_to_file(info);
|
||||
info->exit_code = rc == 0 ? 0 : 1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int run_thread_mode(const bench_config_t *cfg, const char *tmp_dir, const char *workers_csv, worker_info_t *workers, run_metrics_t *metrics) {
|
||||
pthread_t *threads = calloc((size_t)cfg->thread_count, sizeof(pthread_t));
|
||||
if (!threads) {
|
||||
return 1;
|
||||
}
|
||||
bool *created = calloc((size_t)cfg->thread_count, sizeof(bool));
|
||||
if (!created) {
|
||||
free(threads);
|
||||
return 1;
|
||||
}
|
||||
|
||||
log_event("Threaded benchmark start (workers=%ld)", cfg->thread_count);
|
||||
|
||||
for (long i = 0; i < cfg->thread_count; ++i) {
|
||||
workers[i].worker_id = (int)i;
|
||||
workers[i].chunk_start = i * cfg->chunk_size;
|
||||
workers[i].chunk_count = cfg->chunk_size;
|
||||
if (workers[i].chunk_start >= cfg->pi_digits) {
|
||||
workers[i].chunk_count = 0;
|
||||
} else if (workers[i].chunk_start + workers[i].chunk_count > cfg->pi_digits) {
|
||||
workers[i].chunk_count = cfg->pi_digits - workers[i].chunk_start;
|
||||
}
|
||||
snprintf(workers[i].output_path, sizeof(workers[i].output_path), "%s/worker_%06ld.txt", tmp_dir, i);
|
||||
workers[i].stats_path[0] = '\0';
|
||||
|
||||
log_event("Worker start (thread id=%ld chunk_start=%ld chunk_size=%ld)", i, workers[i].chunk_start, workers[i].chunk_count);
|
||||
workers[i].start_time = now_wall();
|
||||
int rc = pthread_create(&threads[i], NULL, thread_worker, &workers[i]);
|
||||
if (rc != 0) {
|
||||
errno = rc;
|
||||
perror("pthread_create");
|
||||
workers[i].exit_code = 1;
|
||||
created[i] = false;
|
||||
} else {
|
||||
created[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (long i = 0; i < cfg->thread_count; ++i) {
|
||||
if (created[i]) {
|
||||
pthread_join(threads[i], NULL);
|
||||
}
|
||||
workers[i].end_time = now_wall();
|
||||
log_event("Worker complete (thread id=%ld duration_s=%.6f exit_code=%d)", i, workers[i].end_time - workers[i].start_time, workers[i].exit_code);
|
||||
}
|
||||
|
||||
FILE *wcsv = fopen(workers_csv, "w");
|
||||
if (wcsv) {
|
||||
fprintf(wcsv, "worker_id,mode,chunk_start,chunk_size,duration_s,exit_code\n");
|
||||
for (long i = 0; i < cfg->thread_count; ++i) {
|
||||
fprintf(wcsv, "%ld,thread,%ld,%ld,%.6f,%d\n",
|
||||
i, workers[i].chunk_start, workers[i].chunk_count,
|
||||
workers[i].end_time - workers[i].start_time, workers[i].exit_code);
|
||||
}
|
||||
fclose(wcsv);
|
||||
}
|
||||
|
||||
free(created);
|
||||
free(threads);
|
||||
metrics->exit_code = 0;
|
||||
log_event("Threaded benchmark complete");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run_process_mode(const bench_config_t *cfg, const char *tmp_dir, const char *workers_csv, const char *pid_file, worker_info_t *workers, run_metrics_t *metrics) {
|
||||
log_event("Process benchmark start (workers=%ld)", cfg->process_count);
|
||||
|
||||
FILE *pf = fopen(pid_file, "w");
|
||||
if (!pf) {
|
||||
perror("fopen pid_file");
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (long i = 0; i < cfg->process_count; ++i) {
|
||||
workers[i].worker_id = (int)i;
|
||||
workers[i].chunk_start = i * cfg->chunk_size;
|
||||
workers[i].chunk_count = cfg->chunk_size;
|
||||
if (workers[i].chunk_start >= cfg->pi_digits) {
|
||||
workers[i].chunk_count = 0;
|
||||
} else if (workers[i].chunk_start + workers[i].chunk_count > cfg->pi_digits) {
|
||||
workers[i].chunk_count = cfg->pi_digits - workers[i].chunk_start;
|
||||
}
|
||||
snprintf(workers[i].output_path, sizeof(workers[i].output_path), "%s/worker_%06ld.txt", tmp_dir, i);
|
||||
snprintf(workers[i].stats_path, sizeof(workers[i].stats_path), "%s/worker_%06ld.stat", tmp_dir, i);
|
||||
|
||||
log_event("Worker start (process id=%ld chunk_start=%ld chunk_size=%ld)", i, workers[i].chunk_start, workers[i].chunk_count);
|
||||
workers[i].start_time = now_wall();
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
perror("fork");
|
||||
workers[i].exit_code = 1;
|
||||
continue;
|
||||
}
|
||||
if (pid == 0) {
|
||||
int rc = compute_chunk_to_file(&workers[i]);
|
||||
if (rc == 0) {
|
||||
if (write_worker_stats(workers[i].stats_path) != 0) {
|
||||
rc = 1;
|
||||
}
|
||||
}
|
||||
_exit(rc == 0 ? 0 : 1);
|
||||
}
|
||||
workers[i].pid = pid;
|
||||
fprintf(pf, "%d\n", pid);
|
||||
fflush(pf);
|
||||
}
|
||||
fclose(pf);
|
||||
|
||||
for (long i = 0; i < cfg->process_count; ++i) {
|
||||
if (workers[i].pid <= 0) {
|
||||
continue;
|
||||
}
|
||||
int status = 0;
|
||||
pid_t waited = waitpid(workers[i].pid, &status, 0);
|
||||
workers[i].end_time = now_wall();
|
||||
if (waited < 0) {
|
||||
perror("waitpid");
|
||||
workers[i].exit_code = 1;
|
||||
} else if (WIFEXITED(status)) {
|
||||
workers[i].exit_code = WEXITSTATUS(status);
|
||||
} else {
|
||||
workers[i].exit_code = 1;
|
||||
}
|
||||
log_event("Worker complete (process id=%ld duration_s=%.6f exit_code=%d)", i, workers[i].end_time - workers[i].start_time, workers[i].exit_code);
|
||||
}
|
||||
|
||||
FILE *wcsv = fopen(workers_csv, "w");
|
||||
if (wcsv) {
|
||||
fprintf(wcsv, "worker_id,mode,chunk_start,chunk_size,duration_s,exit_code\n");
|
||||
for (long i = 0; i < cfg->process_count; ++i) {
|
||||
fprintf(wcsv, "%ld,process,%ld,%ld,%.6f,%d\n",
|
||||
i, workers[i].chunk_start, workers[i].chunk_count,
|
||||
workers[i].end_time - workers[i].start_time, workers[i].exit_code);
|
||||
}
|
||||
fclose(wcsv);
|
||||
}
|
||||
|
||||
metrics->exit_code = 0;
|
||||
log_event("Process benchmark complete");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int concatenate_outputs(const bench_config_t *cfg, const char *tmp_dir, const char *final_path, long worker_count) {
|
||||
log_event("Aggregation start");
|
||||
|
||||
FILE *out = fopen(final_path, "w");
|
||||
if (!out) {
|
||||
perror("fopen final output");
|
||||
return 1;
|
||||
}
|
||||
|
||||
char buf[4096];
|
||||
for (long i = 0; i < worker_count; ++i) {
|
||||
char path[PATH_MAX];
|
||||
snprintf(path, sizeof(path), "%s/worker_%06ld.txt", tmp_dir, i);
|
||||
FILE *in = fopen(path, "r");
|
||||
if (!in) {
|
||||
continue;
|
||||
}
|
||||
size_t n = 0;
|
||||
while ((n = fread(buf, 1, sizeof(buf), in)) > 0) {
|
||||
fwrite(buf, 1, n, out);
|
||||
}
|
||||
fclose(in);
|
||||
}
|
||||
|
||||
fclose(out);
|
||||
log_event("Aggregation complete");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t file_size(const char *path) {
|
||||
struct stat st;
|
||||
if (stat(path, &st) != 0) {
|
||||
return 0;
|
||||
}
|
||||
return (size_t)st.st_size;
|
||||
}
|
||||
|
||||
static void collect_metrics(run_metrics_t *metrics, double start_wall, double end_wall) {
|
||||
struct rusage self_ru;
|
||||
struct rusage child_ru;
|
||||
memset(&self_ru, 0, sizeof(self_ru));
|
||||
memset(&child_ru, 0, sizeof(child_ru));
|
||||
getrusage(RUSAGE_SELF, &self_ru);
|
||||
getrusage(RUSAGE_CHILDREN, &child_ru);
|
||||
|
||||
double self_user = self_ru.ru_utime.tv_sec + self_ru.ru_utime.tv_usec / 1e6;
|
||||
double self_sys = self_ru.ru_stime.tv_sec + self_ru.ru_stime.tv_usec / 1e6;
|
||||
double child_user = child_ru.ru_utime.tv_sec + child_ru.ru_utime.tv_usec / 1e6;
|
||||
double child_sys = child_ru.ru_stime.tv_sec + child_ru.ru_stime.tv_usec / 1e6;
|
||||
|
||||
metrics->wall_s = end_wall - start_wall;
|
||||
metrics->cpu_user_s = self_user + child_user;
|
||||
metrics->cpu_sys_s = self_sys + child_sys;
|
||||
metrics->cpu_total_s = metrics->cpu_user_s + metrics->cpu_sys_s;
|
||||
metrics->cpu_pct = metrics->wall_s > 0.0 ? (100.0 * metrics->cpu_total_s / metrics->wall_s) : 0.0;
|
||||
metrics->max_rss_kb = self_ru.ru_maxrss;
|
||||
|
||||
metrics->max_vsz_kb = 0;
|
||||
read_self_status_kb(&metrics->max_rss_kb, &metrics->max_vsz_kb);
|
||||
}
|
||||
|
||||
static int read_worker_stats(const char *path, long *rss_kb, long *vsz_kb) {
|
||||
*rss_kb = 0;
|
||||
*vsz_kb = 0;
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f) {
|
||||
return -1;
|
||||
}
|
||||
if (fscanf(f, "%ld,%ld", rss_kb, vsz_kb) != 2) {
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void aggregate_worker_memory(const bench_config_t *cfg, run_mode_t mode, const worker_info_t *workers, run_metrics_t *metrics) {
|
||||
double t0 = now_wall();
|
||||
long total_rss = 0;
|
||||
long total_vsz = 0;
|
||||
|
||||
long self_rss = 0;
|
||||
long self_vsz = 0;
|
||||
read_self_status_kb(&self_rss, &self_vsz);
|
||||
total_rss += self_rss;
|
||||
total_vsz += self_vsz;
|
||||
|
||||
if (mode == MODE_PROCESS) {
|
||||
for (long i = 0; i < cfg->process_count; ++i) {
|
||||
if (workers[i].stats_path[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
long rss_kb = 0;
|
||||
long vsz_kb = 0;
|
||||
if (read_worker_stats(workers[i].stats_path, &rss_kb, &vsz_kb) == 0) {
|
||||
total_rss += rss_kb;
|
||||
total_vsz += vsz_kb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
metrics->agg_rss_kb = total_rss;
|
||||
metrics->agg_vsz_kb = total_vsz;
|
||||
metrics->mem_snapshot_s = now_wall() - t0;
|
||||
}
|
||||
|
||||
static void write_results_csv(const char *results_csv, const char *run_id, const char *mode, const bench_config_t *cfg, const run_metrics_t *metrics) {
|
||||
FILE *csv = fopen(results_csv, "w");
|
||||
if (!csv) {
|
||||
perror("fopen results_csv");
|
||||
return;
|
||||
}
|
||||
char ts[64];
|
||||
iso_timestamp(ts, sizeof(ts));
|
||||
char host[128] = "unknown";
|
||||
char kernel[128] = "unknown";
|
||||
gethostname(host, sizeof(host) - 1);
|
||||
struct utsname uts;
|
||||
if (uname(&uts) == 0) {
|
||||
strncpy(kernel, uts.release, sizeof(kernel) - 1);
|
||||
kernel[sizeof(kernel) - 1] = '\0';
|
||||
}
|
||||
|
||||
fprintf(csv, "run_id,timestamp,host,kernel,mode,worker_count,pi_digits,chunk_size,exit_code,wall_s,cpu_user_s,cpu_sys_s,cpu_pct,max_rss_kb,max_vsz_kb,agg_rss_kb,agg_vsz_kb,mem_snapshot_s,final_output_bytes\n");
|
||||
long workers = (strcmp(mode, "thread") == 0) ? cfg->thread_count : cfg->process_count;
|
||||
fprintf(csv, "%s,%s,%s,%s,%s,%ld,%ld,%ld,%d,%.6f,%.6f,%.6f,%.2f,%ld,%ld,%ld,%ld,%.6f,%zu\n",
|
||||
run_id, ts, host, kernel, mode, workers, cfg->pi_digits, cfg->chunk_size,
|
||||
metrics->exit_code, metrics->wall_s, metrics->cpu_user_s, metrics->cpu_sys_s,
|
||||
metrics->cpu_pct, metrics->max_rss_kb, metrics->max_vsz_kb,
|
||||
metrics->agg_rss_kb, metrics->agg_vsz_kb, metrics->mem_snapshot_s, metrics->output_bytes);
|
||||
fclose(csv);
|
||||
}
|
||||
|
||||
static void usage(const char *prog) {
|
||||
fprintf(stderr, "Usage: %s --mode thread|process [--out-dir PATH]\n", prog);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
run_mode_t mode = MODE_THREAD;
|
||||
const char *mode_name = "thread";
|
||||
const char *out_dir = "./pi_digits_run";
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (strcmp(argv[i], "--mode") == 0 && i + 1 < argc) {
|
||||
mode_name = argv[++i];
|
||||
} else if (strncmp(argv[i], "--mode=", 7) == 0) {
|
||||
mode_name = argv[i] + 7;
|
||||
} else if (strcmp(argv[i], "--out-dir") == 0 && i + 1 < argc) {
|
||||
out_dir = argv[++i];
|
||||
} else if (strncmp(argv[i], "--out-dir=", 10) == 0) {
|
||||
out_dir = argv[i] + 10;
|
||||
} else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
} else {
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(mode_name, "thread") == 0) {
|
||||
mode = MODE_THREAD;
|
||||
} else if (strcmp(mode_name, "process") == 0) {
|
||||
mode = MODE_PROCESS;
|
||||
} else {
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
bench_config_t cfg;
|
||||
cfg.thread_count = read_env_long("THREAD_COUNT", 4);
|
||||
cfg.process_count = read_env_long("PROCESS_COUNT", 4);
|
||||
cfg.pi_digits = read_env_long("PI_DIGITS", 2000);
|
||||
cfg.chunk_size = read_env_long("CHUNK_SIZE", 500);
|
||||
|
||||
if (cfg.chunk_size <= 0) {
|
||||
cfg.chunk_size = cfg.pi_digits;
|
||||
}
|
||||
|
||||
log_event("Benchmark start (mode=%s)", mode_name);
|
||||
|
||||
if (ensure_dir_recursive(out_dir) != 0) {
|
||||
perror("mkdir out_dir");
|
||||
return 1;
|
||||
}
|
||||
|
||||
char tmp_dir[PATH_MAX];
|
||||
char final_path[PATH_MAX];
|
||||
char workers_csv[PATH_MAX];
|
||||
char results_csv[PATH_MAX];
|
||||
char pid_file[PATH_MAX];
|
||||
char run_id[64];
|
||||
|
||||
snprintf(run_id, sizeof(run_id), "%ld", (long)time(NULL));
|
||||
snprintf(tmp_dir, sizeof(tmp_dir), "%s/worker_tmp", out_dir);
|
||||
snprintf(final_path, sizeof(final_path), "%s/pi_digits.txt", out_dir);
|
||||
snprintf(workers_csv, sizeof(workers_csv), "%s/workers.csv", out_dir);
|
||||
snprintf(results_csv, sizeof(results_csv), "%s/results.csv", out_dir);
|
||||
snprintf(pid_file, sizeof(pid_file), "%s/workers.pids", out_dir);
|
||||
|
||||
cleanup_outputs(out_dir, tmp_dir, pid_file, final_path, workers_csv, results_csv);
|
||||
|
||||
if (ensure_dir_recursive(tmp_dir) != 0) {
|
||||
perror("mkdir tmp_dir");
|
||||
return 1;
|
||||
}
|
||||
|
||||
long worker_count = (mode == MODE_THREAD) ? cfg.thread_count : cfg.process_count;
|
||||
worker_info_t *workers = calloc((size_t)worker_count, sizeof(worker_info_t));
|
||||
if (!workers) {
|
||||
perror("calloc workers");
|
||||
return 1;
|
||||
}
|
||||
|
||||
run_metrics_t metrics;
|
||||
memset(&metrics, 0, sizeof(metrics));
|
||||
|
||||
double t0 = now_wall();
|
||||
int rc = 0;
|
||||
if (mode == MODE_THREAD) {
|
||||
rc = run_thread_mode(&cfg, tmp_dir, workers_csv, workers, &metrics);
|
||||
} else {
|
||||
rc = run_process_mode(&cfg, tmp_dir, workers_csv, pid_file, workers, &metrics);
|
||||
}
|
||||
|
||||
if (rc != 0) {
|
||||
metrics.exit_code = 1;
|
||||
}
|
||||
|
||||
int agg_rc = concatenate_outputs(&cfg, tmp_dir, final_path, worker_count);
|
||||
if (agg_rc != 0) {
|
||||
metrics.exit_code = 1;
|
||||
}
|
||||
|
||||
aggregate_worker_memory(&cfg, mode, workers, &metrics);
|
||||
|
||||
remove_dir_tree(tmp_dir);
|
||||
unlink(pid_file);
|
||||
|
||||
double t1 = now_wall();
|
||||
collect_metrics(&metrics, t0, t1);
|
||||
metrics.output_bytes = file_size(final_path);
|
||||
|
||||
write_results_csv(results_csv, run_id, mode_name, &cfg, &metrics);
|
||||
|
||||
log_event("Benchmark end (mode=%s wall_s=%.6f cpu_pct=%.2f output_bytes=%zu)",
|
||||
mode_name, metrics.wall_s, metrics.cpu_pct, metrics.output_bytes);
|
||||
|
||||
free(workers);
|
||||
return metrics.exit_code;
|
||||
}
|
||||
Reference in New Issue
Block a user