ns::stats::SQLiteHistory method history save (public)
<instance of ns::stats::SQLiteHistory> history save
Persist the latest published sample if history is enabled, no write is in progress and its timestamp has not already been saved. Claim the write under the shared coordination lock, then perform database operations outside that lock. Store the compressed sample and associated topology in one transaction, deduplicating samples by timestamp. Apply age and storage-budget eviction, retain coverage events and remove topology no longer referenced by retained samples. On failure, roll back the transaction, retain the pending sample for retry and update failure status. Repeated identical warnings are throttled. A subsequent successful write records the failed interval for coverage reporting. Release the database handle and clear the write claim on completion. Does not collect observations or advance the sampling baseline; callers inspect history status for the persistence outcome.
- Testcases:
- No testcase defined.
Source code: set config {} set generation {} set stamp {} :locked { set candidate [nsv_get ns_stats history] set sample [nsv_get ns_stats pair] if {$candidate ne "" && [dict get $candidate enabled] && ![dict get $candidate busy] && $sample ne ""} { set stamp [dict get $sample timestamp] if {$stamp ne [dict get $candidate lastSaved]} { set config $candidate set generation [dict get $config generation] dict set candidate busy true nsv_set ns_stats history $candidate } } } if {$config eq ""} { return } set db {} set transaction false set ok false set errorMessage {} try { # Collection and SQLite work happen outside the shared control mutex. set now [clock seconds] set topology [dict get $config topology] if {$topology eq "" || $now-[dict get $config topologyAt]>=60} { set topology [:describe] foreach k {pid bootTime uptimeSeconds} {dict unset topology process $k} dict set config topology $topology dict set config topologyAt $now } # Stable Tcl serialization is enough for a private cache key; store # the full topology in the table to detect the unlikely CRC collision. set topologyId [format %08x [zlib crc32 [encoding convertto utf-8 $topology]]] set record $sample dict unset record raw dict unset record cpu dict set record topologyId $topologyId set payload [encoding convertto utf-8 $record] set rawSize [string length $payload] if {$rawSize > 1048576} { error "history sample exceeds 1 MiB" } set packed [::ns::stats::pack $record] set packedTopology [::ns::stats::pack $topology] set size [expr {[string length $packed]+[string length $packedTopology]}] set db [:historyConnect $config] :historySchema $db ::ns::stats::sql $db {BEGIN IMMEDIATE} set transaction true set cutoff [expr {([clock seconds]-[dict get $config retention])*1000000}] :historyDrop $db "at < $cutoff" age set page [::ns::stats::scalar $db {PRAGMA page_size}] set max [::ns::stats::scalar $db {PRAGMA max_page_count}] while {1} { set used [expr {([::ns::stats::scalar $db {PRAGMA page_count}] - [::ns::stats::scalar $db {PRAGMA freelist_count}]) * $page}] if {$used+3*$size < $max*$page*0.8} { break } if {![::ns::stats::scalar $db {SELECT count(*) FROM ns_stats_samples}]} { error "history metadata or sample exceeds storage budget" } :historyDrop $db {at IN (SELECT at FROM ns_stats_samples ORDER BY at LIMIT 100)} size } set stored [::ns::stats::scalar $db "SELECT payload FROM ns_stats_topology WHERE id=[::ns::stats::quote $topologyId]"] if {$stored ne "" && [::ns::stats::unpack $stored] ne $topology} { error "history topology fingerprint collision" } ::ns::stats::sql $db "INSERT OR IGNORE INTO ns_stats_topology VALUES([::ns::stats::quote $topologyId],[::ns::stats::quote $packedTopology])" ::ns::stats::sql $db "INSERT OR IGNORE INTO ns_stats_samples VALUES($stamp,[::ns::stats::quote [dict get $record instance]],[::ns::stats::quote $topologyId],[::ns::stats::quote $packed])" foreach gap [dict get $config gaps] { lassign $gap first last n ::ns::stats::sql $db "INSERT INTO ns_stats_events(at,first,last,n,reason) VALUES($stamp,$first,$last,$n,'write-failure')" } ::ns::stats::sql $db {DELETE FROM ns_stats_events WHERE id NOT IN (SELECT id FROM ns_stats_events ORDER BY id DESC LIMIT 64)} ::ns::stats::sql $db {DELETE FROM ns_stats_topology WHERE id NOT IN (SELECT DISTINCT topology FROM ns_stats_samples)} ::ns::stats::sql $db {COMMIT} set transaction false set ok true } on error {message options} { set errorMessage $message if {$transaction} { catch {::ns::stats::sql $db {ROLLBACK}} } if {$message ne [dict get $config lastFailure] || [clock seconds]-[dict get $config lastLoggedAt] >= 60} { ns_log warning "ns_stats: history write failed: $message" dict set config lastLoggedAt [clock seconds] } } finally { if {$db ne ""} { ns_db releasehandle $db } :locked { set current [nsv_get ns_stats history] if {$generation eq [dict get $current generation]} { dict set current busy false dict set current lastLoggedAt [dict get $config lastLoggedAt] if {$ok} { dict set current lastSaved $stamp dict set current lastFailure {} dict set current gaps {} dict set current topology [dict get $config topology] dict set current topologyAt [dict get $config topologyAt] } else { dict incr current failures dict set current lastFailure $errorMessage set gaps [dict get $current gaps] if {$gaps eq ""} { set gaps [list [list $stamp $stamp 1]] } else { lassign [lindex $gaps 0] first last n set gaps [list [list $first $stamp [expr {$n+1}]]] } dict set current gaps $gaps } nsv_set ns_stats history $current } } }XQL Not present: Generic, PostgreSQL, Oracle
![[i]](/resources/acs-subsite/ZoomIn16.gif)