Compare commits
17 Commits
2d241c1e7c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 32d3d5f97c | |||
| 1813b15993 | |||
| a54a29e486 | |||
| 2f79582210 | |||
| f688666a92 | |||
| 3defb64946 | |||
| c9a4b7f85d | |||
| 34d7c37f8a | |||
| 2018c11856 | |||
| e08467972d | |||
| 88b74819b1 | |||
| 2a20323b2d | |||
| aabd7b61ee | |||
| 925841e4d2 | |||
| 5fc6a119ac | |||
| 8f176fdac8 | |||
| c5003910ef |
@@ -1,2 +1,4 @@
|
||||
creation_time_experiment/creation_time_results.txt
|
||||
thread_recursive_scaling/runs/
|
||||
pi_digits_benchmark/runs/
|
||||
pi_digits_benchmark/results.csv
|
||||
@@ -17,12 +17,13 @@ Run individual experiments (examples):
|
||||
```sh
|
||||
bash creation_time_experiment/bash_test_iter.sh
|
||||
bash thread_recursive_scaling/run_thread_recursive.sh
|
||||
bash pi_digits_benchmark/run_pi_digits_benchmark.sh
|
||||
```
|
||||
|
||||
Enable the dangerous stress test (timeboxed):
|
||||
|
||||
```sh
|
||||
ALLOW_DANGEROUS=1 FORK_BOMB_SECONDS=5 bash run_all_experiments.sh
|
||||
PROC_SAMPLE_STEP=50 ALLOW_DANGEROUS=1 FORK_BOMB_SECONDS=5 bash run_all_experiments.sh
|
||||
```
|
||||
|
||||
## Key Commands for lifting linux kernel restrictions on alpine linux for threads & processes:
|
||||
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
LC_ALL=C
|
||||
|
||||
DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
cd "$DIR"
|
||||
|
||||
gcc -O2 -pthread -o creation_time creation_time.c
|
||||
|
||||
echo "Compiled creation_time"
|
||||
|
||||
CREATION_TIME_ITERS=${CREATION_TIME_ITERS:-"100 1000 10000"}
|
||||
CREATION_TIME_SECONDS=${CREATION_TIME_SECONDS:-60}
|
||||
|
||||
RUNS_DIR="$DIR/runs"
|
||||
mkdir -p "$RUNS_DIR"
|
||||
RUN_ID=$(date +"%Y%m%d_%H%M%S")
|
||||
RUN_OUT="$RUNS_DIR/$RUN_ID"
|
||||
mkdir -p "$RUN_OUT"
|
||||
|
||||
LOG_FILE="$RUN_OUT/activity.log"
|
||||
CSV="$RUN_OUT/creation_time.csv"
|
||||
STDOUT_LOG="$RUN_OUT/creation_time_stdout.log"
|
||||
STDERR_LOG="$RUN_OUT/creation_time_stderr.log"
|
||||
|
||||
log() {
|
||||
echo "[$(date -Iseconds)] $*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
count_processes() {
|
||||
ls -d /proc/[0-9]* 2>/dev/null | wc -l
|
||||
}
|
||||
|
||||
count_threads_total() {
|
||||
awk '/^Threads:/ {sum+=$2} END {print sum+0}' /proc/[0-9]*/status 2>/dev/null || echo 0
|
||||
}
|
||||
|
||||
get_mem_kb() {
|
||||
awk '/^MemTotal:/ {t=$2} /^MemAvailable:/ {a=$2} END {print t" "a}' /proc/meminfo
|
||||
}
|
||||
|
||||
sample_process_csv() {
|
||||
local pid="$1"
|
||||
local csv="$2"
|
||||
local start_uptime="$3"
|
||||
local max_seconds="$4"
|
||||
|
||||
local have_pidstat=0
|
||||
if command -v pidstat >/dev/null 2>&1; then
|
||||
have_pidstat=1
|
||||
else
|
||||
log "pidstat not found; install sysstat for accurate CPU sampling"
|
||||
fi
|
||||
|
||||
local prev_cpu_total=0
|
||||
local prev_cpu_idle=0
|
||||
if [ -r /proc/stat ]; then
|
||||
read -r _ c_user c_nice c_system c_idle c_iowait c_irq c_softirq c_steal _ _ < /proc/stat
|
||||
prev_cpu_total=$((c_user + c_nice + c_system + c_idle + c_iowait + c_irq + c_softirq + c_steal))
|
||||
prev_cpu_idle=$((c_idle + c_iowait))
|
||||
fi
|
||||
|
||||
while kill -0 "$pid" 2>/dev/null; do
|
||||
local now_uptime elapsed_s
|
||||
now_uptime=$(awk '{print $1}' /proc/uptime)
|
||||
elapsed_s=$(awk -v s="$start_uptime" -v n="$now_uptime" 'BEGIN{printf "%.3f", (n-s)}')
|
||||
|
||||
if [ "$max_seconds" -gt 0 ]; then
|
||||
if awk -v e="$elapsed_s" -v m="$max_seconds" 'BEGIN{exit (e>m)?0:1}'; then
|
||||
log "Timeout reached (${max_seconds}s), stopping PID=$pid"
|
||||
kill -TERM "-$pid" 2>/dev/null || true
|
||||
sleep 2
|
||||
kill -KILL "-$pid" 2>/dev/null || true
|
||||
break
|
||||
fi
|
||||
fi
|
||||
|
||||
local proc_count thread_total
|
||||
proc_count=$(count_processes || echo 0)
|
||||
thread_total=$(count_threads_total || echo 0)
|
||||
|
||||
local load1 load5 load15
|
||||
read -r load1 load5 load15 _ < /proc/loadavg
|
||||
|
||||
local mem_total mem_avail mem_used
|
||||
read -r mem_total mem_avail < <(get_mem_kb || echo "0 0")
|
||||
mem_used=$((mem_total - mem_avail))
|
||||
|
||||
local cpu rss vsz nlwp
|
||||
rss=$(awk '/^VmRSS:/ {print $2}' "/proc/$pid/status" 2>/dev/null || echo 0)
|
||||
vsz=$(awk '/^VmSize:/ {print $2}' "/proc/$pid/status" 2>/dev/null || echo 0)
|
||||
nlwp=$(awk '/^Threads:/ {print $2}' "/proc/$pid/status" 2>/dev/null || echo 0)
|
||||
if [ "$have_pidstat" -eq 1 ]; then
|
||||
cpu=$(pidstat -p "$pid" 1 1 2>/dev/null | awk -v p="$pid" '$1 ~ /^[0-9]/ && $3==p {print $8; exit}')
|
||||
cpu=${cpu:-0.00}
|
||||
else
|
||||
cpu=0.00
|
||||
fi
|
||||
local stk=0 heap=0
|
||||
|
||||
local sys_cpu_pct=0.00
|
||||
if [ -r /proc/stat ]; then
|
||||
read -r _ c_user c_nice c_system c_idle c_iowait c_irq c_softirq c_steal _ _ < /proc/stat
|
||||
local cpu_total=$((c_user + c_nice + c_system + c_idle + c_iowait + c_irq + c_softirq + c_steal))
|
||||
local cpu_idle=$((c_idle + c_iowait))
|
||||
local dt_total=$((cpu_total - prev_cpu_total))
|
||||
local dt_idle=$((cpu_idle - prev_cpu_idle))
|
||||
sys_cpu_pct=$(awk -v t="$dt_total" -v i="$dt_idle" 'BEGIN{ if (t<=0) printf "0.00"; else printf "%.2f", (100.0*(t-i))/t }')
|
||||
prev_cpu_total=$cpu_total
|
||||
prev_cpu_idle=$cpu_idle
|
||||
fi
|
||||
|
||||
printf "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n" \
|
||||
"$(date -Iseconds)" "$elapsed_s" "$load1" "$load5" "$load15" \
|
||||
"$mem_total" "$mem_avail" "$mem_used" "$proc_count" "$thread_total" \
|
||||
"$pid" "$cpu" "$sys_cpu_pct" "${rss:-0}" "${vsz:-0}" "${stk:-0}" "${heap:-0}" "${nlwp:-0}" \
|
||||
>> "$csv"
|
||||
|
||||
sleep 0.2
|
||||
done
|
||||
}
|
||||
|
||||
CMD="cd '$DIR' && { for n in $CREATION_TIME_ITERS; do echo \"=== iterations=\$n ===\"; ./creation_time -n \"\$n\"; done; } >'$STDOUT_LOG' 2>'$STDERR_LOG'"
|
||||
|
||||
log "Starting creation_time: $CMD"
|
||||
|
||||
echo "timestamp,elapsed_s,load1,load5,load15,mem_total_kb,mem_available_kb,mem_used_kb,proc_count,thread_count_total,pid,cpu_pct,sys_cpu_pct,rss_kb,vsz_kb,stack_kb,heap_kb,proc_threads" > "$CSV"
|
||||
|
||||
START_UPTIME=$(awk '{print $1}' /proc/uptime)
|
||||
|
||||
if command -v setsid >/dev/null 2>&1; then
|
||||
setsid bash -c "$CMD" &
|
||||
PID=$!
|
||||
else
|
||||
bash -c "$CMD" &
|
||||
PID=$!
|
||||
fi
|
||||
|
||||
sample_process_csv "$PID" "$CSV" "$START_UPTIME" "$CREATION_TIME_SECONDS"
|
||||
|
||||
set +e
|
||||
wait "$PID"
|
||||
EXIT_CODE=$?
|
||||
set -e
|
||||
|
||||
log "Finished creation_time (exit_code=$EXIT_CODE)"
|
||||
|
||||
echo
|
||||
echo "Run complete"
|
||||
echo " stdout: $STDOUT_LOG"
|
||||
echo " stderr: $STDERR_LOG"
|
||||
echo " samples: $CSV"
|
||||
echo " log: $LOG_FILE"
|
||||
echo " exit_code=$EXIT_CODE"
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
LC_ALL=C
|
||||
|
||||
DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
cd "$DIR"
|
||||
|
||||
gcc -O2 -o fork_bomb fork_bomb.c
|
||||
|
||||
echo "Compiled fork_bomb"
|
||||
|
||||
# This experiment is dangerous: it forks processes until the OS limit is hit.
|
||||
# You MUST set ALLOW_DANGEROUS=1 to run it.
|
||||
ALLOW_DANGEROUS=${ALLOW_DANGEROUS:-0}
|
||||
FORK_BOMB_SECONDS=${FORK_BOMB_SECONDS:-5}
|
||||
PROC_SAMPLE_STEP=${PROC_SAMPLE_STEP:-50}
|
||||
|
||||
RUNS_DIR="$DIR/runs"
|
||||
mkdir -p "$RUNS_DIR"
|
||||
RUN_ID=$(date +"%Y%m%d_%H%M%S")
|
||||
|
||||
OUT_CSV="$RUNS_DIR/${RUN_ID}_fork_bomb.csv"
|
||||
|
||||
if [ "$ALLOW_DANGEROUS" -ne 1 ]; then
|
||||
echo "Skipping fork_bomb (set ALLOW_DANGEROUS=1 to run)"
|
||||
echo "timestamp,elapsed_s,load1,load5,load15,mem_total_kb,mem_available_kb,mem_used_kb,proc_count,thread_count_total,pid,cpu_pct,sys_cpu_pct,forks_per_sec,rss_kb,vsz_kb,stack_kb,heap_kb,proc_threads" > "$OUT_CSV"
|
||||
echo " csv: $OUT_CSV"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Running fork_bomb for ${FORK_BOMB_SECONDS}s (PROC_SAMPLE_STEP=$PROC_SAMPLE_STEP)"
|
||||
echo " csv: $OUT_CSV"
|
||||
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
timeout "$FORK_BOMB_SECONDS" sh -c "FORK_BOMB_CSV='$OUT_CSV' PROC_SAMPLE_STEP='$PROC_SAMPLE_STEP' '$DIR/fork_bomb'" || true
|
||||
else
|
||||
FORK_BOMB_CSV="$OUT_CSV" PROC_SAMPLE_STEP="$PROC_SAMPLE_STEP" ./fork_bomb &
|
||||
FB_PID=$!
|
||||
sleep "$FORK_BOMB_SECONDS" || true
|
||||
kill -TERM "-$FB_PID" 2>/dev/null || true
|
||||
sleep 2
|
||||
kill -KILL "-$FB_PID" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
pkill -f "./fork_bomb" 2>/dev/null || true
|
||||
|
||||
echo "Run complete"
|
||||
echo " csv: $OUT_CSV"
|
||||
@@ -0,0 +1,33 @@
|
||||
# Pi Digits Benchmark
|
||||
|
||||
This benchmark compares pthread-based parallelism and fork()-based parallelism
|
||||
for computing digits of pi in independent chunks.
|
||||
|
||||
## Configuration (environment variables)
|
||||
|
||||
- `THREAD_COUNT` : number of worker threads
|
||||
- `PROCESS_COUNT` : number of worker processes
|
||||
- `PI_DIGITS` : total digits of pi to compute
|
||||
- `CHUNK_SIZE` : digits per worker
|
||||
|
||||
Defaults: `THREAD_COUNT=4`, `PROCESS_COUNT=4`, `PI_DIGITS=2000`, `CHUNK_SIZE=500`
|
||||
|
||||
## Run
|
||||
|
||||
```sh
|
||||
# Run both modes sequentially
|
||||
bash run_pi_digits_benchmark.sh
|
||||
|
||||
# Run a single mode
|
||||
bash run_pi_digits_benchmark.sh thread
|
||||
bash run_pi_digits_benchmark.sh process
|
||||
```
|
||||
|
||||
## Outputs
|
||||
|
||||
Per-run outputs are stored under `runs/`:
|
||||
|
||||
- `pi_digits.txt` — final concatenated digits
|
||||
- `workers.csv` — per-worker durations and chunk metadata
|
||||
- `results.csv` — per-run summary metrics (includes aggregated RSS/VSZ/PSS and snapshot overhead)
|
||||
- `stdout.log` / `stderr.log` — benchmark logs
|
||||
@@ -0,0 +1,778 @@
|
||||
// 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;
|
||||
long agg_pss_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 long read_self_pss_kb(void) {
|
||||
FILE *f = fopen("/proc/self/smaps", "r");
|
||||
if (!f) {
|
||||
return 0;
|
||||
}
|
||||
long total = 0;
|
||||
char line[256];
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
long v = 0;
|
||||
if (sscanf(line, "Pss: %ld", &v) == 1) {
|
||||
total += v;
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
return total;
|
||||
}
|
||||
|
||||
static int write_worker_stats(const char *path) {
|
||||
long rss_kb = 0;
|
||||
long vsz_kb = 0;
|
||||
long pss_kb = 0;
|
||||
if (read_self_status_kb(&rss_kb, &vsz_kb) != 0) {
|
||||
return -1;
|
||||
}
|
||||
pss_kb = read_self_pss_kb();
|
||||
FILE *f = fopen(path, "w");
|
||||
if (!f) {
|
||||
return -1;
|
||||
}
|
||||
fprintf(f, "%ld,%ld,%ld\n", rss_kb, vsz_kb, pss_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 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 total_pss = 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;
|
||||
total_pss += read_self_pss_kb();
|
||||
|
||||
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;
|
||||
long pss_kb = 0;
|
||||
FILE *f = fopen(workers[i].stats_path, "r");
|
||||
if (f && fscanf(f, "%ld,%ld,%ld", &rss_kb, &vsz_kb, &pss_kb) == 3) {
|
||||
fclose(f);
|
||||
total_rss += rss_kb;
|
||||
total_vsz += vsz_kb;
|
||||
total_pss += pss_kb;
|
||||
} else if (f) {
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
metrics->agg_rss_kb = total_rss;
|
||||
metrics->agg_vsz_kb = total_vsz;
|
||||
metrics->agg_pss_kb = total_pss;
|
||||
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,agg_pss_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,%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->agg_pss_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;
|
||||
}
|
||||
|
||||
double t_calc = now_wall();
|
||||
collect_metrics(&metrics, t0, t_calc);
|
||||
aggregate_worker_memory(&cfg, mode, workers, &metrics);
|
||||
|
||||
int agg_rc = concatenate_outputs(&cfg, tmp_dir, final_path, worker_count);
|
||||
if (agg_rc != 0) {
|
||||
metrics.exit_code = 1;
|
||||
}
|
||||
|
||||
remove_dir_tree(tmp_dir);
|
||||
unlink(pid_file);
|
||||
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;
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
LC_ALL=C
|
||||
|
||||
DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
cd "$DIR"
|
||||
|
||||
gcc -O2 -pthread -o pi_digits_benchmark pi_digits_benchmark.c
|
||||
|
||||
echo "Compiled pi_digits_benchmark"
|
||||
|
||||
THREAD_COUNT=${THREAD_COUNT:-4}
|
||||
PROCESS_COUNT=${PROCESS_COUNT:-4}
|
||||
PI_DIGITS=${PI_DIGITS:-2000}
|
||||
CHUNK_SIZE=${CHUNK_SIZE:-500}
|
||||
|
||||
RUNS_DIR="$DIR/runs"
|
||||
mkdir -p "$RUNS_DIR"
|
||||
RUN_ID=$(date +"%Y%m%d_%H%M%S")
|
||||
|
||||
RESULTS_CSV="$DIR/results.csv"
|
||||
if [ ! -f "$RESULTS_CSV" ]; then
|
||||
echo "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,agg_pss_kb,mem_snapshot_s,final_output_bytes" > "$RESULTS_CSV"
|
||||
fi
|
||||
|
||||
run_mode() {
|
||||
local mode="$1"
|
||||
local out_dir="$RUNS_DIR/${RUN_ID}_${mode}"
|
||||
local stdout_log="$out_dir/stdout.log"
|
||||
local stderr_log="$out_dir/stderr.log"
|
||||
|
||||
mkdir -p "$out_dir"
|
||||
|
||||
echo "Running mode=$mode out_dir=$out_dir"
|
||||
THREAD_COUNT="$THREAD_COUNT" PROCESS_COUNT="$PROCESS_COUNT" PI_DIGITS="$PI_DIGITS" CHUNK_SIZE="$CHUNK_SIZE" \
|
||||
./pi_digits_benchmark --mode "$mode" --out-dir "$out_dir" >"$stdout_log" 2>"$stderr_log"
|
||||
|
||||
if [ -f "$out_dir/results.csv" ]; then
|
||||
tail -n 1 "$out_dir/results.csv" >> "$RESULTS_CSV"
|
||||
fi
|
||||
|
||||
echo " stdout: $stdout_log"
|
||||
echo " stderr: $stderr_log"
|
||||
echo " results: $out_dir/results.csv"
|
||||
echo " workers: $out_dir/workers.csv"
|
||||
echo " pi output: $out_dir/pi_digits.txt"
|
||||
}
|
||||
|
||||
MODE=${1:-both}
|
||||
case "$MODE" in
|
||||
thread)
|
||||
run_mode thread
|
||||
;;
|
||||
process)
|
||||
run_mode process
|
||||
;;
|
||||
both)
|
||||
run_mode thread
|
||||
run_mode process
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [thread|process|both]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
+33
-9
@@ -150,15 +150,22 @@ compile_fork_bomb() {
|
||||
"$ROOT_DIR/fork_bomb/fork_bomb.c"
|
||||
}
|
||||
|
||||
compile_thread_stress() {
|
||||
log "Compiling thread_stress"
|
||||
gcc -O2 -pthread -o "$ROOT_DIR/thread_stress_test/thread_stress" \
|
||||
"$ROOT_DIR/thread_stress_test/thread_stress.c"
|
||||
compile_thread_recursive() {
|
||||
log "Compiling thread_recursive"
|
||||
gcc -O2 -pthread -o "$ROOT_DIR/thread_recursive_scaling/thread_recursive" \
|
||||
"$ROOT_DIR/thread_recursive_scaling/thread_recursive.c" -lm
|
||||
}
|
||||
|
||||
compile_pi_digits_benchmark() {
|
||||
log "Compiling pi_digits_benchmark"
|
||||
gcc -O2 -pthread -o "$ROOT_DIR/pi_digits_benchmark/pi_digits_benchmark" \
|
||||
"$ROOT_DIR/pi_digits_benchmark/pi_digits_benchmark.c"
|
||||
}
|
||||
|
||||
compile_creation_time
|
||||
compile_fork_bomb
|
||||
compile_thread_stress
|
||||
compile_thread_recursive
|
||||
compile_pi_digits_benchmark
|
||||
|
||||
# Defaults (override by env vars)
|
||||
ALLOW_DANGEROUS=${ALLOW_DANGEROUS:-0}
|
||||
@@ -166,6 +173,10 @@ FORK_BOMB_SECONDS=${FORK_BOMB_SECONDS:-5}
|
||||
CREATION_TIME_ITERS=${CREATION_TIME_ITERS:-"100 1000 10000"}
|
||||
CREATION_TIME_SECONDS=${CREATION_TIME_SECONDS:-60}
|
||||
PROC_SAMPLE_STEP=${PROC_SAMPLE_STEP:-50}
|
||||
THREAD_COUNT=${THREAD_COUNT:-4}
|
||||
PROCESS_COUNT=${PROCESS_COUNT:-4}
|
||||
PI_DIGITS=${PI_DIGITS:-2000}
|
||||
CHUNK_SIZE=${CHUNK_SIZE:-500}
|
||||
|
||||
CREATION_TIME_STDOUT="$OUT_DIR/creation_time_stdout.log"
|
||||
CREATION_TIME_STDERR="$OUT_DIR/creation_time_stderr.log"
|
||||
@@ -188,6 +199,7 @@ if [ "$ALLOW_DANGEROUS" -eq 1 ]; then
|
||||
sleep 2
|
||||
kill -KILL "-$FB_PID" 2>/dev/null || true
|
||||
fi
|
||||
pkill -f "./fork_bomb" 2>/dev/null || true
|
||||
else
|
||||
log "Skipping fork_bomb (set ALLOW_DANGEROUS=1 to run)"
|
||||
echo "timestamp,elapsed_s,load1,load5,load15,mem_total_kb,mem_available_kb,mem_used_kb,proc_count,thread_count_total,pid,cpu_pct,sys_cpu_pct,rss_kb,vsz_kb,stack_kb,heap_kb,proc_threads" > "$OUT_DIR/fork_bomb.csv"
|
||||
@@ -195,14 +207,26 @@ fi
|
||||
|
||||
run_with_sampling \
|
||||
"thread_recursive_scaling" \
|
||||
"cd '$ROOT_DIR/thread_recursive_scaling' && ./run_thread_recursive.sh 6 6 6" \
|
||||
"cd '$ROOT_DIR/thread_recursive_scaling' && exec ./thread_recursive -b 6 -d 6 -s 6" \
|
||||
"$OUT_DIR/thread_recursive_scaling.csv" \
|
||||
0
|
||||
|
||||
PI_BENCH_OUT_BASE="$OUT_DIR/pi_digits_benchmark"
|
||||
PI_THREAD_STDOUT="$OUT_DIR/pi_digits_thread_stdout.log"
|
||||
PI_THREAD_STDERR="$OUT_DIR/pi_digits_thread_stderr.log"
|
||||
PI_PROC_STDOUT="$OUT_DIR/pi_digits_process_stdout.log"
|
||||
PI_PROC_STDERR="$OUT_DIR/pi_digits_process_stderr.log"
|
||||
|
||||
run_with_sampling \
|
||||
"thread_stress" \
|
||||
"cd '$ROOT_DIR/thread_stress_test' && ./thread_stress" \
|
||||
"$OUT_DIR/thread_stress.csv" \
|
||||
"pi_digits_benchmark_thread" \
|
||||
"cd '$ROOT_DIR/pi_digits_benchmark' && THREAD_COUNT='$THREAD_COUNT' PROCESS_COUNT='$PROCESS_COUNT' PI_DIGITS='$PI_DIGITS' CHUNK_SIZE='$CHUNK_SIZE' ./pi_digits_benchmark --mode thread --out-dir '$PI_BENCH_OUT_BASE/thread' >'$PI_THREAD_STDOUT' 2>'$PI_THREAD_STDERR'" \
|
||||
"$OUT_DIR/pi_digits_thread.csv" \
|
||||
0
|
||||
|
||||
run_with_sampling \
|
||||
"pi_digits_benchmark_process" \
|
||||
"cd '$ROOT_DIR/pi_digits_benchmark' && THREAD_COUNT='$THREAD_COUNT' PROCESS_COUNT='$PROCESS_COUNT' PI_DIGITS='$PI_DIGITS' CHUNK_SIZE='$CHUNK_SIZE' ./pi_digits_benchmark --mode process --out-dir '$PI_BENCH_OUT_BASE/process' >'$PI_PROC_STDOUT' 2>'$PI_PROC_STDERR'" \
|
||||
"$OUT_DIR/pi_digits_process.csv" \
|
||||
0
|
||||
|
||||
log "All experiments finished"
|
||||
|
||||
@@ -29,7 +29,7 @@ Automated run + recording:
|
||||
bash run_thread_recursive.sh
|
||||
|
||||
# Custom parameters
|
||||
bash run_thread_recursive.sh 2 6 20
|
||||
bash run_thread_recursive.sh 6 6 6
|
||||
```
|
||||
|
||||
Recorded artifacts:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
set -euo pipefail
|
||||
LC_ALL=C
|
||||
|
||||
DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
cd "$DIR"
|
||||
|
||||
gcc -O2 -pthread -o thread_recursive thread_recursive.c -lm
|
||||
@@ -12,109 +12,145 @@ echo "Compiled thread_recursive"
|
||||
# Usage:
|
||||
# ./run_thread_recursive.sh [branch] [depth] [hold_seconds]
|
||||
# Example:
|
||||
# ./run_thread_recursive.sh 2 6 10
|
||||
# ./run_thread_recursive.sh 6 6 6
|
||||
|
||||
BRANCH=${1:-6}
|
||||
DEPTH=${2:-6}
|
||||
HOLD=${3:-6}
|
||||
|
||||
RESULTS_CSV="$DIR/results.csv"
|
||||
RUNS_DIR="$DIR/runs"
|
||||
mkdir -p "$RUNS_DIR"
|
||||
|
||||
RUN_ID=$(date +"%Y%m%d_%H%M%S")
|
||||
STDOUT_LOG="$RUNS_DIR/${RUN_ID}_stdout.log"
|
||||
STDERR_LOG="$RUNS_DIR/${RUN_ID}_stderr.log"
|
||||
SAMPLE_LOG="$RUNS_DIR/${RUN_ID}_samples.csv"
|
||||
RUN_OUT="$RUNS_DIR/$RUN_ID"
|
||||
mkdir -p "$RUN_OUT"
|
||||
|
||||
if [ ! -f "$RESULTS_CSV" ]; then
|
||||
echo "run_id,timestamp,host,kernel,branch,depth,hold_s,exit_code,wall_s,max_rss_kb,max_vsz_kb,max_threads,avg_cpu,max_cpu,total_created_threads,estimated_full_tree" > "$RESULTS_CSV"
|
||||
LOG_FILE="$RUN_OUT/activity.log"
|
||||
CSV="$RUN_OUT/thread_recursive_scaling.csv"
|
||||
|
||||
log() {
|
||||
echo "[$(date -Iseconds)] $*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
count_processes() {
|
||||
ls -d /proc/[0-9]* 2>/dev/null | wc -l
|
||||
}
|
||||
|
||||
count_threads_total() {
|
||||
awk '/^Threads:/ {sum+=$2} END {print sum+0}' /proc/[0-9]*/status 2>/dev/null || echo 0
|
||||
}
|
||||
|
||||
get_mem_kb() {
|
||||
awk '/^MemTotal:/ {t=$2} /^MemAvailable:/ {a=$2} END {print t" "a}' /proc/meminfo
|
||||
}
|
||||
|
||||
sample_process_csv() {
|
||||
local pid="$1"
|
||||
local csv="$2"
|
||||
local start_uptime="$3"
|
||||
local max_seconds="$4"
|
||||
|
||||
local have_pidstat=0
|
||||
if command -v pidstat >/dev/null 2>&1; then
|
||||
have_pidstat=1
|
||||
else
|
||||
log "pidstat not found; install sysstat for accurate CPU sampling"
|
||||
fi
|
||||
|
||||
echo "Running: ./thread_recursive -b $BRANCH -d $DEPTH -s $HOLD"
|
||||
local prev_cpu_total=0
|
||||
local prev_cpu_idle=0
|
||||
if [ -r /proc/stat ]; then
|
||||
read -r _ c_user c_nice c_system c_idle c_iowait c_irq c_softirq c_steal _ _ < /proc/stat
|
||||
prev_cpu_total=$((c_user + c_nice + c_system + c_idle + c_iowait + c_irq + c_softirq + c_steal))
|
||||
prev_cpu_idle=$((c_idle + c_iowait))
|
||||
fi
|
||||
|
||||
while kill -0 "$pid" 2>/dev/null; do
|
||||
local now_uptime elapsed_s
|
||||
now_uptime=$(awk '{print $1}' /proc/uptime)
|
||||
elapsed_s=$(awk -v s="$start_uptime" -v n="$now_uptime" 'BEGIN{printf "%.3f", (n-s)}')
|
||||
|
||||
if [ "$max_seconds" -gt 0 ]; then
|
||||
if awk -v e="$elapsed_s" -v m="$max_seconds" 'BEGIN{exit (e>m)?0:1}'; then
|
||||
log "Timeout reached (${max_seconds}s), stopping PID=$pid"
|
||||
kill -TERM "-$pid" 2>/dev/null || true
|
||||
sleep 2
|
||||
kill -KILL "-$pid" 2>/dev/null || true
|
||||
break
|
||||
fi
|
||||
fi
|
||||
|
||||
local proc_count thread_total
|
||||
proc_count=$(count_processes || echo 0)
|
||||
thread_total=$(count_threads_total || echo 0)
|
||||
|
||||
local load1 load5 load15
|
||||
read -r load1 load5 load15 _ < /proc/loadavg
|
||||
|
||||
local mem_total mem_avail mem_used
|
||||
read -r mem_total mem_avail < <(get_mem_kb || echo "0 0")
|
||||
mem_used=$((mem_total - mem_avail))
|
||||
|
||||
local cpu rss vsz nlwp
|
||||
rss=$(awk '/^VmRSS:/ {print $2}' "/proc/$pid/status" 2>/dev/null || echo 0)
|
||||
vsz=$(awk '/^VmSize:/ {print $2}' "/proc/$pid/status" 2>/dev/null || echo 0)
|
||||
nlwp=$(awk '/^Threads:/ {print $2}' "/proc/$pid/status" 2>/dev/null || echo 0)
|
||||
if [ "$have_pidstat" -eq 1 ]; then
|
||||
cpu=$(pidstat -p "$pid" 1 1 2>/dev/null | awk -v p="$pid" '$1 ~ /^[0-9]/ && $3==p {print $8; exit}')
|
||||
cpu=${cpu:-0.00}
|
||||
else
|
||||
cpu=0.00
|
||||
fi
|
||||
local stk=0 heap=0
|
||||
|
||||
local sys_cpu_pct=0.00
|
||||
if [ -r /proc/stat ]; then
|
||||
read -r _ c_user c_nice c_system c_idle c_iowait c_irq c_softirq c_steal _ _ < /proc/stat
|
||||
local cpu_total=$((c_user + c_nice + c_system + c_idle + c_iowait + c_irq + c_softirq + c_steal))
|
||||
local cpu_idle=$((c_idle + c_iowait))
|
||||
local dt_total=$((cpu_total - prev_cpu_total))
|
||||
local dt_idle=$((cpu_idle - prev_cpu_idle))
|
||||
sys_cpu_pct=$(awk -v t="$dt_total" -v i="$dt_idle" 'BEGIN{ if (t<=0) printf "0.00"; else printf "%.2f", (100.0*(t-i))/t }')
|
||||
prev_cpu_total=$cpu_total
|
||||
prev_cpu_idle=$cpu_idle
|
||||
fi
|
||||
|
||||
printf "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n" \
|
||||
"$(date -Iseconds)" "$elapsed_s" "$load1" "$load5" "$load15" \
|
||||
"$mem_total" "$mem_avail" "$mem_used" "$proc_count" "$thread_total" \
|
||||
"$pid" "$cpu" "$sys_cpu_pct" "${rss:-0}" "${vsz:-0}" "${stk:-0}" "${heap:-0}" "${nlwp:-0}" \
|
||||
>> "$csv"
|
||||
|
||||
sleep 0.2
|
||||
done
|
||||
}
|
||||
|
||||
CMD="cd '$DIR' && exec ./thread_recursive -b $BRANCH -d $DEPTH -s $HOLD"
|
||||
|
||||
log "Starting thread_recursive_scaling: $CMD"
|
||||
|
||||
echo "timestamp,elapsed_s,load1,load5,load15,mem_total_kb,mem_available_kb,mem_used_kb,proc_count,thread_count_total,pid,cpu_pct,sys_cpu_pct,rss_kb,vsz_kb,stack_kb,heap_kb,proc_threads" > "$CSV"
|
||||
|
||||
START_UPTIME=$(awk '{print $1}' /proc/uptime)
|
||||
./thread_recursive -b "$BRANCH" -d "$DEPTH" -s "$HOLD" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
|
||||
|
||||
if command -v setsid >/dev/null 2>&1; then
|
||||
setsid bash -c "$CMD" &
|
||||
PID=$!
|
||||
else
|
||||
bash -c "$CMD" &
|
||||
PID=$!
|
||||
|
||||
echo "Sampling metrics for PID=$PID (1s interval)"
|
||||
echo "sec_from_start,cpu_pct,rss_kb,vsz_kb,nlwp" > "$SAMPLE_LOG"
|
||||
|
||||
MAX_RSS=0
|
||||
MAX_VSZ=0
|
||||
MAX_THR=0
|
||||
MAX_CPU=0
|
||||
SUM_CPU=0
|
||||
SAMPLE_COUNT=0
|
||||
|
||||
CLK_TCK=$(getconf CLK_TCK)
|
||||
PREV_UPTIME=$(awk '{print $1}' /proc/uptime)
|
||||
PREV_TICKS=0
|
||||
if [ -r "/proc/$PID/stat" ]; then
|
||||
PREV_TICKS=$(awk '{print $14 + $15}' "/proc/$PID/stat" 2>/dev/null || echo 0)
|
||||
fi
|
||||
|
||||
while kill -0 "$PID" 2>/dev/null; do
|
||||
NOW_UPTIME=$(awk '{print $1}' /proc/uptime)
|
||||
ELAPSED_S=$(awk -v s="$START_UPTIME" -v n="$NOW_UPTIME" 'BEGIN{printf "%.3f", (n-s)}')
|
||||
|
||||
if [ -r "/proc/$PID/status" ] && [ -r "/proc/$PID/stat" ]; then
|
||||
RSS=$(awk '/^VmRSS:/ {print $2}' "/proc/$PID/status" 2>/dev/null || echo 0)
|
||||
VSZ=$(awk '/^VmSize:/ {print $2}' "/proc/$PID/status" 2>/dev/null || echo 0)
|
||||
NLWP=$(awk '/^Threads:/ {print $2}' "/proc/$PID/status" 2>/dev/null || echo 0)
|
||||
CUR_TICKS=$(awk '{print $14 + $15}' "/proc/$PID/stat" 2>/dev/null || echo 0)
|
||||
|
||||
RSS=${RSS:-0}
|
||||
VSZ=${VSZ:-0}
|
||||
NLWP=${NLWP:-0}
|
||||
|
||||
DT=$(awk -v a="$PREV_UPTIME" -v b="$NOW_UPTIME" 'BEGIN{print b-a}')
|
||||
DC=$(awk -v a="$PREV_TICKS" -v b="$CUR_TICKS" 'BEGIN{print b-a}')
|
||||
CPU=$(awk -v dt="$DT" -v dc="$DC" -v hz="$CLK_TCK" 'BEGIN{ if (dt<=0) printf "0.00"; else printf "%.2f", (100.0*dc)/(dt*hz) }')
|
||||
|
||||
echo "$ELAPSED_S,$CPU,$RSS,$VSZ,$NLWP" >> "$SAMPLE_LOG"
|
||||
|
||||
MAX_RSS=$(awk -v a="$MAX_RSS" -v b="$RSS" 'BEGIN{print (b>a)?b:a}')
|
||||
MAX_VSZ=$(awk -v a="$MAX_VSZ" -v b="$VSZ" 'BEGIN{print (b>a)?b:a}')
|
||||
MAX_THR=$(awk -v a="$MAX_THR" -v b="$NLWP" 'BEGIN{print (b>a)?b:a}')
|
||||
MAX_CPU=$(awk -v a="$MAX_CPU" -v b="$CPU" 'BEGIN{print (b>a)?b:a}')
|
||||
SUM_CPU=$(awk -v s="$SUM_CPU" -v c="$CPU" 'BEGIN{print s+c}')
|
||||
SAMPLE_COUNT=$((SAMPLE_COUNT + 1))
|
||||
|
||||
PREV_UPTIME=$NOW_UPTIME
|
||||
PREV_TICKS=$CUR_TICKS
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
sample_process_csv "$PID" "$CSV" "$START_UPTIME" 0
|
||||
|
||||
set +e
|
||||
wait "$PID"
|
||||
EXIT_CODE=$?
|
||||
set -e
|
||||
END_UPTIME=$(awk '{print $1}' /proc/uptime)
|
||||
WALL_S=$(awk -v s="$START_UPTIME" -v e="$END_UPTIME" 'BEGIN{printf "%.6f", (e-s)}')
|
||||
|
||||
AVG_CPU=0
|
||||
if [ "$SAMPLE_COUNT" -gt 0 ]; then
|
||||
AVG_CPU=$(awk -v s="$SUM_CPU" -v n="$SAMPLE_COUNT" 'BEGIN{printf "%.2f", s/n}')
|
||||
fi
|
||||
MAX_CPU_FMT=$(awk -v m="$MAX_CPU" 'BEGIN{printf "%.2f", m}')
|
||||
|
||||
TOTAL_CREATED=$(grep -E "total_created_threads:" "$STDOUT_LOG" | awk -F': ' '{print $2}' | tail -n1)
|
||||
ESTIMATED=$(grep -E "estimated_full_tree:" "$STDOUT_LOG" | awk -F': ' '{print $2}' | tail -n1)
|
||||
TOTAL_CREATED=${TOTAL_CREATED:-NA}
|
||||
ESTIMATED=${ESTIMATED:-NA}
|
||||
|
||||
TS=$(date +"%Y-%m-%dT%H:%M:%S%z")
|
||||
HOST=$(hostname)
|
||||
KERNEL=$(uname -r)
|
||||
|
||||
echo "$RUN_ID,$TS,$HOST,$KERNEL,$BRANCH,$DEPTH,$HOLD,$EXIT_CODE,$WALL_S,$MAX_RSS,$MAX_VSZ,$MAX_THR,$AVG_CPU,$MAX_CPU_FMT,$TOTAL_CREATED,$ESTIMATED" >> "$RESULTS_CSV"
|
||||
log "Finished thread_recursive_scaling (exit_code=$EXIT_CODE)"
|
||||
|
||||
echo
|
||||
echo "Run complete"
|
||||
echo " stdout: $STDOUT_LOG"
|
||||
echo " stderr: $STDERR_LOG"
|
||||
echo " samples: $SAMPLE_LOG"
|
||||
echo " results csv: $RESULTS_CSV"
|
||||
echo " exit_code=$EXIT_CODE wall_s=$WALL_S max_rss_kb=$MAX_RSS max_threads=$MAX_THR avg_cpu=$AVG_CPU max_cpu=$MAX_CPU_FMT"
|
||||
echo " samples: $CSV"
|
||||
echo " log: $LOG_FILE"
|
||||
echo " exit_code=$EXIT_CODE"
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
|
||||
#define THREAD_NUM 10000
|
||||
|
||||
void *task(void *args){
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(){
|
||||
pthread_t pid[THREAD_NUM];
|
||||
|
||||
int i = 0;
|
||||
for(i=0; i<THREAD_NUM; i++){
|
||||
if(pthread_create(&pid[i],NULL,task,NULL) > 0){
|
||||
for(int j=0; j<i; j++){
|
||||
pthread_join(pid[j],NULL);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user