nsstats.tcl

Does not contain a contract.

Location:
/packages/acs-subsite/www/admin/nsstats.tcl

Related Files

[ hide source ] | [ make this the default ]

File Contents

#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
#

#
# nsstats.tcl --
#
#   Simple web-based interface for NaviServer runtime statistics.
#   The base interface is implemented here; modules can register ADP extensions.
#
#   To use it, set enabled to 1 and place this file somewhere under
#   NaviServer pageroot which is usually /usr/local/ns/pages and point
#   browser to it.
#

# If this page needs to be restricted assign username and password in
# the config file in the section "ns/module/nsstats" or here locally
# in this file.
#
set user     [ns_config ns/module/nsstats user "nsadmin"]
set password [ns_config ns/module/nsstats password ""]
set enabled  [ns_config ns/module/nsstats enabled 1]
set debug    0

set ::templateFile nsstats

if { ![nsv_exists _ns_stats threads_0] } {
    nsv_set _ns_stats thread_0      "OK"
    nsv_set _ns_stats thread_-1     "ERROR"
    nsv_set _ns_stats thread_-2     "TIMEOUT"
    nsv_set _ns_stats thread_200    "MAXTLS"
    nsv_set _ns_stats thread_1      "DETACHED"
    nsv_set _ns_stats thread_2      "JOINED"
    nsv_set _ns_stats thread_4      "EXITED"
    nsv_set _ns_stats thread_32     "NAMESIZE"

    nsv_set _ns_stats sched_1       "thread"
    nsv_set _ns_stats sched_2       "once"
    nsv_set _ns_stats sched_4       "daily"
    nsv_set _ns_stats sched_8       "weekly"
    nsv_set _ns_stats sched_16      "paused"
    nsv_set _ns_stats sched_32      "running"

    nsv_set _ns_stats sched_thread  1
    nsv_set _ns_stats sched_once    2
    nsv_set _ns_stats sched_daily   4
    nsv_set _ns_stats sched_weekly  8
    nsv_set _ns_stats sched_paused  16
    nsv_set _ns_stats sched_running 32
}
set severity [expr {$debug ? "notice" : "debug"}]

# Server-scoped extension registry. Modules publish a dictionary of pages under
# their provider name in nsstats:plugins; no nsstats initialization order is needed.
# Registry data is trusted module configuration, never taken from request input.
proc ::_ns_stats.plugins.install {} {
    set ::pluginPages {}
    if {[catch {nsv_array get nsstats:plugins} providers]} {return}
    foreach provider [lsort [dict keys $providers]] {
        set pages [dict get $providers $provider]
        if {[catch {dict size $pages}]} {
            ns_log warning "nsstats: ignoring malformed plugin $provider"
            continue
        }
        dict for {id entry} $pages {
            set available true
            if {[catch {
                if {![regexp {^[a-z][a-z0-9_.-]*$} $id] || $id eq "index"
                    || [dict exists $::titles $id] || [dict exists $::pluginPages $id]} {error "duplicate or invalid page identifier"}
                foreach key {label title group scope adp} {dict get $entry $key}
                if {![dict exists $::navGroups [dict get $entry group]]} {error "unknown navigation group"}
                if {[dict get $entry scope] ni {global server mixed source}} {error "invalid scope"}
                set file [dict get $entry adp]
                if {[file pathtype $file] ne "absolute" || [file extension $file] ne ".adp"
                    || ![file isfile $file] || ![file readable $file]} {error "ADP file unavailable"}
                if {[dict exists $entry requires] && [info commands [dict get $entry requires]] eq ""} {set available false}
            } message]} {
                ns_log warning "nsstats: ignoring plugin $provider page '$id': $message"
                continue
            }
            if {!$available} {continue}
            dict set ::pluginPages $id $entry
            dict set ::titles $id [dict get $entry title]
            dict set ::navScopes $id [dict get $entry scope]
            dict set ::navLinks $id [dict get $entry label]
            set group [dict get $entry group]
            lassign [dict get $::navGroups $group] label pages
            lappend pages $id [dict get $entry label]
            dict set ::navGroups $group [list $label $pages]
        }
    }
}

proc ::_ns_stats.renderPage {page} {
    if {[dict exists $::pluginPages $page]} {
        # Called only after the ordinary nsstats access check. Empty ADP output
        # denotes a direct response (JSON/download), just like built-in handlers.
        return [ns_adp_parse -file [dict get $::pluginPages $page adp]]
    }
    if {![dict exists $::titles $page] || [info commands ::_ns_stats.$page] eq ""} {error "Unknown statistics page"}
    return [::_ns_stats.$page]
}

proc ::_ns_stats.identity {} {
    set identity [ns_conn authuser]
    if {[info commands ad_conn] ne "" && ![catch {ad_conn user_id} userId] && $userId > 0} {
        set identity "openacs:$userId"
    } elseif {$identity eq ""} {
        set identity "peer:[ns_conn peeraddr -source direct]"
    }
    return "nsstats:[ns_info server]:$identity"
}

proc ::_ns_stats.table {heading columns rows} {
    set html "<h3>[ns_quotehtml $heading]</h3><div class='w3-responsive'><table class='data data-table w3-table w3-hoverable'><thead><tr>"
    foreach column $columns {append html "<th>[ns_quotehtml $column]</th>"}
    append html "</tr></thead><tbody>"
    foreach row $rows {
        append html "<tr>"
        foreach value $row {append html "<td>[ns_quotehtml $value]</td>"}
        append html "</tr>"
    }
    append html "</tbody></table></div>"
    return $html
}

set ::pluginPages {}

# One hierarchy shared by the menus, directory and breadcrumbs. Existing page
# identifiers remain unchanged; optional pages are omitted when unavailable.
set ::navGroups {
    overview {Overview {
        process {Process and servers} utilization Utilization utilization.episodes {TCP loss history} threads Threads
        list-lsof {Open files}
    }}
    memory {{Memory & storage} {
        mem.tcl {Allocator memory} dbpools {Database pools}
        stats.history {Statistics history}
        mem.cache Caches mem.nsvsize {Shared variables} mem.adp ADP
    }}
    background {Background {
        background.jobs Jobs background.sched {Scheduled procedures}
        proxy-workers {Proxy workers}
    }}
    logs {Logs {
        log.logfile {System log} log.httpclient {HTTP client log}
        log.levels {Log severities}
    }}
    administration {Administration {
        config.file {Configuration file} config.params {Configuration parameters}
        tcl.libraries {Tcl Libraries}
        locks.mutex {Mutex and RW-locks} locks.nsv {NSV locks}
    }}
}
set ::navScopes {
    process global utilization global utilization.episodes global threads global list-lsof global
    stats.history global mem.tcl global dbpools global mem.cache server mem.nsvsize server mem.adp server
    background.jobs global background.sched global proxy-workers global
    config.file global config.params global tcl.libraries server locks.mutex server locks.nsv server
    log.logfile global log.httpclient source
    log.levels global
}

proc ::_ns_stats.navigation.scope {page} {
    if {$page eq "mem.nsvsize" && ![catch {nsv_array scope _ns_stats}]} {
        return mixed
    }
    return [dict get $::navScopes $page]
}

proc ::_ns_stats.navigation.scopeLabel {scope} {
    return [dict get {global Global server {Current server} mixed {Both scopes} source {Log source}} $scope]
}

# Preserve label lookup for templates that consume navLinks.
set ::navLinks {}
dict for {group definition} $::navGroups {
    lassign $definition label entries
    foreach {page title} $entries {
        dict set ::navLinks [lindex [split $page #] 0] $title
    }
}

proc ::_ns_stats.navigation.groups {} {
    set result {}
    dict for {group definition} $::navGroups {
        lassign $definition label entries
        set available {}
        foreach {target title} $entries {
            set page [lindex [split $target #] 0]
            if {[info procs ::_ns_stats.$page] eq "" && ![dict exists $::pluginPages $page]} {
                continue
            }
            if {$page in {stats.history utilization.episodes} && [info commands ns_stats] eq ""} {
                continue
            }
            if {$page eq "utilization" && [info commands ns_json] eq ""} {
                continue
            }
            if {$page eq "dbpools" && [info commands ns_db] eq ""} {
                continue
            }
            if {$page eq "proxy-workers" && [info commands ns_proxy] eq ""} {
                continue
            }
            lappend available $target $title
        }
        if {$available ne ""} {
            dict set result $group [list $label $available]
        }
    }
    return $result
}

proc ::_ns_stats.navigation.url {target} {
    lassign [split $target #] page anchor
    set url "?@page=$page$::rawparam"
    if {$anchor ne ""} {
        append url #$anchor
    }
    return [ns_quotehtml $url]
}

proc ::_ns_stats.navigation {} {
    set groups ""
    dict for {group definition} [::_ns_stats.navigation.groups] {
        lassign $definition label entries
        append groups "<details class='ns-nav-group'><summary>[ns_quotehtml $label]</summary><div class='ns-nav-items'>"
        foreach {target title} $entries {
            set current ""
            if {$target eq $::page} {
                set current " aria-current='page'"
            }
            set scope [::_ns_stats.navigation.scope $target]
            append groups "<a href='[::_ns_stats.navigation.url $target]'$current><span>[ns_quotehtml $title]</span><span class='ns-scope'>[::_ns_stats.navigation.scopeLabel $scope]</span></a>"
        }
        append groups "</div></details>"
    }
    # Named details form exclusive groups: opening a menu closes its sibling.
    # Keep desktop and mobile groups separate so hidden copies do not interfere.
    set desktop [string map {"class='ns-nav-group'" "class='ns-nav-group' name='nsstats-desktop'"} $groups]
    set mobile [string map {"class='ns-nav-group'" "class='ns-nav-group' name='nsstats-mobile'"} $groups]
    set html "<nav class='ns-navigation' aria-label='Statistics navigation'><div class='ns-nav-desktop'>$desktop</div><details class='ns-nav-mobile'><summary>Menu</summary><div>$mobile</div></details></nav>"
    append html "<div class='ns-server-context'>Current server: <strong>[ns_quotehtml [ns_info server]]</strong></div>"
    append html {<script>
(function () {
  document.addEventListener('click', function (event) {
    if (event.target.closest('.ns-navigation')) return;
    document.querySelectorAll('.ns-navigation details[open]').forEach(function (menu) {
      menu.open = false;
    });
  });
  document.addEventListener('keydown', function (event) {
    if (event.key !== 'Escape') return;
    const focused = document.activeElement.closest('.ns-navigation details[open]');
    document.querySelectorAll('.ns-navigation details[open]').forEach(function (menu) {
      menu.open = false;
    });
    if (focused) focused.querySelector('summary').focus();
  });
})();
</script>}
    return $html
}

proc ::_ns_stats.navigation.breadcrumb {} {
    if {$::page eq "index"} {
        return "<span aria-current='page'>Main menu</span>"
    }
    set title [expr {[dict exists $::titles $::page] ? [dict get $::titles $::page] : "Main menu"}]
    set label ""
    set destination index
    dict for {group definition} [::_ns_stats.navigation.groups] {
        lassign $definition groupLabel entries
        if {$::page in $entries} {
            set label $groupLabel
            set destination index#nav-$group
            break
        }
    }
    if {$::page in {mapped requestprocs url2file}} {
        set label Process
        set destination process
    }
    set html "<a href='[::_ns_stats.navigation.url index]'>Main menu</a>"
    if {$label ne ""} {
        append html " &gt; <a href='[::_ns_stats.navigation.url $destination]'>[ns_quotehtml $label]</a>"
    }
    append html " &gt; <span aria-current='page'>[ns_quotehtml $title]</span>"
    return $html
}

proc ::_ns_stats.navigation.style {} {
    return {<style>
.ns-navigation {background:var(--color-menubar-bg,#00376f);color:#fff;padding:0 12px;position:relative;z-index:20;font:14px Arial,sans-serif;}
.ns-nav-desktop {display:flex;flex-wrap:wrap;gap:4px;}
.ns-nav-group {position:relative;}
.ns-navigation summary {cursor:pointer;padding:14px 12px;min-height:20px;}
.ns-navigation summary:focus-visible,.ns-navigation a:focus-visible {outline:2px solid #ffcc00;outline-offset:-2px;}
.ns-nav-items {position:absolute;min-width:220px;background:var(--color-dropdown-content-bg,#fff);border:1px solid var(--color-border,#ccc);box-shadow:0 4px 12px #0003;}
.ns-nav-items a {display:flex;justify-content:space-between;align-items:center;gap:16px;padding:12px;color:var(--color-anchor,#004080);text-decoration:none;}
.ns-nav-items a:hover,.ns-nav-items a[aria-current] {background:var(--color-dropdown-hover-bg,#e6eef7);}
.ns-nav-mobile {display:none;}
.ns-measurement-controls input[name="statsWindow"] {width:6em;box-sizing:border-box;padding:3px 6px;border:1px solid var(--color-border,#888);background:var(--color-body-bg,#fff);color:var(--color-body-text,#000);opacity:1;}
.ns-module-reload {display:inline-block;margin:0 0 0 6px;vertical-align:middle;}
.ns-module-reload button {display:inline-flex;align-items:center;justify-content:center;padding:4px;border:1px solid var(--color-border,#888);border-radius:3px;background:var(--color-body-bg,#fff);color:var(--color-anchor,#004080);cursor:pointer;}
.ns-module-reload button:focus-visible {outline:2px solid currentColor;outline-offset:2px;}
.data-table.ns-tcl-library {width:100%;table-layout:auto;}
.data-table.ns-tcl-library th:first-child,.data-table.ns-tcl-library td:first-child {width:1%;white-space:nowrap;vertical-align:top;}
.data-table.ns-tcl-library .ns-library-version {width:1%;white-space:nowrap;vertical-align:top;}
.data-table.ns-tcl-library .ns-library-files {width:auto;}
.data-table.ns-tcl-library .ns-library-files code {white-space:normal;overflow-wrap:anywhere;}
.data-table.ns-tcl-library .ns-library-action {width:1%;white-space:nowrap;text-align:right;vertical-align:top;}
.data-table.ns-tcl-library .ns-module-reload {margin:0;}
.ns-scope {font-size:11px;white-space:nowrap;opacity:.75;}
.ns-server-context {padding:8px 15px;font-size:13px;background:var(--color-secondary-bg,#f1f1f1);color:var(--color-body-text,#000);}
.ns-directory li a {display:flex;justify-content:space-between;gap:12px;}
.ns-directory-filter {display:block;margin-bottom:16px;}
.ns-directory-filter select {padding:8px;font:inherit;}
.ns-directory {display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:20px;}
.ns-directory section {border:1px solid var(--color-border,#ccc);padding:16px;border-radius:4px;}
.ns-directory li {margin:12px 0;}
.ns-directory-search {display:block;margin:0 0 20px;}
.ns-directory-search input {display:block;box-sizing:border-box;width:100%;max-width:480px;padding:10px;margin-top:6px;font:inherit;}
@media(max-width:800px) {
 .ns-nav-desktop {display:none;}
 .ns-nav-mobile {display:block;}
 .ns-nav-items {position:static;box-shadow:none;min-width:0;}
 .ns-nav-group {border-top:1px solid #ffffff40;}
 .ns-navigation summary {padding:14px 8px;}
 .ns-directory {grid-template-columns:1fr;}
}
</style>}
}

set ::titles {
    index             "Main menu"
    dbpools           "Database pools"
    background.jobs   "Jobs"
    background.sched  "Scheduled Procedures"
    config.file       "Configuration File"
    config.params     "Configuration Parameters"
    list-lsof         "Open Files"
    locks.mutex       "Memory Lock Statistics (Mutex and RW-Locks)"
    locks.nsv         "Shared Variable Lock Statistics (nsv)"
    log.httpclient    "HTTP Client Logfile Analysis"
    log.levels        "Log Severities"
    log.logfile       "System Logfile"
    mapped            "Connection Pool Mappings"
    mem.adp           "ADP"
    mem.cache         "Cache Statistics (ns_cache)"
    mem.nsvsize       "Shared Variable Statistics (nsv)"
    mem.tcl           "Allocated Memory"
    process           "Process Information"
    tcl.libraries     "Tcl Libraries"
    proxy-workers     "nsproxy Workers"
    requestprocs      "Request Handlers"
    threads           "Running Threads"
    utilization.episodes "TCP loss history"
    utilization       "Utilization"
    url2file          "Url2File Mappings"
}

::_ns_stats.plugins.install

#
# Version compatibility proc
#
catch {ns_config} errorMsg
if {[lsearch $errorMsg ?-all?]} {
    proc ::ns_config_get_all {section key} {ns_config -all $section $key}
} {
    # Older versions of NaviServer don't support the "-all" flag,
    # continue to return just the first value as before
    proc ::ns_config_get_all {section key} {ns_config $section $key}
}

proc ::_ns_stats.header {args} {
    if {[llength $args] == 1} {
        set ::title "NaviServer Stats: [ns_info hostname] - [lindex $args 0]"
        set ::nav "<a href='?@page=index$::rawparam'>Main Menu</a> &gt; <span class='current'>[lindex $args 0]</span>"
        set ::current_page [lindex $args 0]
    } elseif {[llength $args] == 2} {
        set node [lindex $args 0]
        if {[llength $node] > 1} {
            lassign $node node link
            set menu_entry "<a href='$link'>$node</a>"
        } else {
            set menu_entry $node
        }
        set ::current_page [lindex $args 1]
        set ::title "NaviServer Stats: [ns_info hostname] - $node - [lindex $args 1]"
        set ::nav "<a href='?@page=index$::rawparam'>Main Menu</a> &gt; $menu_entry &gt; <span class='current'>[lindex $args 1]</span>"
    } else {
        set ::title "NaviServer Stats: [ns_info hostname]"
        set ::nav "<span class='current'>Main Menu</span>"
    }
    set ::rawLabel [expr {$::raw ? "true" : "false"}]
    set s [ns_getform]
    ns_set update $s raw [expr {!$::raw}]
    set ::rawUrl [ns_conn url]?[join [lmap {k v} [ns_set array $s] {if {$k in {statsToken statsInterval} || [string match -nocase *Token $k] || [string match -nocase *Action $k] || [string match -nocase *Confirm $k]} {continue}; set _ [ns_urlencode $k]=[ns_urlencode $v]}] &]
    if {![info exists ::extraHeadEntries]} {
        set ::extraHeadEntries ""
    }
    return ""
}
set ::fallbackTemplate {
<!DOCTYPE html>
<html>
<head>
<title><%= $::title %></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type='text/css'>
/* tooltip styling. by default the element to be styled is .tooltip  */
.tip {
   cursor: help;
   text-decoration:underline;
   color: #777777;
}
body { font-family: verdana,arial,helvetica,sans-serif; font-size: 8pt; color: #000000; background-color: #ffffff;}
td,th   { font-family: verdana,arial,helvetica,sans-serif; font-size: 8pt; padding: 4px;}
pre     { font-family: courier new, courier; font-size: 10pt; }
form    { font-family: verdana,helvetica,arial,sans-serif; font-size: 10pt; }
i       { font-style: italic; }
b       { font-style: bold; }
hl      { font-family: verdana,arial,helvetica,sans-serif; font-style: bold; font-size: 12pt; }
small   { font-size: smaller; }

table {background-color: #cccccc; padding:0px; border-spacing: 1px;}
td td.subtitle {
   text-align: right; white-space: nowrap; font-style: italic; font-size: 7pt; background-color: #f5f5f5;
}
td.coltitle {text-align: right; background-color: #eaeaea;}
td.colsection {font-size: 12pt; font-style: bold;}
td.colsection h3 {margin-top:2px;margin-bottom:2px;}
td.colsection h4 {margin-top:2px;;margin-bottom:2px;}
td.colvalue {background-color: #ffffff;}
td.defaulted {color: #aaa;}
td.unread {color: red;}
td.notneeded {color: orange;}

tr.sortable td.selected   { background: #666666; color: #ffffff; }
tr.sortable td.unselected { background: #999999; color: #ffffff; }
tr.data td.selected       { background: #ececec; }
tr.data td.unselected     { background: #ffffff; }

.tooltip {
  position: relative;
  /*display: inline-block;*/
  /*border-bottom: 1px dotted black;*/ /* If you want dots under the hoverable text */
}

.tooltip .tooltiptext {
   visibility: hidden;
   width: 200px;
   background-color: #999;
   color: #fff;
   text-align: center;
   padding: 5px 0;
   margin-left: 15px;
   margin-top: -5px;
   border-radius: 6px;
   position: absolute;
   z-index: 1;
}
.tooltip.unread .tooltiptext { background-color: #900;}
.tooltip.unread .tooltiptext::after {border-color: transparent #900 transparent transparent;}
.tooltip.defaulted .tooltiptext { background-color: #aaa;}
.tooltip.defaulted .tooltiptext::after { border-color: transparent #aaa transparent transparent;}
.tooltip.notneeded .tooltiptext { background-color: orange;}
.tooltip.notneeded .tooltiptext::after { border-color: transparent orange transparent transparent;}

.tooltip:hover .tooltiptext {visibility: visible;}
.tooltip .tooltiptext::after {
   content: " ";
   position: absolute;
   top: 50%;
   right: 100%; /* To the left of the tooltip */
   margin-top: -5px;
   border-width: 5px;
   border-style: solid;
   border-color: transparent #999 transparent transparent;
}

table.navbar {border: 1px; padding: 2px; border-spacing: 0px; width: 100%;}
table.navbar td {padding: 5px; background: #666699; color: #ffffff; font-size: 10px;}
table.navbar td .current {color: #ffcc00;}
table.navbar td a {color: #ffffff; text-decoration: none;}

table.data {padding: 0px; border-spacing: 1px}
table.data td.coltitle {width: 110px; text-align: right; background-color: #eaeaea;}
table.data td td.subtitle {text-align: right; white-space: nowrap; font-style: italic; font-size: 7pt; background-color: #f5f5f5;}
table.data th {background-color: #999999; color: #ffffff; font-weight: normal; text-align: left;}
table.data td {background-color: #ffffff; padding: 4px;}
table.data td table {background-color: #ffffff; border-spacing: 0px;}
table.data td table td {padding: 2px;}

table.requestprocs td.Arg { white-space: pre; font-size: 6pt; }
div.methodfilter .w3-check { width: 12px; height: 12px; top: 2px; }
div.methodfilter label { margin-right: 6px; }
</style>
<%= $::extraHeadEntries %>
<%= [::_ns_stats.navigation.style] %>
</head>

<body>
<%= [::_ns_stats.navigation] %>
  <table class='navbar table table-responsive w-100 d-block d-md-table'>
    <tr>
      <td valign='middle'><b><%= [::_ns_stats.navigation.breadcrumb] %></b></td>
      <td valign='middle' align='right'><%= [::_ns_stats.measurementControls] %>Raw: <a class='current' href='<%= $::rawUrl %>'><%= $::rawLabel %></a>
       &middot; <b><%= [::_ns_stats.fmtTime [ns_time]] %></b></td>
    </tr>
  </table>
  <br>

<%= $html %>
<%= $::footer %>
}

proc ::_ns_stats.footer {} {
    set ::footer "" ;#"</body></html>"
}

proc ::_ns_stats.index {} {
    ::_ns_stats.header
    set html {<label class='ns-directory-search'>Find a page<input type='search' id='ns-directory-search' placeholder='Search pages' aria-controls='ns-directory'></label><label class='ns-directory-filter'>Scope <select id='ns-directory-scope' aria-controls='ns-directory'><option value='all'>All scopes</option><option value='global'>Global</option><option value='server'>Current server</option></select></label><div class='ns-directory' id='ns-directory'>}
    dict for {group definition} [::_ns_stats.navigation.groups] {
        lassign $definition label entries
        append html "<section id='nav-$group'><h3>[ns_quotehtml $label]</h3><ul>"
        foreach {target title} $entries {
            set scope [::_ns_stats.navigation.scope $target]
            append html "<li data-scope='$scope'><a href='[::_ns_stats.navigation.url $target]'><span>[ns_quotehtml $title]</span><span class='ns-scope'>[::_ns_stats.navigation.scopeLabel $scope]</span></a></li>"
        }
        append html "</ul></section>"
    }
    append html {</div><script>
(function () {
 const input = document.getElementById('ns-directory-search');
 const directory = document.getElementById('ns-directory');
 const scope = document.getElementById('ns-directory-scope');
 function filterPages() {
   const query = input.value.trim().toLowerCase();
   directory.querySelectorAll('section').forEach(function (section) {
     let visible = false;
     section.querySelectorAll('li').forEach(function (item) {
       const matchesScope = scope.value === 'all' || item.dataset.scope === scope.value || item.dataset.scope === 'mixed';
       item.hidden = !matchesScope || !(section.querySelector('h3').textContent + ' ' + item.textContent).toLowerCase().includes(query);
       visible = visible || !item.hidden;
     });
     section.hidden = !visible;
   });
 }
 input.addEventListener('input', filterPages);
 scope.addEventListener('change', filterPages);
})();
</script>}
    ::_ns_stats.footer
    return $html
}

# Process-wide diagnostic history; viewing this page does not configure it.
proc ::_ns_stats.stats.history {} {
    ::_ns_stats.header "Statistics history"
    set html "<p>Process-wide sampling history. Age and size limits can both remove older samples; the retention setting does not guarantee that duration is retained.</p>"
    if {[info commands ns_stats] eq "" || [catch {ns_stats history status} status]} {
        append html "<p>Statistics history is unavailable.</p>"
    } else {
        set rows {}
        foreach {key label unit} {
            enabled Persistence text pool {Database pool} text owner {Owner server} text
            retention {Maximum sample age} duration maxBytes {Configured storage budget} bytes
            failures {Write failures} text lastFailure {Last write failure} text
        } {
            if {![dict exists $status $key]} {continue}
            set value [dict get $status $key]
            if {$unit eq "bytes"} {set value [format "%.2f MiB" [expr {$value / 1048576.0}]]}
            if {$unit eq "duration"} {set value [format "%.2f hours" [expr {$value / 3600.0}]]}
            lappend rows [list $label $value]
        }
        append html [::_ns_stats.table "Persistence and cleanup settings" {Setting Value} $rows]
        if {[dict get $status enabled] && ![catch {ns_stats history storage} storage]} {
            set rows {}
            foreach {key label unit} {
                retainedFrom {Oldest retained sample} timestamp retainedTo {Newest retained sample} timestamp
                retainedSeconds {Retained duration} duration sampleCount {Retained samples} text
                allocatedBytes {Allocated database} bytes usedBytes {Used database} bytes
                freeBytes {Free database pages} bytes databaseLimitBytes {Effective SQLite size cap} bytes
                bytesPerSample {Average storage per sample} bytes bytesPerSecond {Observed storage rate} rate
            } {
                if {![dict exists $storage $key]} {continue}
                set value [dict get $storage $key]
                if {$value eq ""} {set value Unknown} else {
                    switch $unit {
                        timestamp {set value [::_ns_stats.fmtTime $value]}
                        duration {set value [format "%.2f hours" [expr {$value / 3600.0}]]}
                        bytes {set value [format "%.2f KiB" [expr {$value / 1024.0}]]}
                        rate {set value [format "%.2f KiB/s" [expr {$value / 1024.0}]]}
                    }
                }
                lappend rows [list $label $value]
            }
            append html [::_ns_stats.table "Retained history and database usage" {Metric Value} $rows]
            append html "<p>The effective SQLite cap leaves headroom within the configured budget. Cleanup can prune before that cap is reached. Storage averages describe retained samples and can change with server activity.</p>"
        }
    }
    ::_ns_stats.footer
    return $html
}

proc ::_ns_stats.dbpools {} {
    ::_ns_stats.header "Database pools"
    set rows [::_ns_stats.process.dbpools]
    if {$rows eq ""} {
        set html "<p>Database pool statistics are unavailable.</p>"
    } else {
        set html "<p>Database pool statistics are process-wide. A pool can be shared by multiple virtual servers; Process shows which pools each server can access.</p><table class='data'><tbody>[join $rows \n]</tbody></table>"
    }
    ::_ns_stats.footer
    return $html
}

#
# MCP administration is read-only and uses the normal nsstats access checks.
# Configuration and credential fields are explicitly selected so secret values
# and token digests never enter the rendered page.
#
proc ::_ns_stats.mem.adp {} {
    set col         [ns_queryget col 1]
    set reverseSort [ns_queryget reversesort 1]

    set numericSort 1
    set colTitles   [list File Device Inode "Modify Time" "Ref Count" Evals Size Blocks Scripts]

    if {$col == 1} {
        set numericSort 0
    }

    set results ""

    foreach {file stats} [ns_adp_stats] {
        set s  ""

        foreach {k v} $stats {
            if {"mtime" eq $k} {
                lappend s [::_ns_stats.fmtTime $v]
            } else {
                lappend s $v
            }
        }
        lappend results [concat $file $s]
    }

    set rows [::_ns_stats.sortResults $results [expr {$col - 1}] $numericSort $reverseSort]

    append html \
        [::_ns_stats.header ADP] \
        [::_ns_stats.results mem $col $colTitles ?@page=mem.adp $rows $reverseSort] \
        [::_ns_stats.footer]

    return $html
}

proc ::_ns_stats.mem.cache.histogram {cacheName sorted} {
    set nrEntries [llength $sorted]
    if {$nrEntries < 1} {
        return ""
    }
    set stats [ns_cache_stats $cacheName]
    set utilization [format %.2f [expr {[dict get $stats size]*100.0/[dict get $stats maxsize]}]]
    set nrBuckets [expr {$nrEntries > 50 ? 50 : $nrEntries}]
    set bucketSize [expr {$nrEntries/$nrBuckets}]
    set r ""
    #append r "<pre>nrEntries $nrEntries bucketSize $bucketSize\n"
    set reuses {}
    set labels {}
    set reused 0
    for {set b 0} {$b < $nrBuckets} {incr b} {
        set subset [lrange $sorted [expr {$b*$bucketSize}] [expr {($b+1)*$bucketSize - 1}]]
        set sumHits 0
        foreach e $subset {
            set hits [lindex $e 2]
            incr sumHits $hits
            if {$hits > 1} {
                incr reused
            }
        }
        set avgHits [expr {$sumHits*1.0/$bucketSize}]
        lappend reuses $avgHits
        #lappend labels '[expr {$b+1}]'
        lappend labels '[format %.2f [expr {(($b+1)*100.0)*$bucketSize*($utilization/100)/$nrEntries}]]'
        #append r "$b: from [expr {$b*$bucketSize}] to [expr {($b+1)*$bucketSize - 1}] sumHits $sumHits avgHits $avgHits\n"
    }
    #append r </pre>\n
    set ::extraHeadEntries {
        <script src="https://code.highcharts.com/highcharts.js"></script>
        <script src="https://code.highcharts.com/modules/exporting.js"></script>
        <script src="https://code.highcharts.com/modules/export-data.js"></script>
    }
    set data [join $reuses ,]
    set categories [join $labels ,]
    set maxSize [::_ns_stats.hr [dict get $stats maxsize]]B
    set sufficient [::_ns_stats.hr [expr {[dict get $stats size] * 1.1 * $reused / $nrEntries }] %.0f]B
    # margin: 0 auto
    set config "[ns_cache_configure $cacheName]"
    append r [subst -nocommands {
        <div id="histogram" style="min-width: 310px; height: 400px; width: 70%; "></div>
        <script>
        Highcharts.chart('histogram', {
            chart:    { type: 'column' },
            title:    { text: 'Cache-entry reuse in $cacheName' },
            subtitle: { text: '$config<br>(Entries: $nrEntries, reused: $reused, bucket size: $bucketSize, utilization: $utilization%, cache size: $maxSize, sufficient: $sufficient)' },
            yAxis:    { min: 1, title: { text: 'Hits' }, type: 'logarithmic', minorTickInterval: 0.1 },
            xAxis:    { title: { text: 'Percent'}, categories: [$categories] },
            legend:   {enabled: false},
            tooltip:  {
                headerFormat: '<span style="font-size:10px">{point.key}</span><table>',
                pointFormat: '<tr><td style="color:{series.color};padding:0">{series.name}: </td>' +
                '<td style="padding:0"><b>{point.y:.1f} hits</b></td></tr>',
                footerFormat: '</table>',
                shared: true,
                useHTML: true
            },
            plotOptions: { column: { pointPadding: 0, borderWidth: 0, groupPadding: 0, shadow: false } },
            series: [{ name: 'Reuse', data: [$data] }]
        });
        </script>
    }]
    return $r
}

proc ::_ns_stats.mem.cache {} {
    set col         [ns_queryget col 1]
    set reverseSort [ns_queryget reversesort 1]
    set statDetails [ns_queryget statDetails ""]
    set currentUrl  "./[lindex [ns_conn urlv] end]?@page=mem.cache&col=$col&reverseSort=$reverseSort"

    if {$statDetails ne ""} {
        set max  [ns_queryget max 50]
        set body ""
        set stats [ns_cache_stats -contents $statDetails]
        set sorted [lsort -decreasing -integer -index 2 $stats]
        set h [ ::_ns_stats.mem.cache.histogram $statDetails $sorted]
        append body \
            $h \
            "<h4>$max most frequently used entries from cache '$statDetails'</h4>\n" \
            "<table class='data' width='70%'><tr><th>Key</th><th>Size</th><th>Hits</th><th>Expire</th></tr>\n"
        foreach row [lrange $sorted 0 $max] {
            lassign $row key hits size expire
            if {$expire == 0} {
                set expire -1
            } else {
                lassign [split [ns_time format $expire] .] secs usecs
                set expire [::_ns_stats.fmtTime $secs]
            }
            append body "<tr><td>[ns_quotehtml $key]</td>" \
                "<td align='right'>$hits</td>" \
                "<td align='right'>$size</td>" \
                "<td align='center'>$expire</td>"\
                "</tr>\n"
        }
        append body <table>

        append html \
            [::_ns_stats.header [list Cache $currentUrl] $statDetails] \
            $body \
            [::_ns_stats.footer]


    } else {

        set numericSort 1
        if {$col == 1} {
            set numericSort 0
        }

        set results ""
        set totalRequests [::_ns_stats.totalRequests]

        array set t {saved ""}
        set totalSaved 0
        foreach cache [ns_cache_names] {
            array set t {commit 0 rollback 0 collisions 0 waits 0 rewaits 0 timeouts 0}
            array set t [ns_cache_stats $cache]
            set avgSize [expr {$t(entries) > 0 ? $t(size)/$t(entries) : 0}]
            lappend results [list $cache $t(maxsize) $t(size) \
                                 [expr {$t(size)*100.0/$t(maxsize)}] \
                                 $t(entries) $avgSize $t(flushed) \
                                 $t(hits) \
                                 [format %.4f [expr {$totalRequests > 0 ? $t(hits)*1.0/$totalRequests : 0}]] \
                                 [format %.f [expr {$t(entries)>0 ? $t(hits)*1.0/$t(entries) : 0}]] \
                                 $t(missed) $t(hitrate) $t(expired) $t(pruned) \
                                 $t(commit) $t(rollback) \
                                 $t(collisions) $t(waits) $t(rewaits) $t(timeouts) \
                                 [expr {$t(hits) > 0 ? $t(saved)*1.0/$t(hits) : 0}] \
                                 [expr {$totalRequests > 0 ? $t(saved)/$totalRequests : 0}] \
                                ]
            set totalSaved [expr {$totalSaved + $t(saved)}]
        }

        set colTitles   {
            Cache Max Current Utilization Entries "Avg Size" Flushes Hits Hits/Req Reuse Misses
            "Hit Rate" Expired Pruned Commit Rollback Collisions Waits Rewaits Timeouts
            "Saved/Hit" "Saved/Req"
        }
        set rows [::_ns_stats.sortResults $results [expr {$col - 1}] $numericSort $reverseSort]

        set table {}
        foreach row $rows {
            set cache_name [lindex $row 0]
            lset row 0 "<a href='$currentUrl&statDetails=$cache_name'>$cache_name</a>"
            lset row 1 [::_ns_stats.hr [lindex $row 1]]
            lset row 2 [::_ns_stats.hr [lindex $row 2]]
            lset row 3 [format %.2f [lindex $row 3]]%
            lset row 4 [::_ns_stats.hr [lindex $row 4]]
            lset row 5 [::_ns_stats.hr [lindex $row 5]]
            lset row 6 [::_ns_stats.hr [lindex $row 6]]
            lset row 7 [::_ns_stats.hr [lindex $row 7]]
            lset row 10 [::_ns_stats.hr [lindex $row 10]]
            lset row 11 [format %.2f [lindex $row 11]]%
            lset row 12 [::_ns_stats.hr [lindex $row 12]]
            lset row 13 [::_ns_stats.hr [lindex $row 13]]
            lset row 16 [::_ns_stats.hr [lindex $row 16]]
            lset row 17 [::_ns_stats.hr [lindex $row 17]]
            lset row 18 [::_ns_stats.hr [lindex $row 18]]
            lset row 19 [::_ns_stats.hr [lindex $row 19]]
            lset row 20 [::_ns_stats.hr [lindex $row 20]]s
            lset row 21 [::_ns_stats.hr [lindex $row 21]]s
            lappend table $row
        }

        append html \
            [::_ns_stats.header Cache] \
            "<p class='summary'>ns_cache operations saved since the start of the server [::_ns_stats.fmtSeconds $totalSaved] on [::_ns_stats.hr $totalRequests] requests " \
            "([::_ns_stats.hr [expr {$totalSaved/$totalRequests}]]s per request on average)</p>" \n \
            [::_ns_stats.results cache $col $colTitles ?@page=mem.cache $table $reverseSort {
                left
                right right right right right
                right right right right right
                right right right right right
                right right right right right
                right
            }] \
            [::_ns_stats.footer]
    }
    return $html
}

# Prefer a ready shared sample while sampling runs; allow an explicit snapshot.
# Neither read changes sampling. An absent command selects the legacy collector.
# Display-window controls read retained samples; they never reconfigure sampling.
# Normalize the form and collector identically; do not alter sampler settings.
proc ::_ns_stats.displayWindow {status} {
    set requested [expr {[info commands ns_queryget] ne "" ? [ns_queryget statsWindow ""] : ""}]
    set effective $requested
    set notice {}
    set maximum [::_ns_stats.dictGetDef $status maxWindowSeconds 900]
    set available [::_ns_stats.dictGetDef $status availableWindowSeconds $maximum]
    set maximum [expr {min($maximum,$available)}]
    if {$requested ne "" && [info commands ns_baseunit] ne ""
        && ![catch {ns_baseunit -time $requested} seconds] && $seconds>$maximum} {
        # Zero selects the latest published interval while history is warming up.
        set effective [expr {$maximum>0 ? "[format %.6gs $maximum]" : "0s"}]
        set notice "Requested display window $requested exceeds the currently usable maximum of [format %.6gs $maximum]."
        if {$maximum>0} {
            append notice " Using $effective instead."
        } else {
            append notice " Using the latest published interval (0s) while continuous history builds up."
        }
    }
    return [dict create requested $requested effective $effective notice $notice]
}

proc ::_ns_stats.displayWindowHelp {status} {
    set retention [::_ns_stats.dictGetDef $status retentionSeconds 900]
    set count [::_ns_stats.dictGetDef $status maxSamples 180]
    set maximum [::_ns_stats.dictGetDef $status maxWindowSeconds 900]
    set interval [dict get $status interval]
    set available [::_ns_stats.dictGetDef $status availableWindowSeconds {}]
    set help "The display window uses in-memory samples: retention [format %.6g $retention]s, up to $count samples; configured window limit [format %.6g $maximum]s."
    if {$available ne ""} {append help " Continuous history currently available: [format %.6g $available]s."}
    set hourlySamples [expr {int(ceil(3600.0/$interval))+1}]
    append help " For a 1h window, set ns_param samplingRetention 1h and ns_param samplingMaxSamples $hourlySamples in ns_section ns/stats, then restart NaviServer and allow an hour of continuous sampling. Larger limits retain more data in memory. historyRetention controls SQLite history and does not extend this display window."
    if {![dict exists $status maxWindowSeconds]} {
        append help " The loaded core has a fixed 900s limit; install the updated NaviServer tcl/ns_stats.tcl to make memory retention configurable."
    }
    return $help
}

proc ::_ns_stats.measurementControls {} {
    if {$::page ni {utilization threads} || [info commands ns_stats] eq ""} {return ""}
    set mode [ns_queryget statsMode auto]
    set options ""
    foreach {value label} {auto {Shared sampling} snapshot {Live snapshot}} {
        set selected [expr {$mode eq $value ? " selected" : ""}]
        append options "<option value='$value'$selected>$label</option>"
    }
    set status [ns_stats sampling status]
    set period [dict get [::_ns_stats.displayWindow $status] effective]
    if {$period eq ""} {set period "[dict get $status interval]s"}
    set html "<form class='ns-measurement-controls' method='get' style='display:inline-block;margin-right:12px'><input type='hidden' name='@page' value='$::page'><input type='hidden' name='raw' value='$::raw'><label>Measurements: <select name='statsMode' onchange='this.form.submit()'>$options</select></label>"
    if {[dict get $status running] && $mode ne "snapshot"} {
        append html " <label>Display window: <input name='statsWindow' size='5' value='[ns_quotehtml $period]' aria-label='Display window, for example 60s'></label> <button type='submit'>Show</button> <small>Sampling every [ns_quotehtml [dict get $status interval]]s</small>"
    }
    append html "</form>"
    return $html
}

proc ::_ns_stats.runtimeSnapshot {} {
    if {[info commands ::ns_stats] eq ""} {
        return {}
    }
    set mode auto
    if {[info commands ns_queryget] ne ""} {
        set mode [ns_queryget statsMode auto]
    }
    if {$mode ni {auto snapshot}} {
        set mode auto
    }
    set displayError ""
    set status [ns_stats sampling status]
    if {$mode eq "auto" && [dict get $status running]} {
        set selection [::_ns_stats.displayWindow $status]
        set window [dict get $selection effective]
        if {$window eq ""} {
            set sample [ns_stats sample]
        } elseif {[catch {ns_stats sample -window $window} sample]} {
            set displayError "Display window unavailable: $sample"
            if {![catch {ns_stats info lookup syntax sample} syntax]
                && ![string match {*-window*} $syntax]} {
                set displayError "The loaded ns_stats does not support display windows. Install the updated NaviServer tcl/ns_stats.tcl and restart NaviServer."
            }
            set sample {sampling {ready false}}
        }
        if {[dict get $sample sampling ready]} {
            dict set sample collectorMode sample
            dict set sample samplingStatus $status
            dict set sample displayNotice [dict get $selection notice]
            return $sample
        }
    }
    set snapshot [ns_stats snapshot]
    dict set snapshot displayError $displayError
    dict set snapshot requestedWindow [expr {[info exists selection] ? [dict get $selection effective] : ""}]
    dict set snapshot displayNotice [expr {[info exists selection] ? [dict get $selection notice] : ""}]
    dict set snapshot samplingPending [expr {$mode eq "auto" && [dict get $status running]}]
    dict set snapshot collectorMode snapshot
    dict set snapshot samplingStatus $status
    return $snapshot
}

proc ::_ns_stats.sampled {snapshot} {
    return [expr {[dict exists $snapshot collectorMode] && [dict get $snapshot collectorMode] eq "sample"}]
}

# Reconstruct only the preceding counter values needed by the existing tables.
# Unknown/reset deltas produce decreased counters and remain unknown in renderers.
proc ::_ns_stats.utilization.samplePrevious {current snapshot} {
    set previous $current
    dict for {thread data} [dict get $snapshot drivers] {
        dict for {key value} [dict get $data counters] {
            set delta [::_ns_stats.dictGetDef $data deltas $key {}]
            dict set previous drivers $thread $key [expr {$delta eq "" ? $value+1 : $value-$delta}]
        }
    }
    dict for {server data} [dict get $snapshot servers] {
        dict for {pool entry} [dict get $data pools] {
            dict for {key value} [dict get $entry counters] {
                if {![string is double -strict $value]} continue
                set delta [::_ns_stats.dictGetDef $entry deltas $key {}]
                dict set previous servers $server pools $pool stats $key [expr {$delta eq "" ? $value+1 : $value-$delta}]
            }
        }
    }
    dict set previous timestamp [expr {[dict get $snapshot timestamp]-int([dict get $snapshot sampling elapsedSeconds]*1000000)}]
    return $previous
}

proc ::_ns_stats.utilization.sampleTotal {snapshot role key} {
    set total 0.0
    dict for {server data} [dict get $snapshot servers] {
        dict for {pool entry} [dict get $data pools] {
            set value [::_ns_stats.dictGetDef $entry $role $key {}]
            if {$value eq ""} {return -1.0}
            set total [expr {$total+$value}]
        }
    }
    return $total
}

# Describe freshness separately from the period used to calculate rates.
proc ::_ns_stats.utilization.sourcePanel {sample {page utilization}} {
    set snapshot [::_ns_stats.dictGetDef $sample coreSnapshot {}]
    if {$snapshot eq ""} {return {}}
    set status [::_ns_stats.dictGetDef $snapshot samplingStatus {}]
    set notice [::_ns_stats.dictGetDef $snapshot displayNotice {}]
    set html {}
    if {$notice ne ""} {
        append html "<p><strong>[ns_quotehtml $notice]</strong> [ns_quotehtml [::_ns_stats.displayWindowHelp $status]]</p>"
    }
    if {[::_ns_stats.sampled $snapshot]} {
        set age [dict get $snapshot sampling ageSeconds]
        if {$age>2*[dict get $status interval]} {
            append html "<p><strong>Measurements are stale: collected [format %.2f $age]s ago.</strong></p>"
        }
        if {$notice ne ""} {
            set period [dict get $snapshot sampling elapsedSeconds]
            append html "<p>Rates and CPU averages cover the measured [format %.3f $period]s interval ending at [clock format [expr {[dict get $snapshot timestamp]/1000000}] -format {%Y-%m-%d %H:%M:%S %Z}]. Queue depths and other gauges show the interval endpoint.</p>"
        }
        return $html
    }
    if {[::_ns_stats.dictGetDef $snapshot samplingPending false]} {
        set window [::_ns_stats.dictGetDef $snapshot requestedWindow ""]
        set label [expr {$window eq "" ? "Shared sampling: waiting for the first measurement period" : "Shared sampling: display window $window is not yet available"}]
        set details "Sampling every [dict get $status interval]s. No continuous interval matching this window is available yet. Rates and CPU averages are unknown."
        set error [::_ns_stats.dictGetDef $snapshot displayError ""]
        if {$error ne ""} {
            set label "Shared sampling: display window cannot be calculated"
            set details "$error Rates and CPU averages are unknown."
        }
        append html "<p><strong>[ns_quotehtml $label].</strong> [ns_quotehtml $details]"
        if {$notice eq ""} {append html " [ns_quotehtml [::_ns_stats.displayWindowHelp $status]]"}
        append html "</p>"
    } else {
        set reason [expr {[::_ns_stats.dictGetDef $status running false] ? "Live snapshot selected" : "Sampling is stopped"}]
        append html "<p><strong>Live snapshot.</strong> $reason; rates use page observations.</p>"
    }
    append html "<p>Queue depths and other gauges show the state observed at [clock format [expr {[dict get $snapshot timestamp]/1000000}] -format {%Y-%m-%d %H:%M:%S %Z}]; that timestamp is not a measurement duration.</p>"
    return $html
}

# Reduce stored records to plot/table data while paging, rather than retaining
# thousands of complete thread/driver snapshots in the page interpreter.
proc ::_ns_stats.utilization.historyPoint {record} {
    set ready [dict get $record sampling ready]
    set requests -1.0
    set poolRates {}
    dict for {server data} [dict get $record servers] {
        dict for {pool entry} [dict get $data pools] {
            set rate [::_ns_stats.dictGetDef $entry rates requests {}]
            dict set poolRates $server/$pool $rate
        }
    }
    if {$ready} {set requests [::_ns_stats.utilization.sampleTotal $record rates requests]}
    set n [::_ns_stats.utilization.sampleTotal $record deltas requests]
    set timings {}
    foreach key {queuetime filtertime runtime tracetime} {
        set total [::_ns_stats.utilization.sampleTotal $record deltas $key]
        dict set timings $key [expr {$ready && $n>0 && $total>=0 ? 1000.0*$total/$n : -1.0}]
    }
    set groups [::_ns_stats.dictGetDef $record cpuPercent groups {}]
    set cpu -1.0
    if {$ready && $groups ne ""} {
        set cpu 0.0
        dict for {group value} $groups {set cpu [expr {$cpu+$value}]}
    }
    return [dict create timestamp [dict get $record timestamp] instance [dict get $record instance] \
        sampling [dict get $record sampling] requests $requests pools $poolRates timings $timings cpu $cpu groups $groups]
}

# Episode inspection does not need pool timing or CPU projections.
proc ::_ns_stats.utilization.linuxEpisodePoint {record} {
    return [dict create timestamp [dict get $record timestamp] instance [dict get $record instance] \
        sampling [dict get $record sampling] \
        linuxCounters [::_ns_stats.dictGetDef $record linuxNetwork counters {}] \
        previousLinuxCounters [::_ns_stats.dictGetDef $record previousLinuxNetwork counters {}]]
}

proc ::_ns_stats.utilization.readHistory {minutes {projector ::_ns_stats.utilization.historyPoint} {reader ::ns_stats}} {
    set to [clock seconds]
    set from [expr {$to-$minutes*60}]
    set status [{*}$reader history status]
    set source memory
    if {[::_ns_stats.dictGetDef $status pool {}] ne ""} {
        if {[dict get $status owner] ne [ns_info server]} {
            return [dict create available false reason "Persistent history belongs to virtual server '[dict get $status owner]'; open nsstats there."]
        }
        set source sqlite
    }
    set points {}
    set cursor 0
    set coverage {}
    set gaps {}
    set restarts {}
    set events {}
    set resets 0
    set overlap false
    set more true
    set pages 0
    # Bound request work; at ten-second cadence this covers a full day.
    while {$more && [llength $points]<10000} {
        if {$source eq "sqlite"} {
            set page [{*}$reader history query -from $from -to $to -limit 1000 -cursor $cursor]
        } else {
            set page [{*}$reader sampling history -from $from -to $to -limit 180 -cursor $cursor]
        }
        incr pages
        foreach record [dict get $page records] {
            lappend points [{*}$projector $record]
        }
        set current [dict get $page coverage]
        set gaps [concat $gaps [::_ns_stats.dictGetDef $current gaps {}]]
        set restarts [concat $restarts [::_ns_stats.dictGetDef $current restartBoundaries {}]]
        set events [concat $events [::_ns_stats.dictGetDef $current events {}]]
        incr resets [::_ns_stats.dictGetDef $current resetSamples 0]
        set overlap [expr {$overlap || [::_ns_stats.dictGetDef $current evictionOrFailureOverlap false]}]
        set coverage $current
        set more [dict get $page more]
        if {$more} {
            set next [dict get $page nextCursor]
            if {$next eq "" || $next<=$cursor} {error "History paging did not advance"}
            set cursor $next
        }
    }
    dict set coverage gaps [lsort -unique $gaps]
    dict set coverage restartBoundaries [lsort -unique $restarts]
    dict set coverage events [lsort -unique $events]
    dict set coverage resetSamples $resets
    dict set coverage evictionOrFailureOverlap $overlap
    # Current core final-page coverage supersedes page-local diagnostics.
    # Older cores still require conservative aggregation across pages.
    if {[::_ns_stats.dictGetDef $current windowExamined false]} {
        set coverage $current
    } elseif {$pages>1} {
        dict set coverage complete false
    }
    if {$more} {dict set coverage complete false}
    return [dict create available true source $source persistence $status points $points from $from to $to \
        coverage $coverage storage [::_ns_stats.dictGetDef $page storage {}] truncated $more pages $pages]
}

# Produce literal JSON, including nulls for unknown observations and gaps.
proc ::_ns_stats.utilization.historyChart {id title unit points kind} {
    set names {Total}
    if {$kind eq "pools"} {
        set names {}
        foreach point $points {set names [concat $names [dict keys [dict get $point pools]]]}
        set names [lsort -unique $names]
    } elseif {$kind eq "cpu"} {
        set names {connection driver writer spooler other}
    }
    set series {}
    set seriesIndex 0
    foreach name $names {
        set data {}
        set index 0
        set previous {}
        foreach point $points {
            set stamp [expr {[dict get $point timestamp]/1000}]
            set sampling [dict get $point sampling]
            if {$previous ne ""} {
                set elapsed [expr {([dict get $point timestamp]-[dict get $previous timestamp])/1000000.0}]
                if {[dict get $point instance] ne [dict get $previous instance]
                    || $elapsed>1.5*max([dict get $sampling intervalSeconds],[dict get $previous sampling intervalSeconds])} {
                    lappend data $index array [list 0 number [expr {[dict get $previous timestamp]/1000+1}] 1 null {}]
                    incr index
                }
            }
            set value {}
            if {[dict get $sampling ready]} {
                switch -- $kind {
                    pools {set value [::_ns_stats.dictGetDef $point pools $name {}]}
                    cpu {set value [::_ns_stats.dictGetDef $point groups $name {}]}
                    default {set value [dict get $point requests]}
                }
            }
            set type number
            if {$value eq "" || $value<0} {set type null; set value {}}
            lappend data $index array [list 0 number $stamp 1 $type $value]
            incr index
            set previous $point
        }
        lappend series $seriesIndex object [list name string $name data array $data]
        incr seriesIndex
    }
    set config [list {} object [list chart object {type string line height number 300} \
        title object [list text string $title] time object {useUTC boolean false} \
        xAxis object {type string datetime} yAxis object [list title object [list text string $unit]] \
        plotOptions object {series object {connectNulls boolean false marker object {enabled boolean false}}} \
        series array $series]]
    set json [string map [list < {\u003c} > {\u003e} & {\u0026}] [ns_json value -- $config]]
    set jsonId [ns_json value -- [list {} string $id]]
    return "<div id='[ns_quotehtml $id]' class='utilization-chart'></div><script>Highcharts.chart($jsonId,$json);</script>"
}

proc ::_ns_stats.utilization.historyPanel {} {
    if {[info commands ::ns_stats] eq ""} {return {}}
    set minutes [ns_queryget historyMinutes 15]
    if {$minutes ni {15 60 360 1440}} {set minutes 15}
    set options {}
    foreach {value label} {15 {Last 15 minutes} 60 {Last hour} 360 {Last 6 hours} 1440 {Last 24 hours}} {
        set selected [expr {$minutes==$value ? " selected" : ""}]
        append options "<option value='$value'$selected>$label</option>"
    }
    set html "<h2>Recent sampled history</h2><form method='get'><input type='hidden' name='@page' value='utilization'><input type='hidden' name='raw' value='[expr {[info exists ::raw] && $::raw ? 1 : 0}]'><input type='hidden' name='statsMode' value='[ns_quotehtml [ns_queryget statsMode auto]]'><input type='hidden' name='statsWindow' value='[ns_quotehtml [ns_queryget statsWindow ""]]'><label>Period <select name='historyMinutes'>$options</select></label> <button type='submit'>Show history</button></form>"
    if {[catch {::_ns_stats.utilization.readHistory $minutes} result]} {
        return "$html<p>History unavailable: [ns_quotehtml $result]</p>"
    }
    if {![dict get $result available]} {return "$html<p>[ns_quotehtml [dict get $result reason]]</p>"}
    set points [dict get $result points]
    set coverage [dict get $result coverage]
    set source [dict get $result source]
    set status [dict get $result persistence]
    set label [expr {$source eq "sqlite" ? "Persistent SQLite history (including retained samples before restart)" : "Process memory: configured age and sample limits apply; lost on process restart"}]
    append html "<p>[ns_quotehtml $label]. [::_ns_stats.hr [llength $points]] samples returned. No sampling or persistence settings are changed by this page.</p>"
    set rows [list [list Source $label] \
        [list "Paging complete" [expr {![dict get $result truncated]}]] \
        [list "Coverage reported complete" [::_ns_stats.dictGetDef $coverage complete false]] \
        [list Gaps [::_ns_stats.hr [llength [dict get $coverage gaps]]]] \
        [list "Restart boundaries" [::_ns_stats.hr [llength [dict get $coverage restartBoundaries]]]] \
        [list "Baseline/reset samples" [::_ns_stats.hr [dict get $coverage resetSamples]]] \
        [list "Eviction or failure overlap" [dict get $coverage evictionOrFailureOverlap]] \
        [list "Dropped through (epoch microseconds)" [::_ns_stats.dictGetDef $coverage droppedThrough {}]]]
    if {$source eq "sqlite"} {
        lappend rows [list "Persistence enabled" [dict get $status enabled]] \
            [list "Write failures since persistence started" [::_ns_stats.hr [dict get $status failures]]]
    }
    foreach {key label} {sampleCount {Retained samples} retainedSeconds {Retained seconds}
                         allocatedBytes {SQLite allocated bytes} usedBytes {SQLite used bytes}
                         databaseLimitBytes {SQLite database cap (bytes)} bytesPerSample {Bytes per retained sample}} {
        if {[dict exists $result storage $key]} {
            lappend rows [list $label [::_ns_stats.hr [dict get $result storage $key]]]
        }
    }
    foreach {key label} {retainedFrom {Oldest retained sample} retainedTo {Newest retained sample}} {
        set value [::_ns_stats.dictGetDef $coverage $key 0]
        lappend rows [list $label [expr {$value>0 ? [clock format [expr {int($value)}] -format {%Y-%m-%d %H:%M:%S %Z}] : "unknown"}]]
    }
    append html [::_ns_stats.results history-coverage 0 {Metric Value} {?@page=utilization} $rows 0 {left left}]
    if {[dict get $result truncated]} {append html "<p><strong>History limited to 10,000 samples; select a shorter period.</strong></p>"}
    if {$points eq ""} {return "$html<p>No retained samples in this interval. Start sampling to collect observations; enable persistence for longer history and restart survival.</p>"}
    append html "<p>Rates and timings describe each measured sampling interval. Gauges describe its end. Missing/reset intervals are unknown; chart lines break across gaps and restarts. CPU percentages show measured thread groups relative to one core.</p>"
    append html "<div class='utilization-charts'>" \
        [::_ns_stats.utilization.historyChart history-requests {Requests per second by server/pool} requests/s $points pools] \
        [::_ns_stats.utilization.historyChart history-cpu {Measured CPU by thread group} {CPU % of one core} $points cpu] "</div>"
    set rows {}
    foreach point [lreverse [lrange $points end-19 end]] {
        set elapsed [::_ns_stats.dictGetDef $point sampling elapsedSeconds {}]
        set requests [dict get $point requests]
        set row [list [clock format [expr {[dict get $point timestamp]/1000000}] -format {%Y-%m-%d %H:%M:%S %Z}] \
            [expr {$elapsed eq "" ? "" : [::_ns_stats.hr $elapsed]}] \
            [expr {$requests >= 0 ? [::_ns_stats.hr $requests] : "\u2014"}]]
        foreach key {queuetime filtertime runtime tracetime} {
            set value [dict get $point timings $key]
            lappend row [expr {$value>=0 ? [::_ns_stats.hr $value %.3f] : "unknown"}]
        }
        set cpu [dict get $point cpu]
        lappend row [expr {$cpu>=0 ? [::_ns_stats.hr $cpu %.1f] : "unknown"}]
        lappend rows $row
    }
    append html "<h3>Latest 20 retained intervals</h3>" \
        [::_ns_stats.results history-intervals 0 {{Interval ending} {Seconds} {Requests/s} {Queue ms} {Filter ms} {Run ms} {Trace ms} {Measured CPU %}} {?@page=utilization} $rows 0 {left right right right right right right right}]
    return $html
}

# Adjacent affected sampling intervals form an episode. Unknown/reset samples,
# missing Linux probes, clean intervals and sampling gaps break an episode.
proc ::_ns_stats.utilization.linuxEpisodes {points} {
    set episodes {}
    set episode {}
    set examined 0
    set unknown 0
    set keys {tcpListenOverflows tcpListenDrops tcpBacklogDrops tcpReceiveDrops}
    foreach point $points {
        set sampling [dict get $point sampling]
        set elapsed [::_ns_stats.dictGetDef $sampling elapsedSeconds 0]
        set end [expr {[dict get $point timestamp]/1000000.0}]
        set start [::_ns_stats.dictGetDef $sampling from {}]
        set valid [expr {[dict get $sampling ready] && $elapsed ne "" && $elapsed>0
                         && $elapsed<=1.5*[dict get $sampling intervalSeconds]
                         && $start ne ""}]
        set counts {}
        set affected false
        set complete true
        set known 0
        foreach key $keys {
            set current [::_ns_stats.dictGetDef $point linuxCounters $key {}]
            set previous [::_ns_stats.dictGetDef $point previousLinuxCounters $key {}]
            if {$current eq "" || $previous eq "" || $current<$previous} {
                set complete false
                dict set counts $key {}
            } else {
                incr known
                set delta [expr {$current-$previous}]
                dict set counts $key $delta
                if {$delta>0} {set affected true}
            }
        }
        set valid [expr {$valid && $known>0}]
        if {!$valid || !$complete} {incr unknown}
        if {$valid} {incr examined}
        # Partial intervals may retain positive evidence for known counters,
        # but cannot connect episodes across unavailable/reset observations.
        if {!$valid || !$affected || !$complete} {
            if {$episode ne ""} {lappend episodes $episode; set episode {}}
            if {!$valid || !$affected} {continue}
        }
        if {$episode ne "" && ([dict get $episode end]!=$start
                                || [dict get $episode instance] ne [dict get $point instance])} {
            lappend episodes $episode
            set episode {}
        }
        if {$episode eq ""} {
            set episode [dict create start $start end $end instance [dict get $point instance] \
                counts $counts intervals 1]
        } else {
            dict set episode end $end
            dict incr episode intervals
            foreach key $keys {
                dict set episode counts $key [expr {[dict get $episode counts $key]+[dict get $counts $key]}]
            }
        }
        if {!$complete} {lappend episodes $episode; set episode {}}
    }
    if {$episode ne ""} {lappend episodes $episode}
    return [dict create episodes $episodes examined $examined unknown $unknown]
}

# Keep the full-day query off normal utilization refreshes.
proc ::_ns_stats.utilization.linuxEpisodesLink {} {
    if {[info commands ::ns_stats] eq "" || ![string equal -nocase Linux $::tcl_platform(os)]} {return {}}
    return {<p><a href='?@page=utilization.episodes'>View historical TCP loss episodes for the last 24 hours</a>. History is loaded when opened.</p>}
}

proc ::_ns_stats.utilization.episodes {} {
    set html [::_ns_stats.header "TCP loss history"]
    append html {<p><a href='?@page=utilization'>Back to utilization</a></p>}
    append html [::_ns_stats.utilization.linuxEpisodesPanel] [::_ns_stats.footer]
    return $html
}

proc ::_ns_stats.utilization.linuxEpisodesPanel {} {
    if {[info commands ::ns_stats] eq "" || ![string equal -nocase Linux $::tcl_platform(os)]} {return {}}
    set html "<h2>Historical TCP loss episodes — last 24 hours</h2>"
    if {[catch {::_ns_stats.utilization.readHistory 1440 ::_ns_stats.utilization.linuxEpisodePoint} history]} {
        return "$html<p>History unavailable: [ns_quotehtml $history]</p>"
    }
    if {![dict get $history available]} {return "$html<p>[ns_quotehtml [dict get $history reason]]</p>"}
    set evidence [::_ns_stats.utilization.linuxEpisodes [dict get $history points]]
    set episodes [dict get $evidence episodes]
    append html "<p>[llength $episodes] recorded episodes; [dict get $evidence examined] measured intervals examined; [dict get $evidence unknown] intervals with unavailable observations. Counts describe their recorded intervals, independently of the selected display window and current status. Network counters cover the network namespace, including other services.</p>"
    append html "<p>Only retained observations are evidence. Missing history, failed probes, counter resets and sampling gaps are unknown. A full day requires sufficient configured memory or persistent retention; older samples without Linux observations cannot be reconstructed.</p>"
    if {![::_ns_stats.dictGetDef $history coverage complete false] || [dict get $history truncated]} {
        append html "<p><strong>History coverage is incomplete; this is not a complete 24-hour total.</strong></p>"
    }
    if {[dict get $evidence examined]==0} {
        return "$html<p>TCP episode evidence is unavailable: no usable Linux sampling intervals were retained.</p>"
    }
    if {$episodes eq ""} {
        return "$html<p>No TCP loss episodes found in the available measured intervals.</p>"
    }
    set rows {}
    foreach episode [lreverse [lrange $episodes end-49 end]] {
        set row {}
        foreach key {start end} {
            lappend row [ns_quotehtml [clock format [expr {int([dict get $episode $key])}] -format {%Y-%m-%d %H:%M:%S %Z}]]
        }
        foreach key {tcpListenOverflows tcpListenDrops tcpBacklogDrops tcpReceiveDrops} {
            set count [dict get $episode counts $key]
            lappend row [expr {$count eq "" ? "\u2014" : $count}]
        }
        lappend rows $row
    }
    if {[llength $episodes]>50} {append html "<p>Showing the latest 50 episodes.</p>"}
    append html [::_ns_stats.results linux-episodes 0 \
        {{Interval start} {Interval end} {Listen overflows} {Listen drops} {Backlog drops} {Receive-queue drops}} \
        {?@page=utilization} $rows 0 {left left right right right right}]
    return $html
}

proc ::_ns_stats.requestCounts {} {
    set snapshot [::_ns_stats.runtimeSnapshot]
    set total 0
    set servers {}
    if {$snapshot ne ""} {
        dict for {server data} [dict get $snapshot servers] {
            set count 0
            dict for {pool entry} [dict get $data pools] {
                incr count [dict get $entry counters requests]
            }
            dict set servers $server $count
            incr total $count
        }
        set timestamp [dict get $snapshot timestamp]
    } else {
        foreach server [ns_info servers] {
            set count 0
            foreach pool [ns_server -server $server pools] {
                incr count [dict get [ns_server -server $server -pool $pool stats] requests]
            }
            dict set servers $server $count
            incr total $count
        }
        set timestamp [clock microseconds]
    }
    return [dict create timestamp $timestamp total $total servers $servers coreSnapshot $snapshot]
}

proc ::_ns_stats.requestRates {sampleKey} {
    set counts       [::_ns_stats.requestCounts]
    set total        [dict get $counts total]
    set serverCounts [dict get $counts servers]
    set linuxNetwork [::_ns_stats.linuxNetworkSnapshot]
    set now          [dict get $counts timestamp]

    set snapshot [dict get $counts coreSnapshot]
    if {[::_ns_stats.sampled $snapshot]} {
        set rates {}
        dict for {server data} [dict get $snapshot servers] {
            set totalRate 0.0
            dict for {pool entry} [dict get $data pools] {
                set value [::_ns_stats.dictGetDef $entry rates requests {}]
                if {$value eq ""} {set totalRate -1.0; break}
                set totalRate [expr {$totalRate+$value}]
            }
            dict set rates $server $totalRate
        }
        return [dict create available 1 elapsed [dict get $snapshot sampling elapsedSeconds] \
            total [::_ns_stats.utilization.sampleTotal $snapshot rates requests] servers $rates \
            linuxNetwork $linuxNetwork previousLinuxNetwork {}]
    }

    set sample [dict create \
                    timestamp    $now \
                    total        $total \
                    servers      $serverCounts \
                    linuxNetwork $linuxNetwork \
                   ]

    set previousSample \
        [nsv_set -reset _ns_stats $sampleKey $sample]

    set result [dict create \
                    available            0 \
                    elapsed              -1.0 \
                    total                -1.0 \
                    servers              {} \
                    linuxNetwork         $linuxNetwork \
                    previousLinuxNetwork {}]

    if {[dict exists $previousSample linuxNetwork]} {
        dict set result previousLinuxNetwork \
            [dict get $previousSample linuxNetwork]
    }

    if {![dict exists $previousSample timestamp]} {
        return $result
    }

    set previousTimestamp \
        [dict get $previousSample timestamp]

    set elapsed [expr {($now - $previousTimestamp) / 1000000.0}]

    if {$elapsed <= 0.0} {
        return $result
    }

    set previousTotal [::_ns_stats.dictGetDef $previousSample total -1]
    set totalRate     [::_ns_stats.intervalRate $total $previousTotal $elapsed]

    set serverRates  {}
    foreach server [dict keys $serverCounts] {
        set rate -1.0

        if {[dict exists $previousSample servers $server]} {
            set rate \
                [::_ns_stats.intervalRate \
                     [dict get $serverCounts $server] \
                     [dict get $previousSample servers $server] \
                     $elapsed]
        }

        dict set serverRates $server $rate
    }

    dict set result available 1
    dict set result elapsed   $elapsed
    dict set result total     $totalRate
    dict set result servers   $serverRates

    return $result
}

proc ::_ns_stats.totalRequests {} {
    set totalRequests [dict get [::_ns_stats.requestCounts] total]
    if {$totalRequests == 0} {
        # avoid division by 0
        incr totalRequests
    }
    return $totalRequests
}


proc ::_ns_stats.utilizationSample {} {
    set drivers {}
    set queues  {}
    set servers {}

    set snapshot [::_ns_stats.runtimeSnapshot]
    set collector legacy
    if {$snapshot ne ""} {
        set collector [expr {[::_ns_stats.sampled $snapshot] ? "ns_stats-sample" : "ns_stats"}]
        dict for {thread entry} [dict get $snapshot drivers] {
            set stats [dict merge [dict get $entry counters] \
                [dict create thread $thread module [dict get $entry module]]]
            foreach {source target} {reading reading waitingForConnection waiting closing closing} {
                dict set stats $target [dict get $entry socketStates $source]
            }
            dict set drivers $thread $stats
        }
        dict for {server data} [dict get $snapshot servers] {
            dict for {pool entry} [dict get $data pools] {
                set gauges [dict get $entry gauges]
                dict set servers $server pools $pool [dict create \
                    stats [dict get $entry counters] threads [dict get $gauges threads] \
                    waiting [dict get $gauges waitingRequests] active [dict get $gauges activeRequests]]
            }
        }
    } else {
        foreach stats [ns_driver stats] {
            dict set drivers [dict get $stats thread] $stats
        }
    }

    # Queue diagnostics are not currently exposed by ns_stats.
    foreach kind {driver writer spooler} {
        if {![catch {ns_driver queues $kind} entries]} {
            foreach entry $entries {
                dict set queues $kind [dict get $entry thread] $entry
            }
        }
    }

    if {$snapshot eq ""} {
        foreach server [ns_info servers] {
            foreach pool [ns_server -server $server pools] {
                set stats   [ns_server -server $server -pool $pool stats]
                set threads [ns_server -server $server -pool $pool threads]

                dict set servers $server pools $pool \
                    [dict create \
                         stats   $stats \
                         threads $threads \
                         waiting [ns_server -server $server -pool $pool waiting] \
                         active  [llength \
                                      [ns_server -server $server \
                                           -pool $pool all]]]
            }
        }
    }

    if {$snapshot ne "" && [dict exists $snapshot linuxNetwork]} {
        set linuxNetwork [dict get $snapshot linuxNetwork]
    } elseif {[::_ns_stats.sampled $snapshot]
              || [::_ns_stats.dictGetDef $snapshot samplingPending false]} {
        set linuxNetwork {}
    } else {
        set linuxNetwork [::_ns_stats.linuxNetworkSnapshot]
    }
    set now          [expr {$snapshot eq "" ? [clock microseconds] : [dict get $snapshot timestamp]}]

    set sample [dict create \
                    timestamp    $now \
                    collector    $collector \
                    drivers      $drivers \
                    queues       $queues \
                    servers      $servers \
                    linuxNetwork $linuxNetwork \
                    supplementalTimestamp [clock microseconds]]
    set previous \
        [nsv_set -reset _ns_stats utilizationSample $sample]

    # Do not compare differently shaped pool keys when the collector changes.
    if {$previous ne "" && [::_ns_stats.dictGetDef $previous collector legacy] ne $collector} {
        set previous {}
    }

    #
    # The stored sample may predate the linuxNetwork addition.
    #
    if {![dict exists $previous linuxNetwork]} {
        dict set previous linuxNetwork {}
    }

    set elapsed 0.0
    if {[dict exists $previous timestamp]} {
        set elapsed [expr {($now - [dict get $previous timestamp]) / 1000000.0}]
    }

    set supplementalElapsed 0.0
    if {[dict exists $previous supplementalTimestamp]} {
        set supplementalElapsed [expr {([dict get $sample supplementalTimestamp]-[dict get $previous supplementalTimestamp])/1000000.0}]
    }
    set supplementalPrevious $previous
    if {[::_ns_stats.sampled $snapshot]} {
        set previous [::_ns_stats.utilization.samplePrevious $sample $snapshot]
        set elapsed [dict get $snapshot sampling elapsedSeconds]
    }

    if {[::_ns_stats.dictGetDef $snapshot samplingPending false]} {set elapsed 0.0}

    if {[::_ns_stats.sampled $snapshot]
        || [::_ns_stats.dictGetDef $snapshot samplingPending false]} {
        dict set supplementalPrevious linuxNetwork [::_ns_stats.dictGetDef $snapshot previousLinuxNetwork {}]
        set supplementalElapsed $elapsed
        dict set sample supplementalTimestamp $now
        dict set supplementalPrevious supplementalTimestamp [expr {$now-int($elapsed*1000000)}]
    }

    return [dict create \
                current  $sample \
                previous $previous \
                elapsed  $elapsed \
                coreSnapshot $snapshot \
                supplementalPrevious $supplementalPrevious \
                supplementalElapsed $supplementalElapsed]
}

#
# Return the non-negative difference between two cumulative counters.
# A negative result indicates a counter reset and is reported as -1.
#
proc ::_ns_stats.intervalDelta {current previous} {
    return [expr {
        $current >= $previous
        ? $current - $previous
        : -1.0
    }]
}

#
# Return the change per second between two cumulative counter samples.
#
proc ::_ns_stats.intervalRate {current previous elapsed} {
    if {$elapsed <= 0.0} {
        return -1.0
    }

    set delta [::_ns_stats.intervalDelta $current $previous]

    return [expr {
        $delta >= 0.0
        ? $delta / $elapsed
        : -1.0
    }]
}

proc ::_ns_stats.utilization.colorize {severity value {bold 0}} {
    switch $severity {
        critical { set color red }
        warning  { set color orange }
        default  { return $value }
    }

    if {$bold} {
        set value "<b>$value</b>"
    }

    return "<font color='$color'>$value</font>"
}

proc ::_ns_stats.dictGetDef {dictionary args} {
    set default [lindex $args end]
    set path    [lrange $args 0 end-1]

    if {[dict exists $dictionary {*}$path]} {
        return [dict get $dictionary {*}$path]
    }
    return $default
}


proc ::_ns_stats.utilization.sumPoolCounter {sample counter} {
    set total 0

    if {![dict exists $sample servers]} {
        return $total
    }

    dict for {server serverData} [dict get $sample servers] {
        dict for {pool poolData} [dict get $serverData pools] {
            set total [expr {$total + [::_ns_stats.dictGetDef $poolData stats $counter 0]}]
        }
    }

    return $total
}

proc ::_ns_stats.utilization.sumDriverCounter {sample counter} {
    set total 0
    if {![dict exists $sample drivers]} {
        return $total
    }
    dict for {driver stats} [dict get $sample drivers] {
        incr total [::_ns_stats.dictGetDef $stats $counter 0]
    }
    return $total
}



proc ::_ns_stats.utilization.displayRate {rate {suffix /s}} {
    if {$rate < 0.0} {
        return "\u2014"
    } elseif {$rate < 0.1} {
        return "[format %.3f $rate]$suffix"
    } elseif {$rate < 10.0} {
        return "[format %.2f $rate]$suffix"
    } elseif {$rate < 100.0} {
        return "[format %.1f $rate]$suffix"
    } else {
        return "[format %.0f $rate]$suffix"
    }
}

proc ::_ns_stats.utilization.formatWorker {count measured cpu} {
    if {$count == 0} {
        return "\u2014"
    }
    set cpuDisplay [expr { $measured > 0 ? "[format %.1f $cpu]%" : "\u2014"}]
    return "<span class='nowrap'>$count / $cpuDisplay</span>"
}

#
# Read a file, returning an empty string when it is unavailable.
#
proc ::_ns_stats.linuxReadFile {path} {
    set content ""
    if {[file readable $path]} {
        try {
            set f [open $path r]
            set content [read $f]
        } on error {errorMsg} {
            # ignore errors
        } finally {
            close $f
        }
    }
    return $content
}
#
# Parse files such as /proc/net/snmp and /proc/net/netstat. These
# contain pairs of lines, with names in the first line and values in
# the second line.
#
proc ::_ns_stats.linuxReadNamedCounters {path} {
    set content [::_ns_stats.linuxReadFile $path]
    set result  {}

    if {$content eq ""} {
        return $result
    }

    set lines [split [string trim $content] \n]
    set count [llength $lines]

    for {set i 0} {$i + 1 < $count} {incr i} {
        set nameFields  [regexp -all -inline {\S+} [lindex $lines $i]]
        set valueFields [regexp -all -inline {\S+} [lindex $lines [expr {$i + 1}]]]

        if {[llength $nameFields] < 2
            || [llength $valueFields] < 2} {
            continue
        }

        set nameSection  [string trimright [lindex $nameFields 0] :]
        set valueSection [string trimright [lindex $valueFields 0] :]

        if {$nameSection ne $valueSection
            || [llength $nameFields] != [llength $valueFields]} {
            continue
        }

        foreach name [lrange $nameFields 1 end] value [lrange $valueFields 1 end] {
            if {[string is integer -strict $value]} {
                dict set result $nameSection $name $value
            }
        }

        incr i
    }

    return $result
}
#
# Parse /proc/net/snmp6. Unlike /proc/net/snmp, this file has one
# name/value pair per line.
#
proc ::_ns_stats.linuxReadSnmp6 {} {
    set content [::_ns_stats.linuxReadFile /proc/net/snmp6]
    set result  {}

    foreach line [split [string trim $content] \n] {
        set fields [regexp -all -inline {\S+} $line]

        if {[llength $fields] == 2
            && [string is integer -strict [lindex $fields 1]]} {
            dict set result [lindex $fields 0] [lindex $fields 1]
        }
    }

    return $result
}

#
# Return aggregate counters from all non-loopback network interfaces.
#
proc ::_ns_stats.linuxReadDeviceCounters {} {
    set content [::_ns_stats.linuxReadFile /proc/net/dev]
    set result  {}

    if {$content eq ""} {
        return $result
    }

    set rxDrops  0
    set txDrops  0
    set rxErrors 0
    set txErrors 0
    set found    0

    foreach line [split $content \n] {
        if {![regexp {^\s*([^:]+):\s*(.*)$} $line _ interface values]} {
            continue
        }

        set interface [string trim $interface]
        if {$interface eq "lo"} {
            continue
        }

        set fields [regexp -all -inline {\S+} $values]

        #
        # /proc/net/dev fields:
        #
        # RX: bytes packets errors dropped fifo frame compressed multicast
        # TX: bytes packets errors dropped fifo colls carrier compressed
        #
        if {[llength $fields] < 16} {
            continue
        }

        incr rxErrors [lindex $fields 2]
        incr rxDrops  [lindex $fields 3]
        incr txErrors [lindex $fields 10]
        incr txDrops  [lindex $fields 11]
        set found 1
    }

    if {$found} {
        set result [dict create \
                        interfaceRxDrops  $rxDrops \
                        interfaceTxDrops  $txDrops \
                        interfaceRxErrors $rxErrors \
                        interfaceTxErrors $txErrors]
    }

    return $result
}

#
# Return the inode numbers of sockets owned by the NaviServer process.
#
proc ::_ns_stats.linuxProcessSocketInodes {} {
    set result {}

    foreach fd [glob -nocomplain /proc/self/fd/*] {
        if {[catch {file readlink $fd} target]} {
            continue
        }

        if {[regexp {^socket:\[([0-9]+)\]$} $target _ inode]} {
            dict set result $inode 1
        }
    }

    return $result
}

#
# Return aggregate kernel queues for sockets owned by NaviServer.
#
# For TCP listeners, rx_queue is an accept-queue count. For connected
# TCP and UDP sockets, tx_queue and rx_queue describe queued data.
#
proc ::_ns_stats.linuxReadSocketQueues {} {
    set socketInodes [::_ns_stats.linuxProcessSocketInodes]

    if {[dict size $socketInodes] == 0} {
        return {}
    }

    set result [dict create \
                    tcpListenQueued     0 \
                    tcpListenMax        0 \
                    tcpListeners        0 \
                    tcpReceiveBytes     0 \
                    tcpReceiveMax       0 \
                    tcpSendBytes        0 \
                    tcpSendMax          0 \
                    udpReceiveBytes     0 \
                    udpReceiveMax       0 \
                    udpSendBytes        0 \
                    udpSendMax          0 \
                    udpSocketDrops      0 \
                    udpSocketsWithDrops 0 \
                    udpSocketDropMax    0
               ]

    set tablesRead 0

    foreach protocol {tcp udp} {
        foreach path [list \
                          /proc/self/net/$protocol \
                          /proc/self/net/${protocol}6] {
            set content [::_ns_stats.linuxReadFile $path]

            if {$content eq ""} {
                continue
            }
            incr tablesRead

            foreach line [lrange [split [string trim $content] \n] 1 end] {
                set fields [regexp -all -inline {\S+} $line]

                #
                # Relevant fields:
                #
                #  3: socket state
                #  4: tx_queue:rx_queue
                #  9: socket inode
                #
                if {[llength $fields] < 10} {
                    continue
                }

                set inode [lindex $fields 9]

                if {![dict exists $socketInodes $inode]} {
                    continue
                }

                set state      [lindex $fields 3]
                set queueField [lindex $fields 4]

                if {[scan $queueField %x:%x txQueue rxQueue] != 2} {
                    continue
                }

                if {$protocol eq "tcp" && $state eq "0A"} {
                    dict incr result tcpListeners
                    dict incr result tcpListenQueued $rxQueue

                    if {$rxQueue > [dict get $result tcpListenMax]} {
                        dict set result tcpListenMax $rxQueue
                    }

                } elseif {$protocol eq "tcp"} {
                    dict incr result tcpReceiveBytes $rxQueue
                    dict incr result tcpSendBytes    $txQueue

                    if {$rxQueue > [dict get $result tcpReceiveMax]} {
                        dict set result tcpReceiveMax $rxQueue
                    }
                    if {$txQueue > [dict get $result tcpSendMax]} {
                        dict set result tcpSendMax $txQueue
                    }

                } else {
                    dict incr result udpReceiveBytes $rxQueue
                    dict incr result udpSendBytes    $txQueue

                    if {$rxQueue > [dict get $result udpReceiveMax]} {
                        dict set result udpReceiveMax $rxQueue
                    }
                    if {$txQueue > [dict get $result udpSendMax]} {
                        dict set result udpSendMax $txQueue
                    }

                    #
                    # Linux exposes the cumulative socket drop counter
                    # as the final field in /proc/net/udp{,6}.
                    #
                    set drops [lindex $fields end]
                    if {[string is integer -strict $drops]} {
                        dict incr result udpSocketDrops $drops

                        if {$drops > 0} {
                            dict incr result udpSocketsWithDrops
                        }
                        if {$drops > [dict get $result udpSocketDropMax]} {
                            dict set result udpSocketDropMax $drops
                        }
                    }
                }
            }
        }
    }

    return [expr {$tablesRead > 0 ? $result : {}}]
}

# Whole block devices only: partitions would duplicate their parent counters.
# stat fields and inflight are documented in Documentation/block/stat.rst
# and Documentation/ABI/stable/sysfs-block in the Linux kernel.
proc ::_ns_stats.linuxDiskSnapshot {} {
    set disks {}
    foreach path [lsort [glob -nocomplain -directory /sys/block *]] {
        set name [file tail $path]
        if {[string match loop* $name] || [string match ram* $name]} {continue}
        set fields [regexp -all -inline {\S+} [::_ns_stats.linuxReadFile $path/stat]]
        if {[llength $fields] < 11} {continue}
        set valid 1
        foreach value $fields {
            if {![string is entier -strict $value] || $value < 0} {set valid 0; break}
        }
        if {!$valid} {continue}
        set disk [dict create reads [lindex $fields 0] readMs [lindex $fields 3] \
                      writes [lindex $fields 4] writeMs [lindex $fields 7]]
        set inflight [regexp -all -inline {\S+} [::_ns_stats.linuxReadFile $path/inflight]]
        if {[llength $inflight] == 2 && [string is entier -strict [lindex $inflight 1]]
            && [lindex $inflight 1] >= 0} {
            dict set disk writeQueue [lindex $inflight 1]
        }
        dict set disks $name $disk
    }
    return $disks
}

# Latency includes time queued and serviced; writes in flight are an
# instantaneous gauge, including writes already being serviced.
proc ::_ns_stats.utilization.diskRows {current previous elapsed {levels {}}} {
    set rows {}
    dict for {name disk} $current {
        set latency {}
        foreach {label count ticks} {read reads readMs write writes writeMs} {
            set text {—}
            if {$elapsed > 0 && [dict exists $previous $name]} {
                set old [dict get $previous $name]
                set n [::_ns_stats.intervalDelta [dict get $disk $count] \
                           [dict get $old $count]]
                set ms [::_ns_stats.intervalDelta [dict get $disk $ticks] \
                            [dict get $old $ticks]]
                if {$n > 0 && $ms >= 0} {set text "[format %.2f [expr {double($ms)/$n}]] ms"}
            }
            lappend latency "$label $text"
        }
        set queue [::_ns_stats.dictGetDef $disk writeQueue {—}]
        set text "[join $latency {, }]; write queue depth $queue"
        set level [::_ns_stats.dictGetDef $levels $name ok]
        lappend rows [list [::_ns_stats.utilization.colorize $level "Linux Disk [ns_quotehtml $name]"] \
            [::_ns_stats.utilization.colorize $level [::_ns_stats.utilization.tooltip $text \
                 {Host block-device counters. Mean read/write completion latency over the observed interval (queue plus service time); unavailable without completed I/O and two valid samples. Write queue depth is the current number of writes in flight, including service. Stacked devices are listed separately; do not sum them.}]]]
    }
    return $rows
}

# Track consecutive observed intervals, separately for reads and writes.
# Idle I/O, missing devices and counter resets break the streak.
proc ::_ns_stats.utilization.diskHealth {current previous elapsed history warning critical} {
    set result {}
    dict for {name disk} $current {
        foreach {label count ticks} {read reads readMs write writes writeMs} {
            set level ok
            set ms -1
            if {$elapsed > 0 && [dict exists $previous $name]} {
                set old [dict get $previous $name]
                set n [::_ns_stats.intervalDelta [dict get $disk $count] [dict get $old $count]]
                set t [::_ns_stats.intervalDelta [dict get $disk $ticks] [dict get $old $ticks]]
                if {$n > 0 && $t >= 0} {
                    set ms [expr {double($t)/$n}]
                    if {$ms >= $critical} {set level critical} elseif {$ms >= $warning} {set level warning}
                }
            }
            foreach threshold {warning critical} {
                set streak 0
                if {$level eq "critical" || ($level eq "warning" && $threshold eq "warning")} {
                    set streak [expr {1 + [::_ns_stats.dictGetDef $history $name $label $threshold 0]}]
                }
                dict set result $name $label $threshold $streak
            }
            dict set result $name $label latency $ms
        }
    }
    return $result
}

# Every valid measurement window is assessed immediately. Sustained conditions
# are evaluated separately from background samples, without page-local state.
proc ::_ns_stats.utilization.diskWarnings {current previous elapsed severityVar reasonsVar} {
    upvar 1 $severityVar severity $reasonsVar reasons
    if {![string equal -nocase Linux $::tcl_platform(os)]} {return {}}
    set disks [::_ns_stats.dictGetDef $current linuxNetwork disks {}]
    if {[dict size $disks] == 0} {
        return [list [list {Linux Disk Metrics} {Unavailable: Linux block-device statistics could not be read.}]]
    }
    set warning [ns_config -int -min 1 ns/module/nsstats diskLatencyWarningMs 20]
    set critical [ns_config -int -min $warning ns/module/nsstats diskLatencyCriticalMs 100]
    set health [::_ns_stats.utilization.diskHealth $disks \
        [::_ns_stats.dictGetDef $previous linuxNetwork disks {}] $elapsed {} $warning $critical]
    set levels {}
    dict for {name disk} $health {
        foreach label {read write} {
            set entry [dict get $disk $label]
            set level ok
            if {[dict get $entry critical] > 0} {
                set level critical
            } elseif {[dict get $entry warning] > 0} {
                set level warning
            }
            if {$level eq "ok"} {continue}
            if {$level eq "critical" || [::_ns_stats.dictGetDef $levels $name ok] eq "ok"} {
                dict set levels $name $level
            }
            if {$level eq "critical" || $severity eq "ok"} {set severity $level}
            set reason "disk [ns_quotehtml $name] $label latency [format %.2f [dict get $entry latency]] ms (measurement interval; $level threshold [expr {$level eq "critical" ? $critical : $warning}] ms)"
            set queue [::_ns_stats.dictGetDef $disks $name writeQueue 0]
            set oldQueue [::_ns_stats.dictGetDef $previous linuxNetwork disks $name writeQueue 0]
            if {$label eq "write" && $queue > 0 && $oldQueue > 0} {
                append reason "; writes in flight at both interval endpoints, current depth $queue"
            }
            lappend reasons $reason
        }
    }
    return [::_ns_stats.utilization.diskRows $disks \
        [::_ns_stats.dictGetDef $previous linuxNetwork disks {}] $elapsed $levels]
}

# Assess only actual sampler intervals, never a display-window aggregate or
# a page-to-page baseline. Missing/idle/reset data breaks per-device streaks.
proc ::_ns_stats.utilization.diskSustained {records samples warning critical} {
    set history {}
    set count 0
    set previous {}
    set from {}
    set to {}
    foreach record [lrange $records [expr {[llength $records]-$samples}] end] {
        set sampling [::_ns_stats.dictGetDef $record sampling {}]
        set elapsed [::_ns_stats.dictGetDef $sampling elapsedSeconds 0]
        set cadence [::_ns_stats.dictGetDef $sampling intervalSeconds 0]
        set start [::_ns_stats.dictGetDef $sampling from {}]
        set end [::_ns_stats.dictGetDef $sampling to {}]
        set disks [::_ns_stats.dictGetDef $record linuxNetwork disks {}]
        set baseline [::_ns_stats.dictGetDef $record previousLinuxNetwork disks {}]
        if {![::_ns_stats.dictGetDef $sampling ready false] || $elapsed eq "" || $elapsed<=0
            || $cadence<=0 || $elapsed>1.5*$cadence || $start eq "" || $end eq ""
            || $disks eq "" || $baseline eq ""} {
            set history {};set count 0;set previous {};set from {};set to {}
            continue
        }
        if {$previous ne "" && ([dict get $record instance] ne [dict get $previous instance]
                                || abs($start-$to)>0.000001)} {
            set history {};set count 0;set from {}
        }
        if {$count==0} {set from $start}
        incr count
        set history [::_ns_stats.utilization.diskHealth $disks $baseline $elapsed $history $warning $critical]
        set previous $record
        set to $end
    }
    return [dict create ready [expr {$count>=$samples}] count $count health $history from $from to $to]
}

proc ::_ns_stats.utilization.diskBackgroundRead {samples {reader ::ns_stats}} {
    if {$samples<1 || $samples>10000} {error "background disk assessment requires 1..10000 samples"}
    # No -window: use the last background publication, independent of the form.
    set latest [{*}$reader sample]
    if {![dict exists $latest timestamp]} {error "No background publication is available yet."}
    if {![::_ns_stats.dictGetDef $latest sampling running false]} {error "Background sampling is stopped."}
    set cadence [dict get $latest sampling intervalSeconds]
    if {[::_ns_stats.dictGetDef $latest sampling ageSeconds Inf]>2*$cadence} {
        error "Background samples are stale; sustained disk latency is unknown."
    }
    if {[::_ns_stats.dictGetDef $latest linuxNetwork disks {}] eq ""} {
        error "Background disk observations are unavailable."
    }
    set records {}
    if {$samples>1} {
        # Exclude the publication itself from the time query, then append it.
        # This avoids dropping the latest fractional-second endpoint and races
        # with a newer publication while the query is in progress.
        set to [expr {int([dict get $latest timestamp]/1000000)}]
        set from [expr {max(0,$to-min(86400,int(ceil(1.5*$samples*$cadence))))}]
        set cursor 0
        if {$from<$to} {
            while {1} {
                set page [{*}$reader sampling history -from $from -to $to -limit 180 -cursor $cursor]
                foreach record [dict get $page records] {
                    if {[dict get $record timestamp]<[dict get $latest timestamp]} {lappend records $record}
                }
                if {![dict get $page more]} {break}
                set next [dict get $page nextCursor]
                if {$next eq "" || $next<=$cursor || [llength $records]>=10000} {
                    error "Recent background disk history is incomplete."
                }
                set cursor $next
            }
        }
    }
    lappend records $latest
    return [lrange $records [expr {[llength $records]-$samples}] end]
}

proc ::_ns_stats.utilization.diskSustainedPanel {} {
    if {![string equal -nocase Linux $::tcl_platform(os)]} {return {}}
    set html {<h3>Sustained disk latency (background samples)</h3>}
    if {[info commands ::ns_stats] eq ""} {
        return "$html<p>Unavailable: background sampling requires ns_stats.</p>"
    }
    set warning [ns_config -int -min 1 ns/module/nsstats diskLatencyWarningMs 20]
    set critical [ns_config -int -min $warning ns/module/nsstats diskLatencyCriticalMs 100]
    set samples [ns_config -int -min 1 ns/module/nsstats diskLatencySamples 3]
    if {[catch {::_ns_stats.utilization.diskBackgroundRead $samples} records]} {
        return "$html<p>[ns_quotehtml $records]</p>"
    }
    set result [::_ns_stats.utilization.diskSustained $records $samples $warning $critical]
    if {![dict get $result ready]} {
        return "$html<p>Sustained condition unknown: need $samples consecutive valid background intervals; [dict get $result count] available.</p>"
    }
    set alerts {}
    dict for {name disk} [dict get $result health] {
        foreach label {read write} {
            set entry [dict get $disk $label]
            set level ok
            if {[dict get $entry critical]>=$samples} {set level critical
            } elseif {[dict get $entry warning]>=$samples} {set level warning}
            if {$level ne "ok"} {
                lappend alerts [::_ns_stats.utilization.colorize $level \
                    "[string toupper $level]: disk [ns_quotehtml $name] $label latency exceeded the $level threshold for $samples consecutive background intervals" 1]
            }
        }
    }
    set bounds {}
    foreach key {from to} {
        lappend bounds [clock format [expr {int([dict get $result $key])}] -format {%Y-%m-%d %H:%M:%S %Z}]
    }
    append html "<p>Latest $samples background intervals: [join $bounds { to }]. This assessment is independent of the selected display window.</p>"
    if {$alerts eq ""} {
        append html "<p>No sustained disk latency threshold crossings detected. Idle, missing or reset device counters break that device's streak.</p>"
    } else {
        append html "<p>[join $alerts {; }].</p>"
    }
    return $html
}

proc ::_ns_stats.linuxNetworkSnapshot {} {

    if {![string equal -nocase Linux $::tcl_platform(os)]} {
        return {}
    }

    set snmp    [::_ns_stats.linuxReadNamedCounters /proc/net/snmp]
    set netstat [::_ns_stats.linuxReadNamedCounters /proc/net/netstat]
    set snmp6   [::_ns_stats.linuxReadSnmp6]
    set devices [::_ns_stats.linuxReadDeviceCounters]
    set queues  [::_ns_stats.linuxReadSocketQueues]
    set disks   [::_ns_stats.linuxDiskSnapshot]

    set counters {}

    foreach {target source section name} {
        tcpListenOverflows netstat TcpExt ListenOverflows
        tcpListenDrops     netstat TcpExt ListenDrops
        tcpBacklogDrops    netstat TcpExt TCPBacklogDrop
        tcpReceiveDrops    netstat TcpExt TCPRcvQDrop
        tcpRetransmits     snmp    Tcp    RetransSegs
        tcpInputErrors     snmp    Tcp    InErrs
        udpReceiveDrops4   snmp    Udp    RcvbufErrors
        udpSendDrops4      snmp    Udp    SndbufErrors
        udpInputErrors4    snmp    Udp    InErrors
    } {
        set data [set $source]

        if {[dict exists $data $section $name]} {
            dict set counters $target [dict get $data $section $name]
        }
    }

    foreach {target name} {
        udpReceiveDrops6 Udp6RcvbufErrors
        udpSendDrops6    Udp6SndbufErrors
        udpInputErrors6  Udp6InErrors
    } {
        if {[dict exists $snmp6 $name]} {
            dict set counters $target [dict get $snmp6 $name]
        }
    }

    #
    # Provide combined IPv4/IPv6 UDP counters for the summary.
    #
    foreach {combined ipv4 ipv6} {
        udpReceiveDrops udpReceiveDrops4 udpReceiveDrops6
        udpSendDrops    udpSendDrops4    udpSendDrops6
        udpInputErrors  udpInputErrors4  udpInputErrors6
    } {
        set found 0
        set total 0

        foreach name [list $ipv4 $ipv6] {
            if {[dict exists $counters $name]} {
                incr total [dict get $counters $name]
                set found 1
            }
        }

        if {$found} {
            dict set counters $combined $total
        }
    }

    dict for {name value} $devices {
        dict set counters $name $value
    }

    if {[dict size $counters] == 0
        && [dict size $queues] == 0 && [dict size $disks] == 0} {
        return {}
    }

    if {[dict exists $queues udpSocketDrops]} {
        dict set counters udpSocketDrops [dict get $queues udpSocketDrops]
        dict unset queues udpSocketDrops
    }

    return [dict create \
                counters $counters \
                gauges   $queues \
                disks    $disks]
}

proc ::_ns_stats.utilization.tooltip {text tooltip} {
    return [format {<span title="%s">%s</span>} [ns_quotehtml $tooltip] [ns_quotehtml $text]]
}

proc ::_ns_stats.utilization.displayIntervalCounter {delta rate label} {
    if {$delta < 0} {
        return "\u2014"
    }

    set noun [expr {$delta == 1 ? $label : "${label}s"}]

    if {$delta == 0} {
        return "0 $noun"
    }

    if {$rate >= 0.0} {
        set rateDisplay [expr {$rate > 0.0 && $rate < 0.001
                              ? "<0.001/s"
                              : [::_ns_stats.utilization.displayRate $rate]}]
        return "$delta $noun in interval ($rateDisplay)"
    }

    return "$delta $noun in interval"
}

proc ::_ns_stats.utilization.linuxRows {current previous elapsed severityVar reasonsVar} {
    upvar 1 $severityVar severity
    upvar 1 $reasonsVar  reasons

    set linuxRows {}
    set linuxNetwork [::_ns_stats.dictGetDef $current linuxNetwork {}]

    if {[dict size $linuxNetwork] > 0} {
        set previousLinuxNetwork [::_ns_stats.dictGetDef $previous linuxNetwork {}]

        set currentCounters  [::_ns_stats.dictGetDef $linuxNetwork counters {}]
        set previousCounters [::_ns_stats.dictGetDef $previousLinuxNetwork counters {}]
        set gauges           [::_ns_stats.dictGetDef $linuxNetwork gauges {}]

        foreach {counter deltaVariable rateVariable} {
            tcpListenOverflows tcpListenOverflowsDelta tcpListenOverflowsRate
            tcpListenDrops     tcpListenDropsDelta     tcpListenDropsRate
            tcpBacklogDrops    tcpBacklogDropsDelta    tcpBacklogDropsRate
            tcpReceiveDrops    tcpReceiveDropsDelta    tcpReceiveDropsRate
            udpReceiveDrops    udpReceiveDropsDelta    udpReceiveDropsRate
            udpSocketDrops     udpSocketDropsDelta     udpSocketDropsRate
            udpSendDrops       udpSendDropsDelta       udpSendDropsRate
            interfaceRxDrops   interfaceRxDropsDelta   interfaceRxDropsRate
            interfaceTxDrops   interfaceTxDropsDelta   interfaceTxDropsRate
            interfaceRxErrors  interfaceRxErrorsDelta  interfaceRxErrorsRate
            interfaceTxErrors  interfaceTxErrorsDelta  interfaceTxErrorsRate
        } {
            set $deltaVariable -1
            set $rateVariable  -1.0

            if {[dict exists $currentCounters $counter]
                && [dict exists $previousCounters $counter]} {

                set currentValue  [dict get $currentCounters $counter]
                set previousValue [dict get $previousCounters $counter]
                set delta         [::_ns_stats.intervalDelta $currentValue $previousValue]

                set $deltaVariable $delta

                if {$delta >= 0 && $elapsed > 0.0} {
                    set $rateVariable [expr {$delta / double($elapsed)}]
                }
            }
        }

        #
        # TCP kernel state.
        #
        set tcpAlerts  {}
        set tcpDetails {}

        foreach {delta rate label tooltip alert} \
            [list \
                 $tcpListenDropsDelta \
                 $tcpListenDropsRate \
                 "listen drop" \
                 "TCP packets discarded while being processed by listening sockets. Interpret this counter together with listen overflows, request-queue drops, queue occupancy, and memory-pressure indicators." \
                 false \
                 \
                 $tcpListenOverflowsDelta \
                 $tcpListenOverflowsRate \
                 "listen overflow" \
                 "TCP accept-queue overflow events across the network namespace, including other services and retransmitted attempts. Identify the affected listener before checking its backlog and net.core.somaxconn." \
                 true \
                 \
                 $tcpBacklogDropsDelta \
                 $tcpBacklogDropsRate \
                 "backlog drop" \
                 "TCP packets discarded from an established socket's processing backlog." \
                 true \
                 \
                 $tcpReceiveDropsDelta \
                 $tcpReceiveDropsRate \
                 "receive-queue drop" \
                 "TCP packets discarded because receive-queue resources were unavailable." \
                 true \
                ] \
            {

                set text [::_ns_stats.utilization.displayIntervalCounter $delta $rate $label]
                set display [::_ns_stats.utilization.tooltip $text $tooltip]

                if {$delta > 0 && $alert} {
                    lappend tcpAlerts [::_ns_stats.utilization.colorize warning $display 1]
                } elseif {$delta >= 0} {
                    lappend tcpDetails $display
                }
            }

        if {[dict size $gauges] > 0} {
            set listeners          [::_ns_stats.dictGetDef $gauges tcpListeners 0]
            set listenQueued       [::_ns_stats.dictGetDef $gauges tcpListenQueued 0]
            set largestListenQueue [::_ns_stats.dictGetDef $gauges tcpListenMax 0]
            set tcpReceiveBytes    [::_ns_stats.dictGetDef $gauges tcpReceiveBytes 0]
            set tcpSendBytes       [::_ns_stats.dictGetDef $gauges tcpSendBytes 0]
            set tcpRxDisplay [::_ns_stats.utilization.tooltip \
                                  "RX [::_ns_stats.hr $tcpReceiveBytes]B" \
                                  "Total unread TCP payload currently queued for NaviServer across all open non-listening TCP sockets. This is queue occupancy, not kernel memory usage or configured capacity."]
            set tcpTxDisplay [::_ns_stats.utilization.tooltip \
                                  "TX [::_ns_stats.hr $tcpSendBytes]B" \
                                  "Total TCP payload currently queued or awaiting acknowledgment across all open non-listening TCP sockets owned by NaviServer. This is queue occupancy, not kernel memory usage or configured capacity."]

            lappend tcpDetails \
                "$listenQueued queued across $listeners listeners, busiest accept queue $largestListenQueue" \
                "data queues $tcpRxDisplay, $tcpTxDisplay"
        }

        set tcpSeverity [expr {[llength $tcpAlerts] > 0 ? "warning" : "ok"}]
        set tcpLabel    [::_ns_stats.utilization.colorize $tcpSeverity "Linux TCP"]
        set tcpDisplay  [join [concat $tcpAlerts $tcpDetails] {; }]

        foreach {delta rate label} [list \
                                        $tcpListenOverflowsDelta $tcpListenOverflowsRate "TCP listen overflow" \
                                        $tcpBacklogDropsDelta    $tcpBacklogDropsRate    "TCP backlog drop" \
                                        $tcpReceiveDropsDelta    $tcpReceiveDropsRate    "TCP receive-queue drop"] {
            if {$delta > 0} {
                lappend reasons [::_ns_stats.utilization.displayIntervalCounter $delta $rate $label]
                if {$severity eq "ok"} {
                    set severity warning
                }
            }
        }

        #
        # UDP kernel state.
        #
        set udpAlerts  {}
        set udpQueues  {}
        set udpDetails {}
        set udpSocketsWithDrops 0
        set udpSocketDropMax    0

        if {[dict size $gauges] > 0} {
            set udpReceiveBytes [::_ns_stats.dictGetDef $gauges udpReceiveBytes 0]
            set udpReceiveMax   [::_ns_stats.dictGetDef $gauges udpReceiveMax 0]
            set udpSendBytes    [::_ns_stats.dictGetDef $gauges udpSendBytes 0]
            set udpSendMax      [::_ns_stats.dictGetDef $gauges udpSendMax 0]
            set udpSocketsWithDrops [::_ns_stats.dictGetDef $gauges udpSocketsWithDrops 0]
            set udpSocketDropMax    [::_ns_stats.dictGetDef $gauges udpSocketDropMax 0]

            lappend udpQueues \
                "RX queue [::_ns_stats.hr $udpReceiveBytes]B total, [::_ns_stats.hr $udpReceiveMax]B largest" \
                "TX queue [::_ns_stats.hr $udpSendBytes]B total, [::_ns_stats.hr $udpSendMax]B largest"
        }

       #
        # Namespace-wide receive-buffer drops, supplemented with the
        # subset attributable to UDP sockets owned by NaviServer.
        #
        set receiveText [::_ns_stats.utilization.displayIntervalCounter $udpReceiveDropsDelta $udpReceiveDropsRate "receive-buffer drop"]
        if {($udpReceiveDropsDelta > 0 || $udpSocketDropsDelta > 0)
            && $udpSocketDropsDelta >= 0
        } {
            append receiveText " ($udpSocketDropsDelta on NaviServer sockets"

            if {$udpSocketsWithDrops > 0} {
                append receiveText "; $udpSocketsWithDrops " \
                    [expr {$udpSocketsWithDrops == 1
                           ? "socket with prior drops"
                           : "sockets with prior drops"}] \
                    ", largest cumulative $udpSocketDropMax"
            }

            append receiveText ")"
        }

        set sendText [::_ns_stats.utilization.displayIntervalCounter $udpSendDropsDelta $udpSendDropsRate "send-buffer drop"]
        foreach {rate text} [list \
                                 $udpReceiveDropsRate $receiveText \
                                 $udpSendDropsRate    $sendText] {
            if {$rate > 0.0} {
                lappend udpAlerts [::_ns_stats.utilization.colorize warning $text 1]
            } elseif {$rate >= 0.0} {
                lappend udpDetails $text
            }
        }

        set udpSeverity [expr {[llength $udpAlerts] > 0 ? "warning" : "ok"}]
        set udpLabel    [::_ns_stats.utilization.colorize $udpSeverity "Linux UDP"]
        set udpDisplay  [join [concat $udpAlerts $udpQueues $udpDetails] {; }]

        #
        # Add precise UDP and interface problems to the global status.
        #
        foreach {delta rate label} [list \
                                        $udpReceiveDropsDelta  $udpReceiveDropsRate  "UDP receive-buffer drop" \
                                        $udpSendDropsDelta     $udpSendDropsRate     "UDP send-buffer drop" \
                                        $interfaceRxDropsDelta $interfaceRxDropsRate "interface RX drop" \
                                        $interfaceTxDropsDelta $interfaceTxDropsRate "interface TX drop" \
                                        $interfaceRxErrorsDelta $interfaceRxErrorsRate "interface RX error" \
                                        $interfaceTxErrorsDelta $interfaceTxErrorsRate "interface TX error"] {

            if {$delta > 0} {
                lappend reasons [::_ns_stats.utilization.displayIntervalCounter $delta $rate $label]
                if {$severity eq "ok"} {
                    set severity warning
                }
            }
        }

        #
        # Network-interface counters.
        #
        set interfaceParts {}
        foreach {delta rate label} [list \
                                        $interfaceRxDropsDelta  $interfaceRxDropsRate  "RX drop" \
                                        $interfaceTxDropsDelta  $interfaceTxDropsRate  "TX drop" \
                                        $interfaceRxErrorsDelta $interfaceRxErrorsRate "RX error" \
                                        $interfaceTxErrorsDelta $interfaceTxErrorsRate "TX error"] {
            if {$delta >= 0} {
                set display [::_ns_stats.utilization.displayIntervalCounter $delta $rate $label]
                if {$delta > 0} {
                    set display [::_ns_stats.utilization.colorize warning $display 1]
                }
                lappend interfaceParts $display
            }
        }

        set interfaceSeverity [expr {
                                     $interfaceRxDropsDelta > 0
                                     || $interfaceTxDropsDelta > 0
                                     || $interfaceRxErrorsDelta > 0
                                     || $interfaceTxErrorsDelta > 0
                                     ? "warning"
                                     : "ok"
                                 }]
        set linuxTitle \
            "Linux kernel statistics visible to the NaviServer process; inside a container, these describe the container's network stack"
        set interfaceTitle \
            "Aggregate counters for non-loopback Linux interfaces visible to the NaviServer process"

        lappend linuxRows \
            [list "<span title='$linuxTitle'>$tcpLabel</span>" $tcpDisplay] \
            [list "<span title='$linuxTitle'>$udpLabel</span>" $udpDisplay] \
            [list "<span title='$interfaceTitle'>Linux Interfaces</span>" [::_ns_stats.utilization.colorize $interfaceSeverity [join $interfaceParts {; }]]]
    }
    return $linuxRows
}


proc ::_ns_stats.utilization.globalSummary {sample threadCpu} {
    set current  [dict get $sample current]
    set previous [dict get $sample previous]
    set elapsed  [dict get $sample elapsed]

    #
    # Current pool/thread gauges.
    #
    set activeRequests       0
    set poolWaiting          0
    set connThreadsCurrent   0
    set connThreadsIdle      0
    set connThreadsStopping  0
    set connThreadsMax       0

    dict for {server serverData} [dict get $current servers] {
        dict for {pool poolData} [dict get $serverData pools] {
            incr activeRequests      [dict get $poolData active]
            incr poolWaiting         [::_ns_stats.dictGetDef $poolData waiting 0]
            set threads              [::_ns_stats.dictGetDef $poolData threads {}]
            incr connThreadsCurrent  [::_ns_stats.dictGetDef $threads current 0]
            incr connThreadsIdle     [::_ns_stats.dictGetDef $threads idle 0]
            incr connThreadsStopping [::_ns_stats.dictGetDef $threads stopping 0]
            incr connThreadsMax      [::_ns_stats.dictGetDef $threads max 0]
        }
    }

    set connThreadsBusy [expr {
        max(0, $connThreadsCurrent
               - $connThreadsIdle
               - $connThreadsStopping)
    }]

    set connThreadUtilization [expr {
        $connThreadsCurrent > 0
        ? 100.0 * $connThreadsBusy / $connThreadsCurrent
        : 0.0
    }]

    #
    # Current driver-local socket gauges.
    #
    set driverWaiting [::_ns_stats.utilization.sumDriverCounter $current waiting]
    set driverReading [::_ns_stats.utilization.sumDriverCounter $current reading]
    set driverClosing [::_ns_stats.utilization.sumDriverCounter $current closing]

    #
    # Interval pool counters.
    #
    set currentRequests  [::_ns_stats.utilization.sumPoolCounter $current requests]
    set previousRequests [::_ns_stats.utilization.sumPoolCounter $previous requests]

    set currentQueued    [::_ns_stats.utilization.sumPoolCounter $current queued]
    set previousQueued   [::_ns_stats.utilization.sumPoolCounter $previous queued]

    set currentSpooled   [::_ns_stats.utilization.sumPoolCounter $current spools]
    set previousSpooled  [::_ns_stats.utilization.sumPoolCounter $previous spools]

    set currentDropped   [::_ns_stats.utilization.sumPoolCounter $current dropped]
    set previousDropped  [::_ns_stats.utilization.sumPoolCounter $previous dropped]

    set requestRate      [::_ns_stats.intervalRate $currentRequests $previousRequests $elapsed]
    set queuedRate       [::_ns_stats.intervalRate $currentQueued $previousQueued $elapsed]
    set spooledRate      [::_ns_stats.intervalRate $currentSpooled $previousSpooled $elapsed]
    set droppedRate      [::_ns_stats.intervalRate $currentDropped $previousDropped $elapsed]
    set droppedDelta     [::_ns_stats.intervalDelta $currentDropped $previousDropped]
    set droppedText      [::_ns_stats.utilization.displayIntervalCounter $droppedDelta $droppedRate "dropped request"]

    #
    # Interval-average request timing.
    #
    set requestDelta [expr {$currentRequests - $previousRequests}]
    set timingValues {}

    foreach {counter label} {
        queuetime  queue
        filtertime filter
        runtime    run
        tracetime  trace
    } {
        set currentTime  [::_ns_stats.utilization.sumPoolCounter $current $counter]
        set previousTime [::_ns_stats.utilization.sumPoolCounter $previous $counter]

        if {$requestDelta > 0 && $currentTime >= $previousTime} {
            set average [expr {
                ($currentTime - $previousTime) / double($requestDelta)
            }]
            lappend timingValues "$label [::_ns_stats.hr $average]s"
        }
    }

    set snapshot [::_ns_stats.dictGetDef $sample coreSnapshot {}]
    if {[::_ns_stats.sampled $snapshot]} {
        set requestRate [::_ns_stats.utilization.sampleTotal $snapshot rates requests]
        set queuedRate [::_ns_stats.utilization.sampleTotal $snapshot rates queued]
        set spooledRate [::_ns_stats.utilization.sampleTotal $snapshot rates spools]
        set droppedRate [::_ns_stats.utilization.sampleTotal $snapshot rates dropped]
        set droppedDelta [::_ns_stats.utilization.sampleTotal $snapshot deltas dropped]
        set droppedText [::_ns_stats.utilization.displayIntervalCounter $droppedDelta $droppedRate "dropped request"]
        set requests [::_ns_stats.utilization.sampleTotal $snapshot deltas requests]
        set timingValues {}
        foreach {key label} {queuetime queue filtertime filter runtime run tracetime trace} {
            set delta [::_ns_stats.utilization.sampleTotal $snapshot deltas $key]
            if {$requests>0 && $delta>=0} {
                lappend timingValues "$label [::_ns_stats.hr [expr {$delta/$requests}]]s"
            }
        }
    }

    #
    # CPU percentages. The helper should return dictionaries containing
    # at least "name" and "cpu".
    #
    set driverCpuTotal 0.0
    set driverCpuMax   0.0
    set connCpuTotal   0.0
    set writerCpuTotal 0.0
    set spoolerCpuTotal 0.0

    set measured {}

    dict for {threadKey threadData} $threadCpu {
        set name [::_ns_stats.dictGetDef $threadData name ""]
        set cpu  [::_ns_stats.dictGetDef $threadData cpu  -1.0]

        if {$cpu < 0.0} {
            continue
        }

        switch -glob -- $name {
            "-driver:*" {
                dict set measured driver true
                set driverCpuTotal [expr {$driverCpuTotal + $cpu}]
                set driverCpuMax   [expr {max($driverCpuMax, $cpu)}]
            }
            "-conn:*" {
                dict set measured conn true
                set connCpuTotal [expr {$connCpuTotal + $cpu}]
            }
            "-writer:*" {
                dict set measured writer true
                set writerCpuTotal [expr {$writerCpuTotal + $cpu}]
            }
            "-spooler:*" {
                dict set measured spooler true
                set spoolerCpuTotal [expr {$spoolerCpuTotal + $cpu}]
            }
        }
    }

    #
    # Determine the most important current warning.
    #
    set severity ok
    set reasons  {}

    set linuxRows [::_ns_stats.utilization.linuxRows \
                       $current [::_ns_stats.dictGetDef $sample supplementalPrevious $previous] \
                       [::_ns_stats.dictGetDef $sample supplementalElapsed $elapsed] severity reasons]

    if {[::_ns_stats.sampled $snapshot]
        || [::_ns_stats.dictGetDef $snapshot samplingPending false]} {
        lappend linuxRows {*}[::_ns_stats.utilization.diskWarnings \
            $current [::_ns_stats.dictGetDef $sample supplementalPrevious $previous] $elapsed severity reasons]
        if {![dict exists $snapshot linuxNetwork]} {
            lappend linuxRows [list {Linux diagnostics} {Unavailable for the selected window: the core does not sample Linux diagnostics.}]
        }
    } else {
        lappend linuxRows {*}[::_ns_stats.utilization.diskWarnings \
            $current [::_ns_stats.dictGetDef $sample supplementalPrevious $previous] \
            [::_ns_stats.dictGetDef $sample supplementalElapsed $elapsed] severity reasons]
    }

    if {$droppedRate > 0.0} {
        set severity critical
        lappend reasons \
            $droppedText
    }

    if {$driverCpuMax >= 90.0} {
        if {$severity eq "ok"} {
            set severity warning
        }
        lappend reasons \
            "driver thread at [format %.1f $driverCpuMax]%"
    }

    if {$driverWaiting > 0} {
        if {$severity eq "ok"} {
            set severity warning
        }
        lappend reasons "$driverWaiting ready requests awaiting processing"
    }

    if {$poolWaiting > 0} {
        if {$severity eq "ok"} {
            set severity warning
        }
        set poolWaitingDisplay [::_ns_stats.utilization.colorize "warning" $poolWaiting]
        lappend reasons "$poolWaiting requests waiting for connection threads"
    } else {
        set poolWaitingDisplay $poolWaiting
    }

    if {$connThreadsCurrent > 0
        && $connThreadsBusy == $connThreadsCurrent
        && $connThreadsCurrent >= $connThreadsMax} {
        if {$severity eq "ok"} {
            set severity warning
        }
        lappend reasons "all configured connection threads busy"
    }
    set connectionThreadSeverity [expr {$poolWaiting > 0 && $connThreadsCurrent >= $connThreadsMax && $connThreadsIdle == 0 ? "warning" : "ok"}]
    set connectionThreadsDisplay [::_ns_stats.utilization.colorize $connectionThreadSeverity \
                                      "$connThreadsBusy busy, $connThreadsIdle idle, $connThreadsCurrent current, $connThreadsMax maximum ([format %.1f $connThreadUtilization]% busy)"]

    set driverCpuSeverity [::_ns_stats.categorize {{90.0 critical} {75.0 warning} {0.0 ok}} $driverCpuMax]
    #set driverCpuSeverity [expr {$driverCpuMax >= 90.0 ? "critical" : $driverCpuMax >= 75.0 ? "warning" : "ok" }]
    set driverCpuDisplay  [::_ns_stats.utilization.colorize $driverCpuSeverity \
                               "[format %.1f $driverCpuTotal]% total, [format %.1f $driverCpuMax]% busiest thread"]

    if {![dict exists $measured driver]} {
        set driverCpuDisplay "unknown"
    }
    set connCpuDisplay [expr {[dict exists $measured conn] ? "[format %.1f $connCpuTotal]%" : "unknown"}]
    set writerCpuDisplay [expr {[dict exists $measured writer] ? "[format %.1f $writerCpuTotal]%" : "unknown"}]
    set spoolerCpuDisplay [expr {[dict exists $measured spooler] ? "[format %.1f $spoolerCpuTotal]%" : "unknown"}]

    set droppedSeverity    [expr {$droppedRate > 0.0 ? "critical" : "ok"}]
    set droppedRateDisplay [::_ns_stats.utilization.colorize $droppedSeverity \
                                $droppedText]

    set status [expr {[llength $reasons] == 0 ? "OK" : "[string toupper $severity]: [join $reasons {; }]"}]
    set statusDisplay [::_ns_stats.utilization.colorize $severity $status 1]

    #
    # Return the key/value list expected by ::_ns_stats.process.table.
    #
    set rows [list \
                  [list "Measurement period"   [expr {$elapsed > 0.0 ? [format "%.3fs" $elapsed] : "\u2014; refresh to obtain interval values"}]] \
                  [list "Request Rate"         [::_ns_stats.utilization.displayRate $requestRate]] \
                  [list "Active Requests"      $activeRequests] \
                  [list "Pool Waiting"         $poolWaitingDisplay] \
                  [list "Connection Threads"   $connectionThreadsDisplay] \
                  [list "Driver CPU"           $driverCpuDisplay] \
                  [list "Connection CPU"       $connCpuDisplay] \
                  [list "Writer / Spooler CPU" "$writerCpuDisplay / $spoolerCpuDisplay"] \
                  [list "Driver Socket States"  "$driverReading reading, $driverWaiting ready but unprocessed, $driverClosing closing"] \
                  {*}$linuxRows \
                  [list "Interval Request Handling" "[::_ns_stats.utilization.displayRate $queuedRate] queued, [::_ns_stats.utilization.displayRate $spooledRate] spooled, $droppedRateDisplay"] \
                  [list "Interval Request Timing"    [expr {[llength $timingValues] > 0 ? [join $timingValues ", "] : "\u2014"}]] \
                  [list "Status"                     $statusDisplay]]
    return $rows
}

proc ::_ns_stats.utilization.driverCpuInfo {threadCpu driverThread} {
    set driverName      ""
    set driverCpu       -1.0
    set writerCpu       0.0
    set spoolerCpu      0.0
    set writerCount     0
    set spoolerCount    0
    set writerMeasured  0
    set spoolerMeasured 0

    set directDriverName [format {-driver:%s-}     $driverThread]
    set driverSuffix     [format {*:%s-}           $driverThread]
    set writerPattern    [format {-writer:%s:*-}   $driverThread]
    set spoolerPattern   [format {-spooler:%s:*-}  $driverThread]

    dict for {threadKey data} $threadCpu {
        set name   [::_ns_stats.dictGetDef $data name ""]
        set parent [::_ns_stats.dictGetDef $data parent ""]
        set cpu    [::_ns_stats.dictGetDef $data cpu -1.0]

        if {$name eq $directDriverName
            || ([string match "-driver:*" $name]
                && [string match $driverSuffix $name])} {
            set driverName $name
            set driverCpu  $cpu
            continue
        }

        if {[string match $writerPattern $name]} {
            incr writerCount

            if {$cpu >= 0.0} {
                set writerCpu [expr {$writerCpu + $cpu}]
                incr writerMeasured
            }
            continue
        }

        if {[string match $spoolerPattern $name]} {
            incr spoolerCount

            if {$cpu >= 0.0} {
                set spoolerCpu [expr {$spoolerCpu + $cpu}]
                incr spoolerMeasured
            }
        }
    }

    return [dict create \
                driverName       $driverName \
                driverCpu        $driverCpu \
                writerCpu        $writerCpu \
                writerCount      $writerCount \
                writerMeasured   $writerMeasured \
                spoolerCpu       $spoolerCpu \
                spoolerCount     $spoolerCount \
                spoolerMeasured  $spoolerMeasured]
}

proc ::_ns_stats.utilization.pluralize {count base} {
    return $base[expr {$count>1 ? "s" : ""}]
}

proc ::_ns_stats.utilization.driverRows {sample threadCpu} {
    set current           [dict get $sample current]
    set previous          [dict get $sample previous]
    set elapsed           [dict get $sample elapsed]
    set rows              {}
    set driverNames       {}
    set cpuValues         {}
    set socketPercentages {}

    dict for {driverThread stats} [dict get $current drivers] {
        set module  [dict get $stats module]
        set name    [::_ns_stats.dictGetDef $stats name ""]
        set section ns/module/$module

        set maxQueueSize  [ns_config $section maxqueuesize 1024]
        set writerConfig  [ns_config $section writerthreads 0]
        set spoolerConfig [ns_config $section spoolerthreads 0]

        set waiting [dict get $stats waiting]
        set reading [dict get $stats reading]
        set closing [dict get $stats closing]

        #
        # The sum covers the driver-local lists. It is not the complete
        # drvPtr->queuesize value.
        #
        set localSockets [expr {$waiting + $reading + $closing}]

        #
        # Obtain interval rates when a previous sample for the same
        # driver instance exists.
        #
        set receivedRate  -1.0
        set partialRate   -1.0
        set spooledRate   -1.0
        set errorRate     -1.0
        set errorDelta    -1.0
        set receivedDelta -1.0
        set receivedTotal  0

        if {$elapsed > 0.0
            && [dict exists $previous drivers $driverThread]} {
            set old [dict get $previous drivers $driverThread]

            set receivedRate [::_ns_stats.intervalRate \
                                  [dict get $stats received] \
                                  [dict get $old received] \
                                  $elapsed]

            set receivedDelta [::_ns_stats.intervalDelta \
                                   [dict get $stats received] \
                                   [dict get $old received] ]
            set receivedTotal [dict get $stats received]

            set partialRate [::_ns_stats.intervalRate \
                                 [dict get $stats partial] \
                                 [dict get $old partial] \
                                 $elapsed]

            set spooledRate [::_ns_stats.intervalRate \
                                 [dict get $stats spooled] \
                                 [dict get $old spooled] \
                                 $elapsed]

            set errorDelta [::_ns_stats.intervalDelta [dict get $stats errors] [dict get $old errors]]
            set errorRate [::_ns_stats.intervalRate \
                               [dict get $stats errors] \
                               [dict get $old errors] \
                               $elapsed]
        }

        set cpuInfo [::_ns_stats.utilization.driverCpuInfo $threadCpu $driverThread]
        set driverCpu [dict get $cpuInfo driverCpu]

        set measuredCpuSeconds [expr {$elapsed * $driverCpu / 100.0}]
        set estimatedCapacity [expr {$receivedDelta >= 20 && $measuredCpuSeconds >= 0.05
                                     ? $receivedDelta / $measuredCpuSeconds
                                     : -1}]
        set capacityDisplay [expr {$estimatedCapacity > 0
                                   ? [::_ns_stats.utilization.displayRate $estimatedCapacity]
                                   : "\u2014"}]

        set writerCount     [::_ns_stats.dictGetDef $cpuInfo writerCount 0]
        set writerMeasured  [::_ns_stats.dictGetDef $cpuInfo writerMeasured 0]


        set writerDisplay [::_ns_stats.utilization.formatWorker \
                               [::_ns_stats.dictGetDef $cpuInfo writerCount 0] \
                               [::_ns_stats.dictGetDef $cpuInfo writerMeasured 0] \
                               [::_ns_stats.dictGetDef $cpuInfo writerCpu 0.0]]

        set spoolerDisplay [::_ns_stats.utilization.formatWorker \
                                [::_ns_stats.dictGetDef $cpuInfo spoolerCount 0] \
                                [::_ns_stats.dictGetDef $cpuInfo spoolerMeasured 0] \
                                [::_ns_stats.dictGetDef $cpuInfo spoolerCpu 0.0]]
        set spoolerCount     [::_ns_stats.dictGetDef $cpuInfo spoolerCount 0]
        set spoolerMeasured  [::_ns_stats.dictGetDef $cpuInfo spoolerMeasured 0]
        if {$spoolerCount == 0} {
            set spoolerDisplay "\u2014"
        } elseif {$spoolerMeasured > 0} {
            set spoolerCpu [::_ns_stats.dictGetDef $cpuInfo spoolerCpu 0.0]
            set spoolerDisplay "$spoolerCount [::_ns_stats.utilization.pluralize $spoolerCount thread], [format %.1f $spoolerCpu]% CPU"
        } else {
            set spoolerDisplay "$spoolerCount [::_ns_stats.utilization.pluralize $spoolerCount thread], \u2014 CPU"
        }

        #
        # Build a concise status and retain the reasons.
        #
        set severity ok
        set reasons  {}

        if {$driverCpu >= 90.0} {
            set severity critical
            lappend reasons "CPU [format %.1f $driverCpu]%"
        } elseif {$driverCpu >= 75.0} {
            set severity warning
            lappend reasons "CPU [format %.1f $driverCpu]%"
        }

        if {$waiting > 0} {
            if {$severity eq "ok"} {
                set severity warning
            }
            lappend reasons "$waiting ready requests awaiting processing"
        }

        #
        # Driver errors can result from malformed or aborted connections,
        # so report them without automatically calling the driver
        # saturated.
        #
        set errorText [::_ns_stats.utilization.displayIntervalCounter $errorDelta $errorRate error]
        if {$errorRate > 0.0} {
            lappend reasons \
                $errorText
        }

        if {[llength $reasons] == 0} {
            set status OK
        } else {
            set status "[string toupper $severity]: [join $reasons {; }]"
        }

        if {$receivedTotal > 0} {
            set receivedDisplay [::_ns_stats.utilization.displayRate $receivedRate]
            set partialDisplay  [::_ns_stats.utilization.displayRate $partialRate]
            set spooledDisplay  [::_ns_stats.utilization.displayRate $spooledRate]
        } else {
            set receivedDisplay "\u2014"
            set partialDisplay  "\u2014"
            set spooledDisplay  "\u2014"
        }

        set driverDisplay [::_ns_stats.utilization.colorize $severity $driverThread]
        set statusDisplay [::_ns_stats.utilization.colorize $severity $status 1]

        set cpuSeverity [expr {$driverCpu >= 90.0 ? "critical" : $driverCpu >= 75.0 ? "warning" : "ok" }]
        set driverCpuDisplay [::_ns_stats.utilization.colorize $cpuSeverity \
                                  [expr {$driverCpu < 0.0 ? "\u2014" : "[format %.1f $driverCpu]%" }]]

        set waitingSeverity [expr {$waiting > 0 ? "warning" : "ok"}]
        set waitingDisplay  [::_ns_stats.utilization.colorize $waitingSeverity $waiting]

        set errorSeverity  [expr {$errorRate > 0.0 ? "warning" : "ok"}]
        set errorDisplay   [::_ns_stats.utilization.colorize $errorSeverity $errorText]

        set socketsPercentage [expr {$localSockets * 100.0 / $maxQueueSize}]
        set socketsSeverity   [expr {$socketsPercentage >= 90.0 ? "critical" : $socketsPercentage >= 75.0 ? "warning" : "ok" }]
        set socketsDisplay    [::_ns_stats.utilization.colorize $socketsSeverity \
                                   "$localSockets/$maxQueueSize ([format %.1f $socketsPercentage]%)"]

        lappend driverNames       [dict get $cpuInfo driverName]
        lappend cpuValues         [expr {$driverCpu >= 0.0 ? [format %.1f $driverCpu] : "null"}]
        lappend socketPercentages [expr {$localSockets * 100.0 / $maxQueueSize}]


        lappend rows [list \
                          $driverDisplay \
                          $driverCpuDisplay \
                          $receivedDisplay \
                          $partialDisplay \
                          $spooledDisplay \
                          $errorDisplay \
                          $capacityDisplay \
                          $reading \
                          $waitingDisplay \
                          $closing \
                          $socketsDisplay \
                          $writerDisplay \
                          $spoolerDisplay \
                          $statusDisplay]
    }

    return [dict create \
                rows              $rows \
                names             $driverNames \
                cpuValues         $cpuValues \
                socketPercentages $socketPercentages]
}

proc ::_ns_stats.utilization.poolRows {sample} {
    set current          [::_ns_stats.dictGetDef $sample current {}]
    set previous         [::_ns_stats.dictGetDef $sample previous {}]
    set elapsed          [::_ns_stats.dictGetDef $sample elapsed 0.0]
    set rows             {}
    set poolNames        {}
    set threadValues     {}
    set connectionValues {}
    set loadValues       {}

    set currentTotalRequests  [::_ns_stats.utilization.sumPoolCounter $current requests]
    set previousTotalRequests [::_ns_stats.utilization.sumPoolCounter $previous requests]
    set totalRequestRate      [::_ns_stats.intervalRate $currentTotalRequests $previousTotalRequests $elapsed]

    set snapshot [::_ns_stats.dictGetDef $sample coreSnapshot {}]
    if {[::_ns_stats.sampled $snapshot]} {
        set totalRequestRate [::_ns_stats.utilization.sampleTotal $snapshot rates requests]
    }
    set currentServers        [::_ns_stats.dictGetDef $current servers {}]

    foreach server [lsort [dict keys $currentServers]] {
        set serverData [::_ns_stats.dictGetDef $currentServers $server {}]
        set pools      [::_ns_stats.dictGetDef $serverData pools {}]

        foreach pool [lsort [dict keys $pools]] {
            set poolData [::_ns_stats.dictGetDef $pools $pool {}]
            set stats    [::_ns_stats.dictGetDef $poolData stats {}]
            set threads  [::_ns_stats.dictGetDef $poolData threads {}]

            set oldPoolData [::_ns_stats.dictGetDef $previous servers $server pools $pool {}]
            set oldStats    [::_ns_stats.dictGetDef $oldPoolData stats {}]
            set poolLabel   [expr {$pool eq "" ? "default" : $pool}]

            #
            # Current gauges.
            #
            set active         [::_ns_stats.dictGetDef $poolData active 0]
            set waiting        [::_ns_stats.dictGetDef $poolData waiting 0]
            set threadCurrent  [::_ns_stats.dictGetDef $threads current 0]
            set threadIdle     [::_ns_stats.dictGetDef $threads idle 0]
            set threadStopping [::_ns_stats.dictGetDef $threads stopping 0]
            set threadMax      [::_ns_stats.dictGetDef $threads max 0]

            #
            # Pool-specific configuration.
            #
            set configPath     [expr {$pool in {{} default} ? "ns/server/$server" : "ns/server/$server/pool/$pool"}]
            set maxConnections [ns_config $configPath maxconnections 100]

            #
            # Cumulative counters.
            #
            set requests     [::_ns_stats.dictGetDef $stats requests 0]
            set oldRequests  [::_ns_stats.dictGetDef $oldStats requests 0]
            set requestDelta [expr {$requests - $oldRequests}]
            set requestRate  [::_ns_stats.intervalRate $requests $oldRequests $elapsed]

            set queuedRate   [::_ns_stats.intervalRate \
                                  [::_ns_stats.dictGetDef $stats queued 0] \
                                  [::_ns_stats.dictGetDef $oldStats queued 0] \
                                  $elapsed]

            set spooledRate  [::_ns_stats.intervalRate \
                                  [::_ns_stats.dictGetDef $stats spools 0] \
                                  [::_ns_stats.dictGetDef $oldStats spools 0] \
                                  $elapsed]

            set droppedRate  [::_ns_stats.intervalRate \
                                  [::_ns_stats.dictGetDef $stats dropped 0] \
                                  [::_ns_stats.dictGetDef $oldStats dropped 0] \
                                  $elapsed]
            set droppedDelta [::_ns_stats.intervalDelta \
                                  [::_ns_stats.dictGetDef $stats dropped 0] \
                                  [::_ns_stats.dictGetDef $oldStats dropped 0]]
            set droppedText [::_ns_stats.utilization.displayIntervalCounter $droppedDelta $droppedRate "dropped request"]

            #
            # Share of the total process-wide request rate.
            #
            if {$requestRate >= 0.0 && $totalRequestRate > 0.0} {
                set globalShare [expr { 100.0 * $requestRate / $totalRequestRate }]
                set globalShareDisplay [format %.1f%% $globalShare]
            } else {
                set globalShareDisplay "\u2014"
            }

            #
            # Compute interval-average timing values.
            #
            set averageQueue   -1.0
            set averageFilter  -1.0
            set averageRun     -1.0
            set averageTrace   -1.0
            set averageService -1.0

            if {$requestDelta > 0} {
                foreach {counter variableName} {
                    queuetime  averageQueue
                    filtertime averageFilter
                    runtime    averageRun
                    tracetime  averageTrace
                } {
                    set currentValue  [::_ns_stats.dictGetDef $stats $counter 0.0]
                    set previousValue [::_ns_stats.dictGetDef $oldStats $counter 0.0]
                    set delta [expr {$currentValue - $previousValue}]

                    if {$delta >= 0.0} {
                        set $variableName [expr {$delta / double($requestDelta)}]
                    }
                }

                if {$averageFilter >= 0.0
                    && $averageRun >= 0.0
                    && $averageTrace >= 0.0} {
                    set averageService [expr {$averageFilter + $averageRun + $averageTrace}]
                }
            }

            set queueDisplay   [expr {$averageQueue   >= 0.0 ? "[::_ns_stats.hr $averageQueue]s"   : "\u2014"}]
            set serviceDisplay [expr {$averageService >= 0.0 ? "[::_ns_stats.hr $averageService]s" : "\u2014"}]

            #
            # Estimate the maximum request rate using the configured
            # maximum number of connection threads and the interval
            # average service time.
            #
            set estimatedCapacity -1.0
            set estimatedLoad     -1.0

            if {$threadMax > 0 && $averageService > 0.0} {
                set estimatedCapacity [expr {$threadMax / $averageService}]

                if {$requestRate >= 0.0} {
                    set estimatedLoad [expr {100.0 * $requestRate / $estimatedCapacity}]
                }
            }

            set capacityDisplay [expr {$estimatedCapacity >= 0.0
                                       ? "[::_ns_stats.utilization.displayRate $estimatedCapacity]"
                                       : "\u2014"
                                   }]

            #
            # Classify only clear pressure indicators.
            #
            set severity ok
            set reasons  {}

            if {$droppedRate > 0.0} {
                set severity critical
                lappend reasons  $droppedText
            }

            if {$waiting > 0} {
                if {$severity eq "ok"} {
                    set severity warning
                }
                lappend reasons "$waiting waiting"
            }

            if {$threadCurrent > 0
                && $threadIdle == 0
                && $threadCurrent >= $threadMax} {
                if {$severity eq "ok"} {
                    set severity warning
                }
                lappend reasons "all connection threads busy"
            }

            set estimatedLoadSeverity ok
            if {$estimatedLoad >= 90.0} {
                if {$severity eq "ok"} {
                    set severity warning
                    set estimatedLoadSeverity warning
                }
                lappend reasons "estimated load [format %.1f $estimatedLoad]%"
            }

            if {[llength $reasons] == 0} {
                set status OK
            } else {
                set status "[string toupper $severity]: [join $reasons {; }]"
            }

            set serverDisplay [::_ns_stats.utilization.colorize $severity $server]
            set poolDisplay   [::_ns_stats.utilization.colorize $severity $poolLabel]
            set statusDisplay [::_ns_stats.utilization.colorize $severity $status 1]

            set threadBusy [expr {max(0, $threadCurrent - $threadIdle - $threadStopping)}]
            set threadBusyPercentage [expr {$threadCurrent > 0 ? 100.0 * $threadBusy / $threadCurrent : 0.0 }]

            set threadSeverity [expr {$waiting > 0 || $threadBusyPercentage > 90.0 ? "warning" : "ok" }]
            set threadDisplay  [::_ns_stats.utilization.colorize $threadSeverity "[format {%d/%d/%d} $threadBusy $threadCurrent $threadMax]&nbsp;→"]
            set busyDisplay    [::_ns_stats.utilization.colorize $threadSeverity [format %.1f%% $threadBusyPercentage]]

            set droppedSeverity [expr {$droppedRate > 0.0 ? "critical" : "ok"}]
            set droppedDisplay [::_ns_stats.utilization.colorize $droppedSeverity $droppedText]

            set waitingSeverity       [expr {$waiting > 0 ? "warning" : "ok"}]
            set currentConnections    [expr {$waiting + $active}]
            set connectionsPercentage [expr {$currentConnections * 100.0 / $maxConnections}]
            set connectionsSeverity   [expr {$connectionsPercentage >= 90.0 ? "critical" : $connectionsPercentage >= 75.0 ? "warning" : "ok"}]
            if {$connectionsSeverity eq "ok" && $waitingSeverity ne "ok"} {
                set connectionsSeverity $waitingSeverity
            }
            set connectionsDisplay    [::_ns_stats.utilization.colorize $connectionsSeverity \
                                           "($active\u00a0+\u00a0$waiting)&nbsp;/$maxConnections&nbsp;→"]
            set connectionsPercentageDisplay [::_ns_stats.utilization.colorize $connectionsSeverity [format %.1f $connectionsPercentage]%]

            set loadDisplay [::_ns_stats.utilization.colorize $estimatedLoadSeverity \
                                 [expr {$estimatedLoad >= 0.0 ? "[format %.1f $estimatedLoad]%" : "\u2014" }]]

            lappend poolNames        $server/$poolLabel
            lappend threadValues     $threadBusyPercentage
            lappend connectionValues $connectionsPercentage
            lappend loadValues       $estimatedLoad

            lappend rows [list \
                              $serverDisplay \
                              $poolDisplay \
                              [::_ns_stats.utilization.displayRate $requestRate] \
                              $globalShareDisplay \
                              $threadDisplay \
                              $busyDisplay \
                              $connectionsDisplay \
                              $connectionsPercentageDisplay \
                              $queueDisplay \
                              $serviceDisplay \
                              $capacityDisplay \
                              $loadDisplay \
                              [::_ns_stats.utilization.displayRate $queuedRate] \
                              [::_ns_stats.utilization.displayRate $spooledRate] \
                              $droppedDisplay \
                              $statusDisplay]
        }
    }

    return [dict create \
                rows              $rows \
                names             $poolNames \
                threadValues      $threadValues \
                connectionValues $connectionValues \
                loadValues       $loadValues]
}


#
# Render a Highcharts utilization chart.
#
# -categories is a Tcl list of category labels.
#
# -series is a Tcl list of dicts:
#
#   [list \
#       [dict create name CPU     data $cpuValues] \
#       [dict create name Sockets data $socketPercentages]]
#
# Series values must be numbers or the literal "null".
#
proc ::_ns_stats.utilization.chart {args} {
    ns_parseargs {
        {-id ""}
        {-title ""}
        {-subtitle ""}
        {-categories {}}
        {-series {}}
        {-ytitle "Utilization (%)"}
        {-minimum 0.0}
        {-maximum 100.0}
        {-warning 75.0}
        {-critical 90.0}
        {-suffix "%"}
        {-minheight 320}
        {-baseheight 100}
        {-rowheight 36}
        {-class "utilization-chart"}
    } $args

    if {$id eq ""} {
        error "::_ns_stats.utilization.chart: -id must not be empty"
    }
    if {![regexp {^[[:alpha:]][[:alnum:]_-]*$} $id]} {
        error "::_ns_stats.utilization.chart: invalid HTML id '$id'"
    }
    if {$title eq ""} {
        error "::_ns_stats.utilization.chart: -title must not be empty"
    }
    if {[llength $series] == 0} {
        error "::_ns_stats.utilization.chart: -series must not be empty"
    }

    foreach {option value} [list \
                                -minimum  $minimum \
                                -maximum  $maximum \
                                -warning  $warning \
                                -critical $critical] {
        if {![string is double -strict $value]} {
            error "::_ns_stats.utilization.chart: $option must be numeric"
        }
    }

    foreach {option value} [list \
                                -minheight  $minheight \
                                -baseheight $baseheight \
                                -rowheight  $rowheight] {
        if {![string is integer -strict $value] || $value < 0} {
            error "::_ns_stats.utilization.chart: $option must be a non-negative integer"
        }
    }

    set categoryCount [llength $categories]
    set chartHeight   [expr {max($minheight, $baseheight + $categoryCount * $rowheight)}]

    #
    # Encode the category labels.
    #
    set categoryTriples [lmap category $categories {list 0 string $category}]
    set jsonCategories  [ns_json value -type array [concat {*}$categoryTriples]]

    #
    # Encode every series. The JSON object itself is assembled here,
    # while all dynamic values are encoded by ns_json.
    #
    set jsonSeriesEntries {}

    foreach seriesSpec $series {
        if {![dict exists $seriesSpec name]} {
            error "::_ns_stats.utilization.chart: series has no 'name'"
        }
        if {![dict exists $seriesSpec data]} {
            error "::_ns_stats.utilization.chart: series has no 'data'"
        }

        set seriesName [dict get $seriesSpec name]
        set seriesData [dict get $seriesSpec data]

        if {[llength $seriesData] != $categoryCount} {
            error "::_ns_stats.utilization.chart: series '$seriesName' has \
                [llength $seriesData] values, but there are $categoryCount categories"
        }

        set dataTriples {}
        foreach value $seriesData {
            set type [expr {$value eq "null" ? "null" : "number"}]
            lappend dataTriples  [list 0 $type $value]
        }

        set jsonSeriesName [ns_json value -type string $seriesName]
        set jsonSeriesData [ns_json value -type array [concat {*}$dataTriples]]

        lappend jsonSeriesEntries "\{name: $jsonSeriesName, data: $jsonSeriesData\}"
    }

    set jsonSeries "\[[join $jsonSeriesEntries ,]\]"

    set jsonId          [ns_json value -type string $id]
    set jsonTitle       [ns_json value -type string $title]
    set jsonSubtitle    [ns_json value -type string $subtitle]
    set jsonYTitle      [ns_json value -type string $ytitle]
    set jsonLabelFormat [ns_json value -type string "{point.y:.1f}$suffix"]

    set htmlId    [ns_quotehtml $id]
    set htmlClass [ns_quotehtml $class]

    return [ns_trim -delimiter | [subst -nocommands {
        |<div id="$htmlId" class="$htmlClass"></div>
        |<script>
        |Highcharts.chart($jsonId, {
        |    chart: {
        |        type: 'bar',
        |        height: $chartHeight
        |    },
        |    title: {
        |        text: $jsonTitle
        |    },
        |    subtitle: {
        |        text: $jsonSubtitle
        |    },
        |    xAxis: {
        |        categories: $jsonCategories,
        |        alternateGridColor: 'rgba(0, 0, 0, 0.035)',
        |        gridLineWidth: 1,
        |        gridLineColor: '#d8d8d8',
        |        tickLength: 0
        |    },
        |    yAxis: {
        |        min: $minimum,
        |        max: $maximum,
        |        title: {
        |            text: $jsonYTitle
        |        },
        |        plotBands: [
        |            {
        |                from: $warning,
        |                to: $critical,
        |                color: 'rgba(255, 165, 0, 0.08)'
        |            },
        |            {
        |                from: $critical,
        |                to: $maximum,
        |                color: 'rgba(255, 0, 0, 0.08)'
        |            }
        |        ],
        |        plotLines: [
        |            {
        |                value: $warning,
        |                color: 'orange',
        |                width: 1
        |            },
        |            {
        |                value: $critical,
        |                color: 'red',
        |                width: 1
        |            }
        |        ]
        |    },
        |    plotOptions: {
        |        series: {
        |            grouping: true,
        |            groupPadding: 0.12,
        |            pointPadding: 0.06,
        |            borderWidth: 0,
        |            minPointLength: 3,
        |            dataLabels: {
        |                enabled: true,
        |                allowOverlap: false,
        |                crop: false,
        |                overflow: 'allow',
        |                format: $jsonLabelFormat
        |            }
        |        }
        |    },
        |    series: $jsonSeries
        |});
        |</script>
    }]]
}

#
# Return a display label with a native browser tooltip.
#
proc ::_ns_stats.tooltip {label tooltip {noquote 0} {class ""}} {
    if {$tooltip ne ""} {
        set spanClass "nsstats-tooltip $class"
        set title [subst {title="[ns_quotehtml $tooltip]"}]
    } else {
        set spanClass "$class"
        set title ""
    }
    set label [expr {$noquote ? $label : [ns_quotehtml $label]}]
    return [subst {<span class="$spanClass" $title">$label</span>}]
}

#
# Provide lists for titles and alignments for ns_stats.results tables
#
proc ::_ns_stats.columnLists {columnSpecs} {
    set titles {}
    set alignments {}

    foreach {title spec} $columnSpecs {
        set tooltip [string trim [::_ns_stats.dictGetDef $spec tooltip ""]]
        set title [::_ns_stats.tooltip \
                       $title \
                       $tooltip \
                       [::_ns_stats.dictGetDef $spec noquote 0] \
                       [::_ns_stats.dictGetDef $spec class ""]]
        lappend titles $title
        lappend alignments [dict get $spec align]
    }

    return [list $titles $alignments]
}

proc ::_ns_stats.utilization {} {
    set sample        [::_ns_stats.utilizationSample]

    set cpuData       [::_ns_stats.threadCpuPercentages utilizationThreadCpuSample [dict get $sample coreSnapshot]]
    set threadCpu     [::_ns_stats.dictGetDef $cpuData byThread {}]
    set driverInfo    [::_ns_stats.utilization.driverRows $sample $threadCpu]
    set driverRows    [dict get $driverInfo rows]

    set poolInfo      [::_ns_stats.utilization.poolRows $sample]
    set poolRows      [dict get $poolInfo rows]

    set globalSummary [::_ns_stats.utilization.globalSummary $sample $threadCpu]

    set driverColumnSpecs {
        "Driver Thread" {
            align left
        }
        "CPU %" {
            align right
        }
        "Received/s" {
            align right
            tooltip {
                Attempts to submit parsed requests to connection pools
            }
        }
        "Partial/s" {
            align right
            tooltip {
                Receive attempts that left the request incomplete
            }
        }
        "Upload-spooled/s" {
            align right
            tooltip {
                Requests handed to upload spooler threads
            }
        }
        "Errors (interval)" {
            align right
        }
        "Est. capacity" {
            align right
            tooltip {
                Observed completed receives extrapolated to 100% utilization
                of one logical CPU. This is a workload-dependent CPU-capacity
                estimate, not a configured or guaranteed limit. Omitted when
                the sample contains insufficient activity.
            }
        }
        "Reading" {
            align right
            tooltip {
                Sockets with incomplete requests or keep-alive sockets awaiting
                the next request
            }
        }
        "Pending" {
            align right
            tooltip {
                Requests ready for processing but still retained by the driver
            }
        }
        "Closing" {
            align right
            tooltip {
                Sockets retained temporarily for graceful shutdown
            }
        }
        "Sockets <span class='nowrap'>(used/max)</span>" {
            align right
            noquote true
            tooltip {
                Sockets currently retained by this driver thread
                (reading, pending, or closing),
                relative to its configured maxqueuesize.
            }
        }
        "Writers" {
            align right
            tooltip {
                Live threads / combined CPU percentage
            }
        }
        "Spoolers" {
            align right
            tooltip {
                Live threads / combined CPU percentage
            }
        }
        "Status" {
            align left
        }
    }

    set poolColumnSpecs {
        Server {
            align left
        }
        Pool {
            align left
        }
        Req/s {
            align right
        }
        "Global Share" {
            align right
        }
        "Threads <span class='nowrap'>(busy/current/max)</span>" {
            align right
            noquote true
        }
        Busy {
            align left
        }
        "Connections <span class='nowrap'>(active + waiting)/max</span>" {
            align right
            noquote true
            tooltip {
                Running requests plus requests waiting for a connection thread,
                relative to the pool's maxconnections limit.
            }
        }
        Busy {
            align left
        }
        "Avg Queue" {
            align right
        }
        "Avg Service" {
            align right
        }
        "Est. Capacity" {
            align right
            tooltip {
                Estimated maximum request rate, calculated as maximum connection
                threads divided by average service time.
            }
        }
        "Est. Load" {
            align right
            tooltip {
                Observed request rate divided by estimated capacity. This
                interval-based estimate may not reflect short bursts or current
                queueing.
            }
        }
        Queued/s {
            align right
            class nowrap
        }
        "Writer Jobs" {
            align right
        }
        "Dropped (interval)" {
            align right
            class nowrap
        }
        Status {
            align left
        }
    }

    lassign [::_ns_stats.columnLists $driverColumnSpecs] \
        driverTitles driverAlign

    lassign [::_ns_stats.columnLists $poolColumnSpecs] \
        poolTitles poolAlign


    set ::extraHeadEntries [ns_trim -delimiter | {
        |<style>
        |.utilization-charts {
        |   display: grid;
        |   grid-template-columns: repeat(auto-fit, minmax(520px, 1fr));
        |   gap: 1rem;
        |}
        |.utilization-chart {
        |   min-height: 280px;
        | }
        |.nsstats-tooltip {
        |   text-decoration-line: underline;
        |   text-decoration-style: dashed;
        |   text-decoration-color: currentColor;
        |   text-underline-offset: 3px;
        |   cursor: help;
        |}
        |</style>
        |<script src="https://code.highcharts.com/highcharts.js"></script>
        |<script src="https://code.highcharts.com/modules/exporting.js"></script>
        |<script src="https://code.highcharts.com/modules/export-data.js"></script>
    }]

    set driverChart [::_ns_stats.utilization.chart \
                         -id driver-utilization \
                         -title "Network driver utilization" \
                         -subtitle "CPU is relative to one logical CPU; sockets are relative to maxqueuesize" \
                         -categories [dict get $driverInfo names] \
                         -series [list \
                                      [dict create \
                                           name CPU \
                                           data [dict get $driverInfo cpuValues]] \
                                      [dict create \
                                           name Sockets \
                                           data [dict get $driverInfo socketPercentages]]] \
                        ]

    set poolChart [::_ns_stats.utilization.chart \
                       -id pool-utilization \
                       -title "Server and connection pool utilization" \
                       -subtitle "Thread busy ratio; active + waiting connections relative to maxconnections; request rate relative to estimated capacity" \
                       -rowheight 48 \
                       -categories [dict get $poolInfo names] \
                       -series [list \
                                    [dict create \
                                         name Thread \
                                         data [dict get $poolInfo threadValues]] \
                                    [dict create \
                                         name Connections \
                                         data [dict get $poolInfo connectionValues]] \
                                    [dict create \
                                         name "Est. load" \
                                         data [dict get $poolInfo loadValues]]] \
                      ]

    append html \
        [::_ns_stats.header "Utilization and Bottlenecks"] \
        [::_ns_stats.utilization.sourcePanel $sample] \
        [::_ns_stats.results global-summary 0 {Metric Value} "?@page=utilization" $globalSummary 0 {left left}] \
        [::_ns_stats.utilization.diskSustainedPanel] \
        "\n<p></p>" \
        "\n<div class='utilization-charts'>$driverChart $poolChart</div>" \
        "<h2>Network Drivers</h2>" \
        [::_ns_stats.results drivers 0 $driverTitles "?@page=utilization" $driverRows 0 $driverAlign] \
        "<h2 id='nsstats-pools'>Servers and Connection Pools</h2>" \
        [::_ns_stats.results utilization-pools 0 $poolTitles "?@page=utilization" $poolRows 0 $poolAlign] \
        [::_ns_stats.utilization.linuxEpisodesLink] \
        [::_ns_stats.utilization.historyPanel] \
        [::_ns_stats.footer]
    return $html
}


proc ::_ns_stats.locks.mutex {} {
    set col         [ns_queryget col 1]
    set reverseSort [ns_queryget reversesort 1]

    set numericSort 1
    set colTitles   [list Name ID Locks Busy Contention "Total Lock" "Avg Lock" "Total Wait" \
                         "Max Wait" "Locks/Req" "Pot.Locks/sec" "Pot.Reqs/sec" "Read" "Write" "Write %"]
    set rows        ""

    if {$col == 1} {
        set numericSort 0
    }

    set results ""
    set sumWait 0
    set sumLockTime 0
    set sumLocks 0
    set totalRequests [::_ns_stats.totalRequests]

    set non_per_req_locks {interp jobThreadPool ns:sched tcljob:jobs}
    lappend non_per_req_locks {*}[ns_config ns/module/nsstats bglocks ""]
    foreach s [ns_info servers] {
        lappend non_per_req_locks tcljob:ns_eval_q:$s
    }
    set non_per_req_locks [lsort $non_per_req_locks]

    foreach l [ns_info locks] {
        lassign $l name owner id nlock nbusy totalWait maxWait totalLock read write
        set sumWait     [expr {$sumWait + $totalWait}]
        if {$name ni $non_per_req_locks} {
            set sumLockTime [expr {$sumLockTime + $totalLock}]
        }
        set sumLocks    [expr {$sumLocks + $nlock}]
        set avgLock     [expr {$totalLock ne "" && $nlock > 0 ? $totalLock * 1.0 / $nlock : 0}]
        if {$nlock > 2 && $name ni $non_per_req_locks} {
            set maxLocksPerSec [expr {1.0/$avgLock}]
            set locksPerReq    [expr {$nlock*1.0/$totalRequests}]
            set maxReqsPerSec  [expr {$maxLocksPerSec/$locksPerReq}]
        } else {
            set maxLocksPerSec [expr {1.0/0}]
            set locksPerReq    -1
            set maxReqsPerSec  [expr {1.0/0}]
        }

        if {$nbusy == 0} {
            set contention 0.0
        } else {
            set contention [format %5.4f [expr {double($nbusy*100.0/$nlock)}]]
        }
        set writePercent   [expr {$write ne "" && $write+$read > 0 ? ($write*100.0/($write+$read)) : ""}]

        lappend results [list $name $id $nlock $nbusy $contention \
                             $totalLock $avgLock $totalWait $maxWait \
                             $locksPerReq $maxLocksPerSec $maxReqsPerSec $read $write $writePercent]
    }

    foreach result [::_ns_stats.sortResults $results [expr {$col - 1}] $numericSort $reverseSort] {
        lassign $result name id nlock nbusy contention totalLock avgLock totalWait maxWait \
            locksPerReq maxLocksPerSec maxReqsPerSec read write writePercent
        set contention     [format %.4f $contention]
        set totalLock      [format %.4f $totalLock]
        set avgLock        [format %.8f $avgLock]
        set relWait        [expr {$sumWait > 0 ? $totalWait/$sumWait : 0}]
        set locksPerReq    [format %.2f $locksPerReq]
        set maxLocksPerSec [::_ns_stats.hr $maxLocksPerSec]
        set maxReqsPerSec  [::_ns_stats.hr $maxReqsPerSec]

        set writePercent   [expr {$writePercent ne "" ? "[format %.2f $writePercent]%" : ""}]
        set read           [expr {$read ne "" ? [::_ns_stats.hr $read] : $read}]
        set write          [expr {$write ne "" ? [::_ns_stats.hr $write] : $write}]

        set color black
        set ccolor [expr {$contention < 2   ? $color : $contention < 5   ? "orange" : "red"}]
        set tcolor [expr {$relWait    < 0.1 ? $color : $totalWait  < 0.5 ? "orange" : "red"}]
        set wcolor [expr {$maxWait    < 0.01 ? $color : $maxWait    < 0.1   ? "orange" : "red"}]
        set ncolor [expr {"orange" in [list $ccolor $tcolor $wcolor] ? "orange" : $color}]
        set ncolor [expr {"red "   in [list $ccolor $tcolor $wcolor] ? "red" : $ncolor}]

        lappend rows [list \
                          "<font color=$ncolor>$name</font>" \
                          "<font color=$color>$id</font>" \
                          "<font color=$color>[::_ns_stats.hr $nlock]</font>" \
                          "<font color=$color>[::_ns_stats.hr $nbusy]</font>" \
                          "<font color=$ccolor>$contention%</font>" \
                          "<font color=$color>[::_ns_stats.hr $totalLock]s</font>" \
                          "<font color=$color>[::_ns_stats.hr $avgLock]s</font>" \
                          "<font color=$tcolor>[::_ns_stats.hr $totalWait]s</font>" \
                          "<font color=$wcolor>[::_ns_stats.hr $maxWait]s</font>" \
                          "<font color=$color>$locksPerReq</font>" \
                          "<font color=$color>$maxLocksPerSec</font>" \
                          "<font color=$color>$maxReqsPerSec</font>" \
                          "<font color=$color>$read</font>" \
                          "<font color=$color>$write</font>" \
                          "<font color=$color>$writePercent</font>" \
                         ]
    }

    set avgLock          [expr {$sumLockTime/$sumLocks}]
    set locksPerReq      [expr {$sumLocks/$totalRequests}]
    set lockTimePerReq   [expr {$sumLockTime/$totalRequests}]
    set maxLocksPerSec   [expr {1.0/$avgLock}]

    set p_locksPerReq    [::_ns_stats.hr $locksPerReq]
    set p_avgLock        [::_ns_stats.hr $avgLock]
    set p_maxLocksPerSec [::_ns_stats.hr $maxLocksPerSec]
    set p_lockTimePerReq [::_ns_stats.hr $lockTimePerReq]
    set p_maxPages       [::_ns_stats.hr [expr {1.0/$lockTimePerReq}]]
    set p_sumLocks       [::_ns_stats.hr $sumLocks]
    set p_totalRequests  [::_ns_stats.hr $totalRequests]

    set line "Total locks: $p_sumLocks, total requests $p_totalRequests,\
        locks per request $p_locksPerReq, avg lock time $p_avgLock,\
        lock time request req $p_lockTimePerReq, max requests per sec $p_maxPages <br>(except: [join $non_per_req_locks {, }])"
    append html \
        [::_ns_stats.header "Locks"] \
        "<p class='summary'>$line</p>" \
        [::_ns_stats.results locks $col $colTitles ?@page=locks.mutex $rows $reverseSort {
            left right right right right right right right right right right right right right right
        }] \
        [::_ns_stats.footer]

    return $html
}

proc ::_ns_stats.locks.nsv {} {
    set col         [ns_queryget col 2]
    set reverseSort [ns_queryget reversesort 1]
    set all         [ns_queryget all 0]

    set numericSort 1
    set colTitles   [list Array Locks Bucket "Bucket Locks" Busy Contention "Total Wait" "Max Wait"]
    set rows        ""

    if {$col == 1} {
        set numericSort 0
    }

    # get the lock statistics for nsvs
    foreach l [ns_info locks] {
        set name      [lindex $l 0]
        if {![regexp {^nsv:(\d+):} $name _ bucket]} continue
        #set id        [lindex $l 2]
        set nlock     [lindex $l 3]
        set nbusy     [lindex $l 4]
        set totalWait [lindex $l 5]
        set maxWait   [lindex $l 6]
        #set sumWait   [expr {$sumWait + $totalWait}]

        if {$nbusy == 0} {
            set contention 0.0
        } else {
            set contention [format %5.4f [expr {double($nbusy*100.0/$nlock)}]]
        }

        set mutexStats($bucket) [list $nlock $nbusy $contention $totalWait $maxWait]
    }

    set rows ""
    set bucketNr 0
    if {[info commands nsv_bucket] ne ""} {
        foreach b [nsv_bucket] {
            foreach e $b {
                lappend rows [lappend e $bucketNr {*}$mutexStats($bucketNr)]
            }
            incr bucketNr
        }
    }
    set rows [::_ns_stats.sortResults $rows [expr {$col - 1}] $numericSort $reverseSort]
    set max 200
    if {[llength $rows]>$max && !$all} {
        set rows [lrange $rows 0 $max]
        set truncated 1
    }

    set table {}
    foreach row $rows {
        lset row 1 [::_ns_stats.hr [lindex $row 1]]
        lset row 3 [::_ns_stats.hr [lindex $row 3]]
        lset row 4 [::_ns_stats.hr [lindex $row 4]]
        lset row 5 [format %.4f [lindex $row 5]]%
        lset row 6 [::_ns_stats.hr [lindex $row 6]]s
        lset row 7 [::_ns_stats.hr [lindex $row 7]]s
        lappend table $row
    }

    append html \
        [::_ns_stats.header "Nsv Locks"] \
        [::_ns_stats.results nsv-locks $col $colTitles ?@page=locks.nsv \
             $table \
             $reverseSort \
             {left right right right right right right right}]

    if {[info exists truncated]} {
        append html "<a href='?@page=locks.nsv&col=$col&reversesort=$reverseSort&all=1'>...</a><br>"
    }
    append html [::_ns_stats.footer]

    return $html
}

proc ::_ns_stats.mem.nsvsize {} {
    set col         [ns_queryget col 3]
    set reverseSort [ns_queryget reversesort 1]
    set all         [ns_queryget all 0]

    # Scope is optional: 4.99 uses the unchanged server-local interface.
    set hasScope [expr {![catch {nsv_array scope _ns_stats}]}]
    set numericSort 1
    set colTitles   [list Array Elements Bytes "Agv. Content-Size"]
    if {$hasScope} {
        lappend colTitles Scope
    }
    set rows        ""

    if {$col == 1 || ($hasScope && $col == 5)} {
        set numericSort 0
    }

    set nrArrays 0; set totalElements 0; set totalBytes 0
    set rows ""
    # get the array size statistics for nsvs array
    foreach array [nsv_names] {
        incr nrArrays
        set contentBytes 0
        set sizeBytes 0
        set size    [nsv_array size $array]
        foreach {key value} [nsv_array get $array] {
            set valueLength [string length $value]
            incr contentBytes $valueLength
            incr sizeBytes [expr {$valueLength + [string length $key] + 40}] ;# Tcl_HashEntry
        }
        set row [list $array $size $sizeBytes [expr {$size > 0 ? $contentBytes*1.0/$size : 0}]]
        if {$hasScope} {
            set scope unknown
            catch {set scope [nsv_array scope $array]}
            lappend row [ns_quotehtml [expr {$scope eq "process" ? "Global" : ($scope eq "server" ? "Server: [ns_info server]" : "Unknown")}]]
        }
        lappend rows $row
        incr totalElements $size
        incr totalBytes $sizeBytes
    }
    incr totalBytes [expr {$nrArrays * 120}] ;# add approximate size of a single nsv array structure

    set rows [::_ns_stats.sortResults $rows [expr {$col - 1}] $numericSort $reverseSort]
    set table {}
    foreach row $rows {
        lset row 1 [::_ns_stats.hr [lindex $row 1]]
        lset row 2 [::_ns_stats.hr [lindex $row 2]]B
        lset row 3 [format %.2f [lindex $row 3]]
        lappend table $row
    }

    set scopeInfo "Shared variables for server [ns_quotehtml [ns_info server]]."
    set align {left right right right}
    if {$hasScope} {
        set scopeInfo "Global and current-server shared variables; the Scope column identifies their ownership."
        lappend align left
    }
    append html \
        [::_ns_stats.header "Nsv Size"] \
        "<p>$scopeInfo</p>" \
        "<p>Nsv arrays: $nrArrays, elements: [::_ns_stats.hr $totalElements], total bytes: [::_ns_stats.hr $totalBytes]B</p>" \
        [::_ns_stats.results nsv-size $col $colTitles ?@page=mem.nsvsize \
             $table \
             $reverseSort \
             $align]

    append html [::_ns_stats.footer]
    return $html
}

proc ::_ns_stats.log.prepare_content {type content} {
    set content [ns_quotehtml $content]
    switch $type {
        access { regsub -all { ([-][^\]\n\" ]+[-]) } $content \
                     { <a href='nsstats.tcl?@page=log.logfile\&filter=\1'>\1</a> } \
                     content }
        system { regsub -all {\[([-][^\]\n\" ]+[-])\]} $content \
                     [string map [list @X@ [ns_queryget system_log [file tail [ns_info log]]]] \
                          {[<a href='nsstats.tcl?@page=log.logfile\&filter=\1\&system_log=@X@'>\1</a>]}] \
                     content }
    }
    return $content
}

# Literal log searches use byte offsets on binary channels. Keep record recovery,
# excerpts and result pages bounded independently of the size of the log file.
proc ::_ns_stats.log.isheader {line} {
    regsub -all {\x1b\[[0-9;]*m} $line {} line
    return [regexp {^\[[0-9]{2}/[A-Za-z]{3}/[0-9]{4}:[0-9:.]+\]\[[^\]\r\n]*\]\[.*\] [A-Za-z][A-Za-z0-9_-]*:} $line]
}

proc ::_ns_stats.log.find {channel literal snapshot} {
    set offset [tell $channel]
    if {$offset >= $snapshot} {return -1}
    seek $channel $offset start
    set found [ns_fseekchars $channel $literal]
    if {$found < 0 || $found + [string length [encoding convertto utf-8 $literal]] > $snapshot} {return -1}
    return $found
}

# Returns one bounded physical-line prefix and advances past the whole line.
# Very long lines are skipped natively rather than collected with unbounded gets.
proc ::_ns_stats.log.line {channel snapshot limit} {
    set start [tell $channel]
    set text {}
    while {[string length $text]<$limit && [tell $channel]<$snapshot} {
        set chunkStart [tell $channel]
        set chunk [read $channel [expr {min(4096,$limit-[string length $text],$snapshot-$chunkStart)}]]
        set nl [string first \n $chunk]
        if {$nl>=0} {
            append text [string range $chunk 0 $nl]
            set end [expr {$chunkStart+$nl+1}]
            seek $channel $end start
            return [dict create text $text start $start end $end clipped 0]
        }
        append text $chunk
        if {$chunk eq {}} {error "The log was truncated while reading it; start a new search."}
    }
    set end [tell $channel]
    set clipped [expr {$end < $snapshot}]
    if {$clipped} {
        set nl [::_ns_stats.log.find $channel \n $snapshot]
        set end [expr {$nl < 0 ? $snapshot : $nl+1}]
        seek $channel $end start
    }
    return [dict create text $text start $start end $end clipped $clipped]
}

proc ::_ns_stats.log.snapshot {path {previous {}}} {
    file stat $path st
    if {$st(type) ne "file"} {error "The selected log is not a regular file."}
    if {$previous eq {}} {
        set snapshot [dict create dev $st(dev) ino $st(ino) size $st(size)]
    } else {
        set snapshot $previous
        if {$st(dev) != [dict get $snapshot dev] || $st(ino) != [dict get $snapshot ino]
            || $st(size) < [dict get $snapshot size]} {
            error "The log was replaced or truncated; start a new search."
        }
    }
    set size [dict get $snapshot size]
    set f [open $path rb]
    try {
        set head [ns_sha1 [read $f [expr {min(4096,$size)}]]]
        seek $f [expr {max(0,$size-4096)}] start
        set tail [ns_sha1 [read $f [expr {min(4096,$size)}]]]
    } finally {close $f}
    file stat $path after
    if {$after(dev) != $st(dev) || $after(ino) != $st(ino) || $after(size) < $size} {
        error "The log changed while opening it; start a new search."
    }
    if {$previous ne {} && ($head ne [dict get $snapshot head] || $tail ne [dict get $snapshot tail])} {
        error "The log snapshot changed; start a new search."
    }
    dict set snapshot head $head; dict set snapshot tail $tail
    return $snapshot
}

# Recover backwards at most recoveryBytes. A missing header produces an
# explicitly labelled fragment, never an invented association with a request.
proc ::_ns_stats.log.recordstart {f found floor kind recoveryBytes snapshot} {
    # Most hits are close to their header. Grow recovery only when necessary,
    # avoiding a 128 KiB look-behind for every short log record.
    set window [expr {min(4096,$recoveryBytes)}]
    while {1} {
        set begin [expr {max($floor,$found-$window)}]
        seek $f $begin start
        set prefix [read $f [expr {$found-$begin}]]
        set nl [string last \n $prefix]
        if {$kind eq "access"} {
            if {$nl>=0} {return [list [expr {$begin+$nl+1}] 0]}
            # The resume offset is already a known physical-line boundary.
            if {$begin==0 || $begin==$floor} {return [list $begin 0]}
        } else {
            # A match may precede the severity portion of its own header.
            set current [expr {$nl<0 ? $begin : $begin+$nl+1}]
            seek $f $current start
            set probe [read $f [expr {min(4096,$snapshot-$current)}]]
            if {[::_ns_stats.log.isheader [lindex [split $probe \n] 0]]} {return [list $current 0]}
            set start -1; set pos $begin
            foreach line [split $prefix \n] {
                # Ignore a possibly partial first line of the recovery window.
                if {($pos>$begin || $begin==0 || $begin==$floor) && [::_ns_stats.log.isheader $line]} {set start $pos}
                incr pos [expr {[string length $line]+1}]
            }
            if {$start>=0} {return [list $start 0]}
        }
        if {$begin==$floor || $window>=$recoveryBytes} break
        set window [expr {min($recoveryBytes,$window*2)}]
    }
    return [list [expr {$nl<0 ? $begin : $begin+$nl+1}] 1]
}

proc ::_ns_stats.log.readrecord {f start snapshot kind recordBytes} {
    seek $f $start start
    set text {}; set clipped 0; set first 1
    while {[tell $f] < $snapshot} {
        set pos [tell $f]
        set room [expr {$recordBytes-[string length $text]}]
        # Continue locating the next header after the excerpt fills up, keeping
        # only a bounded prefix of each physical line. This skips the remainder
        # of one oversized record, preventing duplicate excerpts for its matches.
        set row [::_ns_stats.log.line $f $snapshot [expr {$room>0 ? max(4096,$room) : 4096}]]
        set line [dict get $row text]
        if {!$first && $kind eq "system" && [::_ns_stats.log.isheader $line]} {
            seek $f $pos start
            break
        }
        set first 0
        if {$room>0} {append text [string range $line 0 $room-1]}
        if {[dict get $row clipped] || [string length $line]>$room} {set clipped 1}
        if {$kind eq "access"} break
    }
    return [dict create text $text next [tell $f] clipped $clipped]
}

proc ::_ns_stats.log.searchfile {path kind literal snapshot offset limits} {
    ::_ns_stats.log.snapshot $path $snapshot
    set size [dict get $snapshot size]
    set f [open $path rb]; set rows {}; set bytes 0; set limited 0
    try {
        seek $f $offset start
        while {$offset<$size} {
            if {[llength $rows]>=[dict get $limits records] || $bytes>=[dict get $limits bytes]} {set limited 1; break}
            set found [::_ns_stats.log.find $f $literal $size]
            if {$found<0} {set offset $size; break}
            lassign [::_ns_stats.log.recordstart $f $found $offset $kind [dict get $limits recovery] $size] start fragment
            set row [::_ns_stats.log.readrecord $f $start $size $kind \
                         [expr {min([dict get $limits record], [dict get $limits bytes]-$bytes)}]]
            set offset [dict get $row next]
            if {$offset<=$found} {error "Log reader did not advance."}
            incr bytes [string length [dict get $row text]]
            dict set row fragment $fragment
            # Labels are separate from the byte-limited raw excerpt.
            lappend rows $row
            seek $f $offset start
        }
    } finally {close $f}
    ::_ns_stats.log.snapshot $path $snapshot
    return [dict create rows $rows bytes $bytes next $offset limited $limited]
}

# Cache values contain only small snapshot/cursor descriptors, never log bodies.
# A cursor is opaque to clients and cannot select an arbitrary filesystem path.
proc ::_ns_stats.log.cursor {operation value} {
    set cache nsstats-log-cursors
    if {$cache ni [ns_cache_names]} {catch {ns_cache_create -maxentry 65536 $cache 1048576}}
    if {$operation eq "get"} {
        if {![regexp {^[a-fA-F0-9]{40}$} $value]} {error "Invalid continuation cursor; start a new search."}
        if {![ns_cache_get $cache $value state] || [dict get $state expires]<[clock seconds]} {
            error "The continuation cursor expired; start a new search."
        }
        return $state
    }
    if {[string length [encoding convertto utf-8 $value]]>60000} {error "Continuation metadata is too large."}
    set token [ns_sha1 [list [clock clicks] [ns_rand] [ns_info server] $value]]
    dict set value expires [expr {[clock seconds]+900}]
    ns_cache_eval -expires 900 $cache $token [list set state $value]
    return $token
}

proc ::_ns_stats.log.pagesize {value} {
    if {![regexp {^[1-9][0-9]{0,3}$} $value] || $value>1000} {
        error "Entries per result must be a whole number between 1 and 1000."
    }
    return $value
}

proc ::_ns_stats.log.searchpage {filter system_log suffix cursor {pageSize 200}} {
    set notices {}; set sections [dict create access {} system {}]
    if {[catch {::_ns_stats.log.pagesize $pageSize} pageSize]} {return "<p>[ns_quotehtml $pageSize]</p>"}
    if {$filter eq {} || [string length [encoding convertto utf-8 $filter]]>4096 || [regexp {[\x00\r\n]} $filter]} {
        return "<p>Use a nonempty literal filter of at most 4096 UTF-8 bytes, without NUL characters or line breaks.</p>"
    }
    if {$cursor ne {}} {
        if {[catch {
            set state [::_ns_stats.log.cursor get $cursor]
            if {[dict get $state server] ne [ns_info server] || [dict get $state filter] ne $filter
                || [dict get $state selected] ne $system_log} {error "The search changed; start a new search."}
        } message]} {return "<p>[ns_quotehtml $message]</p>"}
    } else {
        set files {}; set paths {}
        foreach s [ns_info servers] {
            set section ns/server/$s/module/nslog
            set path [ns_config $section file]
            if {$path eq {}} {
                set defaults [ns_configsection -filter defaults $section]
                if {$defaults eq {}} continue
                set path [ns_set get $defaults file]
            }
            if {$path eq {}} continue
            if {[file pathtype $path] eq "relative"} {set path [file normalize [file join [ns_config ns/parameters logdir] $path]]}
            if {$suffix ne {} && [file exists $path$suffix]} {append path $suffix}
            if {$path ni $paths} {lappend paths $path; lappend files [dict create path $path kind access offset 0]}
        }
        lappend files [dict create path $system_log kind system offset 0]
        if {[llength $files]>64} {
            # Always retain the selected system log, even with many servers.
            set files [concat [lrange $files 0 62] [list [lindex $files end]]]
            lappend notices {Only the first 63 distinct access logs and the selected system log are searched.}
        }
        set ready {}; set identities {}
        foreach item $files {
            if {[catch {::_ns_stats.log.snapshot [dict get $item path]} snapshot]} {
                lappend notices "Cannot read [file tail [dict get $item path]]: $snapshot"
            } else {
                if {[dict get $snapshot ino]!=0} {
                    set identity [list [dict get $item kind] [dict get $snapshot dev] [dict get $snapshot ino]]
                } else {
                    # Some platforms do not expose a usable inode through Tcl.
                    set identity [list [dict get $item kind] [file normalize [dict get $item path]]]
                }
                if {$identity in $identities} continue
                lappend identities $identity
                dict set item snapshot $snapshot; lappend ready $item
            }
        }
        set state [dict create files $ready index 0 shown 0 server [ns_info server] filter $filter selected $system_log]
    }
    set files [dict get $state files]; set index [dict get $state index]
    # Validate every retained snapshot before showing any results on this page.
    if {[catch {
        foreach item $files {::_ns_stats.log.snapshot [dict get $item path] [dict get $item snapshot]}
    } message]} {return "<p>[ns_quotehtml $message]</p>"}
    set records 0; set bytes 0
    while {$index<[llength $files] && $records<$pageSize && $bytes<2097152} {
        set item [lindex $files $index]
        set limits [dict create records [expr {$pageSize-$records}] bytes [expr {2097152-$bytes}] record 131072 recovery 131072]
        if {[catch {::_ns_stats.log.searchfile [dict get $item path] [dict get $item kind] $filter \
                        [dict get $item snapshot] [dict get $item offset] $limits} result]} {
            lappend notices $result
            # Never issue a continuation against a changed/unreadable source.
            set index [llength $files]
            break
        }
        foreach row [dict get $result rows] {
            set text [encoding convertfrom utf-8 [dict get $row text]]
            regsub -all {\x1b\[[0-9;]*m} $text {} text
            set label {}
            if {[dict get $row fragment]} {append label "\[Fragment: record start was not found within 128 KiB.\]\n"}
            if {[dict get $row clipped]} {append label "\[Excerpt: record exceeds the displayed byte limit.\]\n"}
            # Records normally include their line terminator. Add a separator
            # only for clipped excerpts or final lines without a newline.
            if {[string index $text end] ne "\n"} {append text \n}
            dict append sections [dict get $item kind] $label$text
            incr records
        }
        incr bytes [dict get $result bytes]
        dict set item offset [dict get $result next]
        lset files $index $item
        if {[dict get $item offset]>=[dict get $item snapshot size]} {incr index} else break
    }
    # Older retained cursors have no cumulative count: keep their count-only
    # display rather than inventing an ordinal for a search already in progress.
    set shown [expr {[dict exists $state shown] ? [dict get $state shown] : -1}]
    if {$shown>=0 && $records>0 && ($cursor ne {} || $index<[llength $files])} {
        set first [expr {$shown+1}]; set last [expr {$shown+$records}]
        if {$records==1} {
            set html "<p>Matching log entry $first.</p>"
        } else {
            set html "<p>Matching log entries $first–$last.</p>"
        }
    } elseif {$records==0} {
        set html [expr {$cursor eq {} ? "<p>No matching log entries.</p>" : "<p>No further matching log entries.</p>"}]
    } else {
        set countLabel [expr {$records==1 ? "matching log entry" : "matching log entries"}]
        set html "<p>$records $countLabel.</p>"
    }
    foreach notice $notices {append html "<p>[ns_quotehtml $notice]</p>"}
    if {$index<[llength $files]} {
        dict set state files $files; dict set state index $index
        if {$shown>=0} {dict set state shown [expr {$shown+$records}]}
        if {[catch {::_ns_stats.log.cursor put $state} token]} {
            append html "<p>More matches may be available, but continuation is unavailable. Start a new search.</p>"
        } else {
            append html "<form method='post' action='./nsstats.tcl'><input type='hidden' name='@page' value='log.logfile'>" \
                "<input type='hidden' name='filter' value='[ns_quotehtml $filter]'>" \
                "<input type='hidden' name='system_log' value='[ns_quotehtml [file tail $system_log]]'>" \
                "<input type='hidden' name='log_limit' value='$pageSize'>" \
                "<input type='hidden' name='log_cursor' value='$token'><button type='submit'>Show more</button> <span>More matches may be available.</span></form>"
        }
    }
    foreach {kind title} {access {Access log} system {System log}} {
        set text [dict get $sections $kind]
        if {$text ne {}} {append html "<h4>$title:</h4><pre>[::_ns_stats.log.prepare_content $kind $text]</pre>"}
    }
    return $html
}

proc ::_ns_stats.log.logfile {} {
    set content ""
    set colorcodemap [list \
                          [binary decode hex 1b5b303b33326d] "" \
                          [binary decode hex 1b5b303b33396d] "" \
                          [binary decode hex 1b5b306d] "" \
                          [binary decode hex 1b5b313b33316d] "" \
                          [binary decode hex 1b5b313b33396d] "" \
                         ]
    set filter [ns_queryget filter ""]
    set filter_html [ns_quotehtml $filter]
    set limitError {}
    if {[catch {::_ns_stats.log.pagesize [ns_queryget log_limit 200]} pageSize]} {
        set limitError $pageSize; set pageSize 200
    }
    set system_log_tail [ns_queryget system_log [file tail [ns_info log]]]
    if {[file tail $system_log_tail] ne $system_log_tail} {error "Invalid system log selection"}
    set system_log [file dirname [ns_info log]]/$system_log_tail
    set system_suffix [string range $system_log [string length [ns_info log]] end]

    if {$limitError ne {}} {
        set content "<p>[ns_quotehtml $limitError]</p>"
    } elseif {$filter ne ""} {
        set content [::_ns_stats.log.searchpage $filter $system_log $system_suffix [ns_queryget log_cursor ""] $pageSize]
    } else {
        set log_to_stderr [expr {"-f" in [ns_info argv]}]
        if {!$log_to_stderr && ![file exists $system_log]} {
            #
            # Older version of NaviServer do not provide a working
            # "ns_info argv"
            #
            set log_to_stderr 1
        }
        if {$log_to_stderr} {
            set content [ns_trim -delimiter | [subst {
                | <p>The configured log file <i>$system_log</i> does not exist.
                | <p>Was maybe the server is running in foreground mode (i.e., started with the '-f' flag)?
            }]]
        } else {
            try {
                set f [open $system_log]
                seek $f 0 end
                set n [expr {[tell $f] - 40000}]
                if {$n < 0} {
                    set n 40000
                }
                seek $f $n
                # read the first partial line
                gets $f
                set system_content [string map $colorcodemap [read $f]]
            } on error {errorMsg} {
                # probably, output to console
                ns_log notice nsstats: $errorMsg
                set system_content ""
            } finally {
                if {[info exists f]} {
                    close $f
                }
            }
            set content "<font size=2><pre>[::_ns_stats.log.prepare_content system $system_content]</pre></font>"
        }
    }

    try {
        set errorLogFiles [lsort -decreasing [glob [ns_info log].*]]
    } on error {errorMsg} {
        ns_log notice nsstats: $errorMsg
        set errorLogFiles {}
    }

    set tails [lmap file $errorLogFiles {
        if {[file size $file] < 10} continue
        file tail $file
    }]
    set options [join [lmap tail [list [file tail [ns_info log]] {*}$tails] {
        set selected [expr {"[file dirname $system_log]/$tail" eq $system_log ? " selected" : ""}]
        set _ "<option value='$tail'$selected>$tail</option>"
    }] \n]

    append html \
        [::_ns_stats.header Log] \
        [ns_trim -subst -delimiter | {
            |<form id='logform' method='post' action='./nsstats.tcl'>Filter:
            |  <input type='hidden' name='@page' value='log.logfile'>
            |  <input name='filter' value='$filter_html' size='40'>
            |  <select name='system_log'>$options</select>
            |  <label>Entries per result: <input type='number' name='log_limit' value='$pageSize' min='1' max='1000' step='1' style='width: 6em'></label>
            |  <button type='submit'>Search</button>
            |</form>
            |<script>
            |  const form = document.getElementById('logform');
            |  form.elements.system_log.addEventListener('change', function () {form.submit();});
            |</script>
        }] \
        $content \
        [::_ns_stats.footer]

    return $html
}


set ::tips(module~nslog\$,checkforproxy) "Log peer address provided by X-Forwarded-For. (boolean)"
set ::tips(ns~db~pool~,checkinterval) "Check in this interval if handles are not stale. (time interval)"
set ::tips(ns~db~pool~,maxidle) "Close handles which are idle for at least this interval. (time interval)"
set ::tips(ns~db~pool~,maxopen) "Close handles which open longer than this interval. (time interval)"
set ::tips(ns~parameters\$,asynclogwriter) "Write logfiles (error.log and access.log) asynchronously via writer threads (boolean)"
set ::tips(ns~parameters\$,jobsperthread) "Default number of ns_jobs per thread (similar to connsperthread) (integer)"
set ::tips(ns~parameters\$,jobtimeout) "Default timeout for ns_job (time interval)"
set ::tips(ns~parameters\$,logexpanded) "Double-spaced error.log (boolean)"
set ::tips(ns~parameters\$,logmaxbackup) "The number of old error.log files to keep around if log rolling is enabled.(integer)"
set ::tips(ns~parameters\$,logroll) "If true, the log file will be rolled when the server receives a SIGHUP signal (boolean)"
set ::tips(ns~parameters\$,logusec) "If true, error.log entries will have timestamps with microsecond resolution (boolean)"
set ::tips(ns~parameters\$,schedlogminduration) "Write warning, when a scheduled proc takes more than this time interval (time interval)"
set ::tips(ns~parameters\$,schedsperthread) "Default number of scheduled procs per thread (similar to connsperthread) (integer)"
set ::tips(ns~server~\[^~\]+\$,compressenable) "Compress dynamic content per default. (boolean)"
set ::tips(ns~server~\[^~\]+\$,compresslevel) "Compression level, when compress is enabled. (integer 1-9)"
set ::tips(ns~server~\[^~\]+\$,compressminsize) "Compress dynamic content above this size. (integer)"
set ::tips(ns~server~\[^~\]+\$,connsperthread) "Number of requests per connection thread before it terminates. (integer)"
set ::tips(ns~server~\[^~\]+\$,hackcontenttype) "Force charset into content-type header for dynamic responses. (boolean)"
set ::tips(ns~server~\[^~\]+\$,highwatermark) "When request queue is full above this percentage, create potentially connection threads in parallel. (integer)"
set ::tips(ns~server~\[^~\]+\$,lowwatermark) "When request queue is full above this percentage, create an additional connection threads. (integer)"
set ::tips(ns~server~\[^~\]+\$,noticedetail) "Notice server details (version number) in HTML return notices. (boolean)"
set ::tips(~fastpath\$,directoryadp) "Name of directory ADP"
set ::tips(~fastpath\$,directoryproc) "Name of directory proc"
set ::tips(~module~,deferaccept) "TCP Performance option; use TCP_FASTOPEN or TCP_DEFER_ACCEPT or SO_ACCEPTFILTER. (boolean, false)"
set ::tips(~module~,keepwait) "Timeout for keep-alive. (time interval)"
set ::tips(~module~,closewait) "Timeout for close on socket to drain potential garbage if no keep alive is performed. (time interval)"
set ::tips(~module~,nodelay) "TCP Performance option; use TCP_NODELAY (OS-default on Linux). (boolean)"
set ::tips(~module~,writersize) "Use writer threads for replies above this size. (memory units)"
set ::tips(~module~,writerstreaming) "Use writer threads for streaming HTML output (e.g. ns_write ...). (boolean)"
set ::tips(~module~,writerthreads) "Number of writer threads. (integer)"
set ::tips(~tcl\$,errorlogheaders) "Connection headers to be logged in case of error (list)"


proc ::_ns_stats.skipConfigSection {sectionName} {
    if {[namespace which ::ns_configdoc::sectionSpec] eq ""} {
        return 0
    }

    set spec [::ns_configdoc::sectionSpec $sectionName]
    if {$spec eq ""} {
        return 0
    }
    #
    # Sections with "providesParamDoc" are helper/default sections whose
    # values are intended to be copied into real configuration sections
    # via ns_section -from.  In nsstats, the copied values are already
    # materialized in the target sections, so showing the helper section
    # would usually be redundant.
    #
    return [dict exists $spec :providesParamDoc]
}


proc ::_ns_stats.config.loadDocs {coreFile moduleRoots} {
    # Load the core reference first, then repository-owned installed dictionaries.
    # Missing optional documentation must not prevent displaying configuration.
    namespace eval ::ns_configdoc {}
    set ::ns_configdoc::data {}
    set loaded {}
    if {[file readable $coreFile]} {
        source $coreFile
        lappend loaded [file normalize $coreFile]
    }
    foreach root [lsort -unique $moduleRoots] {
        if {![file isdirectory $root]} {continue}
        foreach path [lsort [glob -nocomplain -directory $root */doc/tcl/config-parameters.tcl]] {
            set path [file normalize $path]
            if {$path in $loaded} {continue}
            set before $::ns_configdoc::data
            if {[catch {source $path} message]} {
                set ::ns_configdoc::data $before
                ns_log warning "nsstats: could not load parameter documentation $path: $message"
                continue
            }
            lappend loaded $path
        }
    }
    return $loaded
}

proc ::_ns_stats.config.params {} {
    set paramDocFile [file join [ns_info home] modules tcl config-parameters.tcl]
    ::_ns_stats.config.loadDocs $paramDocFile [list \
        [ns_library shared] [ns_library private] \
        [file join [ns_info home] tcl] [file join [ns_info home] modules tcl]]

    set out [list]
    foreach section [lsort [ns_configsections]] {
        # We want to have e.g. "aaa/pools" before "aaa/pool/foo",
        # therefore we map "/" to "" to put it in the collating sequence
        # after plain chars
        set sectionName [ns_set name $section]
        if {[::_ns_stats.skipConfigSection $sectionName]} {
            continue
        }

        set tableName [string map {/ ~} $sectionName]

        try {
            set defaults [ns_configsection -filter defaults $sectionName]
            set defaulted [ns_configsection -filter defaulted $sectionName]
            set unread [ns_configsection -filter unread $sectionName]
        } on error {errorMsg} {
            set defaults {}
            set defaulted {}
            set unread {}
        }

        set keys {}
        for { set i 0 } { $i < [ns_set size $section] } { incr i } {
            set key [string tolower [ns_set key $section $i]]
            if {[ns_set isnull $section $i]} {
                # these are most likely parameters, which have just been queried for existence
                ns_log notice "DEBUG: ignore NULL value (section $sectionName key $key)"
                continue
            }
            set value [ns_set value $section $i]
            if {$defaults ne ""} {
                set default [ns_set iget $defaults $key]
                set isUnread [expr {[ns_set ifind $unread $key] == -1 ? "false" : "true"}]
                set isDefaulted [expr {[ns_set ifind $defaulted $key] > -1 ? "true" : "false"}]
            } else {
                set isDefaulted 0
                set isUnread 0
                set default ""
            }
            dict lappend keys $key [list value $value \
                                        default $default \
                                        defaulted $isDefaulted \
                                        unread $isUnread \
                                       ]
        }

        set line ""
        foreach section_key [lsort [dict keys $keys]] {
            set valueDicts [dict get $keys $section_key]

            #
            # Collect runtime defaults for this parameter first. Usually there is only
            # one, but repeated parameters may produce more.
            #
            set defaults {}
            foreach valueDict $valueDicts {
                set default [dict get $valueDict default]
                if {$default ne ""} {
                    lappend defaults $default
                }
            }
            set defaults [lsort -unique $defaults]
            set defaultForTooltip [join $defaults {, }]

            #
            # The parameter tooltip is still shown only when we have documentation.
            #
            set spec ""
            if {[namespace which ::ns_configdoc::get] ne ""} {
                set spec [::ns_configdoc::get $sectionName $section_key]
            }
            if {[namespace which ::ns_configdoc::tooltip] ne ""} {
                set tip [::ns_configdoc::tooltip $sectionName $section_key $defaultForTooltip]
            } else {
                set tip ""
            }

            set titleClasses {coltitle}

            if {$tip ne ""} {
                lappend titleClasses tip
            }

            if {$spec ne "" && [dict exists $spec deprecated]} {
                lappend titleClasses deprecated
            }
            set tipclass [expr {$tip ne "" ? "tip" : ""}]

            set values {}
            set classes {colvalue}
            set tooltip_text ""

            foreach valueDict $valueDicts {
                set value       [dict get $valueDict value]
                set default     [dict get $valueDict default]
                set isDefaulted [dict get $valueDict defaulted]
                set isUnread    [dict get $valueDict unread]

                if {$isDefaulted} {
                    lappend classes defaulted tooltip
                    set tooltip_text {<span class="tooltiptext">Value is default</span>}

                } elseif {$isUnread} {
                    lappend classes unread tooltip
                    set tooltip_text {<span class="tooltiptext">Value was not read during startup</span>}

                } elseif {$default ne "" && $default eq $value} {
                    lappend classes notneeded tooltip
                    set tooltip_text {<span class="tooltiptext">Value is set to default (not needed)</span>}
                }

                lappend values $value
            }

            lappend line "<tr><td title='[ns_quotehtml $tip]' class='[join [lsort -unique $titleClasses] { }]'>$section_key:</td>\n\
        <td class='[lsort -unique $classes]'>[join $values <br>]$tooltip_text</td></tr>"
        }
        set table($tableName) [join $line \n]
    }

    set order {
        ns~parameters ns~encodings ns~mimetypes ns~fastpath ns~threads .br
        ns~modules ns~module~.* .br
        ns~servers ns~server~.* .br
        ns~db~drivers ns~db~driver~* .br
        ns~db~pools ns~db~pool~* .br
    }

    set toc ""
    set sectionhtml ""
    foreach e $order {
        foreach section [lsort [array names table -regexp $e]] {
            set name [string map {~ /} $section]
            lappend toc "<a href='#ref-$name'>$name</a>"
            #set anchor "<a name='ref-$name'>$name</a>"
            append sectionhtml [ns_trim -delimiter | [subst {
                | <section id="ref-$name">
                |  <h2>$name</h2>
                |  <table class="data-table">
                |  <tr><th class="coltitle">Parameter</th><th class="coltitle">Value</th></tr>
                |   $table($section)
                |  </table>
                | </section>
            }]]
            unset table($section)
        }
    }
    if {[array size table] > 0} {
        # append sectionhtml "\n<tr><td colspan='2' class='colsection'><h2>Extra Parameters</h2></td></tr>\n\n"
        foreach section [lsort [array names table]] {
            set name [string map {~ /} $section]
            lappend toc "<a href='#ref-$name'>$name</a>"
            append sectionhtml [ns_trim -delimiter | [subst {
                | <section id="ref-$name">
                |  <h2>$name</h2>
                |  <table class="data-table">
                |  <tr><th class="coltitle">Parameter</th><th class="coltitle">Value</th></tr>
                |   $table($section)
                |  </table>
                | </section>
            }]]
        }
    }
    set ::sidebar [ns_trim -delimiter | [subst {
        |<div class="sidebar">
        |  <ul>
        |   <li>[join $toc </li>\n<li>]</li>\n
        |  </ul>
        | </div>
    }]]

    append html \
        [::_ns_stats.header "Configuration Parameters"] \
        "<p class='summary'>The following values are defined in the configuration database:</p>" \
        "<div class='config values'>$sectionhtml</div>" \
        [::_ns_stats.footer]
    return $html
}

proc ::_ns_stats.config.file {} {
    set config ""
    set configFile [ns_info config]
    if {$configFile ne ""} {
        catch {
            set f [open $configFile]
            set config [read $f]
            close $f
        }
    }
    append html \
        [::_ns_stats.header Log] \
        "<font size=2><pre>[ns_quotehtml $config]</pre></font>" \
        [::_ns_stats.footer]
    return $html
}

# Allocator gauges are useful even for the initial (rate-not-ready) sample.
proc ::_ns_stats.mem.historyPoint {record} {
    set memory {}
    foreach key {current_allocated_bytes heap_size total_physical_bytes} {
        if {[dict exists $record process allocatorBytes $key]} {
            dict set memory $key [dict get $record process allocatorBytes $key]
        }
    }
    return [dict create timestamp [dict get $record timestamp] \
        instance [::_ns_stats.dictGetDef $record instance {}] \
        sampling [dict create intervalSeconds [::_ns_stats.dictGetDef $record sampling intervalSeconds 10]] \
        memory $memory]
}

# Retry transient pool contention per read, not an entire multi-page scan.
# This also works with older ns_stats cores without typed checkout errors.
proc ::_ns_stats.mem.historyQuery {args} {
    foreach delay {0 25 75} {
        if {$delay} {after $delay}
        if {![catch {ns_stats {*}$args} result options]} {return $result}
        if {$result ne "history pool checkout timed out"} {return -options $options $result}
    }
    return -options $options $result
}

# Probe the method signature without attempting a database read. A supported
# query failure is reported, never hidden by retrying the legacy reader.
proc ::_ns_stats.mem.historyRead {minutes} {
    if {![catch {ns_stats info lookup syntax {history allocator}} syntax]
        && [string match {*-from*} $syntax]} {
        set to [clock seconds]
        set result [::_ns_stats.mem.historyQuery history allocator -from [expr {$to-$minutes*60}] -to $to -limit 10000]
        return [dict create available true source sqlite points [dict get $result records] \
            coverage [dict get $result coverage] truncated [dict get $result more] pages 1 \
            timings [dict get $result timings] legacyDecoded [dict get $result legacyDecoded] reader compact]
    }
    set result [::_ns_stats.utilization.readHistory $minutes ::_ns_stats.mem.historyPoint ::_ns_stats.mem.historyQuery]
    dict set result reader legacy
    return $result
}

proc ::_ns_stats.mem.historyCached {cache {message {}}} {
    set html [::_ns_stats.dictGetDef $cache html {}]
    if {$html ne ""} {
        set age [expr {max(0,[clock seconds]-[dict get $cache updated])}]
        append html "<p>Chart refreshed ${age}s ago; sampling is unchanged.</p>"
    }
    if {$message ne ""} {append html "<p role='status'>[ns_quotehtml $message]</p>"}
    return $html
}

# Simplify one continuous SVG segment in viewBox pixels. Extrema (including
# plateau edges) are anchors, so even a subpixel peak is never discarded.
proc ::_ns_stats.mem.historySimplify {points {tolerance 0.5}} {
    set count [llength $points]
    if {$count < 3} {return $points}
    set anchors {0}
    for {set i 1} {$i < $count-1} {incr i} {
        set before [lindex $points [expr {$i-1}] 1]
        set value [lindex $points $i 1]
        set after [lindex $points [expr {$i+1}] 1]
        if {($value >= $before && $value >= $after && ($value > $before || $value > $after))
            || ($value <= $before && $value <= $after && ($value < $before || $value < $after))} {
            lappend anchors $i
        }
    }
    lappend anchors [expr {$count-1}]
    set keep {}; set pending {}; set previous [lindex $anchors 0]
    foreach index $anchors {
        dict set keep $index 1
        if {$index != $previous} {lappend pending [list $previous $index]}
        set previous $index
    }
    # Iterative subdivision avoids deep Tcl recursion on irregular histories.
    while {[llength $pending]} {
        lassign [lindex $pending end] start end
        set pending [lrange $pending 0 end-1]
        lassign [lindex $points $start] x1 y1
        lassign [lindex $points $end] x2 y2
        set largest $tolerance; set split -1
        for {set i [expr {$start+1}]} {$i < $end} {incr i} {
            lassign [lindex $points $i] x y
            if {$x2 == $x1} {set distance [expr {abs($y-$y1)}]} else {
                set distance [expr {abs($y-($y1+($y2-$y1)*($x-$x1)/double($x2-$x1)))}]
            }
            if {$distance > $largest} {set largest $distance; set split $i}
        }
        if {$split >= 0} {
            dict set keep $split 1
            lappend pending [list $start $split] [list $split $end]
        }
    }
    set result {}
    foreach index [lsort -integer [dict keys $keep]] {lappend result [lindex $points $index]}
    return $result
}

proc ::_ns_stats.mem.historySvg {points} {
    set metrics {current_allocated_bytes {Allocated} #0072b2 heap_size {Heap} #d55e00 total_physical_bytes {Physical} #009e73}
    set valid {}
    set maximum 1048576.0
    foreach point $points {
        set stamp [dict get $point timestamp]
        if {![string is wideinteger -strict $stamp] || $stamp < 0} {continue}
        set values {}
        foreach {key label color} $metrics {
            set value [::_ns_stats.dictGetDef $point memory $key {}]
            if {[catch {expr {double($value) >= 0 && double($value) < Inf}} finite] || !$finite} {continue}
            dict set values $key $value
            set maximum [expr {max($maximum,double($value))}]
        }
        dict set point memory $values
        lappend valid $point
    }
    set ordered [lsort -integer -index 0 [lmap point $valid {list [dict get $point timestamp] $point}]]
    set valid [lmap entry $ordered {lindex $entry 1}]
    if {[llength $valid] < 2} {return ""}
    set first [dict get [lindex $valid 0] timestamp]
    set last [dict get [lindex $valid end] timestamp]
    if {$last <= $first} {return ""}
    set lines {}
    set legend {}
    foreach {key label color} $metrics {
        set segments {}; set segment {}; set previous {}
        foreach point $valid {
            set stamp [dict get $point timestamp]
            set value [::_ns_stats.dictGetDef $point memory $key {}]
            set interrupted false
            if {$previous ne ""} {
                set interval [expr {max(1.0,[::_ns_stats.dictGetDef $point sampling intervalSeconds 10], [::_ns_stats.dictGetDef $previous sampling intervalSeconds 10])}]
                set interrupted [expr {[dict get $point instance] ne [dict get $previous instance]
                    || $stamp <= [dict get $previous timestamp]
                    || $stamp-[dict get $previous timestamp] > 1500000*$interval}]
            }
            if {$value eq "" || $interrupted} {
                if {$segment ne ""} {lappend segments $segment; set segment {}}
            }
            if {$value ne ""} {lappend segment [list $stamp $value]}
            set previous $point
        }
        if {$segment ne ""} {lappend segments $segment}
        if {$segments eq ""} {continue}
        append legend "<span style='display:inline-block;margin-right:16px'><span aria-hidden='true' style='color:$color'>&#9644;</span> $label</span>"
        foreach segment $segments {
            # Keep extrema as well as endpoints in each time bucket: a small
            # SVG must retain short memory peaks, rather than stride over them.
            set buckets {}
            foreach pair $segment {
                lassign $pair stamp value
                set bucket [expr {min(119,int(120.0*($stamp-$first)/($last-$first)))}]
                dict lappend buckets $bucket $pair
            }
            set reduced {}
            dict for {bucket pairs} $buckets {
                set ordered [lsort -real -index 1 $pairs]
                set selected [list [lindex $pairs 0] [lindex $pairs end] [lindex $ordered 0] [lindex $ordered end]]
                set reduced [concat $reduced [lsort -unique -integer -index 0 $selected]]
            }
            set coordinates {}
            foreach pair $reduced {
                lassign $pair stamp value
                set x [expr {68+674.0*($stamp-$first)/($last-$first)}]
                set y [expr {164-144.0*$value/$maximum}]
                lappend coordinates [list $x $y]
            }
            set coordinates [lmap pair [::_ns_stats.mem.historySimplify $coordinates] {
                format %.2f,%.2f {*}$pair
            }]
            if {[llength $coordinates] == 1} {
                lassign [split [lindex $coordinates 0] ,] x y
                append lines "<circle cx='$x' cy='$y' r='2' fill='$color'/>"
            } else {
                append lines "<polyline fill='none' stroke='$color' stroke-width='1.8' points='[join $coordinates { }]'/>"
            }
        }
    }
    if {$legend eq ""} {return ""}
    set start [clock format [expr {$first/1000000}] -format {%Y-%m-%d %H:%M:%S %Z}]
    set end [clock format [expr {$last/1000000}] -format {%Y-%m-%d %H:%M:%S %Z}]
    set axis {}
    foreach fraction {0 0.5 1} {
        set y [expr {164-144*$fraction}]
        append axis "<path d='M68 $y H742' stroke='currentColor' opacity='.15'/><text x='60' y='[expr {$y+4}]' text-anchor='end' fill='currentColor'>[format %.1f [expr {$maximum*$fraction/1048576.0}]]</text>"
    }
    # Align to local wall-clock hours, retaining the numeric offset when
    # rounding so the repeated hour at a DST transition is unambiguous.
    set tick [clock scan [clock format [expr {$first/1000000}] -format {%Y-%m-%d %H:00:00 %z}] -format {%Y-%m-%d %H:%M:%S %z}]
    if {$tick*1000000 < $first} {incr tick 3600}
    set hours {}
    set hourCount 0
    while {$tick*1000000 <= $last} {
        set x [format %.2f [expr {68+674.0*($tick*1000000-$first)/($last-$first)}]]
        set label [ns_quotehtml [clock format $tick -format {%H:%M}]]
        set detail [ns_quotehtml [clock format $tick -format {%Y-%m-%d %H:%M %Z (%z)}]]
        append hours "<g class='ns-memory-hour'><title>$detail</title><path d='M$x 20 V170' stroke='currentColor' opacity='.18'/><text x='$x' y='181' text-anchor='end' transform='rotate(-45 $x 181)' fill='currentColor'>$label</text></g>"
        incr hourCount
        incr tick 3600
    }
    set endpoints {}
    if {$hourCount < 2} {
        set endpoints "<text x='68' y='212' fill='currentColor'>[ns_quotehtml [clock format [expr {$first/1000000}] -format {%H:%M}]]</text><text x='742' y='212' text-anchor='end' fill='currentColor'>[ns_quotehtml [clock format [expr {$last/1000000}] -format {%H:%M}]]</text>"
    }
    return "<figure style='max-width:900px;margin:16px 0'><h3>Allocator memory history</h3>\
        <p>Process-wide, MiB. [ns_quotehtml $start] &#8212; [ns_quotehtml $end].</p>\
        <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 760 220' role='img' aria-labelledby='ns-memory-history-title' style='display:block;width:100%;height:auto;font:12px sans-serif'>\
        <title id='ns-memory-history-title'>Allocator memory history in MiB; gaps and restarts break the lines.</title>\
        $axis$hours$lines$endpoints\
        </svg><figcaption>$legend</figcaption></figure>"
}

proc ::_ns_stats.mem.history {} {
    if {[info commands ns_stats] eq "" || [catch {ns_stats history status} status]} {return ""}
    set persistent [expr {[::_ns_stats.dictGetDef $status pool {}] ne ""}]
    if {$persistent && [dict get $status owner] ne [ns_info server]} {
        return "<p>Persistent history belongs to virtual server '[ns_quotehtml [dict get $status owner]]'; open nsstats there.</p>"
    }
    set identity [list [::_ns_stats.dictGetDef $status pool {}] [::_ns_stats.dictGetDef $status owner {}]]
    set key allocator-history
    set now [clock seconds]
    set cache {}
    if {[nsv_exists _ns_stats $key]} {
        set candidate [nsv_get _ns_stats $key]
        if {[dict get $candidate identity] eq $identity} {set cache $candidate}
    }
    if {$cache ne ""} {
        if {$now < [::_ns_stats.dictGetDef $cache retryAfter 0]} {
            return [::_ns_stats.mem.historyCached $cache [dict get $cache error]]
        }
        if {$now-[::_ns_stats.dictGetDef $cache updated 0] < 60} {
            return [::_ns_stats.mem.historyCached $cache]
        }
    }
    # One reader per server. Other page requests use the previous result while
    # refresh is running; do not create competing full-day scans of a one-handle pool.
    if {[nsv_incr _ns_stats $key:busy] != 1} {
        nsv_incr _ns_stats $key:busy -1
        return [::_ns_stats.mem.historyCached $cache "Allocator history refresh is in progress; showing the last available chart, if any."]
    }
    try {
        set refreshStarted [clock microseconds]
        set minutes [expr {$persistent ? 1440 : 15}]
        set cutoff [expr {($now-$minutes*60)*1000000}]
        if {$persistent} {
            set storage [::_ns_stats.mem.historyQuery history storage]
            if {[::_ns_stats.dictGetDef $storage retainedFrom {}] ne ""} {
                set cutoff [expr {max($cutoff,int(ceil([dict get $storage retainedFrom]*1000000)))}]
            }
        }
        set points {}
        if {$cache ne "" && [nsv_exists _ns_stats $key:points]} {
            set saved [nsv_get _ns_stats $key:points]
            if {[dict get $saved identity] eq $identity} {set points [dict get $saved values]}
        }
        if {$persistent && [::_ns_stats.dictGetDef $storage sampleCount -1] == 0} {set points {}}
        set incremental [expr {$points ne ""}]
        if {$incremental} {
            set since [dict get [lindex $points end] timestamp]
            # Overlap one sample interval so the cached/new boundary is replaced,
            # not connected blindly. A final-page coverage pass now covers only
            # the recent tail, rather than decoding yesterday's payloads again.
            set minutes [expr {max(1,min($minutes,int(ceil(($now-$since/1000000.0)/60.0))+1))}]
        } else {
            set minutes [expr {max(1,min($minutes,int(ceil(($now-$cutoff/1000000.0)/60.0))))}]
        }
        set readStarted [clock microseconds]
        set metadataMs [expr {($readStarted-$refreshStarted)/1000.0}]
        if {$persistent} {
            set report [::_ns_stats.mem.historyRead $minutes]
        } else {
            set report [::_ns_stats.utilization.readHistory $minutes ::_ns_stats.mem.historyPoint ::_ns_stats.mem.historyQuery]
            dict set report reader memory
        }
        set readMs [expr {([clock microseconds]-$readStarted)/1000.0}]
        if {![dict get $report available]} {error [dict get $report reason]}
        set merged {}
        foreach point [concat $points [dict get $report points]] {
            set stamp [dict get $point timestamp]
            if {$stamp >= $cutoff && $stamp < $now*1000000} {dict set merged $stamp $point}
        }
        set points {}
        foreach stamp [lsort -integer [dict keys $merged]] {lappend points [dict get $merged $stamp]}
        set capped [expr {[llength $points]>10000}]
        if {$capped} {set points [lrange $points end-9999 end]}
        set svgStarted [clock microseconds]
        set html [::_ns_stats.mem.historySvg $points]
        set svgMs [expr {([clock microseconds]-$svgStarted)/1000.0}]
        set timing [dict create reader [dict get $report reader] metadataMs $metadataMs readMs $readMs svgMs $svgMs \
            totalMs [expr {([clock microseconds]-$refreshStarted)/1000.0}] \
            samples [llength $points] pages [dict get $report pages]]
        if {[dict exists $report timings]} {dict set timing query [dict get $report timings]}
        if {[dict exists $report legacyDecoded]} {dict set timing legacyDecoded [dict get $report legacyDecoded]}
        ns_log notice "nsstats: allocator history refresh: $timing"
        if {$html ne ""} {
            set source [expr {$persistent ? "Persistent history, up to the last 24 hours" : "In-memory history, up to the last 15 minutes"}]
            append html "<p>$source. [llength $points] retained samples; 120 display buckets, preserving peaks and breaks. Lines break at missing values, sampling gaps and process restarts."
            if {$capped || [dict get $report truncated]} {append html " The 10,000-sample display limit was reached."}
            if {$incremental} {
                append html " Earlier samples are reused from the chart cache; full-window coverage is not re-certified by incremental refresh."
            } elseif {![::_ns_stats.dictGetDef $report coverage complete false]} {
                append html " Requested-window coverage is incomplete; this chart shows available samples only."
            }
            append html " This is allocator memory, not OS resident memory or a diagnosis of a leak.</p>"
            append html "<details><summary>Chart generation timings</summary><pre>[ns_quotehtml $timing]</pre></details>"
        }
        nsv_set _ns_stats $key:points [dict create identity $identity values $points]
        set cache [dict create identity $identity updated [clock seconds] html $html]
        nsv_set _ns_stats $key $cache
        return [::_ns_stats.mem.historyCached $cache]
    } on error {message options} {
        if {$cache eq ""} {set cache [dict create identity $identity]}
        set message "Allocator history refresh delayed: $message."
        dict set cache retryAfter [expr {[clock seconds]+15}]
        dict set cache error $message
        nsv_set _ns_stats $key $cache
        return [::_ns_stats.mem.historyCached $cache $message]
    } finally {
        nsv_incr _ns_stats $key:busy -1
    }
}

proc ::_ns_stats.mem.tcl {} {
    #
    # The following works just on Linux. The output is optional.
    #
    set meminfo [::_ns_stats.memsizes [ns_info pid] 1]
    set html    [::_ns_stats.header Memory]
    append html [::_ns_stats.mem.history]
    try {
        ns_info meminfo {*}[expr {[ns_queryget release 0] ? "-release" : ""}]
    } on ok {result} {
        #ns_log notice "meminfo result <$result>"
        if {[dict exists $result stats] && [dict get $result stats] ne ""} {
            append html [ns_trim -delimiter | [subst {
                |<h3>Memory Statistics from TCMalloc (Google Performance Tools)</h3>
                |<p>
                |<strong>Version:</strong> [dict get $result version]<br>
                |<strong>Loaded library:</strong> [dict get $result preload]<br>
                |<strong>Documentation:</strong>
                |<a href="https://github.com/google/tcmalloc/blob/master/docs/stats.md">Understanding Malloc Stats</a></br>
                |<strong>Memory reported from OS:</strong> $meminfo<br>
                |<pre>[dict get $result stats]</pre>
            }]] [::_ns_stats.footer]
            return $html
        }
    } on error {errorMsg} {
        # ignore
    }

    set talloc 0
    set trequest 0
    set tused 0
    set tlocks 0
    set twaits 0
    set tfree 0
    set tops 0
    set ov 0
    set op 0
    set av 0

    if {[info commands ::dict] ne ""} {
        set trans [dict create]
        foreach thread [ns_info threads] {
            dict set trans thread0x[lindex $thread 2] [lindex $thread 0]
        }
    }
    append html \
        "\n<p><strong>Memory reported from OS:</strong> $meminfo</p>" \
        "\n<h4>Memory reported from the Tcl memory allocator:</h4>" \
        "<table border='0' cellpadding='0' cellspacing='0'>\n<tr><td valign=middle>\n"

    foreach p [lsort [ns_info pools]] {
        append html "\
        <b>[lindex $p 0]:</b>
        <b>[::_ns_stats.dictGetDef $trans [lindex $p 0] {}]</b>
        <br><br>
        <table border=0 cellpadding=0 cellspacing=1 bgcolor=#cccccc width='100%'>
        <tr>
            <td valign=middle align=center>
            <table border=0 cellpadding=4 cellspacing=1 width='100%'>
            <tr>
                <td valign=middle bgcolor=#999999><font color=#ffffff>Block Size</font></td>
                <td valign=middle bgcolor=#999999><font color=#ffffff>Frees</font></td>
                <td valign=middle bgcolor=#999999><font color=#ffffff>Gets</font></td>
                <td valign=middle bgcolor=#999999><font color=#ffffff>Puts</font></td>
                <td valign=middle bgcolor=#999999><font color=#ffffff>Bytes Req</font></td>
                <td valign=middle bgcolor=#999999><font color=#ffffff>Bytes Used</font></td>
                <td valign=middle bgcolor=#999999><font color=#ffffff>Overhead</font></td>
                <td valign=middle bgcolor=#999999><font color=#ffffff>Locks</font></td>
                <td valign=middle bgcolor=#999999><font color=#ffffff>Lock Waits</font></td>
             </tr>"

        foreach b [lrange $p 1 end] {
            set bs [lindex $b 0]
            set nf [lindex $b 1]
            set ng [lindex $b 2]
            set np [lindex $b 3]
            set nr [lindex $b 4]
            set nu [expr {$ng - $np}]
            set na [expr {$nu * $bs}]

            incr tops [expr {$ng + $np}]
            incr tlocks [lindex $b 5]
            incr twaits [lindex $b 6]
            incr tfree [expr {$bs * $nf}]
            incr talloc $na
            incr trequest $nr
            incr tused $nu

            if {$nr != 0} {
                set ov [expr {$na - $nr}]
                set op [format %4.2f%% [expr {double($ov) * 100 / $nr}]]
            } else {
                set ov "N/A"
                set op "N/A"
            }

            append html "<tr>"

            foreach e [linsert [lreplace $b 4 4] 4 $nr $na $op] {
                append html "<td bgcolor=#ffffff>$e</td>"
            }

            append html "</tr>"
        }

        append html "\
                </table>
                </td>
        </tr>
        </table>
        <br>"
    }

    if { $trequest > 0 } {
        set ov [expr {$talloc - $trequest}]
        set op [format %4.2f [expr {double($ov) * 100 / $trequest}]]
    }
    if { $tops > 0 } {
        set av [format %4.2f [expr {double(100) - (double($tlocks) * 100) / $tops}]]
    }
    if { $tlocks > 0 } {
        set wr [format %4.2f [expr {double($twaits) / $tlocks}]]
    } else {
        set wr N/A
    }

    append html "\
        </td>
    </tr>
    <tr>
        <td valign=middle>
        <b>Totals:</b><br><br>
        <table>
            <tr><td>Bytes Requested:</td><td>$trequest</td></tr>
            <tr><td>Bytes Free:</td><td>$tfree</td></tr>
            <tr><td>Bytes Allocated:</td><td>$talloc</td></tr>
            <tr><td>Bytes Wasted:</td><td>$ov</td></tr>
            <tr><td>Byte Overhead:</td><td>${op}%</td></tr>
            <tr><td>Locks:</td><td>$tlocks</td></tr>
            <tr><td>Lock Waits:</td><td>$twaits</td></tr>
            <tr><td>Lock Wait Ratio:</td><td>${wr}%</td></tr>
            <tr><td>Gets/Puts:</td><td>${tops}</td></tr>
            <tr><td>Lock Avoidance:</td><td>${av}%</td></tr>
        </table>
        </td>
    </tr>
    </table>"

    append html [::_ns_stats.footer]

    return $html
}

proc ::_ns_stats.process.table {values} {
    set html [subst {
        <table class="data-table w3-table w3-hoverable">
        <tr>
        <th valign="middle">Key</th>
        <th valign="middle">Value</th>
        </tr>
    }]
    foreach {key value} $values {
        append html [subst {
            <tr>
            <td class='coltitle'>$key</td>
            <td class='colvalue'>$value</td>
            </tr>}]
    }

    append html "</table>"
    return $html
}

proc ::_ns_stats.process.dbversion.format {type version} {
    if {![string is entier -strict $version] || $version <= 0} {
        return unknown
    }
    switch -- [string tolower $type] {
        sqlite {
            return [format %d.%d.%d [expr {$version / 1000000}] \
                        [expr {$version / 1000 % 1000}] [expr {$version % 1000}]]
        }
        postgresql - postgres {
            if {$version >= 100000} {
                return [format %d.%d [expr {$version / 10000}] [expr {$version % 10000}]]
            }
        }
    }
    # Other drivers and older PostgreSQL versions may use different encodings.
    return $version
}

proc ::_ns_stats.process.dbversion {pool} {
    set result "Versions: unavailable"
    # Pass a self-contained reader to worker interpreters, where this page's
    # helper procedures are not necessarily defined.
    set reader {{pool} {
        set handle ""
        try {
            # Zero means unlimited waiting in ns_db.
            set handle [ns_db gethandle -timeout 1ms -- $pool]
            if {$handle eq ""} {
                error "no free database handle within 1ms"
            }
            return [ns_db info $handle]
        } on error {message} {
            return [dict create _nsstats_error $message]
        } finally {
            if {$handle ne ""} {
                catch {ns_db releasehandle $handle}
            }
        }
    }}
    try {
        if {![catch {ns_db currenthandles} current] && [dict exists $current $pool]} {
            # nsdb limits pool acquisition per thread. Use another thread's
            # own connection; querying a request-owned handle could overwrite
            # its pending result (notably with nsdbpg's version query).
            set thread [ns_thread create [list apply $reader $pool]]
            set info [ns_thread wait $thread]
        } else {
            set info [apply $reader $pool]
        }
        if {[dict exists $info _nsstats_error]} {
            set result "Versions: unavailable ([ns_quotehtml [dict get $info _nsstats_error]])"
        } elseif {[dict exists $info clientversion] && [dict exists $info serverversion]} {
            set type [dict get $info type]
            set client [::_ns_stats.process.dbversion.format $type [dict get $info clientversion]]
            set server [::_ns_stats.process.dbversion.format $type [dict get $info serverversion]]
            set result "[ns_quotehtml $type]: client [ns_quotehtml $client], database [ns_quotehtml $server]"
        } else {
            set result "Versions: unavailable (driver does not expose version information)"
        }
    } on error {message} {
        # Thread creation and unsupported interfaces must not break the page.
        set result "Versions: unavailable ([ns_quotehtml $message])"
    }
    return $result
}

# The reload implementation and its entry point are installed together. Older
# releases have ns_module metadata/name support without the reload operation.
proc ::_ns_stats.process.reloadAvailable {} {
    expr {[info commands ::ns_module] ne ""
        && [info commands ::_ns_module_reload] ne ""
        && [info commands ::ns_crypto::randombytes] ne ""}
}

# Reload only Tcl definitions belonging to a module registered in this server.
# Mixed modules use the same loader as Tcl-only modules; their binary stays loaded.
proc ::_ns_stats.process.reloadable {module} {
    if {[catch {ns_ictl getmodules} modules] || $module ni $modules
        || [catch {::_ns_module_files $module} files]} {
        return 0
    }
    return [expr {[llength $files] > 0}]
}

proc ::_ns_stats.process.reloadKey {} {
    return module-reload:[::_ns_stats.identity]
}

proc ::_ns_stats.process.reloadToken {} {
    set key [::_ns_stats.process.reloadKey]
    if {![nsv_exists _ns_stats $key]} {
        nsv_set _ns_stats $key [ns_crypto::randombytes -encoding hex 32]
    }
    return [nsv_get _ns_stats $key]
}

proc ::_ns_stats.process.reloadButton {module {page process}} {
    set name [ns_quotehtml $module]
    set token [::_ns_stats.process.reloadToken]
    return "<form class='ns-module-reload' method='post' action='[ns_quotehtml [ns_conn url]]'>\
        <input type='hidden' name='@page' value='[ns_quotehtml $page]'>\
        <input type='hidden' name='moduleReloadAction' value='reload'>\
        <input type='hidden' name='moduleReloadName' value='$name'>\
        <input type='hidden' name='moduleReloadToken' value='[ns_quotehtml $token]'>\
        <button type='submit' title='Reload Tcl code for $name (compiled code unchanged)' aria-label='Reload Tcl code for module $name (compiled code unchanged)'>\
        <svg width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' aria-hidden='true'>\
        <path d='M20 7v5h-5M4 17v-5h5M6 7a7 7 0 0 1 12-1l2 6M18 17a7 7 0 0 1-12 1l-2-6'/></svg></button></form>"
}

# Only called by process after the ordinary nsstats authorization check.
proc ::_ns_stats.process.reload {{page process}} {
    ns_set iupdate [ns_conn outputheaders] Cache-Control no-store
    if {[ns_conn method] ne "POST"} {
        ns_return 405 text/plain "Use POST to reload module Tcl code."
        return ""
    }
    set key [::_ns_stats.process.reloadKey]
    set token [ns_queryget moduleReloadToken {}]
    if {$token eq "" || ![nsv_exists _ns_stats $key] || $token ne [nsv_get _ns_stats $key]} {
        ns_return 403 text/plain "Invalid module reload token; reload the process page."
        return ""
    }
    if {![::_ns_stats.process.reloadAvailable]} {
        ns_return 501 text/plain "Module Tcl code reload is unavailable on this NaviServer."
        return ""
    }
    set module [ns_queryget moduleReloadName {}]
    if {[ns_queryget moduleReloadAction {}] ne "reload"} {
        ns_return 400 text/plain "Invalid module reload action."
        return ""
    }
    if {[catch {ns_server -server [ns_info server] modules} modules]
        || ![dict exists $modules $module]
        || ![::_ns_stats.process.reloadable $module]} {
        ns_return 400 text/plain "Select a loaded module with Tcl definitions in the current virtual server."
        return ""
    }
    if {[catch {ns_module reload $module} files]} {
        ns_return 409 text/plain "Module Tcl code reload failed: $files"
        return ""
    }
    nsv_set _ns_stats $key:result [list $module [llength $files]]
    ns_set iupdate [ns_conn outputheaders] Location "[ns_conn url]?@page=$page"
    ns_return 303 text/plain "Module Tcl code reloaded."
    return ""
}

proc ::_ns_stats.process.reloadNotice {} {
    if {![::_ns_stats.process.reloadAvailable]} {return ""}
    set key [::_ns_stats.process.reloadKey]:result
    if {![nsv_exists _ns_stats $key]} {return ""}
    lassign [nsv_get _ns_stats $key] module count
    nsv_unset _ns_stats $key
    return "<p role='status'>Reloaded Tcl code for module [ns_quotehtml $module] ($count affected Tcl files). Compiled code is unchanged. The updated blueprint is published; active interpreters adopt it through normal propagation.</p>"
}

proc ::_ns_stats.process.modules {server} {
    if {[catch {ns_server -server $server modules} modules]} {
        # Older NaviServer releases only provide the registered module names.
        if {[catch {ns_ictl getmodules -server $server} names]} {
            return {}
        }
        return [list "Loaded Modules" [ns_quotehtml [lsort $names]]]
    }
    set poolDrivers {}
    if {![catch {::_ns_stats.process.dbpoolnames $server} pools]} {
        foreach pool $pools {
            set driver [ns_config ns/db/pool/$pool driver ""]
            if {$driver ne ""} {lappend poolDrivers $driver}
        }
    }
    set reloadAvailable [::_ns_stats.process.reloadAvailable]
    set columns {
        {} Module name Name type Type version Version tag Tag scope Scope
        file File
    }
    set html {<div class="w3-responsive"><table class="data-table modules w3-table w3-hoverable"><thead><tr>}
    foreach {key title} $columns {
        append html "<th>[ns_quotehtml $title]</th>"
    }
    append html {</tr></thead><tbody>}
    foreach module [lsort [dict keys $modules]] {
        set info [dict get $modules $module]
        if {[dict exists $info type] && [dict get $info type] eq "db-driver"} {
            if {![dict exists $info driver] || [dict get $info driver] ni $poolDrivers} {
                continue
            }
        }
        append html <tr>
        foreach {key title} $columns {
            set value ""
            if {$key eq ""} {
                set value $module
            } elseif {[dict exists $info $key]} {
                set value [dict get $info $key]
            }
            set cell [ns_quotehtml $value]
            if {$key eq "" && $reloadAvailable && $server eq [ns_info server]
                && [::_ns_stats.process.reloadable $module]} {
                append cell [::_ns_stats.process.reloadButton $module]
            }
            if {$key eq "tag" && [dict exists $info name]} {
                # Metadata has no repository URL; only link known modules.
                set name [dict get $info name]
                set repository ""
                switch -- $name {
                    quic - nssock - nsssl - nscp - nsdb - nslog - nsproxy - nscgi - nsperm {
                        set repository naviserver
                    }
                    nsdbpg - nsdbsqlite - nsdns - nssmtpd {
                        set repository $name
                    }
                }
                set hash ""
                if {[regexp -- {-[0-9]+-g([0-9a-f]+)(?:[+]|-dirty)?$} $value . hash]
                    || [regexp {^([0-9a-f]{7,40})(?:[+]|-dirty)?$} $value . hash]} {
                    if {$repository ne ""} {
                        set cell "<a href='https://github.com/naviserver-project/$repository/commit/$hash'>$cell</a>"
                    }
                }
            }
            append html "<td>$cell</td>"
        }
        append html </tr>
    }
    append html {</tbody></table></div>}
    return [list "Loaded Modules" $html]
}

proc ::_ns_stats.process.dbpoolnames {server} {
    if {$server eq ""} {set server [ns_info server]}
    if {$server eq [ns_info server]} {
        set pools [ns_db pools]
    } else {
        # ns_db pools has no cross-server interface. Match dbinit's rules:
        # first configured value, wildcard or comma-separated known pools.
        set configured [ns_config ns/server/$server/db pools ""]
        set known [dict keys [ns_db stats]]
        set pools {}
        if {$configured eq "*"} {
            set pools $known
        } else {
            foreach pool [split $configured ,] {
                if {$pool in $known} {lappend pools $pool}
            }
        }
    }
    return [lsort -unique $pools]
}

proc ::_ns_stats.process.dbaccess {{server ""}} {
    if {$server eq ""} {set server [ns_info server]}
    if {[catch {::_ns_stats.process.dbpoolnames $server} pools]} {return unavailable}
    if {[llength $pools] == 0} {return "{}"}
    return [join [lmap pool $pools {ns_quotehtml $pool}] {, }]
}

# Report local SQLite files without opening a database connection. The driver
# publishes the resolved datasource in the configuration; do not guess paths
# for older drivers or interpret SQLite URI and in-memory datasources as files.
proc ::_ns_stats.process.dbfilesize {pool} {
    set section ns/db/pool/$pool
    set driver [ns_config $section driver ""]
    set library [ns_config ns/db/drivers $driver ""]
    if {[file rootname [file tail $library]] ne "nsdbsqlite"} {
        return ""
    }
    set path [ns_config $section datasource ""]
    if {$path eq ":memory:"} {
        return "Database size: in-memory"
    }
    if {$path eq "" || [string match "file:*" $path] || [file pathtype $path] ne "absolute"} {
        return "Database file size: unavailable"
    }
    if {[catch {file size $path} bytes] || ![file isfile $path]} {
        return "Database file size: unavailable"
    }
    set result "Database file: [::_ns_stats.hr $bytes]B"
    foreach {suffix label} {-wal WAL -shm {shared memory}} {
        set auxiliary $path$suffix
        if {[file isfile $auxiliary] && ![catch {file size $auxiliary} bytes]} {
            append result ", $label: [::_ns_stats.hr $bytes]B"
        }
    }
    return $result
}

proc ::_ns_stats.process.dbpools {} {
    set lines ""
    if {![catch {set poolStats [ns_db stats]}]} {
        foreach {pool stats} $poolStats {
            set gethandles [dict get $stats gethandles]
            if {$gethandles > 0} {
                set avgWaitTime [expr {[dict get $stats waittime] / $gethandles}]
                lappend stats avgwaittime $avgWaitTime
            }
            set statements [dict get $stats statements]
            if {$statements > 0} {
                set avgSQLTime [expr {[dict get $stats sqltime] / $statements}]
                lappend stats avgsqltime $avgSQLTime
            }
            set stats [::_ns_stats.pretty {statements gethandles {avgwaittime s} {avgsqltime s}} $stats %.1f]
            set versions [::_ns_stats.process.dbversion $pool]
            set size [::_ns_stats.process.dbfilesize $pool]
            if {$size ne ""} {
                append versions "<br>$size"
            }
            lappend lines "<tr><td class='subtitle'>[ns_quotehtml $pool]:</td><td width='100%'>$versions<br>$stats</td></tr>"
        }
    }
    return $lines
}
proc ::_ns_stats.process.callbacks {} {
    set lines ""
    foreach {entry} [ns_info callbacks] {
        lassign $entry type call
        set args [lrange $entry 2 end]
        lappend lines "<tr><td class='subtitle'>$type:</td><td>$call</td><td width='100%'>$args</td>"
    }
    return $lines
}

proc ::_ns_stats.redirect {url} {
    ns_log notice "RETURN REDIRECT <$url>"
    ns_returnredirect $url
    ns_log notice "RETURN REDIRECT <$url> DONE"
    if {[info commands ad_script_abort] ne ""} {
        #
        # Avoid automatic triggering of ADP interpretation when
        # running under OpenACS.
        #
        ad_script_abort
    }
}


proc ::_ns_stats.log.levels {} {
    set toggle [ns_queryget toggle ""]
    if {$toggle ne ""} {
        set old [ns_logctl severity $toggle]
        ns_logctl severity $toggle [expr {! $old}]
        ::_ns_stats.redirect [ns_conn url]?@page=[ns_queryget @page]
        return
    }
    set values {}
    set dict {1 on 0 off}
    foreach s [lsort [ns_logctl severities]] {
        set label [dict get $dict [ns_logctl severity $s]]
        lappend values $s "<a href='[ns_conn url]?@page=[ns_queryget @page]&toggle=$s'>$label</a>"
    }
    append html \
        [::_ns_stats.header "Log Severity States"] \
        "<p>The following table shows the log severities along with their activation states:<p>\n" \
        [::_ns_stats.process.table $values] \
        [::_ns_stats.footer]
    return $html
}

proc ::_ns_stats.process.format_duration {duration nowms startTime} {
    return [::_ns_stats.hr $duration]s
}

proc ::_ns_stats.process.running_scheds {} {
    set running [lmap j [ns_info scheduled] {
        if {![::_ns_stats.isThreadRunning [lindex $j 1]]} continue; set j
    }]

    set results {}
    set now     [clock seconds]
    set nowms   [clock milliseconds]
    if {[llength $running] > 0} {
        ns_log notice "running $running"
    }

    foreach s $running {
        set id          [lindex $s 0]
        set flags       [lindex $s 1]
        set startTime   [lindex $s 5]
        set proc        [lindex $s 7]
        set arg         [lrange $s 8 end]
        set startFmt    [clock format [expr {int($startTime)}] -format {%H:%M:%S}]
        set duration    [expr {$now - $startTime}]
        set durationFmt [::_ns_stats.process.format_duration $duration $nowms $startTime]
        lappend results "$id: start $startFmt - $proc $arg - duration $durationFmt"
    }
    return $results
}

proc ::_ns_stats.process.running_jobs {} {
    set results {}

    foreach ql [ns_job queuelist] {
        set numrunning [dict get $ql numrunning]
        if {$numrunning > 0} {
            set now   [clock seconds]
            set nowms [clock milliseconds]
            set queue [dict get $ql name]
            foreach jobinfo [ns_job joblist $queue] {
                set state [dict get $jobinfo state]
                if {$state eq "running"} {
                    set startTime   [dict get $jobinfo starttime]
                    set id          [dict get $jobinfo id]
                    set script      [dict get $jobinfo script]
                    set startFmt    [clock format [expr {int($startTime)}] -format {%H:%M:%S}]
                    set duration    [expr {$now - $startTime}]
                    set durationFmt [::_ns_stats.process.format_duration $duration $nowms $startTime]
                    lappend results "$queue $id: start $startFmt - $script - duration $durationFmt"
                }
            }
        }
    }
    return $results
}

proc ::_ns_stats.memsizes {pid {pretty 0}} {
  #
  # return a dict of memory sizes of pid in number of 1K blocks
  #
  lassign {0 0 0} uss rss vsize
  if {[file readable /proc/$pid/statm]} {
    #
    # result in pages, typically 4K
    #
    set F [open /proc/$pid/statm]; set c [read $F]; close $F
    lassign $c vsize rss shared
    #set uss   [format %.2f [expr {($rss-$shared) * 4}]]
    set rss   [format %.2f [expr {$rss           * 4}]]
    set vsize [format %.2f [expr {$vsize         * 4}]]
  }
  if {$rss == 0} {
    set sizes [exec -ignorestderr /bin/ps -o vsz,rss $pid]
    set vsize [lindex $sizes end-1]
    set rss   [lindex $sizes end]
  }
  if {$pretty} {
      set rss [::_ns_stats.hr [expr {$rss*1024}]]B
      set vsize [::_ns_stats.hr [expr {$vsize*1024}]]B
  }
  return [list rss $rss vsize $vsize]
}

proc ::_ns_stats.lsof {pid} {
    try {
        foreach cmd {/bin/lsof /usr/bin/lsof /usr/sbin/lsof} {
            if {[file executable $cmd]} break
        }
        set result [split [exec -ignorestderr -- $cmd -n -P +p [pid] 2>/dev/null] \n]
    } on error {errorMsg} {
        set result {}
    }
    return $result
}

# Tcl packages are inspection-only. Only recorded file identities can be posted
# to the core reload operation, after the usual page authorization and CSRF check.
proc ::_ns_stats.libraries.inventory {module} {
    if {[info commands ::_ns_module_inventory] eq ""} {return {}}
    if {[catch {ns_module files $module} inventory]} {return {}}
    return $inventory
}

proc ::_ns_stats.libraries.matches {filter values} {
    expr {$filter eq "" || [string first [string tolower $filter] [string tolower $values]] >= 0}
}

proc ::_ns_stats.libraries.coreButton {name} {
    set token [::_ns_stats.process.reloadToken]
    return "<form class='ns-module-reload' method='post' action='[ns_quotehtml [ns_conn url]]'>\
        <input type='hidden' name='@page' value='tcl.libraries'>\
        <input type='hidden' name='libraryReloadAction' value='core'>\
        <input type='hidden' name='libraryReloadFile' value='[ns_quotehtml $name]'>\
        <input type='hidden' name='moduleReloadToken' value='[ns_quotehtml $token]'>\
        <button type='submit' aria-label='Reload core Tcl file [ns_quotehtml $name]' title='Reload this Tcl file and its dependent files'>\
        <svg width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' aria-hidden='true'>\
        <path d='M20 7v5h-5M4 17v-5h5M6 7a7 7 0 0 1 12-1l2 6M18 17a7 7 0 0 1-12 1l-2-6'/></svg></button></form>"
}

proc ::_ns_stats.libraries.reload {} {
    ns_set iupdate [ns_conn outputheaders] Cache-Control no-store
    if {[ns_conn method] ne "POST"} {
        ns_return 405 text/plain "Use POST to reload a core Tcl file."
        return ""
    }
    set key [::_ns_stats.process.reloadKey]
    set token [ns_queryget moduleReloadToken {}]
    if {$token eq "" || ![nsv_exists _ns_stats $key] || $token ne [nsv_get _ns_stats $key]} {
        ns_return 403 text/plain "Invalid reload token; reload the Tcl Libraries page."
        return ""
    }
    if {[ns_queryget libraryReloadAction {}] ne "core"} {
        ns_return 400 text/plain "Only managed Tcl files can be reloaded. Package reloading is not supported."
        return ""
    }
    set name [ns_queryget libraryReloadFile {}]
    set inventory [::_ns_stats.libraries.inventory -core]
    if {![::_ns_stats.process.reloadAvailable] || ![dict exists $inventory $name]
        || ![dict get $inventory $name reloadable]} {
        ns_return 400 text/plain "Select a recorded core Tcl file enabled for reload."
        return ""
    }
    if {[catch {ns_module reload -core $name} files]} {
        ns_return 409 text/plain "Core Tcl reload failed: $files"
        return ""
    }
    nsv_set _ns_stats $key:coreResult [list $name [llength $files]]
    ns_set iupdate [ns_conn outputheaders] Location "[ns_conn url]?@page=tcl.libraries"
    ns_return 303 text/plain "Core Tcl file reloaded."
    return ""
}

proc ::_ns_stats.tcl.libraries {} {
    if {[ns_queryget moduleReloadAction {}] ne ""} {
        return [::_ns_stats.process.reload tcl.libraries]
    }
    if {[ns_queryget libraryReloadAction {}] ne ""} {
        return [::_ns_stats.libraries.reload]
    }
    ns_set iupdate [ns_conn outputheaders] Cache-Control no-store
    set filter [ns_queryget tclFilter {}]
    set html [::_ns_stats.header {Tcl Libraries}]
    append html "<p>Files and packages in the current virtual server's interpreter. Reload updates Tcl definitions and dependent files; compiled code is unchanged.</p>"
    append html [::_ns_stats.process.reloadNotice]
    set key [::_ns_stats.process.reloadKey]:coreResult
    if {[nsv_exists _ns_stats $key]} {
        lassign [nsv_get _ns_stats $key] name count
        nsv_unset _ns_stats $key
        append html "<p role='status'>Reloaded core Tcl file [ns_quotehtml $name] ($count affected files). The updated blueprint is published; active interpreters adopt it through normal propagation.</p>"
    }
    append html "<form method='get' action='[ns_quotehtml [ns_conn url]]'><input type='hidden' name='@page' value='tcl.libraries'><label>Filter files and packages <input name='tclFilter' value='[ns_quotehtml $filter]'></label> <button type='submit'>Filter</button></form>"
    set core [::_ns_stats.libraries.inventory -core]
    append html "<h2>Core Tcl files</h2>"
    if {[info commands ::_ns_module_inventory] eq ""} {
        append html "<p>File inventory requires a NaviServer version supporting ns_module files.</p>"
    }
    set active {}; set startup {}
    dict for {name record} $core {
        set path [dict get $record path]
        if {![::_ns_stats.libraries.matches $filter [list $name $path]]} {continue}
        set action {Startup only}
        if {[dict get $record reloadable]} {
            if {[::_ns_stats.process.reloadAvailable]} {
                set action [::_ns_stats.libraries.coreButton $name]
            } else {set action {Reload unavailable}}
        }
        set row "<tr><td><code>[ns_quotehtml $name]</code></td><td class='ns-library-files'><code>[ns_quotehtml $path]</code></td><td class='ns-library-action'>$action</td></tr>"
        if {[dict get $record reloadable]} {append active $row} else {append startup $row}
    }
    set head {<div class='w3-responsive'><table class='data-table ns-tcl-library w3-table'><thead><tr><th>File</th><th class='ns-library-files'>Effective source</th><th class='ns-library-action'>Reload</th></tr></thead><tbody>}
    append html $head $active {</tbody></table></div>}
    append html "<details><summary>Startup-only core files</summary><p>Reload is not enabled for these files because they initialize infrastructure, configuration or resources.</p>$head$startup</tbody></table></div></details>"
    append html "<h2>Module Tcl files</h2>"
    append html {<div class='w3-responsive'><table class='data-table ns-tcl-library w3-table'><thead><tr><th>Module</th><th class='ns-library-version'>Version</th><th class='ns-library-files'>Files</th><th class='ns-library-action'>Reload</th></tr></thead><tbody>}
    if {![catch {ns_server -server [ns_info server] modules} modules]} {
        foreach module [lsort [dict keys $modules]] {
            set info [dict get $modules $module]
            set version [expr {[dict exists $info version] ? [dict get $info version] : {}}]
            set versionLabel [expr {$version eq {} ? "Not reported" : $version}]
            set entries {}
            dict for {name record} [::_ns_stats.libraries.inventory $module] {
                set path [dict get $record path]
                if {![::_ns_stats.libraries.matches $filter [list $module $version $name $path]]} {continue}
                append entries "<div><code>[ns_quotehtml $path]</code>"
                if {![dict get $record reloadable]} {append entries { (startup only)}}
                append entries {</div>}
            }
            if {$entries eq ""} {continue}
            set action {Startup only}
            if {[::_ns_stats.process.reloadAvailable] && [::_ns_stats.process.reloadable $module]} {
                set action [::_ns_stats.process.reloadButton $module tcl.libraries]
            }
            append html "<tr><td>[ns_quotehtml $module]</td><td class='ns-library-version'>[ns_quotehtml $versionLabel]</td><td class='ns-library-files'>$entries</td><td class='ns-library-action'>$action</td></tr>"
        }
    }
    append html {</tbody></table></div>}
    append html {<h2>Loaded Tcl packages — inspection only</h2><p><strong>Tcl packages are listed for information only. Package reloading is not supported.</strong> Only packages with a provided version in this interpreter are listed; merely available packages are omitted.</p><div class='w3-responsive'><table class='data-table w3-table'><thead><tr><th>Package</th><th>Provided version</th></tr></thead><tbody>}
    foreach name [lsort -dictionary [package names]] {
        set version [package provide $name]
        if {$version eq "" || ![::_ns_stats.libraries.matches $filter [list $name $version]]} {continue}
        append html "<tr><td>[ns_quotehtml $name]</td><td>[ns_quotehtml $version]</td></tr>"
    }
    append html {</tbody></table></div>}
    append html [::_ns_stats.footer]
    return $html
}

proc ::_ns_stats.process {} {
    if {[ns_queryget moduleReloadAction {}] ne ""} {
        return [::_ns_stats.process.reload]
    }
    set runtimeSnapshot [::_ns_stats.runtimeSnapshot]
    if {[info commands ns_driver] ne ""} {
        #
        # Get certificates to report expire dates (assumes that the
        # command "openssl" is on the search path)
        #
        set certInfo {}
        set certificateLabel ""
        set driverInfo {}

        foreach entry [ns_driver info] {
            dict unset entry extraheaders
            lappend driverInfo [::_ns_stats.pretty {recvbufsize sendbufsize} $entry %.2f]

            set module [dict get $entry module]
            if {[dict get $entry type] eq "nsssl"} {
                #
                # Use ns_certclt when available. This cmd includes as
                # well the certificates of mass virtual hosting. No
                # external programs are necessary.
                #
                if {[info commands ns_certctl] ne ""} {
                    lappend certInfo [join [lsort -unique [ns_certctl list]] <br>]
                    set certificateLabel "Loaded Certificates"
                } else {
                    set server [dict get $entry server]
                    if {$server ne ""} {
                        set certfile [ns_config ns/server/$server/module/$module certificate]
                    } else {
                        set certfile [ns_config ns/module/$module certificate]
                    }
                    if {![info exists processed($certfile)]} {
                        set notAfter [exec openssl x509 -enddate -noout -in $certfile]
                        regexp {notAfter=(.*)$} $notAfter . date
                        set days [expr {([clock scan $date] - [clock seconds])/(60*60*24.0)}]
                        lappend certInfo "Certificate $certfile will expire in [format %.1f $days] days"
                        set processed($certfile) 1
                    }
                    set certificateLabel "Configured Certificates"
                }
            }
        }
        lappend driverInfo {}
        #
        # Combine driver stats with certificate infos
        #
        foreach tuple [ns_driver stats] {
            lappend driverInfo [::_ns_stats.pretty {received spooled partial} $tuple %.0f]
        }
        set driverInfo [list "Driver Info" [join $driverInfo <br>]]

    } else {
        set driverInfo ""
    }
    set certInfo [expr {$certificateLabel ne ""
                        ? [list $certificateLabel [join $certInfo <br>\n]]
                        : ""}]

    set tag [ns_info tag]
    if {[regexp {[-][0-9]+[-]g([0-9a-f]+)[+]?} $tag . hash]} {
        set tag "<a href='https://github.com/naviserver-project/naviserver/commit/$hash'>$tag</a>"
    } elseif {[regexp {([0-9a-f]+)[ +]} $tag . hash]} {
        set tag "<a href='https://bitbucket.org/naviserver/naviserver/commits/?search=$hash'>$tag</a>"
    }
    if {[regexp {([0-9a-f]+)[ +]} $tag . hash]} {
        set tag "<a href='https://bitbucket.org/naviserver/naviserver/commits/?search=$hash'>$tag</a>"
    }
    set version_info "$::tcl_platform(machine), $::tcl_platform(os) $::tcl_platform(osVersion)"
    try {
        set connect_info [ns_conn details]
        if {$connect_info ne ""} {
            append version_info ", connected via [ns_conn details]"
        }
        append version_info " from client [ns_conn peeraddr]"
    } on error {errorMsg} {
        ns_log notice "This version of NaviServer doesn't support ns_conn details: $errorMsg"
    }

    set proxyItems ""
    if {[info commands ns_proxy] ne ""} {
        #
        # Use catch for the time being to handle forward
        # compatibility (when no ns_proxy stats are available)
        #
        set pool ""
        if {[catch {
            foreach pool [lsort [ns_proxy pools]] {
                #
                # Get configure values and statistics
                #
                set configValues [ns_proxy configure $pool]
                set rawstats [ns_proxy stats $pool]
                set requests [dict get $rawstats requests]
                if {$requests > 0} {
                    set avgruntime [expr {[dict get $rawstats runtime] / $requests}]
                    lappend rawstats avgruntime $avgruntime
                }
                set resultstats [::_ns_stats.pretty {requests {runtime s} {avgruntime s}} $rawstats %.2f]
                set active [join [lmap l [ns_proxy active $pool] {ns_quotehtml $l}] <br>]
                try {
                    set pidinfos {}
                    foreach pid [ns_proxy pids $pool] {
                        append pidinfos "$pid [list [::_ns_stats.memsizes $pid 1]] "
                    }
                    set pidsrow "<tr><td class='subtitle'>Pids:</td><td class='colvalue'>$pidinfos</td></tr>"
                } on error {errorMsg} {
                    set pidsrow ""
                }
                try {
                    set workerinfos {}
                    set nrworkers [llength [ns_proxy workers $pool]]
                    set workerrow "<tr><td class='subtitle'>Workers:</td><td class='colvalue'><a href='?@page=proxy-workers&pool=$pool''>$nrworkers workers</a></td></tr>"
                } on error {errorMsg} {
                    set workerrow ""
                }
                set item ""
                append item \
                    "<tr><td class='subtitle'>Params:</td><td class='colvalue'>$configValues</td></tr>" \
                    "<tr><td class='subtitle'>Stats:</td><td class='colvalue'>$resultstats</td></tr>" \
                    $pidsrow \
                    $workerrow \
                    "<tr><td class='subtitle'>Active:</td><td class='colvalue'>$active</td></tr>"
                lappend proxyItems "nsproxy '$pool'" "<table>$item</table>"
            }

        } errorMsg]} {
            lappend proxyItems "nsproxy '$pool'" "<table>$errorMsg</table>"
        }
    }

    try {
        ns_http taskthreads
    } on ok {taskthreadinfo} {
    } on error {errorMsg} {
        set taskthreadinfo ""
    }

    try {
        ns_info buildinfo
    } on ok {buildinfo} {
    } on error {errorMsg} {
        set buildinfo ""
    }

    set processInfo [::_ns_stats.memsizes [ns_info pid] 1]
    if {$::tcl_platform(platform) ne "windows"} {
        set t [clock milliseconds]; set F [open {|cat > /dev/null} w]; puts $F "timing-pipe-open+puts"; close $F
        dict set processInfo fork-time [expr {[clock milliseconds] - $t}]ms
    }

    set values [list \
                    Version              "[ns_info patchlevel] (tag $tag) $buildinfo" \
                    "Build Date"          [ns_info builddate] \
                    Host                 "[ns_info hostname] ([ns_info address], Tcl $::tcl_patchLevel, $version_info)" \
                    "Boot Time"           [clock format [ns_info boottime] -format %c] \
                    Uptime                [::_ns_stats.fmtSeconds [ns_info uptime]] \
                    Process              "[ns_info pid] [ns_info nsd] [list $processInfo]" \
                    "Open Files"          "<a href='?@page=list-lsof'>[llength [::_ns_stats.lsof [ns_info pid]]]</a>" \
                    Home                  [ns_info home] \
                    Configuration         [ns_info config] \
                    "System Log"          [ns_info log] \
                    "Log Statistics"      [::_ns_stats.pretty {Notice Warning Debug(sql)} [ns_logctl stats] %.0f] \
                    Servers               [join [lmap s [ns_info servers] {string cat "<a href='#$s'>$s</a>: [ns_config ns/servers $s]"}] <br>] \
                    {*}${driverInfo} \
                    {*}${certInfo} \
                    DB-Pools             "<table>[join [::_ns_stats.process.dbpools]]</table>" \
                    Callbacks            "<table>[join [::_ns_stats.process.callbacks]]</table>" \
                    {*}$proxyItems \
                    "Task Threads"        [join $taskthreadinfo <br>] \
                    "Socket Callbacks"    [join [ns_info sockcallbacks] <br>] \
                    "<a href='[::_ns_stats.navigation.url background.sched]'>Running Scheduled Procs (repeated)</a>" [join [::_ns_stats.process.running_scheds] <br>] \
                    "<a href='[::_ns_stats.navigation.url background.jobs]'>Running Jobs</a>" [join [::_ns_stats.process.running_jobs] <br>] \
                   ]

    set html [::_ns_stats.header Process]
    append html "<p><a href='[::_ns_stats.navigation.url tcl.libraries]'>Tcl libraries and loaded packages</a></p>"
    append html [::_ns_stats.process.reloadNotice]
    append html [::_ns_stats.utilization.sourcePanel [dict create coreSnapshot $runtimeSnapshot] process]
    append html [::_ns_stats.process.table $values]

    foreach s [ns_info servers] {
        set requests ""; set addresses ""; set writerThreads ""; set spoolerThreads ""
        foreach driver [ns_driver names] {
            set section [ns_driversection -driver $driver -server $s]
            if {$section eq ""} continue
            set addr [ns_config_get_all ns/module/$driver/servers $s]
            if {$addr ne ""} {
                lappend addresses "$driver: $addr"
                lappend writerThreads $driver: [ns_config $section writerthreads 0]
                lappend spoolerThreads $driver: [ns_config $section spoolerthreads 0]
            } else {
                set port [ns_config $section port]
                if {$port ne ""} {
                    lappend addresses "$driver: [ns_config $section address]:$port"
                    lappend writerThreads $driver: [ns_config $section writerthreads 0]
                    lappend spoolerThreads $driver: [ns_config $section spoolerthreads 0]
                }
            }
        }
        set serverdir ""
        catch {set serverdir [ns_server -server $s serverdir]}

        #
        # Collect summative information
        #
        set total_server_requests 0
        foreach pool [lsort [ns_server -server $s pools]] {
            if {$runtimeSnapshot ne ""} {
                set poolName [expr {$pool eq "" ? "default" : $pool}]
                set rawstats [dict get $runtimeSnapshot servers $s pools $poolName counters]
            } else {
                set rawstats [ns_server -server $s -pool $pool stats]
            }
            dict set pool_info $s $pool rawstats $rawstats
            incr total_server_requests [dict get $rawstats requests]
        }

        #
        # Per connection pool information
        #
        set poolItems ""
        foreach pool [lsort [ns_server -server $s pools]] {
            #
            # Provide a nicer name for the pool.
            #
            set poolLabel "default"
            if {$pool ne {}} {
                set poolLabel $pool
            }

            #
            # Pool and server specific pool path. The empty pool name
            # has to be treated differently.
            #
            set config_path [expr {$pool eq "" ? "ns/server/$s" : "ns/server/$s/pool/$pool"}]

            #
            # Collect statistics
            #
            #ns_log notice "try to get [list dict get $pool_info $s $pool rawstats]"
            set rawstats [dict get $pool_info $s $pool rawstats]
            if {$runtimeSnapshot ne ""} {
                set poolName [expr {$pool eq "" ? "default" : $pool}]
                set gauges [dict get $runtimeSnapshot servers $s pools $poolName gauges]
                set threads [dict get $gauges threads]
                set waiting [dict get $gauges waitingRequests]
            } else {
                set threads [ns_server -server $s -pool $pool threads]
                set waiting [ns_server -server $s -pool $pool waiting]
            }
            set rawthreads [list {*}$threads \
                                waiting $waiting \
                                started [dict get $rawstats connthreads] \
                                maxconnections [ns_config $config_path maxconnections] \
                               ]
            if {$total_server_requests > 0} {
                set poolPercentage <br>[format %.2f%% [expr {100.0*[dict get $rawstats requests]/$total_server_requests}]]
            } else {
                set poolPercentage ""
            }

            set rawreqs [ns_server -server $s -pool $pool all]
            set reqs {}
            foreach req $rawreqs {
                set ts [expr {round([lindex $req end-1])}]
                if {$ts >= 60} {
                    lappend req [clock format [expr {[clock seconds] - $ts}] -format {%y/%m/%d %H:%M:%S}]
                } else {
                    lappend req .
                }
                lappend reqs [ns_quotehtml $req]
            }
            set reqs [join $reqs <br>]
            array set stats $rawstats
            set item \
                "<tr><td class='subtitle'>Connection Threads:</td><td class='colvalue' width='100%'>$rawthreads</td></tr>\n"
            if {$stats(requests) > 0} {
                incr stats(dropped) 0
                #
                # Take total time (except queue time) to calculate the
                # total number of requests that this pool can handle
                # based on collected data (when configured max threads
                # are running).
                #
                set avgTotalTime [expr {($stats(filtertime) + $stats(runtime) + $stats(tracetime)) / $stats(requests)}]
                if {$avgTotalTime > 0} {
                    set maxReqs [expr {[dict get $rawthreads max]/$avgTotalTime}]
                    append item "<tr><td class='subtitle'>Request Handling:</td>" \
                        "<td class='colvalue'>" \
                        "requests " [::_ns_stats.hr $stats(requests) %.1f], \
                        " queued " [::_ns_stats.hr $stats(queued) %1.f] \
                        " ([format %.2f [expr {$stats(queued)*100.0/$stats(requests)}]]%)," \
                        " spooled " [::_ns_stats.hr $stats(spools) %1.f] \
                        " ([format %.2f [expr {$stats(spools)*100.0/$stats(requests)}]]%)," \
                        " dropped " [::_ns_stats.hr $stats(dropped) %1.f] \
                        " possible-max-reqs " [::_ns_stats.hr $maxReqs %1.1f]rps \
                        "</td></tr>\n"
                    append item "<tr><td class='subtitle'>Request Timing:</td>" \
                        "<td class='colvalue'>avg queue time [::_ns_stats.hr [expr {$stats(queuetime)*1.0/$stats(requests)}]]s," \
                        " avg filter time [::_ns_stats.hr [expr {$stats(filtertime)*1.0/$stats(requests)}]]s," \
                        " avg run time [::_ns_stats.hr [expr {$stats(runtime)*1.0/$stats(requests)}]]s" \
                        " avg trace time [::_ns_stats.hr [expr {$stats(tracetime)*1.0/$stats(requests)}]]s" \
                        "</td></tr>\n"
                }
            }
            append item \
                "<tr><td class='subtitle'>Active Requests:</td><td class='colvalue'>$reqs</td></tr>\n"
            set nrMapped [llength [ns_server -pool $pool map]]
            if {$nrMapped > 0} {
                append item \
                    "<tr><td class='subtitle'>Mapped:</td>" \
                    "<td class='colvalue'><a href='?@page=mapped&pool=$pool&server=$s'>$nrMapped</a></td></tr>\n"
            }
            lappend poolItems "Pool '$poolLabel' $poolPercentage" "<table>$item</table>"
        }

        set requestHandlers [ns_trim -delimiter | [subst {
            |Request Handlers:&nbsp;
            |<a href='?@page=requestprocs&server=$s'>[llength [ns_server -server $s requestprocs]]</a>,
            |URL to file mappings:
            |<a href='?@page=url2file&server=$s'>
            |   [llength [ns_server -server $s url2file]]
            |</a>}]]

        try {
            set serverlogdirEntry [list "Log Directory" [ns_server -server $s logdir]]
        } on error {errorMsg} {
            set serverlogdirEntry {}
        }

        set modulesEntry [::_ns_stats.process.modules $s]

        set values [list \
                        "Address"            [join [lsort -unique $addresses] <br>] \
                        "Server Directory"   $serverdir \
                        {*}$serverlogdirEntry \
                        "Page Directory"     [ns_server -server $s pagedir] \
                        "Tcl Library"        [ns_server -server $s tcllib] \
                        "Access Log"         [ns_config ns/server/$s/module/nslog file] \
                        {*}$modulesEntry \
                        "Available DB-Pools"  [::_ns_stats.process.dbaccess $s] \
                        "Writer Threads"     $writerThreads \
                        "Spooler Threads"    $spoolerThreads \
                        "Handlers"           $requestHandlers \
                        "Connection Pools"   [ns_server -server $s pools] \
                        {*}$poolItems \
                        "Active Writer Jobs" [join [lmap l [ns_writer list -server $s] {ns_quotehtml $l}] <br>] \
                        "Active Connchan Jobs" [join [lmap l [ns_connchan list -server $s] {ns_quotehtml $l}] <br>] \
                       ]

        append html \
            "<h2 id='$s'>Server '$s'</h2>" \n \
            [::_ns_stats.process.table $values]
    }

    append html [::_ns_stats.footer]

    return $html
}

proc ::_ns_stats.mapped.table {entries ctxIdx col numericSort reverseSort op} {
    set rows [::_ns_stats.sortResults $entries [expr {$col - 1}] $numericSort $reverseSort]
    set htmlRows [lmap row $rows {
        lassign $row method url filter inherit
        set inheritArg [expr {$inherit eq "noinherit" ? "-noinherit" : ""}]
        set list [lmap cell $row { ns_quotehtml $cell }]
        set cmd [list $op {*}$inheritArg [list $method $url[expr {$filter ne "*" ? $filter : ""}]]]
        if {$ctxIdx ne "" && [lindex $row $ctxIdx] ne ""} {
            set cmd [list [lindex $cmd 0] [linsert [lindex $cmd 1] end [lindex $row $ctxIdx]]]
            #ns_log notice "CMD $cmd"
        }
        set href [ns_conn url]?[ns_conn query]&cmd=[ns_urlencode $cmd]
        lappend list "<a class='button' title='Delete this entry' href='[ns_quotehtml $href]'>$op</a>"
    }]
    return $htmlRows
}
proc ::_ns_stats.mapped.table-nomethod {entries ctxIdx col numericSort reverseSort op} {
    set rows [::_ns_stats.sortResults $entries [expr {$col - 1}] $numericSort $reverseSort]
    set htmlRows [lmap row $rows {
        lassign $row url filter inherit
        set inheritArg [expr {$inherit eq "noinherit" ? "-noinherit" : ""}]
        set list [lmap cell $row { ns_quotehtml $cell }]
        set cmd [list $op {*}$inheritArg [list $url[expr {$filter ne "*" ? $filter : ""}]]]
        if {$ctxIdx ne "" && [lindex $row $ctxIdx] ne ""} {
            set cmd [list [lindex $cmd 0] [linsert [lindex $cmd 1] end [lindex $row $ctxIdx]]]
            #ns_log notice "CMD $cmd"
        }
        set href [ns_conn url]?[ns_conn query]&cmd=[ns_urlencode $cmd]
        lappend list "<a class='button' title='Delete this entry' href='[ns_quotehtml $href]'>$op</a>"
    }]
    return $htmlRows
}



proc ::_ns_stats.mapped {} {
    set col         [ns_queryget col 0]
    set reverseSort [ns_queryget reversesort 1]
    set pool        [ns_queryget pool [ns_conn pool]]
    set server      [ns_queryget server [ns_conn server]]
    set queryContext @page=[ns_queryget @page]&server=$server&pool=$pool

    set cmd         [ns_queryget cmd ""]
    if {[lindex $cmd 0] eq "unmap"} {
        #ns_log notice "CMD <ns_server -server $server -pool $pool {*}$cmd>"
        ns_server -server $server -pool $pool {*}$cmd
        ::_ns_stats.redirect [ns_conn url]?$queryContext&col=$col&reverseSort=$reverseSort
        return
    }

    set colTitles [list Method URL Filter Inheritance Context unmap]
    set mappings [lmap entry [ns_server -server $server -pool $pool map] {
        #ns_log notice "len entry [llength $entry]  llen colTitles [llength $colTitles]"
        if {[llength $entry] == 4} {
            lappend entry ""
        }
        set entry
    }]


    set htmlRows [::_ns_stats.mapped.table \
                      $mappings \
                      4 $col 0 $reverseSort unmap]

    set poolName $pool
    if {$poolName eq ""} {set poolName default}
    set serverName $server
    if {$serverName eq ""} {set serverName default}

    append html \
        [::_ns_stats.header [list Process "?@page=process"] Mapped] \
        "<h4>Server $serverName: Connection Pool mapping for pool <em>$poolName</em></h4>" \
        [::_ns_stats.results process $col $colTitles ?$queryContext $htmlRows $reverseSort] \
        "<p>Back to <a href='?@page=process#[ns_urlencode $server]'>process</a></p>" \
        [::_ns_stats.footer]
    return $html
}

proc ::_ns_stats.checkboxFilter {name boxes hidden} {
    set checkboxes [lmap box $boxes {
        lassign $box value label checked
        ns_trim -delimiter | [subst {
            | <input class="w3-check" name="$name" type="checkbox" value="$value" $checked>
            | <label >$label</label>
        }]
    }]
    set hiddenfields [lmap {key value} $hidden {
        subst { <input type="hidden" name="$key" value="$value">}
    }]
    return [ns_trim -delimiter | [subst {
        |<div class="$name">
        | Registered Methods:
        | <form class="w3-container" action="[ns_conn url]">
        |[join $checkboxes \n]
        |[join $hiddenfields \n]
        | <button type="submit" class="">Filter</button>
        | </form>
        |</div>}]]
}

proc ::_ns_stats.requestprocs {} {
    set col          [ns_queryget col 0]
    set reverseSort  [ns_queryget reversesort 1]
    set server       [ns_queryget server [ns_conn server]]
    set methodFilter [ns_querygetall methodfilter GET]
    set cmd          [ns_queryget cmd ""]
    set filterVars   [lmap selectedFilter $methodFilter {string cat methodfilter=$selectedFilter}]
    set queryContext @page=[ns_queryget @page]&server=$server&[join $filterVars &]

    if {[lindex $cmd 0] eq "unregister"} {
        #ns_log notice "CMD ns_unregister_op -server $server {*}[lindex $cmd 1]"
        ns_unregister_op -server $server {*}[lindex $cmd 1]
        ::_ns_stats.redirect [ns_conn url]?$queryContext&reverseSort=$reverseSort&col=$col
        return
    }

    set registeredHandlers [ns_server -server $server requestprocs]
    set registeredMethods [lsort -unique [lmap entry $registeredHandlers {lindex $entry 0}]]
    set filteredHandlers [lmap entry $registeredHandlers {
        if {[lindex $entry 0] ni $methodFilter} continue
        set entry
    }]
    set filterCheckboxes [lmap m $registeredMethods {
        list $m $m [expr {$m in $methodFilter ? "checked" : ""}]
    }]

    set numericSort 0
    set colTitles   [list Method URL Filter Inheritance Proc Arg unregister]

    set htmlRows [::_ns_stats.mapped.table \
                      [lmap entry $filteredHandlers {
                          set reminder [lassign $entry method url filter inherit proc]
                          list $method $url $filter $inherit $proc $reminder
                      }] \
                      "" $col 0 $reverseSort unregister]

    set serverName $server
    if {$serverName eq ""} {set serverName default}

    set hidden {@page requestprocs}
    foreach var {server col reverseSort} {
        lappend hidden $var [set $var]
    }

    append html \
        [::_ns_stats.header [list Process "?@page=process"] "Request Handlers"] \
        "<h4>Registered Request Handlers of Server <em>$serverName</em></h4>" \
        [::_ns_stats.checkboxFilter methodfilter $filterCheckboxes $hidden] \
        [::_ns_stats.results requestprocs $col $colTitles ?$queryContext $htmlRows $reverseSort] \
        "<p>Back to <a href='?@page=process#[ns_urlencode $server]'>process</a></p>" \
        [::_ns_stats.footer]
    return $html
}

proc ::_ns_stats.url2file {} {
    set col          [ns_queryget col 0]
    set reverseSort  [ns_queryget reversesort 1]
    set server       [ns_queryget server [ns_conn server]]
    set cmd          [ns_queryget cmd ""]
    set queryContext @page=[ns_queryget @page]&server=$server

    if {[lindex $cmd 0] eq "unregister"} {
        #ns_log notice "CMD ns_unregister_url2file -server $server {*}[lindex $cmd 1]"
        ns_unregister_url2file -server $server {*}[lindex $cmd 1]
        ::_ns_stats.redirect [ns_conn url]?$queryContext&reverseSort=$reverseSort&col=$col
        return
    }

    set registeredHandlers [ns_server -server $server url2file]
    set registeredMethods [lsort -unique [lmap entry $registeredHandlers {lindex $entry 0}]]

    set numericSort 0
    set colTitles   [list URL Filter Inheritance Proc Arg unregister]

    set htmlRows [::_ns_stats.mapped.table-nomethod \
                      [lmap entry $registeredHandlers {
                          set reminder [lassign $entry method url filter inherit proc]
                          list $url $filter $inherit $proc $reminder
                      }] \
                      "" $col 0 $reverseSort unregister]

    set serverName $server
    if {$serverName eq ""} {set serverName default}

    append html \
        [::_ns_stats.header [list Process "?@page=process"] "Request-to-File Mappings"] \
        "<h4>Registered Url2File Mapping of Server <em>$serverName</em></h4>" \
        [::_ns_stats.results requestprocs $col $colTitles ?$queryContext $htmlRows $reverseSort] \
        "<p>Back to <a href='?@page=process#[ns_urlencode $server]'>process</a></p>" \
        [::_ns_stats.footer]
    return $html
}

proc ::_ns_stats.list-lsof-filter-lines {list match max} {
    set filtered [lmap line $list {if {![string match $match $line]} continue;set line}]
    if {[llength $filtered] > $max} {
        set filtered [lrange $filtered 0 $max-1]
    }
    return $filtered
}

proc ::_ns_stats.list-lsof {} {
    set all [ns_queryget all 0]
    set max [ns_queryget max 1000]
    set of [::_ns_stats.lsof [ns_info pid]]
    if {$all} {
        set displayed [expr {[llength $of] > $max ? [lrange $of 0 $max-1] : $of}]
        set body [ns_trim -delimiter | [subst {
            |<strong>Number of Open Files:</strong> [llength $of] (max $max)
            |<p>(<a href='?@page=list-lsof&all=0'>filtered</a>)<p>
            |<pre>[join $displayed \n]</pre>
        }]]
    } else {
        set IPv4 [::_ns_stats.list-lsof-filter-lines $of *IPv4* $max]
        set IPv6 [::_ns_stats.list-lsof-filter-lines $of *IPv6* $max]
        set body [ns_trim -delimiter | [subst {
            |<strong>Number of Open Files:</strong> [llength $of]
            | (<a href='?@page=list-lsof&all=1'>all</a>)
            |<p>
        }]]
        try {
            ns_http keepalives
        } on ok {keepalives} {
            append body \
                "<strong>ns_http keep-alive slots:</strong>\n" \
                "<blockquote><pre>[join $keepalives \n]</pre></blockquote>\n"
        }

        if {[llength $IPv4] > 0} {
            append body \
                "<strong>IPv4 Sockets (current [llength $IPv4], max displayed $max):</strong>\n" \
                "<blockquote><pre>[join $IPv4 \n]</pre></blockquote>\n"
        }
        if {[llength $IPv6] > 0} {
            append body \
                "<strong>IPv6 Sockets (current [llength $IPv6], max displayed $max):</strong>\n" \
                "<blockquote><pre>[join $IPv6 \n]</pre></blockquote>\n"
        }
    }
    append html \
        [::_ns_stats.header [list Process "?@page=process"] "Open Files"] \
        $body \
        "<p>Back to <a href='?@page=process'>process</a> page</p>" \
        [::_ns_stats.footer]
    return $html
}

proc ::_ns_stats.proxy-workers {} {
    set col          [ns_queryget col 4]
    set reverseSort  [ns_queryget reversesort 1]
    set pool         [ns_queryget pool]
    set cmd          [ns_queryget cmd ""]
    set queryContext @page=[ns_queryget @page]&pool=$pool
    set workers      [ns_proxy workers $pool]
    set numericSort  1
    set colTitles    [list ID pid created runs state]
    if {$col == 0 || $col == 3} {
        set numericSort 1
    }
    set align [lrepeat [llength $colTitles] right]
    lset align 0 left
    lset align 4 left

    set results [lmap e $workers {
        list [dict get $e id] [dict get $e pid] [dict get $e created] [dict get $e runs] [dict get $e state]
    }]
    set rows [::_ns_stats.sortResults $results [expr {$col - 1}] $numericSort $reverseSort]

    append html \
        [::_ns_stats.header [list Process "?@page=process"] "nsproxy Workers"] \
        "<h4>[llength $results] nsproxy workers for pool <em>$pool</em></h4>" \
        [::_ns_stats.results requestprocs $col $colTitles ?$queryContext $rows $reverseSort $align] \
        "<p>Back to <a href='?@page=process'>process</a> page</p>" \
        [::_ns_stats.footer]
    return $html
}


proc ::_ns_stats.background.sched {} {
    set col             [ns_queryget col 1]
    set reverseSort     [ns_queryget reversesort 1]

    set numericSort     1
    set scheduledProcs  ""

    foreach s [ns_info scheduled] {
        set id          [lindex $s 0]
        set flags       [lindex $s 1]
        set next        [lindex $s 3]
        set lastqueue   [lindex $s 4]
        set laststart   [lindex $s 5]
        set lastend     [lindex $s 6]
        set proc        [lindex $s 7]
        set arg         [lrange $s 8 end]

        if {[catch {
            set duration [expr {$lastend - $laststart}]
        }]} {
            set duration 0
        }

        set state "pending"

        if {[::_ns_stats.isThreadSuspended $flags]} {
            set state suspended
        }

        if {[::_ns_stats.isThreadRunning $flags]} {
            set state running
        }

        lappend scheduledProcs [list $id $state $proc $arg $flags $lastqueue $laststart $lastend $duration $next]
    }

    set rows ""

    foreach s [::_ns_stats.sortResults $scheduledProcs [expr {$col - 1}] $numericSort $reverseSort] {
        set id          [lindex $s 0]
        set state       [lindex $s 1]
        set flags       [join [::_ns_stats.getSchedFlagTypes [lindex $s 4]] "<br>"]
        set next        [::_ns_stats.fmtTime [lindex $s 9]]
        set lastqueue   [::_ns_stats.fmtTime [lindex $s 5]]
        set laststart   [::_ns_stats.fmtTime [lindex $s 6]]
        set lastend     [::_ns_stats.fmtTime [lindex $s 7]]
        set proc        [lindex $s 2]
        set arg         [lindex $s 3]
        set duration    [::_ns_stats.hr [lindex $s 8]]s

        lappend rows [list $id $state $proc $arg $flags $lastqueue $laststart $lastend $duration $next]
    }

    set colTitles [list ID Status Callback Data Flags "Last Queue" "Last Start" "Last End" Duration "Next Run"]
    set align [lrepeat [llength $colTitles] left]
    lset align end-1 right

    append html \
        [::_ns_stats.header "Scheduled Procedures"] \
        [::_ns_stats.results sched $col $colTitles ?@page=background.sched $rows $reverseSort $align] \
        [::_ns_stats.footer]
    return $html
}

proc ::_ns_stats.log.chart.parse-httpclient {line} {
    set fields [split $line]
    set reused 0
    lassign $fields ts tz id status method url elapsed sent received cause
    set ts0 [string range $ts 1 end]
    #
    # Provide robustness when invalid URLs (containing unescaped
    # spaces) were used.
    #
    if {[llength $fields] > 10} {
        lassign [lrange $fields end-4 end] elapsed sent received reused cause
        set url [lrange $fields 5 end-5]
    }

    set host none
    try {
        # remove curly braces and quotes
        regexp {https?://([^/]+)/?} [lindex $url 0 0] . host
    } on error {errorMsg} {
        # remove curly braces
        regexp {https?://([^/]+)/?} [lindex $url 0] . host
    }

    return [list \
                ts0 $ts0 \
                id $id \
                status $status \
                method $method \
                host $host \
                url $url \
                elapsed $elapsed \
                sent $sent \
                received $received \
                reused $reused \
                cause $cause \
                errorLine [expr {$cause ne "ok" ? $line : ""}] \
               ]
}

proc ::_ns_stats.log.chart {path section param title} {
    set logfiles [::_ns_stats.log.logfiles $section $param]
    ns_log notice "nsstats: process $section $param log $path -> $logfiles"
    set t0 [clock milliseconds]

    set filesize [expr {[file exists $path] ? [file size $path] : 0}]
    if {$filesize < 10} {
        if {[llength $logfiles] > 0} {
            set path [lindex $logfiles 0]
            set logfiles [concat $path {*}[lreverse [lrange $logfiles 1 end]]]
        } else {
            return "<p>No $section $param log entries found in [ns_quotehtml $path]</p>"
        }
    }

    set F [open $path]; set logcontent [read $F]; close $F
    set count 0
    set hostInfos {}
    set errorLines {}

    foreach line [split $logcontent \n] {
        #if {$count>10} break
        if {[string trim $line] eq ""} {continue}
        try {
            set data [::_ns_stats.log.chart.parse-$section $line]
            dict with data {
                #
                # Convert time to UTC format for JavaScript: 13/Nov/2022:00:19:49 +0100
                #
                # The timestgamp "ts" in JavaScript (result of Data.parse())
                # is the time since January 1, 1970 in milliseconds
                #
                set ts [clock scan $ts0 -gmt 1 -format {%d/%b/%Y:%H:%M:%S}]

                incr count
                dict lappend responsetime $host $ts $elapsed
                dict incr requestcount0 [list $host $ts]
                if {[dict exists $hostInfos $host]} {
                    set hostInfo [dict get $hostInfos $host]
                } else {
                    set hostInfo {}
                }
                dict incr hostInfo sent $sent
                dict incr hostInfo received $received
                dict incr hostInfo count
                dict incr hostInfo reused $reused
                dict incr hostInfo $status
                if {[dict exists $hostInfo elapsed]} {
                    dict set hostInfo elapsed [expr {[dict get $hostInfo elapsed] + $elapsed}]
                } else {
                    dict set hostInfo elapsed $elapsed
                }
                dict set hostInfos $host $hostInfo
                dict set statusCodes $status 1
                if {$errorLine ne ""} {
                    lappend errorLines $errorLine
                }
            }
        } on error {errorMsg} {
            ns_log warning ::_ns_stats.log.chart: cannot parse <$line>: $errorMsg
            continue
        }
    }
    if {$count == 0} {
        return "[::_ns_stats.log.fileform $logfiles $path [ns_queryget @page {}]]<p>No usable log entries found in [ns_quotehtml $path].</p>"
    }
    set t1 [clock milliseconds]
    set responsetimeSeries {}
    foreach key [lsort [dict keys $responsetime]] {
        set values [join [lmap {ts value} [dict get $responsetime $key] {
            subst -nocommands {[${ts}000, $value]}
        }] ",\n"]
        lappend responsetimeSeries [subst -nocommands {
            {
                name: '$key',
                data:[$values]
            }
        }]
    }
    set requestcount {}
    foreach key [lsort [dict keys $requestcount0]] {
        lassign $key host ts
        dict lappend requestcount $host $ts [dict get $requestcount0 $key]
    }
    set requestcountSeries {}
    foreach key [dict keys $requestcount] {
        set values [join [lmap {ts value} [dict get $requestcount $key] {
            subst -nocommands {[${ts}000, $value]}
        }] ",\n"]
        lappend requestcountSeries [subst -nocommands {
            {
                name: '$key',
                data:[$values]
            }
        }]
    }

    set responsetimeSeries [join $responsetimeSeries ,]
    set requestcountSeries [join $requestcountSeries ,]
    set JS [subst -nocommands {
        Highcharts.chart('responsetime', {
            chart: {
                type: 'lollipop'
            },
            title: {
                text: '$title - Response Time Overview'
            },
            xAxis: {
                type: 'datetime',
            },
            yAxis: {
                title: {text: 'Seconds'}
            },
            series: [$responsetimeSeries]
        });
        Highcharts.chart('requestcount', {
            chart: {
                type: 'lollipop'
            },
            title: {
                text: '$title - Requests per Second'
            },
            subtitle: {
                text: "Total number of requests: $count"
            },
            xAxis: {
                type: 'datetime',
            },
            yAxis: {
                title: {text: 'Count'}
            },
            series: [$requestcountSeries]
        });
    }]
    set codes [lsort [dict keys $statusCodes]]
    foreach host [dict keys $hostInfos] {
        foreach code $codes {
            if {![dict exists $hostInfos $host $code]} {
                dict set hostInfos $host $code 0
            }
        }
    }
    set data [subst {
        <table class="table table-striped fs-3 bg-white"><tr>
        <th class="fs-6">Host</th>
        <th class="fs-6 text-end">Requests</th>
        <th class="fs-6 text-end">Avg Time</th>
        <th class="fs-6 text-end">Sent</th>
        [expr {$section eq "httpclient" ? {
            <th class="fs-6 text-end">Received</th>
            <th class="fs-6 text-end">Reused</th>} : ""}]
        [join [lmap code $codes {set _ "<th class='fs-6 text-end'>$code</th>"}]]
        </tr>
    }]
    foreach host [lsort [dict keys $hostInfos]] {
        set avg [expr {[dict get $hostInfos $host elapsed]/[dict get $hostInfos $host count]}]
        append data [subst {<tr>
            <td class="fs-6">$host</td>
            <td class="fs-6 text-end">[dict get $hostInfos $host count]</td>
            <td class="fs-6 text-end">[::_ns_stats.hr $avg]s</td>
            <td class="fs-6 text-end">[::_ns_stats.hr [dict get $hostInfos $host sent]]B</td>
            [expr {$section eq "httpclient"
                   ? [subst {<td class="fs-6 text-end">[::_ns_stats.hr [dict get $hostInfos $host received]]B</td>
                       <td class="fs-6 text-end">[dict get $hostInfos $host reused]</td>
                   }] : ""}]
            [join [lmap code $codes {set _ "<td class='fs-6 text-end'>[dict get $hostInfos $host $code]</td>"}]]
            </tr>
        }]
    }
    set fileform [::_ns_stats.log.fileform $logfiles $path [ns_queryget @page ""]]
    set t2 [clock milliseconds]
    ns_log notice "nsstats: parse data [expr {$t1-$t0}]ms, graph and table built [expr {$t2-$t1}]ms"

    if {[llength $errorLines] > 0} {
        set errorLines [ns_trim -delimiter | [subst {
            |<h4>Errors:</h4>
            |<hr><pre>[join $errorLines \n]</pre><hr>
        }]]
    }
    return [subst {
        <div id='responsetime'></div>
        <div id='requestcount'></div>
        <script>$JS</script>
        <div class="container">
        <h4>Summative Statistics</h4>
        $data
        </table>
        $errorLines
        <h4>Show other logfile</h4>
        $fileform
        <p>
        </div>
    }]
}

proc ::_ns_stats.log.logfiles {section param} {
    return [lsort [concat {*}[lmap s [ns_info servers] {
        set logfile [ns_config ns/server/$s/$section $param]
        if {$logfile eq "" || ![file exists $logfile]} {
            continue
        }
        lmap file [glob $logfile*] {
            # An empty active log must remain selectable after rotation.
            if {![file isfile $file] || ![file readable $file]} continue
            set file
        }
    }]]]
}

proc ::_ns_stats.log.httpclient {} {
    return [::_ns_stats.log.mkchart httpclient logfile "HTTP Client Log"]
}
proc ::_ns_stats.log.fileform {files selected page {filters {}}} {
    set options {}
    foreach path $files {
        set attribute [expr {$path eq $selected ? "selected" : ""}]
        append options "<option value=\"[ns_quotehtml $path]\" $attribute>[ns_quotehtml $path]</option>"
    }
    set html "<form action=\"nsstats.tcl\" class=\"row g-1\">\
        <input type=\"hidden\" name=\"@page\" value=\"[ns_quotehtml $page]\">\
        <div class=\"col\"><label>Log <select class=\"form-select\" name=\"logfile\">$options</select></label></div>"
    dict for {name value} $filters {
        append html "<div class=\"col\"><label>[ns_quotehtml [string totitle $name]] \
            <input class=\"form-control\" name=\"[ns_quotehtml $name]\" value=\"[ns_quotehtml $value]\"></label></div>"
    }
    append html {<div class="col"><button type="submit" class="btn btn-outline-secondary">Show</button></div></form>}
    return $html
}

proc ::_ns_stats.log.mkchart {section param title {renderer ::_ns_stats.log.chart}} {
    set ::extraHeadEntries {
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
        <script src="https://code.highcharts.com/highcharts.js"></script>
        <script src="https://code.highcharts.com/modules/exporting.js"></script>
        <script src="https://code.highcharts.com/modules/export-data.js"></script>
        <script src="https://code.highcharts.com/highcharts-more.js"></script>
        <script src="https://code.highcharts.com/modules/dumbbell.js"></script>
        <script src="https://code.highcharts.com/modules/lollipop.js"></script>
    }

    set configured_logfile [ns_config ns/server/[ns_info server]/$section $param ""]
    if {$configured_logfile eq ""} {
        set HTML "<p>No $section $param logfiles configured</p>"
    } else {
        set files [::_ns_stats.log.logfiles $section $param]
        set selected_logfile [ns_queryget logfile ""]
        if {$selected_logfile eq ""} {
            set logfile $configured_logfile
            if {$logfile ni $files} {set logfile [lindex $files 0]}
        } else {
            set logfile $selected_logfile
            if {[file tail $selected_logfile] eq $selected_logfile} {
                set logfile [file join [file dirname $configured_logfile] $selected_logfile]
            }
        }
        # Restrict all renderers to configured log files and discovered rotations.
        if {$logfile eq "" || $logfile ni $files} {
            set HTML {<p>No matching log file available.</p>}
        } else {
            try {
                set HTML [{*}$renderer $logfile $section $param $title]
            } on error {message options} {
                ns_log error "nsstats: cannot render $section $param log $logfile: [dict get $options -errorinfo]"
                set HTML "<p>Cannot read log: [ns_quotehtml $message]</p>"
            }
        }
    }
    append html \
        [::_ns_stats.header $title] \
        $HTML \
        [::_ns_stats.footer]
    return $html
}

proc ::_ns_stats.threadCpuPercentages {sampleKey {snapshot {}}} {
    if {$snapshot eq ""} {
        set snapshot [::_ns_stats.runtimeSnapshot]
    }
    set pid         [pid]
    set rawThreads  [ns_info threads]
    set cpuSource   none
    set cpuTimes    {}
    set collected   {}
    set current     [dict create threads {} collectorMode [expr {[::_ns_stats.sampled $snapshot] ? "sample" : ($snapshot eq "" ? "legacy" : "snapshot")}]]
    set cpuAvailable 0

    #
    # Older NaviServer versions do not include the OS TID.
    #
    if {[llength $rawThreads] == 0
        || [llength [lindex $rawThreads 0]] <= 7} {
        return [dict create \
                    available 0 \
                    elapsed   0.0 \
                    rows      $rawThreads \
                    byThread  {}]
    }

    if {$snapshot ne ""} {
        if {[::_ns_stats.dictGetDef $snapshot availability cpu false]} {
            set cpuSource core
        }
    } else {
        try {
            set cpuTimes [ns_info threadcputimes]
            set cpuSource api
        } on error {errorMsg} {
            if {[file isdirectory /proc/$pid/task]} {
                set cpuSource proc
                set HZ 100
            }
        }
    }

    if {$cpuSource eq "none"} {
        return [dict create \
                    available 0 \
                    elapsed   0.0 \
                    rows      $rawThreads \
                    byThread  {}]
    }
    set cpuAvailable 1

    #
    # Collect the current cumulative CPU counters.
    #
    foreach t $rawThreads {
        set name       [lindex $t 0]
        set parent     [lindex $t 1]
        set createTime [lindex $t 4]
        set tid        [lindex $t 7]
        set state      0
        set utime      0.0
        set stime      0.0
        set valid      0

        if {$cpuSource eq "core"} {
            set identity [list $tid $createTime]
            if {[dict exists $snapshot cpu $identity]} {
                set times [dict get $snapshot cpu $identity]
                set utime [expr {[dict get $times user] / 1000000.0}]
                set stime [expr {[dict get $times system] / 1000000.0}]
                set state [::_ns_stats.dictGetDef $times state 0]
                set valid 1
            }
        } elseif {$cpuSource eq "api"} {
            if {[dict exists $cpuTimes $tid]} {
                set times [dict get $cpuTimes $tid]

                set utime [expr {
                    [dict get $times user] / 1000000.0
                }]
                set stime [expr {
                    [dict get $times system] / 1000000.0
                }]

                if {[dict exists $times state]} {
                    set state [dict get $times state]
                }
                set valid 1
            }

        } elseif {$cpuSource eq "proc"} {
            set filename /proc/$pid/task/$tid/stat

            if {[file readable $filename]} {
                set f [open $filename]
                set s [read $f]
                close $f

                #
                # This retains the current parser. A separate improvement
                # could parse the parenthesized comm field more defensively.
                #
                lassign $s statTid comm state ppid pgrp session ttyNr tpgid \
                    flags minflt cminflt majflt cmajflt userJiffies systemJiffies \
                    cutime cstime priority nice numthreads itrealval \
                    starttime vsize rss rsslim startcode endcode \
                    startstack kstkesp kstkeip signal blocked sigignore \
                    sigcatch wchan nswap cnswap extSignal processor

                set state "$state [format %.2d $processor]"
                set utime [expr {$userJiffies   / double($HZ)}]
                set stime [expr {$systemJiffies / double($HZ)}]
                set valid 1
            }
        }

        #
        # Combining the OS TID with the NaviServer thread creation time
        # protects the delta calculation against OS TID reuse.
        #
        set threadKey [list $tid $createTime]

        if {$valid} {
            dict set current threads $threadKey \
                [dict create utime $utime stime $stime]
        }

        lappend collected [dict create \
                               raw        $t \
                               key        $threadKey \
                               name       $name \
                               parent     $parent \
                               tid        $tid \
                               state      $state \
                               utime      $utime \
                               stime      $stime \
                               valid      $valid]
    }

    set sampledAt [expr {$snapshot eq "" ? [clock microseconds] : [dict get $snapshot timestamp]}]
    dict set current sampled_at $sampledAt

    set sampled [::_ns_stats.sampled $snapshot]
    set previous {}
    if {$sampled} {
        nsv_set _ns_stats $sampleKey [dict create collectorMode sample]
    } else {
        set previous [nsv_set -reset _ns_stats $sampleKey $current]
        if {[::_ns_stats.dictGetDef $previous collectorMode legacy] ne [dict get $current collectorMode]} {
            set previous {}
        }
    }

    set elapsed 0.0
    if {$previous ne ""
        && [dict exists $previous sampled_at]} {
        set elapsed [expr {
            ($sampledAt - [dict get $previous sampled_at])
            / 1000000.0
        }]
    }

    if {$sampled} {
        set elapsed [dict get $snapshot sampling elapsedSeconds]
    }

    if {[::_ns_stats.dictGetDef $snapshot samplingPending false]} {set elapsed 0.0}

    #
    # Compute CPU percentages and build both result representations.
    #
    set rows     {}
    set byThread {}

    foreach entry $collected {
        set t          [dict get $entry raw]
        set threadKey  [dict get $entry key]
        set name       [dict get $entry name]
        set parent     [dict get $entry parent]
        set tid        [dict get $entry tid]
        set state      [dict get $entry state]
        set utime      [dict get $entry utime]
        set stime      [dict get $entry stime]
        set valid      [dict get $entry valid]
        set cpu        -1.0

        if {$sampled} {
            set measured [::_ns_stats.dictGetDef $snapshot cpuPercent threads $threadKey percent {}]
            if {$measured ne ""} {set cpu $measured}
        } elseif {$valid
            && $elapsed > 0.0
            && [dict exists $previous threads $threadKey]} {
            set old [dict get $previous threads $threadKey]
            set du  [expr {$utime - [dict get $old utime]}]
            set ds  [expr {$stime - [dict get $old stime]}]

            if {$du >= 0.0 && $ds >= 0.0} {
                set cpu [expr {
                    100.0 * ($du + $ds) / $elapsed
                }]
            }
        }

        dict set byThread $threadKey \
            [dict create \
                 name       $name \
                 parent     $parent \
                 tid        $tid \
                 createTime [lindex $t 4] \
                 state      $state \
                 utime      $utime \
                 stime      $stime \
                 cpu        $cpu]

        #
        # Construct the exact layout consumed by ::_ns_stats.threads:
        #
        #   name parent id flags create
        #   tid state utime stime cpu
        #   proc arg
        #
        lappend rows [list \
                          [lindex $t 0] \
                          [lindex $t 1] \
                          [lindex $t 2] \
                          [lindex $t 3] \
                          [lindex $t 4] \
                          $tid \
                          $state \
                          $utime \
                          $stime \
                          $cpu \
                          [lindex $t 5] \
                          [lindex $t 6]]
    }

    return [dict create \
                available $cpuAvailable \
                elapsed   $elapsed \
                rows      $rows \
                byThread  $byThread]
}

proc ::_ns_stats.threads {} {
    set col         [ns_queryget col 1]
    set reverseSort [ns_queryget reversesort 1]

    set cpuData    [::_ns_stats.threadCpuPercentages threadCpuSample]
    set osInfo     [dict get $cpuData available]
    set threadInfo [dict get $cpuData rows]

    if {$osInfo} {
        set colNumSort {. 0 0 1 1 1 0 1 1 1 1 0}
        set colTitles {
            Thread Parent ID Flags "Create Time"
            TID State utime stime "CPU %" Args
        }
        set align {
            left left right left left
            right right right right right left
        }
    } else {
        set colNumSort {. 0 0 1 1 1 0}
        set colTitles {Thread Parent ID Flags "Create Time" Args}
        set align     {left left right left left left}
    }

    #
    # The available columns depend on whether per-thread CPU information
    # could be obtained. A bookmarked or manually supplied column number
    # might therefore refer to a column not present in the current layout.
    #
    if {![string is integer -strict $col]
        || $col < 1
        || $col > [llength $colTitles]} {
        set col 1
    }

    set rows {}

    foreach t [::_ns_stats.sortResults \
                   $threadInfo \
                   [expr {$col - 1}] \
                   [lindex $colNumSort $col] \
                   $reverseSort] {
        set thread [lindex $t 0]
        set parent [lindex $t 1]
        set id     [lindex $t 2]
        set flags  [::_ns_stats.getThreadType [lindex $t 3]]
        set create [::_ns_stats.fmtTime [lindex $t 4]]

        if {$osInfo} {
            set tid   [lindex $t 5]
            set state [lindex $t 6]
            set utime [::_ns_stats.hr [lindex $t 7]]s
            set stime [::_ns_stats.hr [lindex $t 8]]s
            set cpu   [lindex $t 9]
            set proc  [lindex $t 10]
            set arg   [lindex $t 11]

            set cpuDisplay [expr {
                $cpu < 0.0 ? "\u2014" : [format %.1f $cpu]
            }]

            if {$proc eq "p:0x0"} {
                set proc NULL
            }
            if {$arg eq "a:0x0"} {
                set arg NULL
            }

            lappend rows [list \
                              $thread $parent $id $flags $create \
                              $tid $state $utime $stime \
                              $cpuDisplay $arg]
        } else {
            set proc [lindex $t 5]
            set arg  [lindex $t 6]

            if {$proc eq "p:0x0"} {
                set proc NULL
            }
            if {$arg eq "a:0x0"} {
                set arg NULL
            }

            lappend rows \
                [list $thread $parent $id $flags $create $arg]
        }
    }

    set requestRates [::_ns_stats.requestRates total]
    set totalRateDisplay [expr {
        [dict exists $requestRates total]
        ? "[format %.1f [dict get $requestRates total]] requests per second"
        : "n/a"
    }]

    append html \
        [::_ns_stats.header Threads] \
        "Current Total Request rate: $totalRateDisplay" \
        [::_ns_stats.results threads $col $colTitles \
             ?@page=threads $rows $reverseSort $align] \
        [::_ns_stats.footer]

    return $html
}

proc ::_ns_stats.background.jobs {} {
    set queue       [ns_queryget queue]
    set col         [ns_queryget col 1]
    set reverseSort [ns_queryget reversesort 1]

    set numericSort 1
    set rows        [list]

    if { $queue eq "" } {

        if {$col == 0 || $col == 1 || $col == 4} {
            set numericSort 0
        }

        set colTitles [list "Queue name" Desc maxThreads numRunning Req]

        foreach ql [ns_job queuelist] {
            array set qa $ql
            set name "<a href='?@page=background.jobs&queue=$qa(name)'>$qa(name)</a>"
            lappend results [list $name $qa(desc) $qa(maxthreads) $qa(numrunning) $qa(req)]
        }

        set rows [::_ns_stats.sortResults $results [expr {$col - 1}] $numericSort $reverseSort]

    } else {

        if {$col == 0 || $col == 1 || $col == 2 || $col == 3 || $col == 4} {
            set numericSort 0
        }

        set colTitles   [list ID State Script Code Type Started Stopped Time]
        set results     [list]

        foreach jl [ns_job joblist $queue] {
            array set ja $jl
            set ja(starttime) [::_ns_stats.fmtTime $ja(starttime)]
            set ja(endtime) [::_ns_stats.fmtTime $ja(endtime)]
            set ja(time) "[expr [lindex [split $ja(time) .] 0]/1000] sec"
            lappend results [list $ja(id) $ja(state) $ja(script) $ja(code) $ja(type) $ja(starttime) $ja(endtime) $ja(time)]
        }

        set rows [::_ns_stats.sortResults $results [expr {$col - 1}] $numericSort $reverseSort]
    }

    append html \
        [::_ns_stats.header Jobs] \
        [::_ns_stats.results jobs $col $colTitles ?@page=background.jobs&queue=$queue $rows $reverseSort] \
        [::_ns_stats.footer]
    return $html
}

proc ::_ns_stats.results {
                        name
                        {selectedColNum ""}
                        {colTitles ""}
                        {colUrl ""}
                        {rows ""}
                        {reverseSort ""}
                        {colAlignment ""}
                    } {
    set numCols [llength $colTitles]

    for {set colNum 1} {$colNum <= $numCols} {incr colNum} {
        set colClass($colNum) [expr {$colNum == $selectedColNum ? "selected" : "unselected"}]
    }

    set html [ns_trim -delimiter | [subst {
        |<table class="$name data-table w3-table w3-hoverable">
        |<tr class="sortable">
    }]]

    set i 1

    foreach title $colTitles {
        set url $colUrl

        #ns_log notice DEBUG TITLE col $i title $title
        if {$i == $selectedColNum} {
            if {$reverseSort} {
                append url "&reversesort=0"
            } else {
                append url "&reversesort=1"
            }
        } else {
            append url "&reversesort=$reverseSort"
        }

        set colAlign "left"
        set cssAlign ""

        if {[llength $colAlignment]} {
            set align [lindex $colAlignment $i-1]

            if {[string length $align]} {
                set colAlign $align
                if {$align eq "right"} {
                    set cssAlign right
                }
            }
        }

        append html \
            "<th valign='middle' align='$colAlign' class='coltitle $colClass($i) $cssAlign'>" \
            "<a href='$url&col=$i$::rawparam'>$title</a></th>"

        incr i
    }

    append html "</tr>"

    foreach row $rows {
        set i 1
        append html "<tr class='data'>"
        #ns_log notice DEBUG BODY row $row

        foreach column $row title $colTitles {
            set colAlign "left"
            set cssAlign ""
            #ns_log notice DEBUG BODY col $i

            if {[llength $colAlignment]} {
                set align [lindex $colAlignment $i-1]

                if {[string length $align]} {
                    set colAlign $align
                    if {$align eq "right"} {
                        set cssAlign right
                    }
                }
            }
            append html "<td class='x $colClass($i) $cssAlign' valign='top' align='$colAlign'>$column</td>"
            incr i
        }

        append html "</tr>"
    }

    append html "\
        </table>"

    return $html
}

proc ::_ns_stats.getValue {key} {
    if {![nsv_exists _ns_stats $key]} {
        return ""
    }

    return [nsv_get _ns_stats $key]
}

proc ::_ns_stats.getThreadType {flag} {
    return [::_ns_stats.getValue thread_$flag]
}

proc ::_ns_stats.getSchedFlag {type} {
    return [::_ns_stats.getValue sched_$type]
}

proc ::_ns_stats.isThreadSuspended {flags} {
    return [expr {$flags & [::_ns_stats.getSchedFlag paused]}]
}

proc ::_ns_stats.isThreadRunning {flags} {
    return [expr {$flags & [::_ns_stats.getSchedFlag running]}]
}

proc ::_ns_stats.getSchedFlagTypes {flags} {
    if {$flags & [::_ns_stats.getSchedFlag once]} {
        set types "once"
    } else {
        set types "repeating"
    }

    if {$flags & [::_ns_stats.getSchedFlag daily]} {
        lappend types "daily"
    }

    if {$flags & [::_ns_stats.getSchedFlag weekly]} {
        lappend types "weekly"
    }

    if {$flags & [::_ns_stats.getSchedFlag thread]} {
        lappend types "thread"
    }

    return $types
}

proc ::_ns_stats.fmtSeconds {seconds} {
    if {$seconds == 0} {
        return 0s
    }
    set ms [expr {($seconds - int($seconds))*1000}]
    set seconds [expr {int($seconds)}]
    if {$seconds < 1} {
        return [format %.2f $ms]ms
    }
    if {$seconds < 60} {
        set subseconds [expr {$ms > 0 ? " [format %.2f $ms]ms" : ""}]
        return ${seconds}s$subseconds
    }

    if {$seconds < 3600} {
        set mins [expr {$seconds/60}]
        set secs [expr {$seconds - ($mins * 60)}]

        return "${mins}m ${secs}s"
    }

    set hours [expr {$seconds/3600}]
    set mins  [expr {($seconds - ($hours * 3600))/60}]
    set secs  [expr {$seconds - (($hours * 3600) + ($mins * 60))}]

    if {$hours > 24} {
        set days  [expr {$hours / 24}]
        set hours [expr {$hours % 24}]
        return "${days}d ${hours}h ${mins}m ${secs}s"
    } else {
        return "${hours}h ${mins}m ${secs}s"
    }
}

proc ::_ns_stats.fmtTime {time} {
    if {$time < 0} {
        return "never"
    }
    return [clock format [expr {int($time)}] -format "%H:%M:%S %d-%m-%Y"]
}

proc ::_ns_stats.sortResults {results field numeric {reverse 0}} {
    set ::_sortListTmp(field)     $field
    set ::_sortListTmp(numeric)   $numeric
    set ::_sortListTmp(reverse)   $reverse

    return [lsort -command ::_ns_stats.cmpField $results]
}

proc ::_ns_stats.cmpField {v1 v2} {
    set v1 [lindex $v1 $::_sortListTmp(field)]
    set v2 [lindex $v2 $::_sortListTmp(field)]

    if {$::_sortListTmp(numeric)} {
        if {$::_sortListTmp(reverse)} {
            set cmp [::_ns_stats.cmpNumeric $v2 $v1]
        } else {
            set cmp [::_ns_stats.cmpNumeric $v1 $v2]
        }
    } else {
        if {$::_sortListTmp(reverse)} {
            set cmp [string compare $v2 $v1]
        } else {
            set cmp [string compare $v1 $v2]
        }
    }

    return $cmp
}

proc ::_ns_stats.cmpNumeric {n1 n2} {
    if {$n1 < $n2} {
        return -1
    } elseif {$n1 > $n2} {
        return 1
    }

    return 0
}

proc ::_ns_stats.pretty {keys kvlist {format %.2f}} {
    set stats {}
    set nkeys {}
    foreach k $keys {
        lassign $k key s
        set suffix($key) $s
        lappend nkeys $key
    }
    foreach {k v} $kvlist {
        if {$k in $nkeys} {
            set v [::_ns_stats.hr $v $format]$suffix($k)
        }
        lappend stats $k $v
    }
    return $stats
}

#     set driverCpuSeverity [expr {$driverCpuMax >= 90.0 ? "critical" : $driverCpuMax >= 75.0 ? "warning" : "ok" }]
# set driverCpuSeverity [::_ns_stats.categorize {{90.0 critical} {75.0 warning} {0.0 ok}} $v]
proc ::_ns_stats.categorize {limits value} {
    set category none
    foreach pair $limits {
        lassign $pair threshold category
        if {$value >= $threshold} {
            return $category
        }
    }
    return $categoy
}

proc ::_ns_stats.hr {n {format %.2f}} {
    #
    # Use global setting ::raw for returning raw values
    #
    if {[info exists ::raw] && $::raw} {
        return $n
    }

    #
    # Return the number in human readable form -gn
    #
    #puts format=[format %e $n]
    set r $n
    set units {15 P 12 T 9 G 6 M 3 K 0 "" -3 m -6 µ -9 n}
    if {[regexp {^([0-9.]+)e(.[0-9]+)$} [format %e $n] _ val exp]} {
        set exp [string trimleft $exp +]
        set exp [string trimleft $exp 0]
        if {$exp eq ""} {set exp 0}
        foreach {e u} $units {
            #puts "$exp >= $e"
            if {$exp >= $e} {
                #puts "[format %e $n] $val*10 ** ($exp-$e)"
                set v [format $format [expr {$val*10**($exp-$e)}]]
                if {[string first . $v] > -1} {
                    set v [string trimright [string trimright $v 0] .]
                }
                set r $v$u
                set found 1
                #ns_log notice "NSSTATS result <$n> -> <$r>"
                break
                #puts stderr BREAK
            }
        }
        if {![info exists found]} {
            # ns_log notice "NSSTATS FALL BACK on <$n>"
            # fall back to nano
            set e -9
            if {[regexp {^-0([0-9]+)$} $exp . e1]} {
                set exp -$e1
            }
            #puts "[format %e $n] $val*10 ** ($exp-$e) // exp <$exp>"
            set v [format $format [expr {$val * 10 ** ($exp - $e)}]]
            if {[string first . $v] > -1} {
                set v [string trimright [string trimright $v 0] .]
            }
            set r $v$u
        }
    } else {
        #ns_log notice "NSSTATS FALL BACK NO NUMBER <$n>"
    }
    return $r
}

# Main processing logic
set page [ns_queryget @page]

#ns_log notice severity $severity user $user password $password enabled $enabled debug $debug page $page

#
# raw number display
#
set ::raw [ns_queryget raw 0]
set ::rawparam ""
if {$::raw eq "1"} {
    set ::rawparam "&raw=1"
}

if { ![dict exists $::pluginPages $page] && (![dict exists $::titles $page] || [info commands ::_ns_stats.$page] eq "") } {
    set page process
}

# OpenACS can source this script in a procedure, making page local. Publish the
# validated selection for navigation helpers and ADP templates in either setup.
set ::page $page

proc ::_ns_stats.public_ip {ip} {
    try {
        # For NaviServer 5
        ns_ip public $ip
    } on error {errorMsg} {
        # backwards compatibility
        expr {$ip ni {"127.0.0.1" "::1"}}
    }
}
ns_log $severity "nsstats: enabled $enabled configured user '$user' authuser '[ns_conn authuser]'"

if {$enabled == 0} {
    #
    # When the module is disabled, no access is granted.
    #
    set allowed 0

} elseif {[info commands ::ad_try] ne "" && ("admin" in [ns_conn urlv] || "acs-admin" in  [ns_conn urlv])} {
    #
    # In OpenACS installations, allow access, when we have a site
    # "admin" or a sitewide "acs-admin" link, which are always access
    # checked.
    #
    ns_log $severity "nsstats: OpenACS admin"

    set allowed 1

} elseif {$user ne "" && [info commands ns_perm] ne "" && $user in [lmap {u . .} [ns_perm listusers] {set u}]} {
    #
    # We have the "nsperm" module enabled, and the configured user is
    # in the loaded user table.
    #
    ns_log $severity "nsstats: use nsperm, call 'ns_conn authuser'"
    if {[ns_conn authuser] ne $user} {
        ns_log $severity "ns_conn authuser -> [ns_conn authuser]"
        set allowed 0
        #
        # Add permissions for GET and POST request
        #
        ns_perm allowuser GET  [ns_conn url] $user
        ns_perm allowuser POST [ns_conn url] $user
        #ns_log $severity permissions  \n[join [ns_perm listperms] \n]
    } else {
        # Authentication was already checked by NaviServer via nsperm
        set allowed 1
    }

} elseif {$user ne "" && [ns_conn authuser] eq $user && [ns_conn authpassword] eq $password} {
    #
    # Allow access, when a user is configured, and it is the
    # authenticated user and the password is correct.
    #
    ns_log $severity "check password for user '$user' configured in the file"

    set allowed 1

} elseif {![::_ns_stats.public_ip [ns_conn peeraddr -source direct]]} {
    #
    # Allow access for non-public IP addresses. This is for
    # installation tests on local machines. These addresses are not
    # routed over the public Internet.
    #
    ns_log $severity "nsstats: non-public IP"

    set allowed 1
} else {
    set allowed 0
}
ns_log $severity "nsstats: access for user '$user' granted: $allowed"


# Check user access if configured
if { !$allowed } {
    set html [::_ns_stats.index]
    ns_returnunauthorized
    if {[info exists ::ad_conn(file)]} {
        ad_script_abort
    }
    #return
} else {
    # Produce page
    ns_set iupdate [ns_conn outputheaders] "expires" "now"
    set html [::_ns_stats.renderPage $page]
    if {$html ne ""} {
        if {[info exists ::ad_conn(file)]} {
            set path $::ad_conn(file)
        } else {
            set path [ns_url2file [ns_conn url]]
        }
        set fn [file join {*}[lrange [file split $path] 0 end-1]]/$::templateFile-[ns_info version].adp
        if {![file exists $fn]} {
            set fn [file join {*}[lrange [file split $path] 0 end-1]]/$::templateFile.adp
        }
        #ns_log notice "final script <$fn> path <$path>"
        if {[file exists $fn]} {
            ns_return 200 text/html [ns_adp_parse -file $fn]
        } else {
            ns_return 200 text/html [ns_adp_parse -string $::fallbackTemplate]
        }
    }
    # Direct-response handlers have already returned JSON, a redirect or a
    # download. Abort OpenACS processing for these as well as rendered pages;
    # otherwise its request processor evaluates the companion ADP again.
    if {[info exists ::ad_conn(file)]} {
        ad_script_abort
    }
}
#
# Local variables:
#    mode: tcl
#    tcl-indent-level: 4
#    indent-tabs-mode: nil
# End: