- Methods: All Methods Documented Methods Hide Methods
- Source: Display Source Hide Source
- Variables: Show Variables Hide Variables
Class ::ns::stats::Collector
::ns::stats::Collectorcreate ...
Defined in
Class Relations
::nx::Class create ::ns::stats::Collector \ -superclass ::nx::ObjectMethods (to be applied on instances)
cpuCapacity (scripted, public)
<instance of ns::stats::Collector> cpuCapacity
Inspect host logical CPUs, process affinity and visible cgroup v1/v2 CPU quotas, cpusets and throttling counters. effectiveCores is the minimum known ceiling, not reserved or guaranteed CPU capacity. Missing probes remain unknown and are reflected in constraint completeness and reasons. Counters can include other processes or sibling cgroups; do not sum ancestor counters as independent process usage. Does not change limits.
- Returns:
- Dictionary of visible CPU ceilings, effectiveCores, constraint completeness, cumulative cgroup throttling counters and limitations; unavailable values are empty.
- Testcases:
- No testcase defined.
set result {hostLogicalCpus {} affinityCpus {} cpusetCpus {} quotaCores {} effectiveCores {} quotaStatus unknown constraintsComplete false throttlingAvailable false throttlingScope cgroup-hierarchy throttling {} limitations {Visible CPU ceilings are not reserved capacity. Hidden ancestor limits, host contention and CPU shares can reduce availability. Throttling counters are cumulative for each visible cgroup, including other processes; ancestor counters can include sibling groups.}} set reasons {}; set ceilings {} if {![catch {:cpuListCount [:systemText /sys/devices/system/cpu/online]} count] && $count>0} { dict set result hostLogicalCpus $count; lappend ceilings $count } if {![catch {:systemText /proc/self/status} status] && [regexp -line {^Cpus_allowed_list:\s*([^\n]+)} $status -> list] && ![catch {:cpuListCount [string trim $list]} count] && $count>0} { dict set result affinityCpus $count; lappend ceilings $count } else {lappend reasons affinity-unavailable} set memberships {} if {![catch {:systemText /proc/self/cgroup} text]} { foreach line [split $text \n] { if {![regexp {^[0-9]+:([^:]*):(.*)$} $line -> controllers path]} continue if {$controllers eq ""} {dict set memberships unified $path} foreach controller [split $controllers ,] {dict set memberships $controller $path} } } set quotaSeen false; set quotaMissing false; set hierarchySeen false set groups {}; set visited {} if {![catch {:systemText /proc/self/mountinfo} mounts]} { foreach line [split $mounts \n] { set parts [split $line " "] set separator [lsearch -exact $parts -] if {$separator<6} continue set type [lindex $parts [expr {$separator+1}]] if {$type ni {cgroup cgroup2}} continue set root [:mountPath [lindex $parts 3]] set mount [:mountPath [lindex $parts 4]] set controllers [split [lindex $parts [expr {$separator+3}]] ,] set key {}; set cpu false; set cpuset false if {$type eq "cgroup2" && [dict exists $memberships unified]} { set key unified; set cpu true; set cpuset true } else { foreach controller {cpu cpuset} { if {$controller in $controllers && [dict exists $memberships $controller]} { set key $controller if {$controller eq "cpu"} {set cpu true} else {set cpuset true} } } } if {$key eq ""} continue set member [dict get $memberships $key] # Do not traverse outside a visible mount, including namespace # membership paths containing parent components. if {".." in [file split $member]} {lappend reasons cgroup-path-unresolvable; continue} if {$root eq "/"} {set relative [string trimleft $member /] } elseif {$member eq "/" || $member eq $root} {set relative {} } elseif {[string first "$root/" $member]==0} {set relative [string range $member [expr {[string length $root]+1}] end] } else {continue} set directory [file join $mount $relative] set hierarchySeen true while {1} { if {[lsearch -exact $visited [list $directory $cpu $cpuset]]<0} { lappend visited [list $directory $cpu $cpuset] if {$cpu} { if {$type eq "cgroup2"} { set readable [expr {![catch {:systemText $directory/cpu.max} maximum]}] if {$readable && [regexp {^(max|[0-9]+) ([1-9][0-9]*)$} $maximum -> quota period]} { set quotaSeen true if {$quota ne "max" && $quota>0} {lappend ceilings [expr {double($quota)/$period}]; set q [expr {double($quota)/$period}] if {[dict get $result quotaCores] eq "" || $q<[dict get $result quotaCores]} {dict set result quotaCores $q}} } elseif {$directory ne $mount} {set quotaMissing true} } else { if {![catch {:systemText $directory/cpu.cfs_quota_us} quota] && ![catch {:systemText $directory/cpu.cfs_period_us} period] && [string is entier -strict $quota] && [string is entier -strict $period] && $period>0} { set quotaSeen true if {$quota>0} {set q [expr {double($quota)/$period}]; lappend ceilings $q if {[dict get $result quotaCores] eq "" || $q<[dict get $result quotaCores]} {dict set result quotaCores $q}} } else {set quotaMissing true} } if {![catch {:systemText $directory/cpu.stat} counters]} { set row [dict create path $directory] foreach counter [split $counters \n] { lassign $counter name value if {![string is entier -strict $value] || $value<0} continue switch -- $name { nr_periods {dict set row periods $value} nr_throttled {dict set row throttledPeriods $value} throttled_usec {dict set row throttledSeconds [expr {$value/1000000.0}]} throttled_time {dict set row throttledSeconds [expr {$value/1000000000.0}]} } } if {[dict size $row]>1} {dict set groups $directory $row} } } if {$cpuset} { set file [expr {$type eq "cgroup2" ? "cpuset.cpus.effective" : "cpuset.cpus"}] if {![catch {:systemText $directory/$file} list] && $list ne "" && ![catch {:cpuListCount $list} count] && $count>0} { lappend ceilings $count if {[dict get $result cpusetCpus] eq "" || $count<[dict get $result cpusetCpus]} {dict set result cpusetCpus $count} } } } if {$directory eq $mount} break set directory [file dirname $directory] } } } if {!$hierarchySeen} {lappend reasons cgroup-hierarchy-unavailable} if {!$quotaSeen || $quotaMissing} {lappend reasons quota-unavailable-or-partial} dict set result quotaStatus [expr {[dict get $result quotaCores] ne "" ? "limited" : ($quotaSeen && !$quotaMissing ? "unlimited" : "unknown")}] if {[llength $ceilings]} {dict set result effectiveCores [lindex [lsort -real $ceilings] 0]} dict set result constraintsComplete [expr {$reasons eq ""}] dict set result incompleteReasons [lsort -unique $reasons] dict set result throttling $groups dict set result throttlingAvailable [expr {[dict size $groups]>0}] return $resultdescribe (scripted, public)
<instance of ns::stats::Collector> describe [ -server server ]
Return a dictionary describing the NaviServer process, virtual servers, connection pools and network drivers. An optional server argument restricts virtual-server data; process-wide information retains its process scope. Availability fields identify optional information that could not be collected. Does not start sampling or modify the sampling baseline.
- Switches:
- -server (optional)
- Optional virtual-server name; empty selects all configured servers. Process and driver information remain process-wide.
- Returns:
- Topology dictionary with schemaVersion, scope, process, servers, drivers and availability information.
- Testcases:
- No testcase defined.
set result [dict create schemaVersion 1 scope process process [dict create pid [ns_info pid] bootTime [ns_info boottime] version [ns_info patchlevel] host [ns_info hostname] uptimeSeconds [ns_info uptime]] servers {} drivers {} availability {}] if {![catch {ns_info buildinfo} build]} { dict set result process build $build } dict set result process platform [dict create os $::tcl_platform(os) osVersion $::tcl_platform(osVersion) machine $::tcl_platform(machine)] dict set result process cpuCapacity [:cpuCapacity] foreach s [ns_info servers] { set pools {} foreach p [ns_server -server $s pools] { set name [expr {$p eq "" ? "default" : $p}] set threads [ns_server -server $s -pool $p threads] set limits {} foreach k {min max maxconnections} { if {[dict exists $threads $k]} { dict set limits $k [dict get $threads $k] } } dict set pools $name $limits } set entry [dict create pools $pools databasePools [ns_config ns/server/$s/db pools {}] bindings {} availability {}] if {![catch {ns_server -server $s modules} modules]} { dict set entry modules $modules dict set entry availability modules true } else { dict set entry availability modules false } if {![catch {ns_driver info} infos]} { foreach driver $infos { set module [dict get $driver module] set mappings {} set allMappings true if {[catch {ns_config -all ns/module/$module/servers $s {}} mappings]} { set mappings [ns_config ns/module/$module/servers $s {}] set allMappings false } if {$mappings ne "" || [dict get $driver server] eq $s} { dict set entry bindings $module [dict create address [dict get $driver address] port [dict get $driver port] protocol [dict get $driver protocol] hostMappings $mappings hostMappingsComplete $allMappings] } } } dict set result servers $s $entry } if {![catch {ns_driver info} info]} { set drivers {} foreach driver $info { set entry {} foreach k { module type server location address port defaultport protocol sendwait recvwait recvbufsize sendbufsize libraryversion } { if {[dict exists $driver $k]} { dict set entry $k [dict get $driver $k] } } lappend drivers $entry } dict set result drivers $drivers dict set result availability drivers true } else { dict set result availability drivers false } return [:selectServer $result $server]snapshot (scripted, public)
<instance of ns::stats::Collector> snapshot [ -server server ]
Collect current gauges, cumulative counters, allocator statistics and thread CPU times into a timestamped dictionary. The timestamp is in epoch microseconds; collectionSeconds records collection duration. The observations are collected sequentially and do not constitute an atomic snapshot of the entire process. An optional server argument restricts virtual-server data. Does not publish a sample or advance the sampling baseline.
- Switches:
- -server (optional)
- Optional virtual-server name; empty selects all configured servers. Process metrics remain process-wide.
- Returns:
- Timestamped dictionary of gauges, cumulative counters, thread CPU counters, units and availability; timestamp is in epoch microseconds.
- Testcases:
- No testcase defined.
set start [clock microseconds] set result [dict create schemaVersion 1 scope process process [dict create pid [pid] bootTime [ns_info boottime]] drivers {} servers {} cpu {} availability {} units {time seconds cpuCounter microseconds cpuPercent percentOfOneCore}] dict set result process cpuCapacity [:cpuCapacity] if {![catch {ns_driver stats} stats]} { foreach entry $stats { set counters {} foreach key {received partial spooled errors} { dict set counters $key [dict get $entry $key] } set socketStates [dict create reading [dict get $entry reading] waitingForConnection [dict get $entry waiting] closing [dict get $entry closing]] dict set result drivers [dict get $entry thread] [dict create thread [dict get $entry thread] module [dict get $entry module] counters $counters socketStates $socketStates] } dict set result availability drivers true } else { dict set result availability drivers false } foreach s [ns_info servers] { foreach p [ns_server -server $s pools] { set name [expr {$p eq "" ? "default" : $p}] dict set result servers $s pools $name [dict create counters [ns_server -server $s -pool $p stats] gauges [dict create threads [ns_server -server $s -pool $p threads] waitingRequests [ns_server -server $s -pool $p waiting] activeRequests [llength [ns_server -server $s -pool $p active]]]] } } set cpu {} if {![catch {ns_info threadcputimes} times]} { foreach t [ns_info threads] { set tid [lindex $t 7] if {$tid eq "" || ![dict exists $times $tid]} { continue } set identity [list $tid [lindex $t 4]] dict set cpu $identity [dict merge [dict get $times $tid] [dict create name [lindex $t 0]]] } } set memory {} if {![catch {ns_info meminfo} info]} { foreach k { current_allocated_bytes heap_size total_physical_bytes slack_bytes pageheap_free_bytes pageheap_unmapped_bytes pageheap_committed_bytes central_cache_free_bytes transfer_cache_free_bytes thread_cache_free_bytes current_total_thread_cache_bytes } { if {[dict exists $info $k]} { dict set memory $k [dict get $info $k] } } } dict set result process allocatorBytes $memory dict set result availability allocatorMemory [expr {$memory ne ""}] dict set result cpu $cpu dict set result availability cpu [expr {$cpu ne ""}] if {![catch {ns_db stats} dbstats]} { dict set result databasePools $dbstats dict set result availability databasePools true } else { dict set result availability databasePools false } dict set result timestamp [clock microseconds] dict set result collectionSeconds [expr {([dict get $result timestamp]-$start)/1000000.0}] return [:selectServer $result $server]
- Methods: All Methods Documented Methods Hide Methods
- Source: Display Source Hide Source
- Variables: Show Variables Hide Variables
![[i]](/resources/acs-subsite/ZoomIn16.gif)