github mickem/nscp 0.17.0

5 hours ago

Windows server roles get their own checks, and check messages finally read like numbers

0.17.0 adds twelve new checks — eight for IIS and Remote Desktop Services on
Windows, four for the status pages of the common web servers — and gives every
filter check control over how it renders numbers, so 140.293GB/0.983TB can
become 141.09GB/1006.85GB (or 141,09GB/1.006,85GB). Alongside that, the
filter engine stops quietly doing the wrong thing: text-versus-number
comparisons are numeric, fractional thresholds mean what they say, and an error
inside a syntax template is reported instead of rendering a blank.

Highlights

  • Eight new Windows checks for IIS and Remote Desktop Services. The new
    CheckWindowsApps module covers IIS sites, application pools, worker
    processes and HTTP.sys request queues, plus RDS CAL licensing, session
    counts, per-session load and the Connection Broker counterset.
  • Four new web-server status checks. check_apache_status,
    check_nginx_status, check_phpfpm_status and check_tomcat_status read the
    vendors' machine-readable status endpoints over HTTP(S), sharing
    check_http's auth and TLS handling.
  • Check messages can be told how to render their numbers. Four new options —
    decimals, byte-unit, decimal-separator, thousands-separator — on every
    filter check and every real-time filter (#1428). Perfdata and thresholds are
    untouched.
  • Filter comparisons between text and a bare number are now numeric.
    filter=value > 90 no longer matches value=100 as false because "100" sorts
    before "90", and 90 > value evaluates at all.
  • Fractional thresholds stop being truncated. count > 2.5 meant count > 3
    and working_set > 1.5g meant 1g; both now mean what they say.
  • Host name placeholders resolve across the whole settings subsystem
    including attachment targets and [/includes] (#458) — and are sanitized
    before they land in a local path 🔒.
  • Syslog submission works again after ten years. SyslogClient read its
    connection settings from the wrong place and sent nothing at all; a configured
    syslog target will start receiving traffic on upgrade.
  • Check-specific filter keywords that shadowed the generic summary keywords
    are renamed,
    with the old names kept as deprecated aliases.

Detailed changes

CheckWindowsApps — a new module for Windows server roles

A new Windows-only module carrying IIS and Remote Desktop Services checks, built
on the performance counter sets and enriched from WMI where the role's provider
is installed. The two roles share one module deliberately: every check module
statically links Boost and the filter engine, so a role earns its own DLL only
when it drags in a heavy or optional dependency (the way CheckMySQL carries
libmariadb.dll).

Command Reports
check_iis_app_pools Per-pool state, uptime and recycles. CRITICAL by default when an auto-start pool is not running; a pool that has never started since boot surfaces as unknown rather than hiding.
check_iis_sites Per-site state, connections and uptime, plus requests_per_sec / bytes_per_sec behind averages=true. CRITICAL by default when an auto-start site is stopped.
check_iis_worker_processes Per-w3wp active and served requests, with the <pid>_<pool> instance name split into keywords. An empty set is OK — idle pools spin their workers down.
check_iis_request_queues Per-queue length, rejections and age, defaulting to HTTP.sys' 1000-request limit (warn > 800, critical > 1000).
check_rds_licenses One record per CAL key pack from Win32_TSLicenseKeyPack: total, issued and available licences. Warns at available < 10 and total > 0, critical at available = 0 and total > 0.
check_rds_sessions Active, inactive and total session counts, all three as perfdata. No default thresholds — the interesting limits are per-farm.
check_rds_session_load One record per session (Console, Services, RDP-Tcp <n>) with CPU, working set and, on session hosts, RDP protocol bytes — the per-user attribution check_process cannot give. sessions-only=true skips the session-0 aggregate.
check_rds_broker The Connection Broker counterset. Counter names vary between Windows Server versions, so the check enumerates whatever the counterset exposes and reports one record per counter instead of hard-coding names.

A host without the role gets a clean UNKNOWN naming the missing role, not a WMI
or PDH error dump. The counter plumbing landed as a reusable gather helper that
collects a set of English counter names for every instance of an object in one
query, keeping the existing localized/English/index resolution fallback — it was
verified against live Swedish-localized counters.

CheckNet — status-page checks for the common web servers

Command Endpoint Keywords
check_apache_status mod_status (?auto appended automatically) workers, requests/s, scoreboard
check_nginx_status stub_status active/reading/writing/waiting, cumulative accepts/handled/requests, derived dropped count
check_phpfpm_status FPM status page processes, listen queue, max_children_reached, slow requests; warns by default when requests queue up
check_tomcat_status manager status?XML=true (appended automatically) per-connector thread pool, request/error counters, JVM heap; defaults fire at 75%/90% pool usage

All four share check_http's connection handling — Basic auth, TLS version /
verify / CA, timeout — and go CRITICAL by default when the endpoint is
unreachable, answers non-2xx, or serves something that is not the expected
status format. Numeric parsing pins the classic locale, so a host with a decimal
comma no longer truncates ReqPerSec at the decimal point.

Filter messages — configurable number rendering

check_drivesize reported 140.293GB/0.983TB used: two units and six decimals
in one line, with no way to change either (#1428). Every filter check now takes
four options, and real-time filters take the same values as settings keys
(decimals, byte unit, decimal separator, thousands separator),
inheritable from the default template.

Option Effect
decimals Exactly N decimals. Default -1 keeps the historical "up to three, trailing zeros stripped". Capped at 15.
byte-unit Pin every byte value to one unit, BEB.
decimal-separator Radix character — , for the European rendering.
thousands-separator Digit grouping for the integer part.
check_drivesize drive=/ show-all=true decimals=2 byte-unit=GB
OK /: 141.09GB/1006.85GB used

check_drivesize drive=/ show-all=true decimals=2 byte-unit=GB \
  decimal-separator=, thousands-separator=.
OK /: 141,09GB/1.006,85GB used

The format lives on the evaluation context, so it reaches the message only:
performance data is built from the raw values and keeps its full precision and
its . radix, and so does every number the filter grammar parses out of a
threshold — warning=used>1.5g means the same thing with a decimal comma in
force.

Three defects in the byte formatter came out of this work:

  • format_bytes(used, 'gb') rendered 1.27055e-10, because the unit comparison
    was case sensitive against an uppercase table. Units are now case insensitive
    everywhere.
  • A unit that matched nothing fell out of the comparison having divided seven
    times, rendering value/1024^7. An unknown unit is now reported —
    Filter processing failed: format_bytes failed: Unknown byte unit: ZB — and
    the same check applies inside real-time filters.
  • format_bytes(value, '') failed to parse at all; the empty string literal is
    now accepted.

The filter/where engine — comparisons that mean what they say

  • Text keyword versus bare number is numeric. A string-typed keyword compared
    against an unquoted number used to order lexically, or — with the operands
    reversed — fail to evaluate. Both sides now compare as numbers. This covers
    value/warn/crit/min/max (filter_perf, render_perf), speed
    (check_network), string_value (check_registry_value) and column()
    (check_logfile). A value that is not a number never matches; the check logs
    one warning naming it and stays a certain non-match, not UNKNOWN. Quoted
    literals keep the lexical comparison, as do like, regexp, in,
    keyword-specific converters (state = 'running', age > 30m) and the
    = 'unknown' / = 'never' sentinels.
  • Fractional numbers survive. count > 2.5 used to be rounded into the
    counter's integer domain, and unit literals lost their fraction entirely
    (working_set > 1.5g meant 1g, uptime < 2.5h meant 2h).
  • filter_perf/render_perf/xform_perf: max and min were swapped.
    max read the perf-data minimum bound and min the maximum; they now read
    the bounds they name.
  • Template errors are reported. A function that failed inside detail-syntax
    or top-syntax left the placeholder empty and said nothing; the check now
    returns UNKNOWN with Filter processing failed: ….
  • perf-config's unit: converts instead of relabelling. On byte series
    that do not auto-scale, unit:KB used to change the label only, shipping
    =1536KB for 1536 bytes. The value and the warn/crit bounds now convert.
    An unrecognised unit leaves the value alone rather than dividing it by 1024⁷.

Filter keywords — the clash with the generic summary keywords is resolved

A handful of checks registered a keyword named status, count or total — the
same names as the built-in summary keywords. The check-specific value won in
filter/warning/critical and detail-syntax, while top-syntax and the
reference documentation showed the generic one. Each now has a distinct name:

Check Old New
check_cpu, check_cpu_utilization total usage
check_battery status battery_status
check_network status, total link_status, throughput
check_os_updates count updates
check_patch_age count patches
check_pending_reboot count signals
check_printjobs status job_status
check_printqueue status printer_status
check_installed_software (Linux) status package_status
check_activation status activation_status
check_docker status container_status
check_connections count, total connections, total_connections
check_dns count records
check_http status status_message
check_shadowcopy count copies
check_disk_health total size

The old names remain as undocumented deprecated aliases with unchanged
behaviour, so check_cpu "warn=total > 80" still works.

Settings — host name placeholders, and where they may land

${host}, ${hostname}, ${hostname_lc}, ${hostname_uc} and ${domain} now
resolve in attachment target paths and in [/includes], not only in settings
urls and the url an attachment is fetched from (#458). An unknown ${...} token
in a path is not an error — it resolves to the installation directory — so a
configuration like [/attachments] ${shared-path}/${host}.ini = … never failed,
it quietly wrote one file with the installation directory in its name.

🔒 Because the host name is not fully under the operator's control (DHCP, or any
local privileged process can set it), a value substituted into a path is
reduced to the characters a legal RFC-952 host name can contain: anything else
becomes _, and a dots-only value becomes _. Settings urls and the submit
clients' host name specs are unaffected. See
Security notices.

nscp settings --migrate-to (and the REST migrate) now keeps a placeholder you
pass it as-is in boot.ini while migrating into the expanded per-host file, the
way --switch already did, so the template survives on a fleet-managed machine.

Clients — submission paths that were quietly dead

  • Syslog. SyslogClient read its connection settings from the sender rather
    than the target, so address, port, facility, severity and templates were all
    ignored: the agent logged Undefined facility: and sent nothing. Broken since
    0.4.3 (2015). CheckMKClient had the same defect on its query path.
  • SMTP. The sender's host name was read from the wrong place, so it was always
    empty and the EHLO fell back to localhost. Set ehlo-hostname on the target
    if your mail server applies HELO/EHLO policy.
  • Short command names. A client command shorter than eight characters — cpu,
    run — answered Exception processing command line: basic_string::substr …
    instead of running, in every module built on the shared client machinery
    (NRPE, NSCA, NRDP, Graphite, …).

CheckSystem — check_pending_reboot says since when

The CBS and Windows Update reboot keys exist only while their reboot is queued,
so their last-write time is when the signal appeared. Two new keywords follow
check_registry's naming: written (type_date, plus written_s) and age,
duration-typed so warning=pending = 1 and age > 7d reads as seven days. The
default message gains (pending since <time>) when the time is known (#1415).
The file-rename, computer-rename and domain-join signals carry no timestamp, so
both keywords are optional: they render as unknown, compare false against every
number and emit no perfdata rather than reporting a misleading value.

Data collection — one bad field no longer sinks the cycle

  • WMI. Win32_Processor.LoadPercentage is occasionally NULL, and row::get_int
    had no case for it: the type-mismatch exception escaped half-way through the
    row and the collector threw away the entire cycle's clock speeds and core
    counts (#1391). Optional fields can now opt into boost::none, mandatory ones
    fail with a clear <col> is NULL instead of localized COM text, and
    check_cpu_frequency renders a missing sample as no load sample rather than
    a fabricated 0.
  • PDH. MaxQueueItemAge on an idle, freshly started queue returns
    PDH_CALC_NEGATIVE_DENOMINATOR, which failed a whole gather even when the
    caller passed ignore_errors — making check_iis_request_queues misreport the
    object as missing. With ignore_errors the counter is now skipped for that
    tick (#642, #906); the background collector and every single-counter check
    still throw, so they hear about an uncomputable counter instead of silently
    reading a default.

Windows installer and file layout

A round of fixes to the modern (ProgramData) layout introduced in 0.16.2:
upgrading an enrolled host resolves every path token instead of failing;
ReadLayout gets the install folder before directories resolve; CURRENT_LAYOUT
is set through the public property setter; an upgrade of a modern host no longer
re-creates nsclient.ini in Program Files; a %ProgramData% that cannot be
resolved fails outright instead of half-applying the layout; migrated files get
the destination's ACL rather than the one they came with; resetting a renamed
tree to inherited strips the explicit ACEs; and --migrate-layout legacy
migrates to legacy instead of silently to modern.

Bug fixes

  • nscp settings --show --path … without a --key used to print nothing and
    exit 0; it now reports Invalid command line please use --path and --key with show and exits non-zero.
  • The settings diff behind the REST diff endpoint kept listing an edit for the
    lifetime of the process after it had been written, reporting a modified entry
    whose old value equalled its new one.
  • Only the pending markers the backend confirms are dropped on save, and staged
    deletions are masked in has_key.
  • Boolean option defaults render as true/false in the generated reference
    instead of garbage.

Documentation

Options shared by every filter check (filter, warning, top-syntax, …) and
the generic filter keywords are now single-sourced: they fold out of each
command's reference page into one shared page, so a command's documentation shows
only what is specific to it. Runtime-stubbed Windows-only checks are marked as
Windows only.

Upgrade notes

  • Syslog starts delivering. If you have a syslog target configured, check it
    still points where you want before upgrading — it has not been delivering,
    and it will now. The same applies to SMTP targets, which will start announcing
    this host in EHLO instead of localhost.
  • Host name placeholders in paths now resolve. Check any ${host},
    ${hostname} or ${domain} under [/attachments] or [/includes] and remove
    workarounds — such a file lands somewhere new after upgrade. 🔒 The value is
    sanitized when it lands in a local path. Configurations without a host name
    placeholder are unaffected.
  • Number rendering is opt-in, but it is all-or-nothing per check. Leave all
    four options unset and messages are byte-for-byte unchanged. Set any of
    them and plain float keywords move onto the number format too: with decimals
    unset they render with up to three decimals instead of the legacy
    6-significant-digit form (2.710942.711), and large values stop rendering
    scientific. A pipeline that matches float text in the message may need its
    pattern relaxed.
  • An unknown unit in format_bytes() now returns UNKNOWN instead of a quietly
    wrong number. A syntax string with a typo'd unit will fail until the unit is
    fixed.
  • perf-config unit: on plain byte series changes the metric's magnitude. A
    dashboard that compensated for the old mislabelling will see the metric drop by
    the unit ratio; a graph flat at a near-zero value because of a misspelled
    unit: will jump to its real magnitude.
  • Filter comparisons against a bare number are numeric. Review any filter that
    deliberately relied on text ordering — quote the number to keep the old
    behaviour.
  • Fractional thresholds change meaning. Whole-number thresholds are unchanged;
    expressions that already used a decimal point can behave differently.
  • max and min in filter_perf/render_perf/xform_perf were swapped. A
    filter that compensated needs the two names exchanged back.
  • Renamed filter keywords keep working through deprecated aliases, but three
    default perfdata keys change because the default perf-config names the renamed
    keyword: check_cpu_utilization (Linux) cpu_totalcpu_usage,
    check_patch_age patch_countpatch_patches, check_pending_reboot
    reboot_countreboot_signals. Pass your own perf-config=extra(...) with
    the old name to keep the old key. check_os_updates' default output now reports
    the actual number of updates instead of the matched-row count.
  • check_pending_reboot's default message gains a suffix
    Reboot required: Windows Update (pending since 2026-08-16 09:41:12).
    Notification pipelines matching the exact message text need their pattern
    relaxed.
  • nscp settings --show without --key now fails. Scripts relying on the
    silent success need the missing --key added.

Full Changelog: 0.16.4...0.17.0

Don't miss a new nscp release

NewReleases is sending notifications on new releases.