Class ::ns::stats::Sampler (public)

 ::nx::Class ::ns::stats::Sampler[i]

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} {
        #
        #   Derive interval diagnostics from current and previous snapshots.
        #   Return the current observations augmented with counter deltas,
        #   rates, average request phase times and thread CPU percentages.
        #   Rates use measured elapsed time; interval records the configured
        #   sampling cadence, and instance identifies the sampler lifetime.
        #
        #   Interval results require a previous snapshot from the same process
        #   lifetime and positive elapsed time. Missing baselines or decreased
        #   counters yield empty derived values. CPU comparisons match thread
        #   identifiers and creation times; percentages use one core as 100%.
        #
        #   Does not collect observations or modify shared state.
        #
        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 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 $k] && [dict exists $previous drivers $thread $k]} {
                    set d [:delta [dict get $entry $k] [dict get $previous drivers $thread $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}]
            }
        }
        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} {
        # A decreased cumulative counter has no usable interval delta.
        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 {
        # Evaluate shared-state updates in the calling method's frame.
        uplevel 1 [list nsv_array eval ns_stats $script]
    }
::ns::stats::Sampler public method sample {{-server {}}} {
        #
        #   Return the latest published sample, updating its reported age and
        #   running state. Before any publication, return a dictionary with
        #   sampling readiness false and no interval observations.
        #
        #   An optional server argument restricts virtual-server data; CPU
        #   measurements remain process-wide. Does not collect observations
        #   or advance the baseline. The internal raw baseline is omitted
        #   from the returned dictionary.
        #
        if {$server ne "" && $server ni [ns_info servers]} {
            error "unknown server: $server"
        }
        set result [nsv_get ns_stats pair]
        if {$result eq ""} {
            return [dict create schemaVersion 1  sampling {ready false from {} to {} elapsedSeconds {} ageSeconds {} reset false}  servers {} availability {}]
        }
        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)}]
        dict unset result raw
        # CPU is process-wide; filtering server pools never attributes CPU to a vhost.
        return [:selectServer $result $server]
    }
::ns::stats::Sampler public method {sampling start} {{-interval 10}} {
        #   Start the process-wide sampling callback at the requested interval
        #   in seconds (default 10, range 1..3600). Time units and fractional
        #   seconds are normalized with ns_baseunit. Repeated calls with the
        #   same running interval reuse the scheduler; a different interval
        #   requires stopping the sampler first.
        #
        #   Clears the previous sample and baseline when starting a stopped
        #   sampler. Returns the resulting status dictionary. Persistence
        #   must be enabled separately.

        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} {} {
        #
        #   Return the process-wide sampler configuration and status, including
        #   running state, scheduler identifier, interval, process instance,
        #   collection errors and readiness of the latest interval sample.
        #
        #   latestAt is in epoch seconds; ageSeconds is the elapsed time since
        #   the latest publication. Both are empty before the first sample.
        #   Does not collect new observations or modify sampler state.

        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} {} {
        #
        #   Unschedule collection and invalidate any collection already in
        #   progress so it cannot publish into the stopped sampler.
        #
        #   Retains the latest published sample for subsequent reads.
        #   Returns the resulting status dictionary. Does not disable the
        #   separately configured history persistence callback.
        #
        :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} {} {
        #
        #   Perform one scheduled collection and publish its interval sample.
        #   Skip collection when the sampler is stopped or another collection
        #   is in progress. Collect observations outside the coordination lock,
        #   then calculate and publish only if the sampler generation is still
        #   current.
        #
        #   Retain the raw snapshot as the next interval baseline. Collection
        #   failures retain the previous publication, update error status and
        #   emit a warning. Does not write SQLite history; persistence runs
        #   through a separate callback.
        #
        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]]
                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 ]
Show another procedure: