/*
* Proxy Pool Governor — simulation/replay harness.
*
* Copyright (C) 2026 SWGY, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include "clips.h"
#include "scenario.h"
#include <err.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#define RULES_FILE "gov.clp"
#define MAX_POOLS 256
#define MAX_SERVICES 256
/*
* Runtime mode
*/
typedef enum {
MODE_SIMULATE,
MODE_REPLAY
} RunMode;
/*
* Runtime configuration
*/
typedef struct {
RunMode mode;
const char *scenario_path;
int sim_cycles;
int verbose;
} Config;
/*
* Pool and service names (from scenario or defaults)
*/
static size_t num_pools;
static size_t num_services;
static char pool_names[MAX_POOLS][SCENARIO_MAX_NAME];
static char service_names[MAX_SERVICES][SCENARIO_MAX_NAME];
/*
* Default pools and services for simulation mode
*/
static const char *default_pools[] = {
"residential-us",
"residential-eu",
"datacenter-1",
"datacenter-2",
"mobile-us"
};
static const int default_num_pools = 5;
static const char *default_services[] = {
"search",
"checkout",
"bulk-export"
};
static const int default_num_services = 3;
/*
* Per-service configuration
*/
typedef struct {
const char *service;
double sigma_threshold;
double min_success_rate;
double max_response_time;
double weight_reduction;
int min_weight;
double restore_cooldown;
double service_degrade_threshold;
} ServiceConfig;
static ServiceConfig service_configs[MAX_SERVICES];
static size_t num_service_configs;
/*
* Base weights [service][pool] - dynamically sized
*/
static int base_weights[MAX_SERVICES][MAX_POOLS];
static int effective_weights[MAX_SERVICES][MAX_POOLS];
/*
* Forward declarations for simulation mode
*/
typedef struct {
double base_success_rate;
double base_response_time;
double current_success_rate;
double current_response_time;
int degraded;
int cycles_until_recovery;
int cycles_until_incident;
} PoolHealth;
typedef struct {
double success_sum;
double success_sq_sum;
double rt_sum;
double rt_sq_sum;
int count;
} StatsAccumulator;
static PoolHealth pool_health[MAX_SERVICES][MAX_POOLS];
static StatsAccumulator stats_accum[MAX_SERVICES][MAX_POOLS];
/*
* Random helpers
*/
static double
rand_uniform(void)
{
return (double)rand() / RAND_MAX;
}
static double
rand_normal(double mean, double stddev)
{
double u1 = rand_uniform();
double u2 = rand_uniform();
if (u1 < 1e-10)
u1 = 1e-10;
double z = sqrt(-2.0 * log(u1)) * cos(2.0 * M_PI * u2);
return mean + stddev * z;
}
static double
clamp(double val, double lo, double hi)
{
if (val < lo)
return lo;
if (val > hi)
return hi;
return val;
}
/*
* Initialize default configuration
*/
static void
init_defaults(void)
{
/* Copy default pool names */
num_pools = default_num_pools;
for (int i = 0; i < default_num_pools; i++)
strncpy(pool_names[i], default_pools[i], SCENARIO_MAX_NAME - 1);
/* Copy default service names */
num_services = default_num_services;
for (int i = 0; i < default_num_services; i++)
strncpy(service_names[i], default_services[i], SCENARIO_MAX_NAME - 1);
/* Default service configs */
service_configs[0] = (ServiceConfig){"search", 2.0, 0.85, 5.0, 0.5, 1, 3600.0, 0.7};
service_configs[1] = (ServiceConfig){"checkout", 1.5, 0.95, 3.0, 0.25, 1, 7200.0, 0.7};
service_configs[2] = (ServiceConfig){"bulk-export", 3.0, 0.70, 30.0, 0.75, 1, 1800.0, 0.7};
num_service_configs = 3;
/* Default base weights */
int defaults[3][5] = {
{20, 10, 5, 5, 1}, /* search */
{25, 20, 3, 3, 1}, /* checkout */
{5, 5, 15, 15, 10} /* bulk-export */
};
for (int s = 0; s < 3; s++) {
for (int p = 0; p < 5; p++) {
base_weights[s][p] = defaults[s][p];
effective_weights[s][p] = defaults[s][p];
}
}
}
/*
* Initialize from scenario file
*/
static void
init_from_scenario(const Scenario *scen)
{
num_pools = scen->num_pools;
num_services = scen->num_services;
for (size_t i = 0; i < num_pools; i++)
strncpy(pool_names[i], scen->pool_names[i], SCENARIO_MAX_NAME - 1);
for (size_t i = 0; i < num_services; i++)
strncpy(service_names[i], scen->service_names[i], SCENARIO_MAX_NAME - 1);
/* Use default service configs, matching by name where possible */
ServiceConfig defaults[] = {
{"search", 2.0, 0.85, 5.0, 0.5, 1, 3600.0, 0.7},
{"checkout", 1.5, 0.95, 3.0, 0.25, 1, 7200.0, 0.7},
{"bulk-export", 3.0, 0.70, 30.0, 0.75, 1, 1800.0, 0.7}
};
ServiceConfig fallback = {"unknown", 2.0, 0.85, 5.0, 0.5, 1, 3600.0, 0.7};
for (size_t s = 0; s < num_services; s++) {
int found = 0;
for (int d = 0; d < 3; d++) {
if (strcmp(service_names[s], defaults[d].service) == 0) {
service_configs[s] = defaults[d];
service_configs[s].service = service_names[s];
found = 1;
break;
}
}
if (!found) {
service_configs[s] = fallback;
service_configs[s].service = service_names[s];
}
}
num_service_configs = num_services;
/* Default weights: 10 for all pool-service pairs */
for (size_t s = 0; s < num_services; s++) {
for (size_t p = 0; p < num_pools; p++) {
base_weights[s][p] = 10;
effective_weights[s][p] = 10;
}
}
}
/*
* Initialize simulation state
*/
static void
init_simulation(void)
{
srand(time(NULL));
for (size_t s = 0; s < num_services; s++) {
for (size_t p = 0; p < num_pools; p++) {
pool_health[s][p].base_success_rate = 0.90 + rand_uniform() * 0.05;
pool_health[s][p].base_response_time = 0.8 + rand_uniform() * 0.4;
pool_health[s][p].current_success_rate = pool_health[s][p].base_success_rate;
pool_health[s][p].current_response_time = pool_health[s][p].base_response_time;
pool_health[s][p].degraded = 0;
pool_health[s][p].cycles_until_recovery = 0;
pool_health[s][p].cycles_until_incident = 10 + rand() % 30;
memset(&stats_accum[s][p], 0, sizeof(stats_accum[s][p]));
}
}
}
/*
* Simulate one cycle of pool health changes
*/
static void
simulate_pool_health(void)
{
for (size_t s = 0; s < num_services; s++) {
for (size_t p = 0; p < num_pools; p++) {
PoolHealth *ph = &pool_health[s][p];
if (ph->degraded) {
ph->cycles_until_recovery--;
if (ph->cycles_until_recovery <= 0) {
ph->degraded = 0;
ph->cycles_until_incident = 15 + rand() % 30;
printf(" [SIM] %s/%s recovered\n",
pool_names[p], service_names[s]);
}
} else {
ph->cycles_until_incident--;
if (ph->cycles_until_incident <= 0) {
ph->degraded = 1;
ph->cycles_until_recovery = 5 + rand() % 15;
printf(" [SIM] %s/%s incident started\n",
pool_names[p], service_names[s]);
}
}
if (ph->degraded) {
ph->current_success_rate = clamp(
rand_normal(0.65, 0.10), 0.3, 0.85);
ph->current_response_time = clamp(
rand_normal(ph->base_response_time * 3.0, 0.5),
1.0, 10.0);
} else {
ph->current_success_rate = clamp(
rand_normal(ph->base_success_rate, 0.02),
0.80, 0.99);
ph->current_response_time = clamp(
rand_normal(ph->base_response_time, 0.1),
0.3, 3.0);
}
StatsAccumulator *sa = &stats_accum[s][p];
sa->success_sum += ph->current_success_rate;
sa->success_sq_sum += ph->current_success_rate * ph->current_success_rate;
sa->rt_sum += ph->current_response_time;
sa->rt_sq_sum += ph->current_response_time * ph->current_response_time;
sa->count++;
}
}
}
/*
* Get moving average and stddev for a pool-service pair (simulation mode)
*/
static void
get_simulated_stats(int service, int pool,
double *avg_success, double *stddev_success,
double *avg_rt, double *stddev_rt)
{
StatsAccumulator *sa = &stats_accum[service][pool];
if (sa->count < 2) {
*avg_success = 0.90;
*stddev_success = 0.02;
*avg_rt = 1.0;
*stddev_rt = 0.3;
return;
}
double n = (double)sa->count;
*avg_success = sa->success_sum / n;
double var_success = (sa->success_sq_sum / n) - (*avg_success * *avg_success);
*stddev_success = (var_success > 0) ? sqrt(var_success) : 0.01;
*avg_rt = sa->rt_sum / n;
double var_rt = (sa->rt_sq_sum / n) - (*avg_rt * *avg_rt);
*stddev_rt = (var_rt > 0) ? sqrt(var_rt) : 0.1;
}
/*
* CLIPS injection helpers
*/
static void
inject_service_config(Environment *env, const ServiceConfig *cfg)
{
char buf[512];
snprintf(buf, sizeof(buf),
"(service-config (service %s) "
"(sigma-threshold %f) (min-success-rate %f) (max-response-time %f) "
"(weight-reduction %f) (min-weight %d) (restore-cooldown %f) "
"(service-degrade-threshold %f))",
cfg->service,
cfg->sigma_threshold, cfg->min_success_rate, cfg->max_response_time,
cfg->weight_reduction, cfg->min_weight, cfg->restore_cooldown,
cfg->service_degrade_threshold);
AssertString(env, buf);
}
static void
inject_weight(Environment *env, const char *pool, const char *service,
int base_weight, int eff_weight)
{
char buf[256];
snprintf(buf, sizeof(buf),
"(current-weight (pool %s) (service %s) "
"(base-weight %d) (effective-weight %d))",
pool, service, base_weight, eff_weight);
AssertString(env, buf);
}
static void
inject_stats(Environment *env, const char *pool, const char *service,
double timestamp, double rate_success, double rate_lost_race,
double rate_302, double rate_timeout, double rate_ssl, double rate_other,
double response_time, double avg_success, double avg_response_time,
double stddev_success, double stddev_response_time)
{
char buf[1024];
snprintf(buf, sizeof(buf),
"(pool-service-stats "
"(pool %s) (service %s) (timestamp %f) "
"(rate-success %f) (rate-lost-race %f) (rate-302-unusual %f) "
"(rate-timeout %f) (rate-ssl-error %f) (rate-other %f) "
"(response-time %f) "
"(avg-success %f) (avg-response-time %f) "
"(stddev-success %f) (stddev-response-time %f))",
pool, service, timestamp,
rate_success, rate_lost_race, rate_302,
rate_timeout, rate_ssl, rate_other,
response_time,
avg_success, avg_response_time,
stddev_success, stddev_response_time);
AssertString(env, buf);
}
static void
inject_scenario_record(Environment *env, const Scenario *scen,
const ScenarioRecord *rec)
{
const char *pool = scenario_pool_name(scen, rec->pool);
const char *service = scenario_service_name(scen, rec->service);
if (pool == NULL || service == NULL)
return;
inject_stats(env, pool, service, rec->timestamp,
rec->rate_success, rec->rate_lost_race, rec->rate_302,
rec->rate_timeout, rec->rate_ssl, rec->rate_other,
rec->response_time, rec->avg_success, rec->avg_response_time,
rec->stddev_success, rec->stddev_response_time);
}
/*
* Retract all facts of a given template
*/
static void
retract_all(Environment *env, const char *template_name)
{
Fact *fact = GetNextFact(env, NULL);
while (fact != NULL) {
Fact *next = GetNextFact(env, fact);
if (strcmp(FactDeftemplate(fact)->header.name->contents,
template_name) == 0) {
Retract(fact);
}
fact = next;
}
}
/*
* Clear transient facts between cycles
*/
static void
cleanup_cycle(Environment *env)
{
retract_all(env, "current-weight");
retract_all(env, "pool-service-stats");
retract_all(env, "degradation");
retract_all(env, "service-status");
retract_all(env, "service-config");
}
/*
* Callbacks
*/
static void
on_weight_adjustment(const char *pool, const char *service, int old_weight,
int new_weight, const char *reason, double severity)
{
printf(" ADJUST: %s/%s weight=%d->%d reason=%s severity=%.2f\n",
pool, service, old_weight, new_weight, reason, severity);
for (size_t s = 0; s < num_services; s++) {
if (strcmp(service_names[s], service) != 0)
continue;
for (size_t p = 0; p < num_pools; p++) {
if (strcmp(pool_names[p], pool) != 0)
continue;
effective_weights[s][p] = new_weight;
return;
}
}
}
static void
on_alert(const char *type, const char *service, const char *pool,
const char *message)
{
printf(" ALERT: type=%s service=%s pool=%s msg=%s\n",
type, service, pool ? pool : "(nil)", message);
}
/*
* Process emitted facts
*/
static void
process_weight_adjustments(Environment *env)
{
Fact *fact = GetNextFact(env, NULL);
while (fact != NULL) {
Fact *next = GetNextFact(env, fact);
if (strcmp(FactDeftemplate(fact)->header.name->contents,
"weight-adjustment") == 0) {
CLIPSValue pool, service, old_w, new_w, reason, severity;
GetFactSlot(fact, "pool", &pool);
GetFactSlot(fact, "service", &service);
GetFactSlot(fact, "old-weight", &old_w);
GetFactSlot(fact, "new-weight", &new_w);
GetFactSlot(fact, "reason", &reason);
GetFactSlot(fact, "severity", &severity);
on_weight_adjustment(
pool.lexemeValue->contents,
service.lexemeValue->contents,
(int)old_w.integerValue->contents,
(int)new_w.integerValue->contents,
reason.lexemeValue->contents,
severity.floatValue->contents);
Retract(fact);
}
fact = next;
}
}
static void
process_alerts(Environment *env)
{
Fact *fact = GetNextFact(env, NULL);
while (fact != NULL) {
Fact *next = GetNextFact(env, fact);
if (strcmp(FactDeftemplate(fact)->header.name->contents, "alert") == 0) {
CLIPSValue type, service, pool, message;
GetFactSlot(fact, "type", &type);
GetFactSlot(fact, "service", &service);
GetFactSlot(fact, "pool", &pool);
GetFactSlot(fact, "message", &message);
on_alert(
type.lexemeValue->contents,
service.lexemeValue->contents,
pool.lexemeValue->contents,
message.lexemeValue->contents);
Retract(fact);
}
fact = next;
}
}
/*
* Inject all service configs and weights
*/
static void
inject_configs_and_weights(Environment *env)
{
for (size_t s = 0; s < num_service_configs; s++)
inject_service_config(env, &service_configs[s]);
for (size_t s = 0; s < num_services; s++) {
for (size_t p = 0; p < num_pools; p++) {
inject_weight(env, pool_names[p], service_names[s],
base_weights[s][p], effective_weights[s][p]);
}
}
}
/*
* Print weight matrix
*/
static void
print_weights(void)
{
printf("\n Weights (base/eff):\n");
printf(" %-12s", "");
for (size_t p = 0; p < num_pools; p++)
printf(" %14s", pool_names[p]);
printf("\n");
for (size_t s = 0; s < num_services; s++) {
printf(" %-12s", service_names[s]);
for (size_t p = 0; p < num_pools; p++) {
char buf[16];
snprintf(buf, sizeof(buf), "%d/%d",
base_weights[s][p], effective_weights[s][p]);
printf(" %14s", buf);
}
printf("\n");
}
printf("\n");
}
/*
* Run in simulation mode
*/
static void
run_simulation(Environment *env, int cycles)
{
printf("Running simulation mode: %d cycles\n\n", cycles);
init_defaults();
init_simulation();
print_weights();
double timestamp = 1737300000.0;
for (int i = 0; i < cycles; i++) {
printf("=== Cycle %d (t=%.0f) ===\n", i, timestamp);
simulate_pool_health();
cleanup_cycle(env);
inject_configs_and_weights(env);
/* Inject simulated stats */
for (size_t s = 0; s < num_services; s++) {
for (size_t p = 0; p < num_pools; p++) {
double avg_success, stddev_success, avg_rt, stddev_rt;
get_simulated_stats(s, p, &avg_success, &stddev_success,
&avg_rt, &stddev_rt);
PoolHealth *ph = &pool_health[s][p];
double failure = 1.0 - ph->current_success_rate;
double rate_timeout = ph->degraded ? failure * 0.4 : failure * 0.2;
double rate_ssl = ph->degraded ? failure * 0.2 : failure * 0.1;
double rate_302 = failure * 0.2;
double rate_lost = failure * 0.1;
double rate_other = failure - rate_timeout - rate_ssl - rate_302 - rate_lost;
if (rate_other < 0)
rate_other = 0;
inject_stats(env, pool_names[p], service_names[s], timestamp,
ph->current_success_rate, rate_lost, rate_302,
rate_timeout, rate_ssl, rate_other,
ph->current_response_time,
avg_success, avg_rt, stddev_success, stddev_rt);
}
}
Run(env, -1);
process_weight_adjustments(env);
process_alerts(env);
timestamp += 60.0;
if ((i + 1) % 10 == 0)
print_weights();
}
printf("\nFinal state:\n");
print_weights();
}
/*
* Run in replay mode
*/
static void
run_replay(Environment *env, const char *path)
{
Scenario *scen;
ScenarioRecord rec;
double current_timestamp = -1;
int cycle = 0;
int ret;
scen = scenario_open_read(path);
if (scen == NULL)
err(1, "scenario_open_read: %s", path);
printf("Replaying scenario: %s\n", path);
printf(" Pools: %zu, Services: %zu, Records: %zu\n\n",
scen->num_pools, scen->num_services, scen->num_records);
init_from_scenario(scen);
print_weights();
while ((ret = scenario_read(scen, &rec)) == 1) {
/* New timestamp = new cycle */
if (rec.timestamp != current_timestamp) {
if (current_timestamp >= 0) {
/* Process previous cycle */
Run(env, -1);
process_weight_adjustments(env);
process_alerts(env);
if ((cycle + 1) % 10 == 0)
print_weights();
cycle++;
cleanup_cycle(env);
inject_configs_and_weights(env);
} else {
/* First cycle */
inject_configs_and_weights(env);
}
current_timestamp = rec.timestamp;
printf("=== Cycle %d (t=%.0f) ===\n", cycle, current_timestamp);
}
inject_scenario_record(env, scen, &rec);
}
if (ret < 0)
errx(1, "scenario_read error");
/* Process final cycle */
if (current_timestamp >= 0) {
Run(env, -1);
process_weight_adjustments(env);
process_alerts(env);
}
printf("\nFinal state:\n");
print_weights();
scenario_close(scen);
}
static void
usage(void)
{
fprintf(stderr, "usage: gov-sim [-s cycles] [-r scenario.pps]\n");
fprintf(stderr, " -s cycles Run simulation mode for N cycles (default: 60)\n");
fprintf(stderr, " -r path Replay scenario from file\n");
exit(1);
}
int
main(int argc, char *argv[])
{
Environment *env;
Config cfg = {
.mode = MODE_SIMULATE,
.scenario_path = NULL,
.sim_cycles = 60,
.verbose = 0
};
int ch;
while ((ch = getopt(argc, argv, "s:r:v")) != -1) {
switch (ch) {
case 's':
cfg.mode = MODE_SIMULATE;
cfg.sim_cycles = atoi(optarg);
break;
case 'r':
cfg.mode = MODE_REPLAY;
cfg.scenario_path = optarg;
break;
case 'v':
cfg.verbose = 1;
break;
default:
usage();
}
}
/* Unveil paths */
if (unveil(RULES_FILE, "r") == -1)
err(1, "unveil %s", RULES_FILE);
if (cfg.mode == MODE_REPLAY) {
if (unveil(cfg.scenario_path, "r") == -1)
err(1, "unveil %s", cfg.scenario_path);
}
if (unveil(NULL, NULL) == -1)
err(1, "unveil lock");
/* Create CLIPS environment */
env = CreateEnvironment();
if (env == NULL)
errx(1, "CreateEnvironment failed");
if (Load(env, RULES_FILE) != LE_NO_ERROR)
errx(1, "failed to load %s", RULES_FILE);
if (pledge("stdio rpath", NULL) == -1)
err(1, "pledge");
/* Run appropriate mode */
printf("Proxy Pool Governor\n");
printf("===================\n\n");
if (cfg.mode == MODE_REPLAY)
run_replay(env, cfg.scenario_path);
else
run_simulation(env, cfg.sim_cycles);
DestroyEnvironment(env);
return 0;
}