#include "cst_metrics.h" #include "cJSON.h" #include "os_compat.h" #include #include #include #include struct cst_metrics { FILE *fp; char path[OS_PATH_MAX]; }; cst_metrics_t *cst_metrics_open(const char *workspace, const char *session_path) { const char *off = getenv("CST_METRICS"); if (off && strcmp(off, "0") == 0) return NULL; cst_metrics_t *m = calloc(1, sizeof(*m)); if (!m) return NULL; const char *override = getenv("CST_METRICS_PATH"); if (override && override[0]) { snprintf(m->path, sizeof(m->path), "%s", override); } else { if (!workspace) workspace = "."; char dir[OS_PATH_MAX], agent[OS_PATH_MAX]; os_path_join(agent, sizeof(agent), workspace, ".agent"); os_mkdir(agent, 0755); os_path_join(dir, sizeof(dir), agent, "metrics"); os_mkdir(dir, 0755); const char *base = session_path ? session_path : "session.jsonl"; const char *s1 = strrchr(base, '/'); const char *s2 = strrchr(base, '\\'); if (s2 && (!s1 || s2 > s1)) s1 = s2; if (s1) base = s1 + 1; char leaf[256]; snprintf(leaf, sizeof(leaf), "%s", base); size_t n = strlen(leaf); if (n > 6 && strcmp(leaf + n - 6, ".jsonl") == 0) leaf[n - 6] = 0; char file[300]; snprintf(file, sizeof(file), "%s.jsonl", leaf); os_path_join(m->path, sizeof(m->path), dir, file); } m->fp = fopen(m->path, "a"); if (!m->fp) { free(m); return NULL; } return m; } void cst_metrics_close(cst_metrics_t *m) { if (!m) return; if (m->fp) fclose(m->fp); free(m); } void cst_metrics_emit(cst_metrics_t *m, void *obj) { cJSON *o = obj; if (!o) return; if (m && m->fp) { if (!cJSON_GetObjectItemCaseSensitive(o, "ts")) cJSON_AddNumberToObject(o, "ts", (double)time(NULL)); char *txt = cJSON_PrintUnformatted(o); if (txt) { fputs(txt, m->fp); fputc('\n', m->fp); fflush(m->fp); free(txt); } } cJSON_Delete(o); } const char *cst_metrics_path(const cst_metrics_t *m) { return m ? m->path : NULL; }