Compare commits

...

19 Commits

Author SHA1 Message Date
andreastaliad 32d3d5f97c added standalone run scripts for all tests and experiments 2026-05-28 23:40:52 +03:00
andreastaliad 1813b15993 Revert "added standalone run scripts for all tests and experiments"
This reverts commit a54a29e486.
2026-05-28 23:30:28 +03:00
andreastaliad a54a29e486 added standalone run scripts for all tests and experiments 2026-05-28 23:17:25 +03:00
andreastaliad 2f79582210 updated gitignore 2026-05-26 18:15:18 +03:00
andreastaliad f688666a92 removed binary 2026-05-26 17:46:11 +03:00
andreastaliad 3defb64946 made benchmarking changes for concurency; made finish line before sampling 2026-05-26 17:42:42 +03:00
andreastaliad c9a4b7f85d added chunked Pi calculations test for fork vs thread 2026-05-26 17:18:48 +03:00
andreastaliad 34d7c37f8a few quality changes in README and parameters of script 2026-05-26 16:35:24 +03:00
andreastaliad 2018c11856 removed redundant experiment 2026-05-25 16:13:19 +03:00
andreastaliad e08467972d added fork creation under the same pid 2026-05-25 16:02:59 +03:00
andreastaliad 88b74819b1 Updated README 2026-05-25 16:02:32 +03:00
andreastaliad 2a20323b2d added ./ to pkill 2026-05-25 15:52:54 +03:00
andreastaliad aabd7b61ee added alternative method of killing using pkill 2026-05-25 15:51:12 +03:00
andreastaliad 925841e4d2 Restore repo to 90f149e state 2026-05-25 15:48:41 +03:00
andreastaliad 5fc6a119ac Revert "made parent kill childeren"
This reverts commit 8f176fdac8.
2026-05-25 15:44:19 +03:00
andreastaliad 8f176fdac8 made parent kill childeren 2026-05-25 15:38:39 +03:00
andreastaliad c5003910ef added childeren kill after parent exit 2026-05-25 15:34:46 +03:00
andreastaliad 2d241c1e7c Restore repo to 90f149e state 2026-05-25 15:32:27 +03:00
andreastaliad f7528f9b03 Revert "added auto-reaping to childeren"
This reverts commit 3f16cb502c.
2026-05-25 15:25:50 +03:00
12 changed files with 1235 additions and 160 deletions
+2
View File
@@ -1,2 +1,4 @@
creation_time_experiment/creation_time_results.txt
thread_recursive_scaling/runs/
pi_digits_benchmark/runs/
pi_digits_benchmark/results.csv
+2 -1
View File
@@ -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
View File
@@ -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"
-39
View File
@@ -6,19 +6,10 @@
#include <dirent.h>
#include <ctype.h>
#include <time.h>
#include <signal.h>
#include <termios.h>
#define DEFAULT_NUM_LEN 512
static volatile sig_atomic_t stop_requested = 0;
static void handle_termination(int sig) {
(void)sig;
stop_requested = 1;
}
char *itoa(long num, char *strnum, size_t size){
int isNegative = 0;
@@ -269,27 +260,6 @@ int main(void)
perror("fopen csv");
return 1;
}
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handle_termination;
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
struct sigaction sa_chld;
memset(&sa_chld, 0, sizeof(sa_chld));
sa_chld.sa_handler = SIG_IGN;
sa_chld.sa_flags = SA_NOCLDWAIT;
sigaction(SIGCHLD, &sa_chld, NULL);
pid_t fork_pgid = getpgrp();
int owns_pgrp = 0;
if (!isatty(STDIN_FILENO) || tcgetpgrp(STDIN_FILENO) != fork_pgid) {
if (setpgid(0, 0) == 0) {
fork_pgid = getpgrp();
owns_pgrp = 1;
}
}
fprintf(csv, "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\n");
long count = 0;
@@ -309,9 +279,6 @@ int main(void)
}
while (1) {
if (stop_requested) {
break;
}
pid_t pid = fork();
if (pid < 0) {
@@ -384,12 +351,6 @@ int main(void)
}
}
if (owns_pgrp) {
signal(SIGTERM, SIG_IGN);
signal(SIGINT, SIG_IGN);
killpg(fork_pgid, SIGTERM);
}
fclose(csv);
return 0;
+48
View File
@@ -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"
+33
View File
@@ -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
+778
View File
@@ -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
View File
@@ -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
View File
@@ -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"
+1 -1
View File
@@ -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:
+119 -83
View File
@@ -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"
fi
LOG_FILE="$RUN_OUT/activity.log"
CSV="$RUN_OUT/thread_recursive_scaling.csv"
echo "Running: ./thread_recursive -b $BRANCH -d $DEPTH -s $HOLD"
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' && 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" &
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)
if command -v setsid >/dev/null 2>&1; then
setsid bash -c "$CMD" &
PID=$!
else
bash -c "$CMD" &
PID=$!
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"
-27
View File
@@ -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;
}