Class ::ns::stats::Collector (public)
::nx::Class ::ns::stats::Collector
Defined in /usr/local/ns/lib/nsf2.5.0/lib/nx-traits.tcl
- Testcases:
- No testcase defined.
Source code: namespace eval ::ns::stats {} ::nsf::object::alloc ::nx::Class ::ns::stats::Collector {} ::ns::stats::Collector public method 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. # # @return Dictionary of visible CPU ceilings, effectiveCores, constraint completeness, # cumulative cgroup throttling counters and limitations; unavailable values are # empty. # 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 [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 $result } ::ns::stats::Collector protected method cpuListCount text { set ranges {} foreach item [split $text ,] { if {![regexp {^([0-9]+)(?:-([0-9]+))?$} [string trim $item] -> first last]} { error "Invalid CPU list" } if {$last eq ""} { set last $first } if {$last < $first} { error "Invalid CPU range" } lappend ranges [list $first $last] } set count 0; set end -1 foreach range [lsort -integer -index 0 $ranges] { lassign $range first last if {$last > $end} { incr count [expr {$last-max($first,$end+1)+1}] set end $last } } return $count } ::ns::stats::Collector public method describe {{-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. # # @param server Optional virtual-server name; empty selects all configured servers. # Process and driver information remain process-wide. # @return Topology dictionary with schemaVersion, scope, process, servers, drivers and # availability information. # 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] } ::ns::stats::Collector protected method mountPath value { return [string map [list {\040} " " {\011} "\t" {\134} "\\"] $value] } ::ns::stats::Collector protected method selectServer {record server {validate true}} { # Restrict virtual-server data; historical records may refer to removed servers. if {$server ne ""} { if {$validate && $server ni [ns_info servers]} { error "unknown server: $server" } set selected {} if {[dict exists $record servers $server]} { dict set selected $server [dict get $record servers $server] } dict set record servers $selected } return $record } ::ns::stats::Collector public method snapshot {{-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. # # @param server Optional virtual-server name; empty selects all configured servers. # Process metrics remain process-wide. # @return Timestamped dictionary of gauges, cumulative counters, thread CPU counters, # units and availability; timestamp is in epoch microseconds. # 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}] # Optional probes must not prevent publication of core diagnostics. set linuxNetwork {} if {[catch {::ns::stats::linuxNetworkSnapshot} linuxNetwork]} {set linuxNetwork {}} dict set result linuxNetwork $linuxNetwork dict set result availability linuxNetwork [expr {$linuxNetwork ne ""}] 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] } ::ns::stats::Collector protected method systemText path { set f [open $path r] try {return [string trim [read $f 1048576]]} finally {close $f} }XQL Not present: Generic, PostgreSQL, Oracle
![[i]](/resources/acs-subsite/ZoomIn16.gif)