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;
-}