mirror of https://github.com/sysown/proxysql
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
244 lines
9.3 KiB
244 lines
9.3 KiB
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdbool.h>
|
|
#include <time.h>
|
|
#include <math.h>
|
|
#include <pthread.h>
|
|
#include <libpq-fe.h>
|
|
|
|
#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;
|
|
}
|