Class ::ns::stats::Sampler (public)
::nx::Class ::ns::stats::Sampler![[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::Sampler {}
::ns::stats::Sampler public method calculate {current previous interval instance} {
set stamp [dict get $current timestamp]
set elapsed {}
set ready false
set reset false
if {$previous ne ""
&& [dict get $previous process pid] eq [dict get $current process pid]
&& [dict get $previous process bootTime] eq [dict get $current process bootTime]
} {
set elapsed [expr {($stamp-[dict get $previous timestamp])/1000000.0}]
if {$elapsed > 0} {
set ready true
} else {
set reset true
}
}
set result $current
dict set result instance $instance
dict set result sampling [dict create ready $ready from {} to [expr {$stamp/1000000.0}] elapsedSeconds $elapsed ageSeconds 0 reset $reset intervalSeconds $interval]
if {$ready} {
dict set result sampling from [expr {[dict get $previous timestamp]/1000000.0}]
}
dict for {s server} [dict get $current servers] {
dict for {p entry} [dict get $server pools] {
set deltas {}
set rates {}
set invalid {}
dict for {k v} [dict get $entry counters] {
if {$k ni {requests queued spools spooled dropped queuetime filtertime runtime tracetime}} {
continue
}
set d {}
if {$ready && [dict exists $previous servers $s pools $p counters $k]} {
set d [:delta $v [dict get $previous servers $s pools $p counters $k]]
if {$d eq ""} {
lappend invalid $k
}
}
dict set deltas $k $d
dict set rates $k [expr {$d eq "" ? "" : $d/$elapsed}]
}
dict set result servers $s pools $p deltas $deltas
dict set result servers $s pools $p rates $rates
dict set result servers $s pools $p resetCounters $invalid
set timings {}
set n [expr {[dict exists $deltas requests] ? [dict get $deltas requests] : ""}]
foreach k {queuetime filtertime runtime tracetime} {
set d [expr {[dict exists $deltas $k] ? [dict get $deltas $k] : ""}]
dict set timings $k [expr {$n eq "" || $n == 0 || $d eq "" ? "" : $d/$n}]
}
dict set result servers $s pools $p timingAverageSeconds $timings
}
}
dict for {thread entry} [dict get $current drivers] {
set deltas {}
set rates {}
set invalid {}
foreach k {received partial spooled errors} {
set d {}
if {$ready && [dict exists $entry counters $k] && [dict exists $previous drivers $thread counters $k]} {
set d [:delta [dict get $entry counters $k] [dict get $previous drivers $thread counters $k]]
if {$d eq ""} {
lappend invalid $k
}
}
dict set deltas $k $d
dict set rates $k [expr {$d eq "" ? "" : $d/$elapsed}]
}
dict set result drivers $thread deltas $deltas
dict set result drivers $thread rates $rates
dict set result drivers $thread resetCounters $invalid
}
set percentages {}
set totals {}
set matched 0
dict for {id entry} [dict get $current cpu] {
set percent {}
if {$ready && [dict exists $previous cpu $id]} {
set old [dict get $previous cpu $id]
set user [:delta [dict get $entry user] [dict get $old user]]
set system [:delta [dict get $entry system] [dict get $old system]]
if {$user ne "" && $system ne ""} {
set percent [expr {100.0*($user+$system)/($elapsed*1000000)}]
incr matched
}
}
set name [string trim [dict get $entry name] -]
set group other
foreach {pattern kind} {driver:* driver conn:* connection writer:* writer spooler:* spooler} {
if {[string match $pattern $name]} {
set group $kind
break
}
}
dict set percentages $id [dict create name $name group $group percent $percent]
if {$percent ne ""} {
set total [expr {[dict exists $totals $group] ? [dict get $totals $group] : 0.0}]
dict set totals $group [expr {$total+$percent}]
}
}
if {[dict exists $current process cpuCapacity throttling]} {
dict for {path row} [dict get $current process cpuCapacity throttling] {
foreach field {periods throttledPeriods throttledSeconds} {
set delta {}
if {$ready && [dict exists $row $field] && [dict exists $previous process cpuCapacity throttling $path $field]} {
set old [dict get $previous process cpuCapacity throttling $path $field]
if {[dict get $row $field]>=$old} {set delta [expr {[dict get $row $field]-$old}]}
}
dict set row delta[string toupper [string index $field 0]][string range $field 1 end] $delta
}
dict set row throttledPeriodPercent {}
if {[dict get $row deltaPeriods] ne "" && [dict get $row deltaPeriods]>0 && [dict get $row deltaThrottledPeriods] ne ""} {
dict set row throttledPeriodPercent [expr {100.0*[dict get $row deltaThrottledPeriods]/[dict get $row deltaPeriods]}]
}
dict set result process cpuCapacity throttling $path $row
}
}
dict set result cpuPercent [dict create scope process matchedThreads $matched observedThreads [dict size [dict get $current cpu]] groups $totals threads $percentages]
return $result
}
::ns::stats::Sampler protected method delta {now:double before:double} {
if {$now < $before} {
return {}
}
expr {$now - $before}
}
::ns::stats::Sampler protected method init {} {
:locked {
if {[nsv_get ns_stats control] eq ""} {
nsv_set ns_stats control [dict create running false interval 10 scheduler {} generation 0 busy false instance "[pid]:[ns_info boottime]:[clock microseconds]" errors 0 lastError {}]
}
}
}
::ns::stats::Sampler protected method locked script {
uplevel 1 [list nsv_array eval ns_stats $script]
}
::ns::stats::Sampler protected method retainSample sample {
set record [dict remove $sample raw]
if {[dict exists $record cpuPercent threads]} {
dict unset record cpuPercent threads
}
set records [nsv_get ns_stats recent]
lappend records $record
set cutoff [expr {[dict get $record timestamp]-900000000}]
set dropped [nsv_get ns_stats recentDroppedThrough]
while {[llength $records]>180
|| ([llength $records]>1 && [dict get [lindex $records 0] timestamp]<$cutoff)} {
set dropped [dict get [lindex $records 0] timestamp]
set records [lrange $records 1 end]
}
nsv_set ns_stats recent $records
nsv_set ns_stats recentDroppedThrough $dropped
}
::ns::stats::Sampler public method sample {{-server {}} {-window 0}} {
if {$server ne "" && $server ni [ns_info servers]} {
error "unknown server: $server"
}
set window [ns_baseunit -time $window]
if {$window < 0 || $window > 900} {
error "display window must be 0..900 seconds"
}
:locked {
set result [nsv_get ns_stats pair]
set recent [expr {$window > 0 ? [nsv_get ns_stats recent] : {}}]
}
if {$result eq ""} {
return [dict create schemaVersion 1 sampling {ready false from {} to {} elapsedSeconds {} ageSeconds {} reset false} servers {} availability {}]
}
if {$window > 0} {
set current [:snapshot]
set target [expr {[dict get $current timestamp]-$window*1000000}]
set baseline {}
set distance Inf
foreach record $recent {
if {[dict get $record timestamp] >= [dict get $current timestamp]
|| [dict get $record instance] ne [dict get $result instance]} {
continue
}
set difference [expr {abs([dict get $record timestamp]-$target)}]
if {$difference < $distance} {
set baseline $record
set distance $difference
}
}
set cadence [dict get $result sampling intervalSeconds]
set reason {}
if {$baseline eq "" || $distance > 0.5*$cadence*1000000} {
set baseline {}
set reason insufficient-history
} else {
set previous $baseline
foreach record $recent {
if {[dict get $record timestamp] <= [dict get $baseline timestamp]
|| [dict get $record timestamp] > [dict get $current timestamp]} {
continue
}
if {[dict get $record instance] ne [dict get $result instance]
|| ![dict get $record sampling ready]
|| [dict get $record timestamp]-[dict get $previous timestamp]
> 1.5*max($cadence,[dict get $previous sampling intervalSeconds])*1000000} {
set reason sampling-gap
set baseline {}
break
}
set previous $record
}
}
if {$baseline ne "" && [dict get $current timestamp]-[dict get $previous timestamp]
> 1.5*$cadence*1000000} {
set baseline {}
set reason sampling-gap
}
if {$baseline ne "" && ![dict exists $baseline cpu]} {
dict set baseline cpu {}
}
set result [:calculate $current $baseline $cadence [dict get $result instance]]
dict set result sampling requestedSeconds $window
dict set result sampling incompleteReason $reason
}
dict set result sampling running [dict get [nsv_get ns_stats control] running]
dict set result sampling ageSeconds [expr {max(0,([clock microseconds]-[dict get $result timestamp])/1000000.0)}]
if {[dict exists $result raw]} {dict unset result raw}
return [:selectServer $result $server]
}
::ns::stats::Sampler public method {sampling history} {-from:integer,required -to:integer,required {-server {}} {-limit:integer 180} {-cursor:integer 0}} {
if {$from<0 || $to<=$from || $to-$from>86400 || $to>[clock seconds]
|| $limit<1 || $limit>180 || $cursor<0} {
error "expected an epoch window up to 24 hours, limit 1..180 and nonnegative cursor"
}
if {[string length $server]>512 || [string first \x00 $server]>=0} {
error "invalid server name"
}
:locked {
set retained [nsv_get ns_stats recent]
set dropped [nsv_get ns_stats recentDroppedThrough]
}
set records {}
set gaps {}
set restarts {}
set resets 0
set previous {}
set first {}
set last {}
set more false
foreach record $retained {
set stamp [dict get $record timestamp]
if {$first eq ""} {
set first $stamp
}
set last $stamp
if {$stamp<$from*1000000 || $stamp<=$cursor} {
set previous $record
continue
}
if {$stamp>=$to*1000000} {
if {$previous ne ""} {
set delta [expr {($stamp-[dict get $previous timestamp])/1000000.0}]
if {$delta>1.5*max([dict get $previous sampling intervalSeconds],
[dict get $record sampling intervalSeconds])} {
lappend gaps [dict create from [dict get $previous sampling to] to [dict get $record sampling to]]
}
}
break
}
if {[llength $records]>=$limit} {
set more true
break
}
if {![dict get $record sampling ready]} {
incr resets
}
if {$previous ne ""} {
if {[dict get $previous instance] ne [dict get $record instance]} {
lappend restarts [dict get $record sampling to]
}
set delta [expr {($stamp-[dict get $previous timestamp])/1000000.0}]
if {$delta>1.5*max([dict get $previous sampling intervalSeconds],
[dict get $record sampling intervalSeconds])} {
lappend gaps [dict create from [dict get $previous sampling to] to [dict get $record sampling to]]
}
}
lappend records [:selectServer [dict remove $record cpu] $server false]
set previous $record
}
if {$retained ne ""} {
set first [dict get [lindex $retained 0] timestamp]
set last [dict get [lindex $retained end] timestamp]
}
set overlap [expr {$dropped ne "" && $dropped>=$from*1000000}]
if {!$more && $cursor>0} {
set window [:sampling history -from $from -to $to -server $server -limit 180]
set coverage [dict get $window coverage]
return [dict create schemaVersion 1 source memory from $from to $to records $records nextCursor {} more false retentionSeconds 900 maxSamples 180 coverage $coverage]
}
set complete [expr {$first ne "" && $first<=$from*1000000 && $last>=$to*1000000
&& !$more && $cursor==0 && !$overlap && $gaps eq ""
&& $restarts eq "" && $resets==0}]
set next {}
if {$more} {
set next [dict get [lindex $records end] timestamp]
}
return [dict create schemaVersion 1 source memory from $from to $to records $records nextCursor $next more $more retentionSeconds 900 maxSamples 180 coverage [dict create complete $complete windowExamined [expr {!$more && $cursor==0}] retainedFrom [expr {$first eq "" ? 0 : $first/1000000.0}] retainedTo [expr {$last eq "" ? 0 : $last/1000000.0}] gaps $gaps restartBoundaries $restarts resetSamples $resets evictionOrFailureOverlap $overlap droppedThrough $dropped]]
}
::ns::stats::Sampler public method {sampling start} {{-interval 10}} {
set interval [ns_baseunit -time $interval]
if {$interval < 1 || $interval > 3600} {
error "interval must be 1..3600 seconds"
}
:locked {
set state [nsv_get ns_stats control]
if {[dict get $state running]} {
if {[dict get $state interval]!=$interval} {
error "sampler already running with a different interval; stop it before reconfiguring"
}
} else {
dict incr state generation
dict set state running true
dict set state interval $interval
dict set state busy false
dict set state scheduler [ns_schedule_proc -thread $interval [self] sampling tick]
nsv_set ns_stats pair {}
nsv_set ns_stats control $state
}
}
return [:sampling status]
}
::ns::stats::Sampler public method {sampling status} {} {
set result [nsv_get ns_stats control]
dict unset result busy
dict unset result generation
set sample [nsv_get ns_stats pair]
dict set result ready [expr {$sample ne "" && [dict get $sample sampling ready]}]
dict set result latestAt {}
dict set result ageSeconds {}
if {$sample ne ""} {
set stamp [dict get $sample timestamp]
dict set result latestAt [expr {$stamp/1000000.0}]
dict set result ageSeconds [expr {max(0,([clock microseconds]-$stamp)/1000000.0)}]
}
return $result
}
::ns::stats::Sampler public method {sampling stop} {} {
:locked {
set state [nsv_get ns_stats control]
if {[dict get $state running]} {
ns_unschedule_proc [dict get $state scheduler]
}
dict set state running false
dict incr state generation
dict set state busy false
dict set state scheduler {}
nsv_set ns_stats control $state
}
return [:sampling status]
}
::ns::stats::Sampler public method {sampling tick} {} {
set generation {}
:locked {
set state [nsv_get ns_stats control]
if {![dict get $state running] || [dict get $state busy]} {
return
}
set generation [dict get $state generation]
dict set state busy true
nsv_set ns_stats control $state
}
if {$generation eq ""} {
return
}
try {
set current [:snapshot]
:locked {
set state [nsv_get ns_stats control]
if {$generation!=[dict get $state generation]} {
return
}
set pair [nsv_get ns_stats pair]
set previous [expr {$pair eq "" ? "" : [dict get $pair raw]}]
set sample [:calculate $current $previous [dict get $state interval] [dict get $state instance]]
:retainSample $sample
dict set sample raw $current
nsv_set ns_stats pair $sample
dict set state busy false
dict set state lastError {}
nsv_set ns_stats control $state
}
} on error {message options} {
:locked {
set state [nsv_get ns_stats control]
if {$generation == [dict get $state generation]} {
dict set state busy false
dict incr state errors
dict set state lastError $message
nsv_set ns_stats control $state
}
}
ns_log warning "ns_stats: sampling failed: $message"
}
}
::nsf::relation::set ::ns::stats::Sampler superclass ::ns::stats::Collector
::nx::slotObj -container slot ::ns::stats::Sampler
XQL Not present:Generic, PostgreSQL, Oracle
[
hide source ]
| [
make this the default ]