Class ::nsmcp::AccessCardinalityStore

::nsmcp::AccessCardinalityStore[i] create ... \
           [ -agentPending (default "0") ] \
           [ -attached (default "false") ] \
           [ -buffer (default "") ] \
           [ -checkpoint:required checkpoint:required ] \
           [ -data data ] \
           [ -db (default "") ] \
           [ -directory:required directory:required ] \
           [ -events (default "0") ] \
           [ -handle (default "") ] \
           [ -limits:required limits:required ] \
           [ -peerPending (default "0") ] \
           [ -readResults:boolean (default "false") ]

Defined in /usr/local/ns/tcl/nsmcp/log-traffic-aggregate.tcl

Class Relations

  • class: ::nx::Class[i]
  • superclass: ::nsmcp::Object[i]
::nx::Class create ::nsmcp::AccessCardinalityStore \
     -superclass ::nsmcp::Object

Methods (to be applied on instances)

  • add (scripted, public)

     <instance of nsmcp::AccessCardinalityStore[i]> add row redactor

    Accumulate peer and user-agent observations from a request. Apply the redactor and configured capacity limits before buffering distinct-observation updates.

    Parameters:
    row (required)
    Parsed access-request record.
    redactor (required)
    Redactor object applied before returning diagnostic evidence.
    Returns:
    The observation update result.

    Testcases:
    No testcase defined.
    set peer [dict get $row peerAddress]
    set knownPeer [expr {$peer ni {{} - {[REDACTED]}}}]
    set knownAgent [expr {[dict exists $row userAgent] && [dict get $row userAgent] ni {{} -}}]
    if {[dict get ${:data} status] ne "active"} {
        if {$knownPeer} {
            dict incr :data peerOmitted
        }
        if {$knownAgent} {
            dict incr :data agentOmitted
        }
        return
    }
    if {$knownPeer} {
        set key [ns_crypto::md string -digest sha256 -- $peer]
        :addKey 0 $key {} 0 0
        incr :peerPending
    }
    if {$knownAgent} {
        set agent [dict get $row userAgent]
        set key [ns_crypto::md string -digest sha256 -- $agent]
        set label [$redactor message $agent]
        set redacted [expr {$label ne $agent}]
        set bytes [encoding convertto utf-8 $label]
        set clipped [expr {[string length $bytes]>1024}]
        if {$clipped} {
            set label [encoding convertfrom utf-8 [string range $bytes 0 1023]]
            while {[string length [encoding convertto utf-8 $label]]>1024} {
                set label [string range $label 0 end-1]
            }
            dict incr :data labelTruncatedRecords
        }
        if {$redacted} {
            dict incr :data labelRedactedRecords
        }
        :addKey 1 $key $label $clipped $redacted
        incr :agentPending
    }
    incr :events
    if {${:events}>=512} {
        :flush
    }
  • close (scripted, public)

     <instance of nsmcp::AccessCardinalityStore[i]> close

    Release cardinality-store resources. Destroy the accumulator and allow its destructor to flush or close owned resources.

    Returns:
    The object destruction result.

    Testcases:
    No testcase defined.
    :destroy
  • flush (scripted, public)

     <instance of nsmcp::AccessCardinalityStore[i]> flush

    Persist buffered cardinality observations. Write pending observations to the optional cache and retain explicit capacity or storage failure state.

    Returns:
    The flush result, or an empty result when no observations are pending.

    Testcases:
    No testcase defined.
    if {${:events}==0} {
        return
    }
    if {${:buffer} ne "" && ${:db} ne "" && [dict get ${:data} status] eq "active"} {
        set values {}
        dict for {id row} ${:buffer} {
            lappend values "([ns_dbquotelist [list [dict get $row kind] [dict get $row k] [dict get $row label] [dict get $row clipped] [dict get $row redacted] [dict get $row n]]])"
        }
        try {
            ns_db dml ${:handle} BEGIN
            # Values are quoted individually. Do not cache this bounded
            # batch as a distinct SQL template in DatabaseSession.
            ns_db dml ${:handle} "INSERT INTO nsmcp_access_aggregate.counts VALUES [join $values ,] ON CONFLICT(kind,k) DO UPDATE SET n=n+excluded.n"
            if {[:diskBytes]>[dict get ${:data} maxBytes]} {
                error "aggregation-disk-limit"
            }
            ns_db dml ${:handle} COMMIT
        } on error {message options} {
            set exception [ns_db exception ${:handle}]
            # Closing the native connection rolls back atomically even
            # when SQLite FULL has already ended the transaction. Avoid a
            # speculative ROLLBACK that logs a second spurious error.
            set disk [expr {$message eq "aggregation-disk-limit" || [string match -nocase {*full*} $exception]}]
            :fail write-batch [expr {$disk ? "aggregation-disk-limit" : "sqlite-write-failed"}] $message $options [expr {$disk ? "limited" : "unavailable"}]
            :disconnect true
            dict incr :data peerOmitted ${:peerPending}
            dict incr :data agentOmitted ${:agentPending}
        }
    }
    set :buffer {}
    set :events 0
    set :peerPending 0
    set :agentPending 0
    dict set :data diskBytes [:diskBytes]
  • readable (scripted, public)

     <instance of nsmcp::AccessCardinalityStore[i]> readable

    Report whether the cardinality cache owns a readable database session. This is a current-state check, not a new pool checkout.

    Returns:
    True when a database session is present, otherwise false.

    Testcases:
    No testcase defined.
    return [expr {${:db} ne {}}]
  • results (scripted, public)

     <instance of nsmcp::AccessCardinalityStore[i]> results limit \
        expectedPeers expectedAgents

    Build bounded peer and agent cardinality evidence. Flush observations and compare observed diversity with expected counts to report lower bounds and omissions.

    Parameters:
    limit (required)
    Maximum number of records or entries to return.
    expectedPeers (required)
    Expected peer observation count used to assess completeness.
    expectedAgents (required)
    Expected user-agent observation count used to assess completeness.
    Returns:
    The cardinality result dictionary.

    Testcases:
    No testcase defined.
    :flush
    set result [dict create backend sqlite status [dict get ${:data} status] reason [dict get ${:data} reason]  maxBytes [dict get ${:data} maxBytes] diskBytes [:diskBytes] peakBufferedKeys [:dict getdef ${:data} peakBufferedKeys 0] peerOmitted [dict get ${:data} peerOmitted] agentOmitted [dict get ${:data} agentOmitted]  labelTruncatedRecords [dict get ${:data} labelTruncatedRecords] labelRedactedRecords [dict get ${:data} labelRedactedRecords]]
    foreach key {failure poolWaitCount poolWaitMilliseconds poolWaitLimitMilliseconds} {
        dict set result $key [dict get ${:data} $key]
    }
    foreach {field kind} {peers 0 agents 1} {
        set rows {}
        set totals [dict create distinct 0 records 0 top1 0 top10 0 top100 0 rankedRecords 0 clippedRecords 0 redactedRecords 0]
        if {${:db} ne "" && [catch {
            ${:db} execute -arrayName r -script {
                dict set totals distinct $r(distinctCount)
                dict set totals records $r(records)
                dict set totals clippedRecords $r(clippedRecords)
                dict set totals redactedRecords $r(redactedRecords)
            } "SELECT count(*) AS distinctCount,coalesce(sum(n),0) AS records,coalesce(sum(n*clipped),0) AS clippedRecords,coalesce(sum(n*redacted),0) AS redactedRecords FROM nsmcp_access_aggregate.counts WHERE kind=$kind"
            set i 0
            ${:db} execute -arrayName r -script {
                incr i
                if {$i==1} {
                    dict set totals top1 $r(n)
                }
                if {$i<=10} {
                    dict incr totals top10 $r(n)
                }
                dict incr totals top100 $r(n)
                if {$i<=$limit} {
                    dict incr totals rankedRecords $r(n)
                    lappend rows [dict create value $r(label) count $r(n) valueTruncated $r(clipped) valueRedacted $r(redacted)]
                }
            } "SELECT label,clipped,redacted,n FROM nsmcp_access_aggregate.counts WHERE kind=$kind ORDER BY n DESC,k LIMIT 100"
        } readError readOptions]} {
            :fail read-results sqlite-read-failed $readError $readOptions
            foreach key {status reason failure} {
                dict set result $key [dict get ${:data} $key]
            }
            set rows {}
            set totals {distinct 0 records 0 top1 0 top10 0 top100 0 rankedRecords 0 clippedRecords 0 redactedRecords 0}
            :disconnect true
        }
        dict set result $field $rows
        dict set result ${field}Totals $totals
    }
    dict set result labelTruncatedRecords [dict get $result agentsTotals clippedRecords]
    dict set result labelRedactedRecords [dict get $result agentsTotals redactedRecords]
    if {[dict get $result peersTotals records]+[dict get $result peerOmitted]!=$expectedPeers
        || [dict get $result agentsTotals records]+[dict get $result agentOmitted]!=$expectedAgents} {
        if {[dict get $result status] ne "unavailable"} {
            dict set result reason aggregation-count-mismatch
        }
        dict set result status unavailable
        foreach {field expected omitted} [list peers $expectedPeers peerOmitted agents $expectedAgents agentOmitted] {
            dict set result $field {}
            dict set result ${field}Totals {distinct 0 records 0 top1 0 top10 0 top100 0 rankedRecords 0 clippedRecords 0 redactedRecords 0}
            dict set result $omitted $expected
        }
    }
    return $result
  • snapshot (scripted, public)

     <instance of nsmcp::AccessCardinalityStore[i]> snapshot

    Return current cardinality accumulator state. Flush pending observations before reading the stored counters and completeness flags.

    Returns:
    The cardinality state dictionary.

    Testcases:
    No testcase defined.
    :flush
    return ${:data}