diff --git a/cuwrapper/rc/rc_server_abi.cpp b/cuwrapper/rc/rc_server_abi.cpp index 71e32c2a..7d7c5e6a 100644 --- a/cuwrapper/rc/rc_server_abi.cpp +++ b/cuwrapper/rc/rc_server_abi.cpp @@ -43,6 +43,9 @@ struct RcSession { V2Session core; uint64_t epoch = 0; std::atomic next_nonce{1}; + /* Monotonic creation timestamp for session-age observability; + * deadlines cannot serve that role because READY moves them. */ + uint64_t created_ms = 0; /* staged metadata from finish_staging (GET) or finish_put. */ std::string etag; std::string version_id; @@ -613,6 +616,7 @@ int rc_prepare(rc_server *srv, const rc_prepare_req *req, srv->opts.t_prep_ms ? hipObj::v2::clockSource().nowMs() + srv->opts.t_prep_ms : 0; + rs.created_ms = hipObj::v2::clockSource().nowMs(); /* The session record carries its own id copy: reap logging and the * terminal teardown record read core.id, while the map key is the * only other place the id lives. */ @@ -757,6 +761,44 @@ int rc_session_info(rc_server *srv, rc_str_in session_id, return RC_OK; } +int rc_server_sessions_snapshot(rc_server *srv, rc_snapshot_cb cb, + void *ctx) { + if (!srv || !cb) return RC_E_ARG; + if (srv->closing.load()) return RC_E_INTERNAL; + + /* Fixed records so the vector can move without invalidating the + * string pointers inside; the copies own everything the callback + * reads, so delivery happens outside the map lock and cannot race + * the reaper moving or erasing entries. */ + std::vector recs; + uint64_t now = hipObj::v2::clockSource().nowMs(); + { + std::lock_guard g(srv->map_mtx); + recs.reserve(srv->sessions_map.size()); + for (const auto &kv : srv->sessions_map) { + const RcSession &s = *kv.second; + rc_session_snapshot r{}; + if (s.core.id.size() != 32) continue; /* live ids are 32 hex */ + if (s.core.op.size() >= sizeof(r.op)) continue; + if (s.core.target.size() > sizeof(r.target) - 1) continue; + memcpy(r.session_id, s.core.id.data(), 32); + r.session_id[32] = 0; + memcpy(r.op, s.core.op.data(), s.core.op.size()); + r.op[s.core.op.size()] = 0; + memcpy(r.target, s.core.target.data(), s.core.target.size()); + r.target[s.core.target.size()] = 0; + r.target_len = (uint32_t)s.core.target.size(); + r.state = (uint8_t)s.core.state; + if (s.reap_pending) r.state |= RC_SNAPSHOT_REAP_PENDING; + r.age_ms = s.created_ms ? now - s.created_ms : 0; + r.staging_bytes = s.staging_len; + recs.push_back(r); + } + } + for (const auto &r : recs) cb(&r, ctx); + return RC_OK; +} + int rc_ready_transfer(rc_server *srv, const rc_ready_req *req, rc_ready_resp *resp) { if (!srv || !req || !resp) return RC_E_ARG; diff --git a/cuwrapper/rc/rc_server_abi.h b/cuwrapper/rc/rc_server_abi.h index 4606b1a5..f852c19b 100644 --- a/cuwrapper/rc/rc_server_abi.h +++ b/cuwrapper/rc/rc_server_abi.h @@ -151,6 +151,30 @@ typedef struct { uint8_t op; } rc_session_info_resp; +/* Session observability snapshot. The record is a point-in-time copy; + * the fields are valid only inside the callback invocation. + * state combines the session state machine value with the reap-pending + * marker (RC_SNAPSHOT_REAP_PENDING) because CANCEL and expiry only set + * that marker without moving the state. */ +enum { RC_SNAPSHOT_REAP_PENDING = 0x80 }; + +typedef struct { + char session_id[33]; /* 32 hex + NUL */ + char op[4]; /* "GET" or "PUT" */ + char target[2048]; + uint32_t target_len; + uint8_t state; /* SessState value | RC_SNAPSHOT_REAP_PENDING */ + uint64_t age_ms; /* now - created_ms; 0 when the clock is absent */ + uint64_t staging_bytes; +} rc_session_snapshot; + +/* Copies every live session under the map lock into fixed records and + * invokes cb(rec, ctx) once per record outside the lock, in map order. + * A session whose op or target does not fit is skipped (record too + * small), not truncated. Returns RC_E_ARG for null arguments. */ +typedef void (*rc_snapshot_cb)(const rc_session_snapshot *rec, void *ctx); +int rc_server_sessions_snapshot(rc_server *srv, rc_snapshot_cb cb, void *ctx); + /* Lifecycle. destroy waits for active calls and the reaper. */ int rc_server_init(const rc_device_opts *opts, rc_server **out); void rc_server_destroy(rc_server *srv);