nsmcp::AccessDiagnosticTool method page (public)

 <instance of nsmcp::AccessDiagnosticTool[i]> page context arguments \
    [ floor ] [ aggregationVar ]

Defined in /usr/local/ns/tcl/nsmcp/traffic-tools.tcl

Parameters:
context (required)
arguments (required)
floor (optional, defaults to "0")
aggregationVar (optional)

Testcases:
No testcase defined.
Source code:
if {$aggregationVar ne ""} {upvar 1 $aggregationVar traffic}
set options [:options $arguments]
set server [$context server]
set peers [dict get $options includePeerAddresses]
if {$peers} {${:authorizer} require $context diagnostics.logs.peers.read}
set redactor [expr {$peers ? ${:peerRedactor} : ${:redactor}}]
set result [list server string $server from number [dict get $options from] to number [dict get $options to]]
foreach key {path authenticationState} {if {[dict exists $options $key]} {lappend result $key string [dict get $options $key]}}
set snapshot {}; set maps {}; set mappingIdentity {}
set lookupMode fallback-rule-membership
set routeCache {}
if {${:operation} eq "bot"} {
    ${:authorizer} require $context diagnostics.runtime.read
    set snapshot [${:routing} snapshot $server]
    set target [dict get $options pool]
    if {![dict exists $snapshot pools $target]} {
        return [list {} object [concat $result [list available boolean false reason string {Effective target-pool mappings unavailable.}]]]
    }
    set maps [dict get $snapshot pools $target]
    if {[${:routing} contextSupported $server]} {set lookupMode native-context}
    set category [dict get $options category]
    if {$category ne "" && $category ne "unknown"} {
        set nativeCategory [expr {$category in {
            native-target-in-target native-target-other-pool-candidate
            bot-like-routed-elsewhere-heuristic native-other-pool
        }}]
        if {$nativeCategory != ($lookupMode eq "native-context")} {
            return -code error -errorcode {NSMCP RPC -32602}  "Category '$category' is not produced by lookupMode '$lookupMode'; select a category for the current mode."
        }
    }
    set mappingIdentity [ns_crypto::md string -digest sha256 -- [list [dict get $snapshot pools] $lookupMode]]
    if {[dict exists $options mappingIdentity] && [dict get $options mappingIdentity] ne $mappingIdentity} {error "Effective mappings changed; start a new analysis"}
    lappend result pool string $target lookupMode string $lookupMode mappingIdentity string $mappingIdentity {*}[${:routing} encodeSnapshot $snapshot]
}
if {${:operation} eq "slow"} {
    set thresholds {}
    foreach key {slowlogconntime slowlogfiltertime slowlogqueuetime} {
        set value [ns_config ns/server/$server/module/[dict get $options module] $key {}]
        lappend thresholds $key string $value
    }
    lappend result configuredThresholds object $thresholds
}
set path [:source $server $options]
if {$path eq "" || ![file isfile $path] || ![file readable $path]} {
    return [list {} object [concat $result [list available boolean false reason string {Configured log is missing, disabled or unreadable.}]]]
}
set path [file normalize $path]
file stat $path before
set channel [open $path rb]
try {
    set prefix [read $channel [expr {min(256,$before(size))}]]
    set identity [ns_crypto::md string -digest sha256 -- [list $path $before(dev) $before(ino) $prefix]]
    set sourceBytes $before(size); set end $sourceBytes
    if {[dict exists $options beforeOffset]} {
        if {$identity ne [dict get $options sourceIdentity] || $sourceBytes<[dict get $options sourceBytes]} {error "Log rotated or truncated; start a new query"}
        set sourceBytes [dict get $options sourceBytes]
        set end [dict get $options beforeOffset]
    }
    set start [expr {max($floor,$end-${:maxReadBytes})}]
    seek $channel $start
    set bytes [read $channel [expr {$end-$start}]]
    set discarded 0
    if {$start>0} {
        seek $channel [expr {$start-1}]
        if {[read $channel 1] ne "\n"} {
            set newline [string first \n $bytes]
            if {$newline>=0} {set discarded [expr {$newline+1}]} else {set discarded [string length $bytes]}
        }
    }
    file stat $path after
    if {[list $before(dev) $before(ino)] ne [list $after(dev) $after(ino)] || $after(size)<$sourceBytes} {error "Log changed during read; start a new query"}
} finally {close $channel}
set position [expr {$start+$discarded}]
set cursor $position
if {$cursor==$end && $start>0} {set cursor $start}
set bytes [string range $bytes $discarded end]
set pending [expr {$bytes ne "" && [string index $bytes end] ne "\n"}]
set records {}; set groups {}; set totals {}; set matches 0; set unrecognized 0; set oversized 0; set missingUA 0; set missingPool 0
set selectedMatches 0
foreach line [lrange [split $bytes \n] 0 end-1] {
    set offset $position
    incr position [expr {[string length $line]+1}]
    if {[string length $line]>65536} {incr oversized; continue}
    set record [::nsmcp::accessRecordParser parse [encoding convertfrom utf-8 [string trimright $line \r]]]
    if {$record eq ""} {incr unrecognized; continue}
    set at [dict get $record startTime]
    if {$at<[dict get $options from] || $at>=[dict get $options to]} continue
    if {[dict exists $options path] && [dict get $record path] ne [dict get $options path]} continue
    if {[dict exists $options authenticationState] && [dict get $record authenticationState] ne [dict get $options authenticationState]} continue
    incr matches
    dict set record offset $offset; dict set record endOffset $position
    if {![dict exists $record userAgent]} {incr missingUA}
    if {[dict get $record pool] eq "unknown"} {incr missingPool}
    if {${:operation} eq "bot"} {
        if {$lookupMode eq "native-context"} {
            set routeKey [list [dict get $record method] [dict get $record path]  [dict get $record peerAddress] [dict get $record pool]  [dict exists $record userAgent] [expr {[dict exists $record userAgent] ? [dict get $record userAgent] : ""}]]
            if {[dict exists $routeCache $routeKey]} {
                set classification [dict get $routeCache $routeKey]
            } else {
                set classification [:nativeClassify $server $record $snapshot [dict get $options pool]]
                if {[dict size $routeCache]<4096} {dict set routeCache $routeKey $classification}
            }
        } else {
            set classification [:classify $record $maps [dict get $options pool]]
            dict set classification lookupMode fallback-rule-membership
            dict set classification expectedPool unknown
            dict set classification contextComplete false
            dict set classification lookupError {}
        }
        set category [dict get $classification category]
        dict incr totals $category
        if {[dict get $options category] ne "" && [dict get $options category] ne $category} {continue}
        incr selectedMatches
        if {$aggregationVar ne ""} {:accumulateTraffic traffic $record}
        # Group only after peer and URL redaction. Omitted groups still
        # contribute to category totals and omittedRecords.
        set record [$redactor record $record]
        if {[dict exists $record userAgent]} {dict set record userAgent [$redactor message [dict get $record userAgent]]}
        set record [dict merge $record $classification]
        set key [list [dict get $record category] [dict get $record method] [dict get $record path] [dict get $record pool] [dict get $record status]]
        lappend key [expr {[dict exists $record userAgent] ? [dict get $record userAgent] : "unknown"}]
        lappend key [dict get $record expectedPool] [dict get $record contextComplete] [dict get $record lookupError]
        if {$peers} {lappend key [dict get $record peerAddress]}
        if {![dict exists $groups $key]} {
            if {[dict size $groups]>=[dict get $options limit]} {
                set priority [:evidencePriority $category]
                set victim {}
                set worst $priority
                dict for {existingKey existingGroup} $groups {
                    set rank [:evidencePriority [dict get $existingGroup category]]
                    if {$rank>$worst} {
                        set worst $rank
                        set victim $existingKey
                    }
                }
                if {$victim eq ""} {continue}
                dict unset groups $victim
            }
            set group [dict create method [dict get $record method] path [$redactor queryValues [dict get $record path]] pool [dict get $record pool] status [dict get $record status] userAgent [lindex $key 5] category [dict get $record category] matchingRuleIndexes [dict get $record matchingRuleIndexes] unknownRules [dict get $record unknownRules] sampleRequestIdentifier [dict get $record requestIdentifier] firstAt $at lastAt $at count 0 runTotal 0 runMax 0 timingCount 0]
            foreach field {lookupMode expectedPool contextComplete lookupError} {
                dict set group $field [dict get $record $field]
            }
            if {$peers} {dict set group peerAddress [dict get $record peerAddress]}
            dict set groups $key $group
        }
        set group [dict get $groups $key]; dict incr group count
        dict set group firstAt [expr {min([dict get $group firstAt],$at)}]
        dict set group lastAt [expr {max([dict get $group lastAt],$at)}]
        if {[dict exists $record runTime]} {dict incr group timingCount; dict set group runMax [expr {max([dict get $group runMax],[dict get $record runTime])}]; dict set group runTotal [expr {[dict get $group runTotal]+[dict get $record runTime]}]}
        dict set groups $key $group
    } else {
        set record [$redactor record $record]
        if {[dict exists $record userAgent]} {dict set record userAgent [$redactor message [dict get $record userAgent]]}
        lappend records $record
        set records [lrange $records end-[expr {[dict get $options limit]-1}] end]
    }
}
if {${:operation} eq "bot"} {
    set latest [${:routing} snapshot $server]
    set latestIdentity [ns_crypto::md string -digest sha256 -- [list [dict get $latest pools] $lookupMode]]
    if {$latestIdentity ne $mappingIdentity} {error "Effective mappings changed during analysis; start a new query"}
}
set entries {}; set i 0
if {${:operation} eq "bot"} {
    set ordered {}
    dict for {key group} $groups {
        lappend ordered [list [:evidencePriority [dict get $group category]] $group]
    }
    # The common output loop reverses records for slow-log chronology.
    set records {}
    foreach pair [lsort -integer -index 0 $ordered] {lappend records [lindex $pair 1]}
    set records [lreverse $records]
} else {
    set selectedMatches $matches
}
set returnedRecords {}; set resultBytes 0
foreach record [lreverse $records] {
    set fields [:recordFields $record]
    set bytes [string length [ns_json value -- [list {} object $fields]]]
    if {$resultBytes+$bytes>65536} {
        continue
    }
    incr resultBytes $bytes
    lappend returnedRecords $record
    lappend entries $i object $fields
    incr i
}
set records $returnedRecords
set counts {}; dict for {key value} $totals {lappend counts $key number $value}
set omittedCounts {}
set returnedMatches [llength $records]
if {${:operation} eq "bot"} {
    set remaining {}
    dict for {category value} $totals {
        if {[dict get $options category] eq "" || [dict get $options category] eq $category} {
            dict set remaining $category $value
        }
    }
    set returnedMatches 0
    foreach record $records {
        set count [dict get $record count]
        incr returnedMatches $count
        dict incr remaining [dict get $record category] [expr {-$count}]
    }
    dict for {category value} $remaining {lappend omittedCounts $category number $value}
}
set next {}
if {$cursor>$floor} {
    foreach key {from to limit pool module includePeerAddresses mode} {
        lappend next $key [expr {$key eq "includePeerAddresses" ? "boolean" : ($key in {from to limit} ? "number" : "string")}] [dict get $options $key]
    }
    if {${:operation} eq "bot" && [dict get $options category] ne ""} {
        lappend next category string [dict get $options category]
    }
    foreach key {path authenticationState} {if {[dict exists $options $key]} {lappend next $key string [dict get $options $key]}}
    lappend next beforeOffset number $cursor sourceBytes number $sourceBytes sourceIdentity string $identity
    if {$mappingIdentity ne ""} {lappend next mappingIdentity string $mappingIdentity}
}
set evidencePolicy {Newest matching slow-request records within evidence limits.}
if {${:operation} eq "bot"} {
    set evidencePolicy {Routing candidates first, then unknowns, then routine groups; first encounter within priority. Category filtering selects evidence only; categoryTotals still includes all observed records.}
}
lappend result available boolean true sourcePath string $path sourceIdentity string $identity  matches number $matches categoryTotals object $counts records array $entries  selectedCategory string [dict get $options category] selectedMatches number $selectedMatches  filteredOutRecords number [expr {$matches-$selectedMatches}]  omittedRecords number [expr {$selectedMatches-$returnedMatches}] omittedCategoryTotals object $omittedCounts  evidencePolicy string $evidencePolicy  more boolean [expr {$cursor>$floor}] nextArguments object $next  coverage object [list sourceBytes number $sourceBytes examinedFromOffset number [expr {$start+$discarded}] examinedToOffset number $end  unreadEarlierBytes number $cursor pendingFinalRecord boolean $pending unrecognizedRecords number $unrecognized oversizedRecords number $oversized  missingUserAgent number $missingUA missingPool number $missingPool rotationsExamined boolean false windowComplete boolean false  limitations string {Page-local counts; sum disjoint pages. Current file only. Full-window coverage is not certified. Mappings reflect query time, not historical configuration. lookupMode identifies native-context or fallback-rule-membership. Native lookup uses current URL/path precedence and logged User-Agent and valid peer IP, without disclosing peers by default. Unlogged context predicates remain unknown; completeness checks conservatively inspect all same-method rules. Older versions retain root wildcard User-Agent membership analysis, not precedence replay. Bot-like unmatched uses a separate case-insensitive heuristic, not proof of a bot. Slow-log timings are access-log timings, not SQL profiles.}]
return [list {} object $result]
XQL Not present:
Generic, PostgreSQL, Oracle
[ hide source ] | [ make this the default ]
Show another procedure: