#include #include #include #include #include #include #include #include #define MAX_ITERS 200000 static int cmp_double(const void *a, const void *b) { double da = *(const double *)a, db = *(const double *)b; return (da > db) - (da < db); } static double percentile(double *sorted, int n, double p) { int idx = (int)(n * p); if (idx >= n) idx = n - 1; return sorted[idx]; } struct thread_arg { int thread_id; char conninfo[1024]; int iterations; int warmup; /* results (heap-allocated per thread) */ double *connect_us; double *close_us; double *total_us; int errors; }; static void *thread_worker(void *varg) { struct thread_arg *arg = varg; arg->connect_us = malloc(arg->iterations * sizeof(double)); arg->close_us = malloc(arg->iterations * sizeof(double)); arg->total_us = malloc(arg->iterations * sizeof(double)); arg->errors = 0; /* Warmup */ for (int i = 0; i < arg->warmup; i++) { PGconn *c = PQconnectdb(arg->conninfo); if (PQstatus(c) != CONNECTION_OK) { fprintf(stderr, "[thread %d] Warmup connect failed: %s\n", arg->thread_id, PQerrorMessage(c)); PQfinish(c); continue; } PQfinish(c); } /* Measurement */ struct timespec t0, t1, t2; for (int i = 0; i < arg->iterations; i++) { clock_gettime(CLOCK_MONOTONIC, &t0); PGconn *c = PQconnectdb(arg->conninfo); clock_gettime(CLOCK_MONOTONIC, &t1); if (PQstatus(c) != CONNECTION_OK) { arg->errors++; if (arg->errors <= 3) fprintf(stderr, "[thread %d] Connect failed iter %d: %s\n", arg->thread_id, i, PQerrorMessage(c)); PQfinish(c); clock_gettime(CLOCK_MONOTONIC, &t2); arg->connect_us[i] = (t1.tv_sec - t0.tv_sec) * 1e6 + (t1.tv_nsec - t0.tv_nsec) / 1e3; arg->close_us[i] = (t2.tv_sec - t1.tv_sec) * 1e6 + (t2.tv_nsec - t1.tv_nsec) / 1e3; arg->total_us[i] = arg->connect_us[i] + arg->close_us[i]; continue; } PQfinish(c); clock_gettime(CLOCK_MONOTONIC, &t2); double ct = (t1.tv_sec - t0.tv_sec) * 1e6 + (t1.tv_nsec - t0.tv_nsec) / 1e3; double cl = (t2.tv_sec - t1.tv_sec) * 1e6 + (t2.tv_nsec - t1.tv_nsec) / 1e3; arg->connect_us[i] = ct; arg->close_us[i] = cl; arg->total_us[i] = ct + cl; } return NULL; } int main(int argc, char **argv) { // NOSONAR: benchmark tool, cognitive complexity acceptable const char *host = "127.0.0.1"; int port = 16432; const char *user = "benchuser"; // NOSONAR: benchmark default, not a real credential const char *password = "bench123"; // NOSONAR: benchmark default, not a real credential const char *dbname = "pgbouncer"; const char *ssl_ca = NULL; int iterations = 10000; int warmup = 200; const char *label = ""; int threads = 1; for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--host") == 0 && i+1 < argc) host = argv[++i]; else if (strcmp(argv[i], "--port") == 0 && i+1 < argc) port = atoi(argv[++i]); else if (strcmp(argv[i], "--user") == 0 && i+1 < argc) user = argv[++i]; else if (strcmp(argv[i], "--password") == 0 && i+1 < argc) password = argv[++i]; else if (strcmp(argv[i], "--dbname") == 0 && i+1 < argc) dbname = argv[++i]; else if (strcmp(argv[i], "--ssl-ca") == 0 && i+1 < argc) ssl_ca = argv[++i]; else if (strcmp(argv[i], "--iterations") == 0 && i+1 < argc) iterations = atoi(argv[++i]); else if (strcmp(argv[i], "--warmup") == 0 && i+1 < argc) warmup = atoi(argv[++i]); else if (strcmp(argv[i], "--label") == 0 && i+1 < argc) label = argv[++i]; else if (strcmp(argv[i], "--threads") == 0 && i+1 < argc) threads = atoi(argv[++i]); else { fprintf(stderr, "Unknown arg: %s\n", argv[i]); return 1; } } if (iterations > MAX_ITERS) { fprintf(stderr, "Too many iterations (max %d)\n", MAX_ITERS); return 1; } char conninfo[1024]; snprintf(conninfo, sizeof(conninfo), "host=%s port=%d user=%s password=%s dbname=%s sslmode=require%s%s", host, port, user, password, dbname, ssl_ca ? " sslrootcert=" : "", ssl_ca ? ssl_ca : ""); /* Launch threads */ pthread_t *tids = calloc(threads, sizeof(pthread_t)); struct thread_arg *args = calloc(threads, sizeof(struct thread_arg)); struct timespec wall0, wall1; clock_gettime(CLOCK_MONOTONIC, &wall0); for (int t = 0; t < threads; t++) { args[t].thread_id = t; strncpy(args[t].conninfo, conninfo, sizeof(args[t].conninfo) - 1); args[t].iterations = iterations; args[t].warmup = warmup; pthread_create(&tids[t], NULL, thread_worker, &args[t]); } for (int t = 0; t < threads; t++) pthread_join(tids[t], NULL); clock_gettime(CLOCK_MONOTONIC, &wall1); double wall_elapsed = (wall1.tv_sec - wall0.tv_sec) + (wall1.tv_nsec - wall0.tv_nsec) / 1e9; /* Merge results from all threads */ int total_n = threads * iterations; double *connect_us = malloc(total_n * sizeof(double)); double *close_us = malloc(total_n * sizeof(double)); double *total_us = malloc(total_n * sizeof(double)); int total_errors = 0; int idx = 0; for (int t = 0; t < threads; t++) { total_errors += args[t].errors; memcpy(connect_us + idx, args[t].connect_us, iterations * sizeof(double)); memcpy(close_us + idx, args[t].close_us, iterations * sizeof(double)); memcpy(total_us + idx, args[t].total_us, iterations * sizeof(double)); free(args[t].connect_us); free(args[t].close_us); free(args[t].total_us); idx += iterations; } free(args); free(tids); int n = total_n; /* Compute stats */ double sum_total = 0; for (int i = 0; i < n; i++) sum_total += total_us[i]; double cps = n / wall_elapsed; double *sorted_conn = malloc(n * sizeof(double)); double *sorted_total = malloc(n * sizeof(double)); memcpy(sorted_conn, connect_us, n * sizeof(double)); memcpy(sorted_total, total_us, n * sizeof(double)); qsort(sorted_conn, n, sizeof(double), cmp_double); qsort(sorted_total, n, sizeof(double), cmp_double); double avg_conn = 0, avg_close = 0, avg_total = 0; for (int i = 0; i < n; i++) { avg_conn += connect_us[i]; avg_close += close_us[i]; avg_total += total_us[i]; } avg_conn /= n; avg_close /= n; avg_total /= n; /* Output human-readable */ printf("\n============================================================\n"); printf(" %s\n", label); printf("============================================================\n"); printf(" Threads: %d\n", threads); printf(" Iterations: %d (per thread: %d)\n", n, iterations); printf(" Errors: %d\n", total_errors); printf(" Conns/sec: %.1f\n", cps); printf(" Wall elapsed: %.3f s\n", wall_elapsed); printf("\n"); printf(" %-12s %10s %10s %10s %10s %10s %10s\n", "Metric", "Min", "Avg", "P50", "P90", "P99", "Max"); printf(" %s\n", "------------------------------------------------------------------------"); printf(" %-12s %10.1f %10.1f %10.1f %10.1f %10.1f %10.1f\n", "Connect", sorted_conn[0], avg_conn, percentile(sorted_conn, n, 0.50), percentile(sorted_conn, n, 0.90), percentile(sorted_conn, n, 0.99), sorted_conn[n-1]); printf(" %-12s %10.1f %10.1f %10.1f\n", "Close", close_us[0], avg_close, close_us[n/2]); printf(" %-12s %10.1f %10.1f %10.1f %10.1f %10.1f %10.1f\n", "Total", sorted_total[0], avg_total, percentile(sorted_total, n, 0.50), percentile(sorted_total, n, 0.90), percentile(sorted_total, n, 0.99), sorted_total[n-1]); printf(" (all times in microseconds)\n\n"); /* Output JSON to stderr */ fprintf(stderr, "{"); fprintf(stderr, "\"label\":\"%s\",", label); fprintf(stderr, "\"threads\":%d,", threads); fprintf(stderr, "\"iterations\":%d,", n); fprintf(stderr, "\"errors\":%d,", total_errors); fprintf(stderr, "\"connections_per_sec\":%.1f,", cps); fprintf(stderr, "\"connect_us\":{\"min\":%.1f,\"avg\":%.1f,\"p50\":%.1f,\"p90\":%.1f,\"p99\":%.1f,\"max\":%.1f},", sorted_conn[0], avg_conn, percentile(sorted_conn, n, 0.50), percentile(sorted_conn, n, 0.90), percentile(sorted_conn, n, 0.99), sorted_conn[n-1]); fprintf(stderr, "\"close_us\":{\"min\":%.1f,\"avg\":%.1f,\"p50\":%.1f},", close_us[0], avg_close, close_us[n/2]); fprintf(stderr, "\"total_us\":{\"min\":%.1f,\"avg\":%.1f,\"p50\":%.1f,\"p90\":%.1f,\"p99\":%.1f,\"max\":%.1f},", sorted_total[0], avg_total, percentile(sorted_total, n, 0.50), percentile(sorted_total, n, 0.90), percentile(sorted_total, n, 0.99), sorted_total[n-1]); fprintf(stderr, "\"elapsed_s\":%.3f", wall_elapsed); fprintf(stderr, "}\n"); free(connect_us); free(close_us); free(total_us); free(sorted_conn); free(sorted_total); return 0; }