ns::stats::SQLiteHistory method history query (public)

 <instance of ns::stats::SQLiteHistory[i]> history query -from from  \
    -to to  [ -server server ] [ -limit limit ] [ -cursor cursor ]

Defined in

Return stored samples, associated topology, paging information and coverage for an epoch interval. Queries run in the configuring virtual server and use an operation-local database handle. Historical server selection may include servers no longer active.

Switches:
-from (required, integer)
Inclusive lower bound in epoch seconds; must be nonnegative.
-to (required, integer)
Exclusive upper bound in epoch seconds; after from, at most 24 hours later and no later than now.
-server (optional)
Optional historical virtual-server name, including one no longer configured; empty returns all stored server data.
-limit (optional, integer, defaults to "1000")
Maximum records per page, 1..1000; default 1000.
-cursor (optional, integer, defaults to "0")
Nonnegative timestamp cursor in microseconds, default 0; pass nextCursor unchanged for continuation.
Returns:
Dictionary containing records, referenced topologies, storage statistics, coverage, more and nextCursor. Final-page coverage examines the full requested window.

Testcases:
No testcase defined.
Source code:
if {$from < 0
    || $to <= $from
    || $to - $from > 86400
    || $to > [clock seconds]
    || $limit < 1
    || $limit > 1000
    || $cursor < 0
} {
    error "expected an epoch window up to 24 hours, limit 1..1000 and nonnegative cursor"
}
if {[string length $server]>512 || [string first \x00 $server] >= 0} {
    error "invalid server name"
}
set config [nsv_get ns_stats history]
if {$config eq ""} {
    error "history is not configured"
}
if {[dict get $config owner] ne [ns_info server]} {
    error "query history from its configured owner server"
}
set db [:historyConnect $config]
set transaction false
try {
    :sql $db {BEGIN}
    set transaction true
    set low [expr {$from * 1000000}]
    set high [expr {$to * 1000000}]
    set count [expr {$limit + 1}]
    set rows [:sql $db "SELECT at,topology,payload FROM ns_stats_samples WHERE at >= $low AND at < $high AND at > $cursor ORDER BY at LIMIT $count"]
    set more [expr {[llength $rows] > $limit}]
    set rows [lrange $rows 0 [expr {$limit - 1}]]
    set records {}
    set gaps {}
    set restarts {}
    set previous {}
    set topology {}
    set resets 0
    set boundary [expr {max($low,$cursor)}]
    set operator [expr {$cursor >= $low ? "<=" : "<"}]
    set prior [:scalar $db "SELECT payload FROM ns_stats_samples WHERE at $operator $boundary ORDER BY at DESC LIMIT 1"]

    if {$prior ne ""} {
        set previous [:unpack $prior]
    }
    foreach row $rows {
        set record [:unpack [dict get $row payload]]
        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 {([dict get $record timestamp]-[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]]
            }
        }
        set previous $record
        set id [dict get $row topology]
        dict set record topologyId $id
        if {![dict exists $topology $id]} {
            dict set topology $id [:selectServer  [:unpack [:scalar $db "SELECT payload FROM ns_stats_topology WHERE id=[:quote $id]"]]  $server false]
        }
        lappend records [:selectServer $record $server false]
    }

    if {!$more && $previous ne ""} {
        set following [:scalar $db "SELECT payload FROM ns_stats_samples WHERE at >= $high ORDER BY at LIMIT 1"]
        if {$following ne ""} {
            set nextRecord [:unpack $following]
            set delta [expr {([dict get $nextRecord timestamp]-[dict get $previous timestamp]) / 1000000.0}]
            if {$delta > 1.5 * max([dict get $previous sampling intervalSeconds],[dict get $nextRecord sampling intervalSeconds])} {
                lappend gaps [dict create from [dict get $previous sampling to] to [dict get $nextRecord sampling to]]
            }
        }
    }
    set bounds [lindex [:sql $db {SELECT coalesce(min(at),0) AS first,coalesce(max(at),0) AS last FROM ns_stats_samples}] 0]
    set events [:sql $db {SELECT at,first,last,n,reason FROM ns_stats_events ORDER BY id DESC}]
    set first [dict get $bounds first]
    set last [dict get $bounds last]
    set overlap false
    foreach event $events {
        if {[dict get $event first] < $high && [dict get $event last] >= $low} {
            set overlap true
        }
    }
    set through [:scalar $db {SELECT value FROM ns_stats_meta WHERE key='droppedThrough'}]
    if {$through ne "" && $through >= $low} {
        set overlap true
    }
    set next {}
    if {$more} {
        set next [dict get [lindex $rows end] at]
    }
    # Intermediate coverage describes this page. The final response
    # examines all retained intervals in the requested window again,
    # within this read transaction, independently of the paging cursor.
    if {!$more} {
        set window [:historyWindowCoverage $db $low $high]
        set gaps [dict get $window gaps]
        set restarts [dict get $window restartBoundaries]
        set resets [dict get $window resetSamples]
    }
    set complete [expr {$first>0 && $first<=$low && $last>=$high && !$more
                        && !$overlap && $gaps eq "" && $restarts eq "" && $resets==0}]
    set reasons {}
    if {$more} {lappend reasons more-pages}
    if {$first==0 || $first>$low} {lappend reasons before-retained-history}
    if {$last<$high} {lappend reasons after-latest-sample}
    if {$overlap} {lappend reasons eviction-or-write-failure}
    if {$gaps ne ""} {lappend reasons gaps}
    if {$restarts ne ""} {lappend reasons restarts}
    if {$resets>0} {lappend reasons baseline-or-reset}
    set result [dict create  schemaVersion 1 from $from to $to records $records  topologies $topology nextCursor $next more $more  storage [:historyStorage $db $config]  coverage [dict create  complete $complete  windowExamined [expr {!$more}]  scope [expr {$more ? "page" : "window"}]  incompleteReasons $reasons  retainedFrom [expr {$first / 1000000.0}]  retainedTo [expr {$last / 1000000.0}]  gaps $gaps  restartBoundaries $restarts  resetSamples $resets  events $events  evictionOrFailureOverlap $overlap  droppedThrough $through  writeFailuresSinceStart [dict get $config failures]]]
    :sql $db {COMMIT}
    set transaction false
    return $result
} finally {
    if {$transaction} {
        catch {:sql $db {ROLLBACK}}
    }
    ns_db releasehandle $db
}
XQL Not present:
Generic, PostgreSQL, Oracle
[ hide source ] | [ make this the default ]
Show another procedure: