Skip to content

Feature clienttracking - #121

Open
ascottDI wants to merge 6 commits into
mainfrom
feature-clienttracking
Open

Feature clienttracking#121
ascottDI wants to merge 6 commits into
mainfrom
feature-clienttracking

Conversation

@ascottDI

Copy link
Copy Markdown
Contributor

di.clienttracking

Summary

Adds di.clienttracking, a di.* module that tracks the client sessions connected to a KDB-X
process in an in-memory table: who is connected, from where, when each session opened and closed,
and how many requests and result-bytes each client has been served. It is the modular replacement
for TorQ's code/handlers/trackclients.q (the .clients namespace).

The module registers all of its .z.* hooks through an injected di.handlers instance and never
assigns .z.* directly, so it composes with any other component hooking the same events.

Depends on di.handlers and di.log (both merged). Framework tier; no hard use dependencies —
all providers are injected, so the module loads and is tested standalone.

Motivation

trackclients.q maintains a CLIENTS session table by hand-rolling .z.* handler chaining via
.dotz.set. In the modular framework, ownership of the .z.* events belongs to di.handlers, so
this extraction re-expresses the tracking logic on top of the di.handlers registration contract
while preserving the observable behaviour (a queryable table of current and recent client sessions).

Design

Event handling

di.handlers distinguishes two dispatch models, and this module uses both:

  • Connection lifecycle.z.po, .z.pc, .z.wo, .z.wc are simple events (return value
    discarded), fanned out to every registrant. The module registers monadic observers that open a
    session row on connect and stamp endp on disconnect.
  • Usage counting.z.pg, .z.ps, .z.ws are phased events whose return value is the query
    result. Counting only observes that result, so it is registered as a post handler.

Usage-counting activation

di.handlers does not accept a post registration on a phased event until that event has an exec
owner. init therefore registers the lifecycle observers unconditionally and then calls
enableusage[], which registers the usage post handler on each query event that already has an
owner and skips (with a warn) any that do not. enableusage[] is exported and idempotent so it can
be called again once the process's query owner (e.g. a gateway or di.permissions) is registered.

Integration note for di.torq: call clienttracking.enableusage[] after wiring the process's
query exec owner. Lifecycle tracking requires no such step.

Dependencies (injected via init)

Dependency Key Required Description
logger `log yes dict of binary {[c;m]} functions with at least info, warn, error
handlers `handlers yes a di.handlers instance's register / remove / list functions

init validates both strictly and signals immediately if either is missing or malformed; there is no
fallback. di.log's logdictlog(aninfo..fataldict) is a conforming superset of the required log keys and is passed through without adaptation. Optional config keys —maxidle(timespan),retain(timespan),trackusage` (boolean) — are type-checked when present.

Although the dependency tree lists di.clienttracking → di.handlers, handler management is an
injected dependency per the framework contract, not a hard use import; di.torq supplies the
shared, initialised di.handlers instance to init.

Session table

getclients[] returns the table; each row is one session, and an open session has a null endp:

Column Type Meaning
w `g#int connection handle (.z.w at open)
ipa symbol client IP, dotted-decimal
u symbol client user (.z.u at open)
a int client IP, raw int (.z.a at open)
startp timestamp connection open time
endp timestamp connection close time; null while open
lastp timestamp time of the last request from this client
hits long requests served
sz long approximate total result-bytes returned

Changes from the TorQ implementation

Change Rationale
Unkeyed table (grouped w) instead of keyed-on-handle TorQ keyed CLIENTS on the handle and nulled the key on close, which collides on handle reuse; an unkeyed table represents concurrent and historical sessions per handle cleanly (current session = row with that w and null endp).
errs column removed A post handler runs only after a successful exec, so it cannot observe failed queries; the column would always be zero.
INTRUSIVE mode removed TorQ optionally sent an async eval back to each client to self-report host details. It only works for a cooperating q client and is security-sensitive; removed, along with the columns it populated.
No timer dependency Cleanup runs inline on open/close and is exported as cleanup[] for a host to schedule via di.timer, keeping the injected surface to log + handlers.

Public API

Function Signature Description
init [deps] Wire dependencies and optional config, create the table, register lifecycle handlers, and enable usage counting where possible. Idempotent.
getclients [] Return the session table.
addclient [handle] Manually record an int handle as an open session (equivalent to TorQ's addw).
cleanup [] Run a maintenance sweep: mark departed handles closed, force-close handles idle beyond maxidle, and purge closed rows older than retain. Also runs automatically on every open/close.
enableusage [] (Re)register usage counting on query events that now have an exec owner.
version Module version ("0.1.0").

getapimeta[] exposes the callable API above for central registration with di.api; the framework
plumbing (init, getapimeta, version) is intentionally excluded.

Testing

Run against the real, merged di.handlers and di.log — no dependencies are mocked.

Unit suitetest.csv, 41 checks, hermetic (no sockets). Drives di.handlers' dispatcher by
invoking the function bound to each .z.* event, exercising the full registration → dispatch → update
path. Covers dependency and config validation, the four lifecycle registrations, connect/disconnect
tracking, addclient and its input validation, cleanup of departed handles, usage-counting activation
and the deferral path when no owner exists, teardown on trackusage:0b, and the API-metadata/version
contract.

k4unit:use`di.k4unit
k4unit.moduletest`di.clienttracking

Integration suitetest_integration.csv, 6 checks. Stands up a child q process, tracks the
handle to it, and exercises the idle-reap path (which requires a genuinely live handle): it verifies
that an idle handle is force-closed and its session stamped closed, and that the child is cleaned up.
Requires a q/kdb-x binary on QHOME and skips cleanly if none is available. moduletest loads only
test.csv, so run the integration suite directly:

k4unit:use`di.k4unit
.m.di.0k4unit.KUltf .Q.dd[hsym`$.Q.m.mp`di.clienttracking;`test_integration.csv]
.m.di.0k4unit.KUrt[]
k4unit.getresults[]

Incoming-connection acceptance (.z.po firing on a real socket) is not integration-tested: a process
only accepts inbound connections at its top-level event loop, which the test harness does not reach.
That binding is the responsibility of di.handlers and is covered by its own integration suite; here
the .z.po/.z.pc handlers are exercised through the unit suite.

Limitations

  • Usage counting requires a single-threaded query port. A post handler runs in the query's
    execution context; on a multithreaded (negative \p) port that is not the main thread, so its
    update to the session table hits kdb's 'noupdate restriction. di.handlers isolates the handler,
    so the query still succeeds and the update is skipped with a logged warning. Lifecycle tracking is
    unaffected.
  • Usage counting is activation-ordered — it attaches only to query events that already have an
    exec owner; re-run enableusage[] after an owner is registered.
  • ipa formats .z.a as a dotted-decimal address without reverse-hostname resolution.

Files

di/clienttracking/init.q                  entry point and export list
di/clienttracking/clienttracking.q        implementation
di/clienttracking/clienttracking.md       module documentation
di/clienttracking/test.csv                unit tests (41)
di/clienttracking/test_integration.csv    integration tests (6)

Comment thread di/clienttracking/clienttracking.q
Comment thread di/clienttracking/clienttracking.q
Comment thread di/clienttracking/clienttracking.q
Comment thread di/clienttracking/test_integration.csv Outdated
Comment thread di/clienttracking/clienttracking.q
@DI-Software-Engineering

Copy link
Copy Markdown

DIReview Summary

0 critical | 5 warning(s) | 0 suggestion(s)

⚠️ Spec check skipped — tracker lookup failed (NO_REF_FOUND). Standards axis only.

Comment thread di/clienttracking/test_integration.csv
@DI-Software-Engineering

Copy link
Copy Markdown

DIReview Summary

0 critical | 1 warning(s) | 0 suggestion(s)

⚠️ Spec check skipped — tracker lookup failed (NO_REF_FOUND). Standards axis only.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants