diff --git a/CLAUDE.md b/CLAUDE.md index 786f0cb..1e19544 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -39,7 +39,7 @@ These decisions are final: These items need further discussion before implementation: -- **Statistics subsystem** — Keep as-is, simplify to minimal counters, or remove? (httpstat.c, httprepo.c, 4-tier time-series arrays ~16 KB) +- ~~**Statistics subsystem**~~ — **Done.** Replaced with SMF Type 243 records + simple counters (see #54). - **DSL CGI (httpdsl*.c)** — Clarify scope and naming (Dataset List, not SSI). Keep, refactor, or remove? - **Demo CGIs (hello.c, abend0c1.c, test.c)** — Remove from build or just disable by default? - **Lua CGI / REXX CGI** — Code stays, just not registered by default. lua370 remains a dependency. @@ -137,7 +137,7 @@ httpget.c → Static file serving with MIME detection httpresp.c → HTTP response line + headers (currently HTTP/1.0) httpsend.c → Binary/text data delivery httpdone.c → Close files -httprepo.c → Log request, update statistics +httprepo.c → Write SMF record, update counters httprese.c → Reset for next request or close (currently always closes) httpclos.c → Release HTTPC, close socket ``` @@ -231,7 +231,7 @@ Missing `DD:HTTPDPRM` → server starts with defaults (port 8080, no CGIs). - **hello.c, abend0c1.c, test.c**: Demo/test CGIs **Subsystems:** -- **httpstat.c / httprepo.c**: Statistics (977 LOC) — future TBD +- **httprepo.c**: SMF Type 243 recording + simple counters (~90 LOC) - **Lua runtime**: lauxlib.c, liolib.c, loadlib.c, httpluax.c (3,297 LOC) — stays for Lua CGI - **FTP daemon**: ftp*.c (3,290 LOC) — confirmed for removal - **MQTT telemetry**: HTTPT struct, telemetry_thread — confirmed for removal diff --git a/doc/architecture.md b/doc/architecture.md index 853e0bb..4e5678e 100644 --- a/doc/architecture.md +++ b/doc/architecture.md @@ -46,9 +46,9 @@ v v v v | | +------------------+ +--------+ | | | | +------------------+ +--------------+ +--------------------+ | -| | httpresp.c | | httpstat.c | | httpcred.c | | -| | Response Headers| | Statistics | | httpauth.c | | -| | httpsend.c | | min/h/d/m | | Credentials/RACF | | +| | httpresp.c | | httprepo.c | | httpcred.c | | +| | Response Headers| | SMF Type243 | | httpauth.c | | +| | httpsend.c | | + Counters | | Credentials/RACF | | | +------------------+ +--------------+ +--------------------+ | +------------------------------------------------------------------+ | | | @@ -233,7 +233,7 @@ CSTATE_IN -----> CSTATE_PARSE -----> CSTATE_GET/HEAD/PUT/POST/DELETE | 4. Method | httpget.c etc. | `http_get()` etc. | Open file, determine MIME, serve response | | 5. Response | httpresp.c | `http_resp()` | Generate HTTP status line + headers | | 6. Send | httpsend.c | `http_send_*()` | Binary or text data with encoding conversion | -| 7. Report | httprepo.c | `http_report()` | Log request, update statistics | +| 7. Report | httprepo.c | `http_report()` | Write SMF record, update counters | | 8. Reset | httprese.c | `http_reset()` | Free env vars, clear buffer, prepare for next | ### Dispatch Logic (httppc.c) @@ -382,22 +382,22 @@ Telemetry cache (HTTPTC array) stores latest values per topic. Lua-based configuration loaded from `PARM='CONFIG=dataset(member)'`. Global Lua tables: `httpd`, `ftpd`, `cgi`, `mqtc`. -### Statistics (httpstat.c) +### Statistics (httprepo.c) -Per-minute/hour/day/month request statistics. Tracks response times -(lowest, highest, average), tally counts, and response codes. -Save/load to MVS dataset in binary format. +SMF Type 243 records written per HTTP request via `smf_write()`. +Simple in-memory counters (total_requests, total_errors, +total_bytes_sent, active_connections) displayed via `/F HTTPD,D S`. ### Console Commands (httpcons.c, httpcmd.c) Operator interface via `/F HTTPD,command`: - `D V` — display version - `D T` — display threads -- `D S [n]` — display statistics +- `D S` — display statistics counters - `S MAXTASK n` — set max worker threads - `S MINTASK n` — set min worker threads - `S LOGIN [all|cgi|none]` — set login requirements -- `S STATS ON|OFF` — control statistics collection +- `S STATS ON|OFF [RESET]` — control SMF recording, reset counters ## Network I/O @@ -515,6 +515,6 @@ Build targets: | Credentials | 20 | httpcred.c, httpauth.c, credentials/src/*.c (18 files) | | MQTT telemetry | 2 | httppubf.c, httpdmtt.c | | Debug | 7 | dbgdump.c, dbgenter.c, dbgexit.c, dbgf.c, dbgs.c, dbgtime.c, dbgw.c | -| Statistics | 1 | httpstat.c | +| Statistics/SMF | 1 | httprepo.c | | Utilities | 16 | hello.c, abend0c1.c, httptest.c, httpclos.c, http1123.c, httpd048.c, httpntoa.c, httpgsna.c, httpgtod.c, httpsecs.c, httprise.c, httpsbz.c, httprbz.c, httpsubt.c, httpcmp.c, httpcmpn.c | | String/compare | 4 | httpcmp.c, httpcmpn.c, stck2tv.c, httpdbug.c | diff --git a/include/httpcgi.h b/include/httpcgi.h index eac0689..de46b37 100644 --- a/include/httpcgi.h +++ b/include/httpcgi.h @@ -127,11 +127,12 @@ struct httpc { #define SSI_LEVEL_MAX 10 /* ... max SSI processing level */ UCHAR content_length_set; /* 52 Content-Length was sent */ UCHAR keepalive; /* 53 keep-alive active */ - unsigned short request_count; /* 54 requests on this connection */ - unsigned short unused3; /* 56 available */ + unsigned connect_time; /* 54 SMF time at connect (1/100s) */ + unsigned total_bytes_sent; /* 58 accum bytes all requests */ + unsigned request_count; /* 5C requests on this connection */ -#define CBUFSIZE (0x1000-0x0058) /* ... 4096-88 = 4008 */ - UCHAR buf[CBUFSIZE]; /* 58 data buffer */ +#define CBUFSIZE (0x1000-0x0060) /* ... 4096-96 = 4000 */ + UCHAR buf[CBUFSIZE]; /* 60 data buffer */ }; /* 1000 (4096 bytes) */ /* HTTP mime type */ diff --git a/include/httpd.h b/include/httpd.h index 0ceee8d..80acf9b 100644 --- a/include/httpd.h +++ b/include/httpd.h @@ -54,7 +54,8 @@ typedef struct httpm HTTPM; /* HTTP Mime */ typedef struct httpx HTTPX; /* HTTP function vector */ typedef struct httpv HTTPV; /* HTTP Variables */ typedef struct httpcgi HTTPCGI; /* HTTP CGI path and programs */ -typedef struct httpstat HTTPSTAT; /* HTTP Statistics record */ +typedef struct smf_httpd_request SMF_HTTPD_REQ; /* SMF request */ +typedef struct smf_httpd_session SMF_HTTPD_SESS; /* SMF session */ typedef enum cstate CSTATE; /* HTTP Client state */ typedef enum rdw RDW; /* RDW option */ @@ -78,7 +79,7 @@ struct httpd { unsigned addr; /* 10 our listener IP address */ int port; /* 14 our listener port */ int listen; /* 18 our listener socket */ - FILE *stats; /* 1C statistics file (log) */ + void *unused_1c; /* 1C (was: FILE *stats) */ FILE *dbg; /* 20 debug/trace output */ int tzoffset; /* 24 time zone offset in secs */ @@ -117,20 +118,23 @@ struct httpd { UCHAR cfg_maxtask; /* 64 config max task */ UCHAR cfg_mintask; /* 65 config min task */ UCHAR cfg_client_timeout; /* 66 client timeout seconds */ - UCHAR cfg_st_month_max; /* 67 statistics month records */ - UCHAR cfg_st_day_max; /* 68 statistics day records */ - UCHAR cfg_st_hour_max; /* 69 statistics hour_records */ - UCHAR cfg_st_min_max; /* 6A statistics min records */ + UCHAR smf_level; /* 67 SMF recording level */ +#define SMF_LEVEL_NONE 0 /* ... no SMF recording */ +#define SMF_LEVEL_ERROR 1 /* ... only resp >= 400 */ +#define SMF_LEVEL_AUTH 2 /* ... auth events + errors */ +#define SMF_LEVEL_ALL 3 /* ... every request + sessions */ + UCHAR smf_type; /* 68 SMF record type (def 243) */ + UCHAR unused_69[2]; /* 69-6A available */ UCHAR cfg_cgictx; /* 6B # CGI context pointers */ UCHAR ufs_enabled; /* 6C UFS filesystem enabled */ UCHAR dbg_enabled; /* 6D debug output enabled */ UCHAR bind_tries; /* 6E socket bind retry count */ UCHAR bind_sleep; /* 6F socket bind retry delay */ - HTTPSTAT **st_month; /* 70 statistics month array */ - HTTPSTAT **st_day; /* 74 statistics day array */ - HTTPSTAT **st_hour; /* 78 statistics hour array */ - HTTPSTAT **st_min; /* 7C statictics min array */ - UCHAR *st_dataset; /* 80 statistics load/save dsn */ + unsigned total_requests; /* 70 total HTTP requests */ + unsigned total_errors; /* 74 total error responses */ + unsigned total_bytes_sent; /* 78 total bytes sent */ + unsigned active_connections; /* 7C active client connections */ + void *unused_80; /* 80 (was: st_dataset) */ UCHAR *cgilua_dataset; /* 84 CGI Lua dataset */ UCHAR *cgilua_path; /* 88 CGI Lua package.path */ UCHAR *cgilua_cpath; /* 8C CGI Lua package.cpath */ @@ -214,11 +218,12 @@ struct httpc { #define SSI_LEVEL_MAX 10 /* ... max SSI processing levele*/ UCHAR content_length_set; /* 52 Content-Length was sent */ UCHAR keepalive; /* 53 keep-alive active */ - unsigned short request_count; /* 54 requests on this conn */ - unsigned short unused3; /* 56 available */ + unsigned connect_time; /* 54 SMF time at connect 1/100s*/ + unsigned total_bytes_sent; /* 58 accum bytes all requests */ + unsigned request_count; /* 5C requests on this conn */ -#define CBUFSIZE (0x1000-0x0058) /* ... 4096-88 = 4008 */ - UCHAR buf[CBUFSIZE]; /* 50 data buffer */ +#define CBUFSIZE (0x1000-0x0060) /* ... 4096-96 = 4000 */ + UCHAR buf[CBUFSIZE]; /* 60 data buffer */ /* 1000 */ }; /* 1000 (4096 bytes) */ @@ -240,21 +245,35 @@ struct httpcgi { char *pgm; /* 10 external program name */ }; /* 14 (20 bytes) */ -struct httpstat { - UCHAR eye[8]; /* 00 eye catcher */ -#define HTTPSTAT_EYE "HTTPSTAT" /* ... */ - time64_t first; /* 08 first time stamp */ - time64_t last; /* 10 last time stamp */ - double lowest; /* 18 lowest value */ - double highest; /* 20 highest value */ - double total; /* 28 accumulated seconds */ - unsigned tally; /* 30 number of adds */ - short key1; /* 34 key number */ - short key2; /* 36 key number */ - short resp; /* 38 response code */ - short unused; /* 3A unused / available */ - unsigned unused2; /* 3C unused / available */ -}; /* 40 (64 bytes) */ +/* SMF — HTTP records */ +#define SMF_TYPE_HTTPD_DEFAULT 243 +#define SMF_HTTPD_SUBTYPE_REQ 1 /* Request completed */ +#define SMF_HTTPD_SUBTYPE_SESS 2 /* Session closed */ + +struct smf_httpd_request { + SMF_HEADER hdr; /* 00 Standard SMF Header (18B) */ + char subsys[8]; /* 12 Subsystem ID */ + short subtype; /* 1A 1 = Request completed */ + char userid[8]; /* 1C RACF user (blank=none) */ + unsigned client_addr; /* 24 Client IPv4 address */ + unsigned resp_code; /* 28 HTTP status code */ + unsigned bytes_sent; /* 2C Response bytes */ + unsigned duration_us; /* 30 Request duration (us) */ + char method[8]; /* 34 GET/POST/PUT/DELETE */ + char uri[64]; /* 3C Request URI (truncated) */ +}; /* 7C (124 bytes) */ + +struct smf_httpd_session { + SMF_HEADER hdr; /* 00 Standard SMF Header (18B) */ + char subsys[8]; /* 12 Subsystem ID */ + short subtype; /* 1A 2 = Session closed */ + char userid[8]; /* 1C last RACF user */ + unsigned client_addr; /* 24 Client IPv4 address */ + unsigned connect_time; /* 28 Connect time (1/100s) */ + unsigned duration_us; /* 2C Total session duration us */ + unsigned request_count; /* 30 Requests on connection */ + unsigned total_bytes; /* 34 Total bytes sent */ +}; /* 38 (56 bytes) */ /* HTTP function execution vector */ extern HTTPX *httpx; /* Global pointer to HTTPX */ @@ -462,12 +481,8 @@ extern int httpcred(HTTPC *httpc) asm("HTTPCRED"); extern int httpd048(HTTPD *httpd) asm("HTTPD048"); extern int http_debug(HTTPC *httpc, const char *options) asm("HTTPDBUG"); extern int http_config(HTTPD *httpd, const char *member) asm("HTTPCONF"); -extern int httpstat_add(HTTPD *httpd, HTTPC *httpc) asm("HTTPSTAT"); -extern char **httpstat_report(HTTPD *httpd, unsigned months, unsigned days, unsigned hours, unsigned mins) asm("HTTPSTAR"); -extern void httpstat_report_free(char ***rpt) asm("HTTPSTAF"); -extern void httpstat_clear(HTTPD *httpd) asm("HTTPSTAC"); -extern int httpstat_save(HTTPD *httpd, const char *dataset) asm("HTTPSTAS"); -extern int httpstat_load(HTTPD *httpd, const char *dataset) asm("HTTPSTAL"); +extern void httpsmf(HTTPC *httpc) asm("HTTPSMF"); +extern void httpsmf_session(HTTPD *httpd, HTTPC *httpc) asm("HTTPSMFS"); extern HTTPD *cgihttpd(void) asm("CGIHTTPD"); extern HTTPC *cgihttpc(void) asm("CGIHTTPC"); extern int http_getc(HTTPC *httpc) asm("HTTPGETC"); diff --git a/samplib/httpprm0 b/samplib/httpprm0 index 7bd3629..b263607 100644 --- a/samplib/httpprm0 +++ b/samplib/httpprm0 @@ -47,10 +47,12 @@ # CGI=HTTPLUA /lua/* # CGI=HTTPREXX /rexx/* # -# --- Statistics --- -# CLIENT_STATS=1 -# CLIENT_STATS_MONTH_MAX=24 -# CLIENT_STATS_DAY_MAX=60 -# CLIENT_STATS_HOUR_MAX=48 -# CLIENT_STATS_MINUTE_MAX=120 -# CLIENT_STATS_DATASET= +# --- SMF Recording --- +# SMF=NONE|ERROR|AUTH|ALL [TYPE=nnn] +# NONE - no SMF recording (default) +# ERROR - write SMF on HTTP errors (resp >= 400) +# AUTH - write on auth events (401/403) and errors +# ALL - every request (subtype 1) + session close (subtype 2) +# TYPE - SMF record type (128-255, default 243) +# SMF=NONE +# SMF=ALL TYPE=200 diff --git a/src/httpclos.c b/src/httpclos.c index 6751f18..17d0208 100644 --- a/src/httpclos.c +++ b/src/httpclos.c @@ -23,6 +23,13 @@ httpclos(HTTPC *httpc) unlock(httpd,0); if (httpc) { + if (httpd->active_connections > 0) + httpd->active_connections--; + + // Write SMF session record (subtype 2) on final disconnect + if (httpd->smf_level == SMF_LEVEL_ALL) + httpsmf_session(httpd, httpc); + /* make sure we closed the file */ if (httpc->fp) http_done(httpc); if (httpc->ufp) ufs_fclose(&httpc->ufp); diff --git a/src/httpcons.c b/src/httpcons.c index 80e2d4c..56b4a7b 100644 --- a/src/httpcons.c +++ b/src/httpcons.c @@ -296,91 +296,14 @@ d_stats(char *buf) { CLIBGRT *grt = __grtget(); HTTPD *httpd = grt->grtapp1; - char **rpt = NULL; - unsigned months = 0; - unsigned days = 0; - unsigned hours = 0; - unsigned mins = 0; - char *next = NULL; - unsigned value = strtoul(buf, &next, 10); - unsigned count, n; - int len; - - if (!(httpd->client & HTTPD_CLIENT_STATS)) { - wtof("HTTPD411I Statistics recording is %s.", "OFF"); - goto quit; - } - else { - if (!value || !buf[0]) { - wtof("HTTPD411I Statistics recording is %s.", "ON"); - goto quit; - } - } - - // wtof("%s: enter", __func__); - // wtof("%s: next=%p value=%u", __func__, next, value); - - if (!value) goto quit; - if (!buf[0]) goto quit; - - if (next) { - while(isspace(*next) || ispunct(*next)) next++; - // wtof("%s: next=\"%s\"", __func__, next); + static const char *levels[] = {"NONE","ERROR","AUTH","ALL"}; - len = strlen(next); - if (http_cmpn(next, "DAYS", len)==0) { - days = value; - } - else if (http_cmpn(next, "HOURS", len)==0) { - hours = value; - } - else if (http_cmpn(next, "MINUTES", len)==0) { - mins = value; - } - else if (http_cmpn(next, "MINS", len)==0) { - mins = value; - } - else if (http_cmpn(next, "MONTHS", len)==0) { - months = value; - } - else if (http_cmpn(next, "ALL", len)==0) { - months = (value / (30*24*60)); - if (!months) months++; - days = (value / (24*60)); - if (!days) days++; - hours = (value / 60); - if (!hours) hours++; - mins = value; - } - else if (!next[0]) { - /* default to minutes */ - mins = value; - } - else { - wtof("HTTPD411W Invalid parameter \"%s\", MINUTES assumed", next); - mins = value; - } - } - else { - /* default to minutes */ - mins = value; - } + wtof("HTTPD411I SMF: %s (Type %d)", + levels[httpd->smf_level], (int)httpd->smf_type); + wtof("HTTPD412I Requests: %u Errors: %u Bytes: %u Active: %u", + httpd->total_requests, httpd->total_errors, + httpd->total_bytes_sent, httpd->active_connections); - rpt = httpstat_report(httpd, months, days, hours, mins); - if (rpt) { - count = array_count(&rpt); - // wtof("%s: report count=%u", __func__, count); - for(n=0; n < count; n++) { - wtof("%s", rpt[n]); - } - httpstat_report_free(&rpt); - } - else { - wtof("HTTPD411E Statistics Report Failure"); - } - -quit: - // wtof("%s: exit", __func__); return 0; } @@ -863,83 +786,56 @@ s_stats(char *in) int rc = 0; char *p = NULL; char *next = NULL; - unsigned clear = 0; - unsigned save = 0; + static const char *levels[] = {"NONE","ERROR","AUTH","ALL"}; /* obtain a exclusive lock on httpd */ lock(httpd,LOCK_EXC); if (!in) { - wtof("HTTPD048W Missing STATS ON|OFF [options]"); + wtof("HTTPD048W Missing STATS NONE|ERROR|AUTH|ALL [RESET]"); goto quit; } p = strtok(in, " ,"); if (!p) { - wtof("HTTPD048W Missing STATS ON|OFF [options]"); + wtof("HTTPD048W Missing STATS NONE|ERROR|AUTH|ALL [RESET]"); goto quit; } - if (http_cmp(p, "ON")==0) { - httpd->client |= HTTPD_CLIENT_STATS; - wtof("HTTPD411I Statistics recording is %s.", "ON"); + if (http_cmp(p, "NONE")==0) { + httpd->smf_level = SMF_LEVEL_NONE; + } + else if (http_cmp(p, "ERROR")==0) { + httpd->smf_level = SMF_LEVEL_ERROR; } - else if (http_cmp(p, "OFF")==0) { - httpd->client &= ~HTTPD_CLIENT_STATS; - wtof("HTTPD411I Statistics recording is %s.", "OFF"); + else if (http_cmp(p, "AUTH")==0) { + httpd->smf_level = SMF_LEVEL_AUTH; + } + else if (http_cmp(p, "ALL")==0) { + httpd->smf_level = SMF_LEVEL_ALL; + } + else if (http_cmp(p, "RESET")==0) { + httpd->total_requests = 0; + httpd->total_errors = 0; + httpd->total_bytes_sent = 0; + wtof("HTTPD411I Statistics counters reset"); + goto quit; } else { wtof("HTTPD411E Invalid SET STATS value \"%s\"", p); goto quit; } - - /* Check for any options */ - for(next = strtok(NULL, " ,"); next; next = strtok(NULL, " ,")) { - int len = strlen(next); - if (!len) break; - if (http_cmpn(next, "CLEAR", len)==0) { - clear = 1; - } - else if (http_cmpn(next, "RESET", len)==0) { - clear = 1; - } - else if (http_cmpn(next, "FREE", len)==0) { - clear = 1; - } - else if (http_cmpn(next, "SAVE", len)==0) { - save = 1; - } - else if (next[0]) { - wtof("HTTPD411E Invalid SET STATS option \"%s\"", next); - } + wtof("HTTPD411I SMF level set to %s", levels[httpd->smf_level]); + + // Check for RESET option + next = strtok(NULL, " ,"); + if (next && http_cmpn(next, "RESET", strlen(next))==0) { + httpd->total_requests = 0; + httpd->total_errors = 0; + httpd->total_bytes_sent = 0; + wtof("HTTPD411I Statistics counters reset"); } - if (save) { - if (httpd->st_dataset && httpd->st_dataset[0]) { - rc = httpstat_save(httpd, httpd->st_dataset); - if (rc) { - wtof("HTTPD411E Statistics save to %s failed with rc=%d", httpd->st_dataset, rc); - } - else { - wtof("HTTPD411I Statistics saved to %s", httpd->st_dataset); - } - } - else { - wtof("HTTPD411W Statistics NOT saved. Statistics dataset not configured."); - } - } - - if (clear) { - if (rc==0) { - httpstat_clear(httpd); - wtof("HTTPD411I Statistics array cleared."); - } - else { - wtof("HTTPD411W Statistics array NOT cleared due to errors."); - } - } - - quit: unlock(httpd,LOCK_EXC); return rc; diff --git a/src/httpd.c b/src/httpd.c index 3a7f6c4..c8ca2e1 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -103,9 +103,14 @@ initialize(int argc, char **argv) goto quit; } - if ((httpd->client & HTTPD_CLIENT_STATS) && httpd->st_dataset) { - wtof("HTTPD415I Loading stats from %s", httpd->st_dataset); - httpstat_load(httpd, httpd->st_dataset); + if (httpd->smf_level > SMF_LEVEL_NONE) { + static const char *levels[] = {"NONE","ERROR","AUTH","ALL"}; + if (smf_active()) + wtof("HTTPD415I SMF Type %d level %s", + (int)httpd->smf_type, levels[httpd->smf_level]); + else + wtof("HTTPD415W SMF Type %d configured but SMF inactive", + (int)httpd->smf_type); } /* create socket thread */ @@ -301,10 +306,9 @@ terminate(void) httpd->ufssys = NULL; } - if ((httpd->client & HTTPD_CLIENT_STATS) && httpd->st_dataset) { - wtof("HTTPD416I Saving stats to %s", httpd->st_dataset); - httpstat_save(httpd, httpd->st_dataset); - } + wtof("HTTPD416I Stats: %u requests, %u errors, %u bytes", + httpd->total_requests, httpd->total_errors, + httpd->total_bytes_sent); /* just in case we missed something */ close_fd_set(); @@ -518,6 +522,17 @@ socket_thread(void *arg1, void *arg2) httpc->port = a->sin_port; httpc->state = CSTATE_IN; httpsecs(&httpc->start); + httpd->active_connections++; + + // SMF session tracking + { + time_t now = time(0); + struct tm *tm = localtime(&now); + httpc->connect_time = (unsigned)( + tm->tm_hour * 360000L + + tm->tm_min * 6000L + + tm->tm_sec * 100L); + } /* UFS session created lazily by http_get_ufs() */ mgr = httpd->mgr; diff --git a/src/httpdsrv.c b/src/httpdsrv.c index 048efa8..5c121c8 100644 --- a/src/httpdsrv.c +++ b/src/httpdsrv.c @@ -251,14 +251,7 @@ display_httpd(HTTPD *httpd, HTTPC *httpc) "%d\n", O(listen), httpd->listen); - if (httpd->stats) { - http_printf(httpc, - "+%04X" - "httpd->stats" - "HTTP Statistics File Handle" - "%p\n", - O(stats), httpd->stats, httpd->stats); - } + /* stats file handle removed in 4.0.0 — SMF recording */ if (httpd->dbg) { http_printf(httpc, @@ -427,33 +420,7 @@ display_httpd(HTTPD *httpd, HTTPC *httpc) "%u\n", O(cfg_client_timeout), httpd->cfg_client_timeout); - http_printf(httpc, - "+%04X" - "httpd->cfg_st_month_max" - "Config Statistics Months" - "%u\n", - O(cfg_st_month_max), httpd->cfg_st_month_max); - - http_printf(httpc, - "+%04X" - "httpd->cfg_st_day_max" - "Config Statistics Days" - "%u\n", - O(cfg_st_day_max), httpd->cfg_st_day_max); - - http_printf(httpc, - "+%04X" - "httpd->cfg_st_hour_max" - "Config Statistics Hours" - "%u\n", - O(cfg_st_hour_max), httpd->cfg_st_hour_max); - - http_printf(httpc, - "+%04X" - "httpd->cfg_st_min_max" - "Config Statistics Minutes" - "%u\n", - O(cfg_st_min_max), httpd->cfg_st_min_max); + /* cfg_st_*_max removed in 4.0.0 — replaced by SMF + counters */ http_printf(httpc, "+%04X" @@ -462,40 +429,33 @@ display_httpd(HTTPD *httpd, HTTPC *httpc) "%u\n", O(cfg_cgictx), httpd->cfg_cgictx); - http_printf(httpc, - "+%04X" - "httpd->st_month" - "Statistics Month Array" - "%p\n", - O(st_month), httpd->st_month); - - http_printf(httpc, + http_printf(httpc, "+%04X" - "httpd->st_day" - "Statistics Day Array" - "%p\n", - O(st_day), httpd->st_day); - - http_printf(httpc, + "httpd->total_requests" + "Total HTTP Requests" + "%u\n", + O(total_requests), httpd->total_requests); + + http_printf(httpc, "+%04X" - "httpd->st_hour" - "Statistics Hour Array" - "%p\n", - O(st_hour), httpd->st_hour); - - http_printf(httpc, + "httpd->total_errors" + "Total Error Responses" + "%u\n", + O(total_errors), httpd->total_errors); + + http_printf(httpc, "+%04X" - "httpd->st_min" - "Statistics Minute Array" - "%p\n", - O(st_min), httpd->st_min); - - http_printf(httpc, + "httpd->total_bytes_sent" + "Total Bytes Sent" + "%u\n", + O(total_bytes_sent), httpd->total_bytes_sent); + + http_printf(httpc, "+%04X" - "httpd->st_dataset" - "Statistics Dataset Name" - "\"%s\"\n", - O(st_dataset), httpd->st_dataset); + "httpd->active_connections" + "Active Connections" + "%u\n", + O(active_connections), httpd->active_connections); http_printf(httpc, "+%04X" diff --git a/src/httpprm.c b/src/httpprm.c index 97e0ddf..b0ad904 100644 --- a/src/httpprm.c +++ b/src/httpprm.c @@ -62,11 +62,7 @@ http_config(HTTPD *httpd, const char *member) } } - /* try to open DD:HTTPSTAT */ - httpd->stats = fopen("DD:HTTPSTAT", "w"); - if (!httpd->stats) { - wtof("HTTPD026W Unable to open DD:HTTPSTAT, errno=%d", errno); - } + /* Stats counters initialized to zero by calloc */ /* open debug file if DEBUG=1 */ if (httpd->dbg_enabled && !httpd->dbg) { @@ -103,10 +99,8 @@ set_defaults(HTTPD *httpd) httpd->cfg_maxtask = 9; httpd->cfg_mintask = 3; httpd->cfg_client_timeout = 10; - httpd->cfg_st_month_max = 24; - httpd->cfg_st_day_max = 60; - httpd->cfg_st_hour_max = 48; - httpd->cfg_st_min_max = 120; + httpd->smf_level = SMF_LEVEL_NONE; + httpd->smf_type = SMF_TYPE_HTTPD_DEFAULT; httpd->cfg_cgictx = HTTPD_CGICTX_MAX; httpd->login = 0; /* NONE */ httpd->client = HTTPD_CLIENT_INMSG | HTTPD_CLIENT_INDUMP @@ -118,7 +112,7 @@ set_defaults(HTTPD *httpd) httpd->cgilua_dataset = NULL; httpd->cgilua_path = NULL; httpd->cgilua_cpath = NULL; - httpd->st_dataset = NULL; + httpd->unused_80 = NULL; httpd->docroot[0] = '\0'; httpd->codepage[0] = '\0'; httpd->dbg_enabled = 0; @@ -330,36 +324,35 @@ parse_keyvalue(HTTPD *httpd, const char *key, const char *value) if (atoi(value) > 0) httpd->client |= HTTPD_CLIENT_STATS; else httpd->client &= ~HTTPD_CLIENT_STATS; } - else if (strcmp(key, "CLIENT_STATS_MONTH_MAX") == 0) { - i = atoi(value); - if (i > 0) { - if (i > 255) i = 255; - httpd->cfg_st_month_max = (UCHAR)i; + else if (strcmp(key, "SMF") == 0) { + // Parse level (first word of value) + int vlen = 0; + char *type_arg; + while (value[vlen] && value[vlen] != ' ') vlen++; + if (http_cmpn(value, "NONE", vlen) == 0) { + httpd->smf_level = SMF_LEVEL_NONE; } - } - else if (strcmp(key, "CLIENT_STATS_DAY_MAX") == 0) { - i = atoi(value); - if (i > 0) { - if (i > 255) i = 255; - httpd->cfg_st_day_max = (UCHAR)i; + else if (http_cmpn(value, "ERROR", vlen) == 0) { + httpd->smf_level = SMF_LEVEL_ERROR; } - } - else if (strcmp(key, "CLIENT_STATS_HOUR_MAX") == 0) { - i = atoi(value); - if (i > 0) { - if (i > 255) i = 255; - httpd->cfg_st_hour_max = (UCHAR)i; + else if (http_cmpn(value, "AUTH", vlen) == 0) { + httpd->smf_level = SMF_LEVEL_AUTH; } - } - else if (strcmp(key, "CLIENT_STATS_MINUTE_MAX") == 0) { - i = atoi(value); - if (i > 0) { - if (i > 255) i = 255; - httpd->cfg_st_min_max = (UCHAR)i; + else if (http_cmpn(value, "ALL", vlen) == 0) { + httpd->smf_level = SMF_LEVEL_ALL; + } + else { + wtof("HTTPD028W Invalid SMF level \"%s\"", value); + } + // Check for TYPE=nnn option + type_arg = strstr(value, "TYPE="); + if (type_arg) { + int t = atoi(type_arg + 5); + if (t >= 128 && t <= 255) + httpd->smf_type = (UCHAR)t; + else + wtof("HTTPD028W Invalid SMF TYPE=%d (128-255)", t); } - } - else if (strcmp(key, "CLIENT_STATS_DATASET") == 0) { - if (*value) httpd->st_dataset = strdup(value); } else if (strcmp(key, "KEEPALIVE_TIMEOUT") == 0) { i = atoi(value); diff --git a/src/httprepo.c b/src/httprepo.c index 7e69cc3..2179edd 100644 --- a/src/httprepo.c +++ b/src/httprepo.c @@ -1,419 +1,159 @@ -/* HTTPREPO.C -** Report results of request -** Transitions to next client state. -*/ -#include "httpd.h" - -static char * FmtStats(HTTPC *httpc, char *buf, int size); -#define STATS_FMT "%h %l %u %t \"%r\" %>s %b %T" - -extern int -httprepo(HTTPC *httpc) -{ - CLIBGRT *grt = __grtget(); - HTTPD *httpd = grt->grtapp1; - int rc = 0; - int lock; - char buf[2048]; - - http_enter("httprepo()\n"); - - /* If we're configured for client statistics */ - if (httpd->client & HTTPD_CLIENT_STATS) { - /* Add this client to the statistics records */ - httpstat_add(httpd, httpc); - } - - /* are we recording statistics? */ - if (!httpd->stats) goto quit; /* nope, we're done */ - - FmtStats(httpc, buf, sizeof(buf)); - - fprintf(httpd->stats, "%s\n", buf); - -quit: - /* transition to next state */ - httpc->state = CSTATE_RESET; - http_exit("httprepo(), rc=%d\n", rc); - return rc; -} - -__asm__("\n&FUNC SETC 'FmtStats'"); -static char * -FmtStats(HTTPC *httpc, char *buf, int size) -{ - CLIBGRT *grt = __grtget(); - HTTPD *httpd = grt->grtapp1; - const char *fmt = STATS_FMT; - unsigned pos = 0; - unsigned remain; - int addrlen; - struct sockaddr_in *in; - unsigned not; - unsigned last; - const char *p; - unsigned len; - char key[256]; - struct sockaddr sockaddr; - struct in_addr in_addr; - - buf[0] = 0; - size -= 80; /* leave room at end of buffer */ - - while (*fmt) { - if (pos > size) break; - not = 0; - last = 0; - key[0] = 0; - - if (*fmt == '\\') { - fmt++; - switch(*fmt) { - case 'r': // \r --> CR - buf[pos++] = '\r'; - break; - case 'n': // \n --> NL - buf[pos++] = '\n'; - break; - case '"': // \" --> " - buf[pos++] = '"'; - break; - case '\'': // \' --> ' - buf[pos++] = '\''; - break; - case '\\': // \\ --> \ - buf[pos++] = '\\'; - break; - default : - buf[pos++] = '\\'; - buf[pos++] = *fmt; - break; - } - fmt++; - continue; - } - - if (*fmt != '%') { - /* Not a formatting directive, copy asis */ - buf[pos++] = *fmt++; - continue; - } - - /* % format directives */ - fmt++; - - /* check for negation */ - if (*fmt=='!') { - not = 1; - fmt++; - } - - /* skip resp code conditionals */ - while(isdigit(*fmt)||*fmt==',') fmt++; - - if (*fmt=='>') { - last = 1; - fmt++; - } - - /* Check for {key} */ - if (*fmt=='{') { - fmt++; - len = 0; - while(*fmt && *fmt != '}') { - key[len++] = *fmt++; - } - key[len] = 0; - fmt++; - } - - switch(*fmt) { - case '%': - buf[pos++] = *fmt; - break; - - case 'A': /* A: Local IP-address */ - addrlen = sizeof(sockaddr); - if (getsockname(httpc->socket, &sockaddr, &addrlen)==0) { - in = (struct sockaddr_in*)&sockaddr; - p = http_ntoa(in->sin_addr); - len = p ? strlen(p) : 0; - - if (p) { - remain = (pos < (unsigned)size) ? (unsigned)size - pos : 0; - if (len > remain) len = remain; - memcpy(&buf[pos], p, len); - pos += len; - } - else { - buf[pos++] = '-'; - } - } - else { - buf[pos++] = '-'; - } - break; - - case 'B': /* B: Bytes sent. */ - remain = (pos < (unsigned)size) ? (unsigned)size - pos : 0; - pos += snprintf( &buf[pos], remain, "%u", httpc->sent ); - break; - - case 'b': /* b: Bytes sent. In CLF format - ** i.e. a '-' rather than a 0 when no bytes are sent. - */ - if (httpc->sent) { - remain = (pos < (unsigned)size) ? (unsigned)size - pos : 0; - pos += snprintf( &buf[pos], remain, "%u", httpc->sent ); - } - else { - buf[pos++] = '-'; - } - break; - - case 'c': - /* c: Connection status when response was completed. - ** 'X' = connection aborted before the response completed. - ** '+' = connection may be kept alive after the response is sent. - ** '-' = connection will be closed after the response is sent. - ** we're always closing the connection after the response - */ - buf[pos++] = '-'; - break; - - case 'e': /* e: The contents of the environment variable */ - p = http_get_env(httpc, key); - if (p) { - len = strlen(p); - remain = (pos < (unsigned)size) ? (unsigned)size - pos : 0; - if (len > remain) len = remain; - memcpy(&buf[pos], p, len); - pos += len; - } - else { - buf[pos++] = '-'; - } - break; - - case 'f': /* f: Filename */ - p = http_get_env(httpc, "REQUEST_PATH"); - if (p) p = strrchr(p, '/' ); - if (p) { - p++; - len = strlen(p); - remain = (pos < (unsigned)size) ? (unsigned)size - pos : 0; - if (len > remain) len = remain; - memcpy(&buf[pos], p, len); - pos += len; - } - else { - buf[pos++] = '-'; - } - break; - - case 'h': /* h: Remote host */ -#if 0 - buf[pos++] = '-'; - break; -#else - in_addr.s_addr = httpc->addr; - p = http_ntoa(in_addr); - len = p ? strlen(p) : 0; - - if (p) { - remain = (pos < (unsigned)size) ? (unsigned)size - pos : 0; - if (len > remain) len = remain; - memcpy(&buf[pos], p, len); - pos += len; - } - else { - buf[pos++] = '-'; - } - break; -#endif - case 'H': /* H: The request protocol */ - remain = (pos < (unsigned)size) ? (unsigned)size - pos : 0; - if (4 <= remain) { - memcpy(&buf[pos], "HTTP", 4); - pos += 4; - } - break; - - case 'i': /* i: The contents of {key}: header line(s) in the request - ** sent to the server. - */ - buf[pos++] = '-'; - break; - - case 'I': - buf[pos++] = '-'; - break; - - case 'l': /* l: Remote logname (from identd, if supplied) */ - buf[pos++] = '-'; - break; - - case 'm': /* m: The request method */ - p = http_get_env(httpc, "REQUEST_METHOD"); - if (p) { - len = strlen(p); - remain = (pos < (unsigned)size) ? (unsigned)size - pos : 0; - if (len > remain) len = remain; - memcpy(&buf[pos], p, len); - pos += len; - } - else { - buf[pos++] = '-'; - } - break; - - case 'n': /* n: The contents of note {key} from another module. */ - buf[pos++] = '-'; - break; - - case 'o': /* o: The contents of {key}: header line(s) in the reply. */ - buf[pos++] = '-'; /* not implemented */ - break; - - case 'O': - remain = (pos < (unsigned)size) ? (unsigned)size - pos : 0; - pos += snprintf(&buf[pos], remain, "%u", httpc->sent); - break; - - case 'p': /* p: The canonical Port of the server serving the request - */ - remain = (pos < (unsigned)size) ? (unsigned)size - pos : 0; - pos += snprintf(&buf[pos], remain, "%d", httpd->port ); - break; - - case 'P': /* P: The process ID of the child that serviced the request. - */ - buf[pos++] = '-'; - break; - - case 'q': /* q: The query string - ** (prepended with a ? if a query string exists, - ** otherwise an empty string) - */ - p = http_get_env(httpc, "QUERY_STRING"); - if (p) { - remain = (pos < (unsigned)size) ? (unsigned)size - pos : 0; - pos += snprintf(&buf[pos], remain, "?%s", p); - } - break; - - case 'r': /* r: First line of request */ - p = http_get_env(httpc, "HTTP_REQUEST"); - if (p) { - len = 0; - while(*p && !iscntrl(p[len])) len++; - remain = (pos < (unsigned)size) ? (unsigned)size - pos : 0; - if (len > remain) len = remain; - memcpy(&buf[pos], p, len); - pos += len; - } - else { - buf[pos++] = '-'; - } - break; - - case 's': /* s: Status. For requests that got internally redirected, - ** this is the status of the *original* request - ** --- %...>s for the last. - */ - if (httpc->resp) { - remain = (pos < (unsigned)size) ? (unsigned)size - pos : 0; - pos += snprintf( &buf[pos], remain, "%d", httpc->resp ); - } - else { - buf[pos++] = '-'; - } - break; - - case 't': /* t: Time, in common log format time format - ** {format}t: The time, in the form given by format - */ - remain = (pos < (unsigned)size) ? (unsigned)size - pos : 0; - if (key[0]) { - time64_t t = time64(NULL); - pos += strftime(&buf[pos], remain, key, localtime64(&t)); - } - else { - http_date_rfc1123(time64(NULL), &buf[pos], remain); - pos += strlen(&buf[pos]); - } - break; - - case 'T': /* T: The time taken to serve the request, in seconds. */ - remain = (pos < (unsigned)size) ? (unsigned)size - pos : 0; - pos += snprintf(&buf[pos], remain, "%f", httpc->end - httpc->start); - break; - - case 'u': /* u: Remote user (from auth; - ** may be bogus if return status (%s) is 401) - */ - p = http_get_env(httpc, "REMOTE_USER"); - if (p) { - len = strlen(p); - remain = (pos < (unsigned)size) ? (unsigned)size - pos : 0; - if (len > remain) len = remain; - memcpy(&buf[pos], p, len); - pos += len; - } - else { - buf[pos++] = '-'; - } - break; - - case 'U': /* U: The URL path requested, - ** not including any query string. - */ - p = http_get_env(httpc, "REQUEST_PATH"); - if (p) { - len = strlen(p); - remain = (pos < (unsigned)size) ? (unsigned)size - pos : 0; - if (len > remain) len = remain; - memcpy(&buf[pos], p, len); - pos += len; - } - else { - buf[pos++] = '-'; - } - break; - - case 'v': /* v: The canonical ServerName of the server - ** serving the request. - */ - case 'V': /* V: The server name according to the - ** UseCanonicalName setting. - */ - p = http_get_env(httpc, "SERVER_NAME"); - if (p) { - len = strlen(p); - remain = (pos < (unsigned)size) ? (unsigned)size - pos : 0; - if (len > remain) len = remain; - memcpy(&buf[pos], p, len); - pos += len; - } - else { - buf[pos++] = '-'; - } - break; - - default: - buf[pos++] = '%'; - if (key[0]) { - remain = (pos < (unsigned)size) ? (unsigned)size - pos : 0; - pos += snprintf(&buf[pos], remain, "{%s}", key ); - } - buf[pos++] = *fmt; - break; - } - fmt++; - } - - buf[pos] = 0; - -quit: - return buf; -} +/* HTTPREPO.C +** Report results of request — update counters and write SMF records +** based on configured SMF level. +** Transitions to next client state. +*/ +#include "httpd.h" + +static void httpsmf_write_request(HTTPD *httpd, HTTPC *httpc); + +extern int +httprepo(HTTPC *httpc) +{ + CLIBGRT *grt = __grtget(); + HTTPD *httpd = grt->grtapp1; + + http_enter("httprepo()\n"); + + // Counters always increment regardless of SMF level + httpd->total_requests++; + if (httpc->resp >= 400) + httpd->total_errors++; + httpd->total_bytes_sent += httpc->sent; + + // Per-connection accumulators for session record + httpc->total_bytes_sent += httpc->sent; + + // SMF level check + if (httpd->smf_level == SMF_LEVEL_NONE) + goto done; + + if (httpd->smf_level == SMF_LEVEL_ERROR && httpc->resp < 400) + goto done; + + if (httpd->smf_level == SMF_LEVEL_AUTH) { + // Write on auth events (401/403) and errors (>= 400) + if (httpc->resp != 401 && httpc->resp != 403 && httpc->resp < 400) + goto done; + } + + // SMF_LEVEL_ALL: always write + httpsmf_write_request(httpd, httpc); + +done: + httpc->state = CSTATE_RESET; + http_exit("httprepo(), rc=%d\n", 0); + return 0; +} + +__asm__("\n&FUNC SETC 'HTTPSMF'"); +void +httpsmf(HTTPC *httpc) +{ + httprepo(httpc); +} + +static void +httpsmf_write_request(HTTPD *httpd, HTTPC *httpc) +{ + SMF_HTTPD_REQ rec; + const char *p; + unsigned len; + double elapsed; + + if (!smf_active()) + return; + + memset(&rec, 0, sizeof(rec)); + smf_init(&rec, sizeof(rec), httpd->smf_type); + + memcpy(rec.subsys, "HTTPD ", 8); + rec.subtype = SMF_HTTPD_SUBTYPE_REQ; + + // Userid from credential + if (httpc->cred) { + CREDID id; + credid_dec(&httpc->cred->id, &id); + memcpy(rec.userid, id.userid, 8); + } + else { + memset(rec.userid, ' ', 8); + } + + rec.client_addr = httpc->addr; + rec.resp_code = httpc->resp; + rec.bytes_sent = httpc->sent; + + // Duration in microseconds + elapsed = httpc->end - httpc->start; + if (elapsed < 0.0) elapsed = 0.0; + rec.duration_us = (unsigned)(elapsed * 1000000.0); + + // Method + p = http_get_env(httpc, "REQUEST_METHOD"); + if (p) { + len = strlen(p); + if (len > sizeof(rec.method)) len = sizeof(rec.method); + memcpy(rec.method, p, len); + } + + // URI + p = http_get_env(httpc, "REQUEST_PATH"); + if (p) { + len = strlen(p); + if (len > sizeof(rec.uri)) len = sizeof(rec.uri); + memcpy(rec.uri, p, len); + } + + smf_write(&rec); +} + +/* Write SMF session record (subtype 2) on connection close. +** Called from httpclos.c only when smf_level == SMF_LEVEL_ALL. */ +__asm__("\n&FUNC SETC 'HTTPSMFS'"); +void +httpsmf_session(HTTPD *httpd, HTTPC *httpc) +{ + SMF_HTTPD_SESS rec; + double elapsed; + + if (!smf_active()) + return; + + memset(&rec, 0, sizeof(rec)); + smf_init(&rec, sizeof(rec), httpd->smf_type); + + memcpy(rec.subsys, "HTTPD ", 8); + rec.subtype = SMF_HTTPD_SUBTYPE_SESS; + + // Last userid from credential + if (httpc->cred) { + CREDID id; + credid_dec(&httpc->cred->id, &id); + memcpy(rec.userid, id.userid, 8); + } + else { + memset(rec.userid, ' ', 8); + } + + rec.client_addr = httpc->addr; + rec.connect_time = httpc->connect_time; + rec.request_count = httpc->request_count; + rec.total_bytes = httpc->total_bytes_sent; + + // Session duration from connect_time to now (1/100s → microseconds) + { + time_t now = time(0); + struct tm *tm = localtime(&now); + unsigned close_time = (unsigned)( + tm->tm_hour * 360000L + + tm->tm_min * 6000L + + tm->tm_sec * 100L); + if (close_time >= httpc->connect_time) + rec.duration_us = (close_time - httpc->connect_time) * 10000; + else + rec.duration_us = 0; + } + + smf_write(&rec); +} diff --git a/src/httpstat.c b/src/httpstat.c deleted file mode 100644 index 4971c45..0000000 --- a/src/httpstat.c +++ /dev/null @@ -1,509 +0,0 @@ -#include "httpd.h" - -static char lockword[] = "HTTPD Statistics"; - -static HTTPSTAT *find_by_key(HTTPSTAT ***stat, short key1, short key2, short resp); -static HTTPSTAT *new_stat(short key1, short key2, short resp); -static HTTPSTAT *update_stat(HTTPSTAT *stat, time64_t *now, double *sec); -typedef enum rpttype { - RPT_MONTHS, - RPT_DAYS, - RPT_HOURS, - RPT_MINS -} RPTTYPE; -static int stat_report(HTTPD *httpd, unsigned cnt, char ***rpt, RPTTYPE type); -static int save_stats(FILE *fp, HTTPSTAT ***array, const char *label); -static void clear(HTTPSTAT ***array); - -int httpstat_load(HTTPD *httpd, const char *dataset) -{ - int rc = 0; - FILE *fp = NULL; - HTTPSTAT *stat; - int which = -1; - int len; - char buf[256]; - - lock(lockword, LOCK_SHR); - - fp = fopen(dataset, "r,record"); - if (!fp) { - wtof("HTTPD430E Unable to open \"%s\" for input", dataset); - goto quit; - } - - while(len=fread(buf, sizeof(buf), 1, fp)) { - if (buf[0]=='.' && buf[1]=='/') { - if (memcmp(&buf[2], "MON", 3)==0) { - which = RPT_MONTHS; - continue; - } - if (memcmp(&buf[2], "DAY", 3)==0) { - which = RPT_DAYS; - continue; - } - if (memcmp(&buf[2], "HOU", 3)==0) { - which = RPT_HOURS; - continue; - } - if (memcmp(&buf[2], "MIN", 3)==0) { - which = RPT_MINS; - continue; - } - wtof("%s: Invalid record in %s", __func__, dataset); - wtodumpf(buf, len, "%s: Invalid record", __func__); - rc = EINVAL; - goto quit; - } - - if (which < 0) continue; - - if (memcmp(buf, HTTPSTAT_EYE, sizeof(HTTPSTAT_EYE)-1)!=0) { - wtof("%s: Invalid record in %s", __func__, dataset); - wtodumpf(buf, len, "%s: Invalid record", __func__); - rc = EINVAL; - goto quit; - } - - stat = calloc(1, sizeof(HTTPSTAT)); - if (!stat) { - wtof("%s: Out of memory", __func__); - rc = ENOMEM; - goto quit; - } - - memcpy(stat, buf, sizeof(HTTPSTAT)); - - switch (which) { - case RPT_MONTHS: - rc = array_add(&httpd->st_month, stat); - break; - case RPT_DAYS: - rc = array_add(&httpd->st_day, stat); - break; - case RPT_HOURS: - rc = array_add(&httpd->st_hour, stat); - break; - case RPT_MINS: - rc = array_add(&httpd->st_min, stat); - break; - } /* switch */ - - if (rc) { - wtof("%s: Out of memory", __func__); - rc = ENOMEM; - goto quit; - } - } /* while */ - - rc = 0; - -quit: - unlock(lockword, LOCK_SHR); - - if (fp) fclose(fp); - - return rc; -} - -int httpstat_save(HTTPD *httpd, const char *dataset) -{ - int rc = 0; - FILE *fp = NULL; - - lock(lockword, LOCK_SHR); - - fp = fopen(dataset, "w,record,recfm=fb,lrecl=80,blksize=27920,space=trk(1,1)"); - if (!fp) { - wtof("HTTPD430E Unable to open \"%s\" for output", dataset); - goto quit; - } - - if (httpd->st_month) { - rc = save_stats(fp, &httpd->st_month, "MONTH"); - if (rc) goto quit; - } - if (httpd->st_day) { - rc = save_stats(fp, &httpd->st_day, "DAY"); - if (rc) goto quit; - } - if (httpd->st_hour) { - rc = save_stats(fp, &httpd->st_hour, "HOUR"); - if (rc) goto quit; - } - if (httpd->st_min) { - rc = save_stats(fp, &httpd->st_min, "MINUTE"); - if (rc) goto quit; - } - -quit: - unlock(lockword, LOCK_SHR); - - if (fp) fclose(fp); - return rc; -} - -static int save_stats(FILE *fp, HTTPSTAT ***array, const char *label) -{ - int rc = 0; - unsigned count, n; - HTTPSTAT *stat; - char buf[80] = {0}; - - // wtof("%s: enter label=%s", __func__, label); - - snprintf(buf, sizeof(buf), "./%s", label); - rc = fwrite(buf, sizeof(buf), 1, fp); - if (rc != 1) goto error; - - memset(buf, 0, sizeof(buf)); - count = array_count(array); - for(n=1; n <= count; n++) { - stat = array_get(array, n); - if (!stat) continue; - - memcpy(buf, stat, sizeof(HTTPSTAT)); - rc = fwrite(buf, sizeof(buf), 1, fp); - if (rc != 1) goto error; - } - - rc = 0; - goto quit; - -error: - // wtof("%s: error rc=%d", __func__, rc); - rc = errno; - -quit: - // wtof("%s: exit rc=%d", __func__, rc); - return rc; -} - - -void httpstat_report_free(char ***rpt) -{ - unsigned count, n; - char *buf; - - if (rpt) { - count = array_count(rpt); - for(n=count; n > 0; n--) { - buf = array_get(rpt, n); - if (buf) free(buf); - } - array_free(rpt); - *rpt = NULL; - } -} - -char **httpstat_report(HTTPD *httpd, unsigned months, unsigned days, unsigned hours, unsigned mins) -{ - char **rpt = NULL; - int rc; - - lock(lockword, LOCK_SHR); - - if (months) { - rc = stat_report(httpd, months, &rpt, RPT_MONTHS); - if (rc) goto quit; - } - if (days) { - rc = stat_report(httpd, days, &rpt, RPT_DAYS); - if (rc) goto quit; - } - if (hours) { - rc = stat_report(httpd, hours, &rpt, RPT_HOURS); - if (rc) goto quit; - } - if (mins) { - rc = stat_report(httpd, mins, &rpt, RPT_MINS); - if (rc) goto quit; - } - -quit: - unlock(lockword, LOCK_SHR); - return rpt; -} - -static int -stat_report(HTTPD *httpd, unsigned cnt, char ***rpt, RPTTYPE type) -{ - unsigned results = 0; - HTTPSTAT ***array; - const char *title; - time64_t now; - time64_t low; - int rc; - HTTPSTAT *stat; - struct tm *tm; - int len; - unsigned count; - unsigned n; - char buf[256]; - -#define SEC_PER_MONTH (30*24*60*60) -#define SEC_PER_DAY (24*60*60) -#define SEC_PER_HOUR (60*60) -#define SEC_PER_MIN (60) - - now = time64(NULL); - switch(type) { - case RPT_MONTHS: - title = "HTTPD411I Stats for past %u Month%s"; - __64_sub_u32(&now, cnt * SEC_PER_MONTH, &low); - array = &httpd->st_month; - break; - case RPT_DAYS: - title = "HTTPD411I Stats for past %u Day%s"; - __64_sub_u32(&now, cnt * SEC_PER_DAY, &low); - array = &httpd->st_day; - break; - case RPT_HOURS: - title = "HTTPD411I Stats for past %u Hour%s"; - __64_sub_u32(&now, cnt * SEC_PER_HOUR, &low); - array = &httpd->st_hour; - break; - case RPT_MINS: - default: - title = "HTTPD411I Stats for past %u Minute%s"; - __64_sub_u32(&now, cnt * SEC_PER_MIN, &low); - array = &httpd->st_min; - break; - } - - rc = array_addf(rpt, title, cnt, cnt > 1 ? "s" : ""); - if (rc) goto quit; - - count = array_count(array); - if (count) { - rc = array_addf(rpt, "HTTPD412I Lowest Average Highest Resp - - - - Time Period - - - -"); - } - else { - rc = array_addf(rpt, "HTTPD404I No Statistics exist"); - goto quit; - } - if (rc) goto quit; - - for (n=count; n > 0; n--) { - stat = array_get(array, n); - if (!stat) continue; - - if (__64_cmp(&stat->last, &low)== __64_SMALLER) continue; - - tm = localtime64(&stat->first); - len = strftime(buf, sizeof(buf), "%b %d %H:%M - ", tm); - tm = localtime64(&stat->last); - len = strftime(&buf[len], sizeof(buf)-len, "%b %d %H:%M", tm); - - rc = array_addf(rpt, "HTTPD413I %7.3f %7.3f %7.3f %03d %s", - stat->lowest, (stat->total / (stat->tally*1.0)), stat->highest, - stat->resp, buf); - if (rc) goto quit; - results++; - } - - if (!results) { - rc = array_addf(rpt, "HTTPD404I No statistic records matched"); - if (rc) goto quit; - } - -quit: - rc = array_addf(rpt, "HTTPD414I --------------------------------------------------------"); - - return rc; -} - -static void clear(HTTPSTAT ***array) -{ - unsigned count, n; - - if (array) { - count = array_count(array); - for(n=count; n > 0; n--) { - HTTPSTAT *stat = array_del(array, n); - if (!stat) continue; - free(stat); - } - array_free(array); - } -} - -void httpstat_clear(HTTPD *httpd) -{ - lock(lockword, LOCK_EXC); - - if (httpd->st_day) { - clear(&httpd->st_day); - httpd->st_day = NULL; - } - if (httpd->st_hour) { - clear(&httpd->st_hour); - httpd->st_hour = NULL; - } - if (httpd->st_min) { - clear(&httpd->st_min); - httpd->st_min = NULL; - } - if (httpd->st_month) { - clear(&httpd->st_month); - httpd->st_month = NULL; - } - - unlock(lockword, LOCK_EXC); -} - -int -httpstat_add(HTTPD *httpd, HTTPC *httpc) -{ - time64_t now = time64(NULL); - double sec = httpc->end - httpc->start; - short resp = httpc->resp; - struct tm *tm = localtime64(&now); - short year = tm->tm_year; - short month = tm->tm_mon + 1; - short day = tm->tm_yday; - short hour = tm->tm_hour; - short min = tm->tm_min; - HTTPSTAT *stat; - unsigned count; - - lock(lockword, LOCK_EXC); - - /* Minute */ - stat = find_by_key(&httpd->st_min, hour, min, resp); - if (!stat) goto quit; - update_stat(stat, &now, &sec); - - // wtodumpf(stat, sizeof(HTTPSTAT), "minute=%d", min); - - /* Hour */ - stat = find_by_key(&httpd->st_hour, day, hour, resp); - if (!stat) goto quit; - update_stat(stat, &now, &sec); - - // wtodumpf(stat, sizeof(HTTPSTAT), "hour=%d", hour); - - /* Day */ - stat = find_by_key(&httpd->st_day, year, day, resp); - if (!stat) goto quit; - update_stat(stat, &now, &sec); - - // wtodumpf(stat, sizeof(HTTPSTAT), "day=%d", day); - - /* Month */ - stat = find_by_key(&httpd->st_month, year, month, resp); - if (!stat) goto quit; - update_stat(stat, &now, &sec); - - // wtodumpf(stat, sizeof(HTTPSTAT), "month=%d", month); - - /* Limit to httpd->cfg_st_min_max minute records */ - count = array_count(&httpd->st_min); - if (count <= httpd->cfg_st_min_max) { - /* we don't need to look at anything else */ - goto quit; - } - else { - /* pop the first record and free it */ - stat = array_del(&httpd->st_min, 1); - free(stat); - } - - /* Limit to httpd->cfg_st_hour_max hour records */ - count = array_count(&httpd->st_hour); - if (count > httpd->cfg_st_hour_max) { - /* pop the first record and free it */ - stat = array_del(&httpd->st_hour, 1); - free(stat); - } - - /* Limit to httpd->cfg_st_day_max day records */ - count = array_count(&httpd->st_day); - if (count > httpd->cfg_st_day_max) { - /* pop the first record and free it */ - stat = array_del(&httpd->st_day, 1); - free(stat); - } - - /* Limit to httpd->cfg_st_month_max month records */ - count = array_count(&httpd->st_month); - if (count > httpd->cfg_st_month_max) { - /* pop the first record and free it */ - stat = array_del(&httpd->st_month, 1); - free(stat); - } - -quit: - unlock(lockword, LOCK_EXC); - return 0; -} - -static HTTPSTAT * -update_stat(HTTPSTAT *stat, time64_t *now, double *sec) -{ - if (!stat) goto quit; - - if (!stat->first.u32[1]) stat->first = *now; - stat->last = *now; - stat->total += *sec; - stat->tally += 1; - if (stat->tally > 9999 || stat->total > 9999.0) { - /* to prevent overflow we'll average the total now and reset the tally */ - stat->total = stat->total / (stat->tally * 1.0); - stat->tally = 1; - } - if (*sec < stat->lowest) stat->lowest = *sec; - if (*sec > stat->highest) stat->highest = *sec; - -quit: - return stat; -} - -static HTTPSTAT * -find_by_key(HTTPSTAT ***pstat_array, short key1, short key2, short resp) -{ - HTTPSTAT **pstat = *pstat_array; - HTTPSTAT *stat = NULL; - unsigned count, n; - - // wtof("%s: pstat_array=%p", __func__, pstat_array); - // wtof("%s: pstat =%p", __func__, pstat); - - count = array_count(pstat_array); - // wtof("%s: count=%u", __func__, count); - for(n=0; n < count; n++) { - stat = pstat[n]; - if (stat->key1 == key1 && stat->key2 == key2 && stat->resp == resp) { - goto quit; - } - } - - stat = new_stat(key1, key2, resp); - // wtof("%s: after new_stat() stat=%p", __func__, stat); - if (stat) { - array_add(pstat_array, stat); - // count = array_count(pstat_array); - // wtof("%s: after array_add() count=%u", __func__, count); - } - -quit: - return stat; -} - -static HTTPSTAT * -new_stat(short key1, short key2, short resp) -{ - HTTPSTAT *stat = calloc(1, sizeof(HTTPSTAT)); - - if (stat) { - strcpy(stat->eye, HTTPSTAT_EYE); - stat->total = 0.0; - stat->highest = 0.0; - stat->lowest = 9999.0; - stat->key1 = key1; - stat->key2 = key2; - stat->resp = resp; - } - - return stat; -}