Class ::ns::stats::Collector (public)
::nx::Class ::ns::stats::Collector![[i]](/resources/acs-subsite/ZoomIn16.gif)
Defined in
- Testcases:
-
No testcase defined.
Source code:
namespace eval ::ns::stats {}
::nsf::object::alloc ::nx::Class ::ns::stats::Collector {}
::ns::stats::Collector public method cpuCapacity {} {
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]
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 $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 {}}} {
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}} {
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 {}}} {
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]
}
::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
[
hide source ]
| [
make this the default ]