From 30b7250f6b25732ae78c39de29db60468d190cf5 Mon Sep 17 00:00:00 2001 From: JeremyFunk Date: Mon, 19 Oct 2026 19:55:02 +0200 Subject: [PATCH 01/11] feat(landing): Windows server monitoring guide --- apps/landing/src/pages/guides/index.astro | 7 + .../guides/windows-server-monitoring.astro | 736 ++++++++++++++++++ apps/landing/src/pages/llms.txt.ts | 1 + 3 files changed, 744 insertions(+) create mode 100644 apps/landing/src/pages/guides/windows-server-monitoring.astro diff --git a/apps/landing/src/pages/guides/index.astro b/apps/landing/src/pages/guides/index.astro index 7ca330c958..26caabde9d 100644 --- a/apps/landing/src/pages/guides/index.astro +++ b/apps/landing/src/pages/guides/index.astro @@ -66,6 +66,13 @@ const investigationGuides = [ description: "Measure the right latency clock, isolate the slow requests, and trace a regression to the release and operation responsible.", read: "14 min", }, + { + href: "/guides/windows-server-monitoring", + label: "Infrastructure", + title: "Windows server monitoring tools and an OpenTelemetry setup", + description: "The Performance Monitor counters and event IDs to watch, the built-in tools, the types of monitoring tools, and a Collector config for metrics, services and event logs.", + read: "12 min", + }, ]; const fieldNotes = (await getCollection("blog") as BlogPost[]) diff --git a/apps/landing/src/pages/guides/windows-server-monitoring.astro b/apps/landing/src/pages/guides/windows-server-monitoring.astro new file mode 100644 index 0000000000..c98c73755d --- /dev/null +++ b/apps/landing/src/pages/guides/windows-server-monitoring.astro @@ -0,0 +1,736 @@ +--- +import Layout from "../../layouts/Layout.astro"; +import Nav from "../../components/Nav.astro"; +import Footer from "../../components/Footer.astro"; +import BottomCTA from "../../components/BottomCTA.astro"; +import Breadcrumb from "../../components/Breadcrumb.astro"; +import FAQ from "../../components/FAQ.astro"; +import SeoHead from "../../components/SeoHead.astro"; +import TableOfContents from "../../components/TableOfContents.astro"; +import { CONTRIBUTORS } from "../../lib/changelog-meta"; + +const pageTitle = "Windows server monitoring tools: built-in, open source and OpenTelemetry"; +const pageDescription = + "Windows server monitoring tools compared by type, the Performance Monitor counters worth watching, and a working OpenTelemetry Collector setup for metrics and event logs."; +const published = "2026-10-19"; +const updatedLabel = "October 19, 2026"; +const author = CONTRIBUTORS.jeremyfunk; +const authorUrl = `https://github.com/${author.github}`; + +const counters: { area: string; counter: string; watch: string; metric: string }[] = [ + { area: "CPU", counter: "\\Processor(_Total)\\% Processor Time", watch: "Above 80 to 90% for several minutes", metric: "system.cpu.utilization" }, + { area: "CPU", counter: "\\System\\Processor Queue Length", watch: "More than about 2 waiting threads per core, sustained", metric: "windows.system.processor_queue_length" }, + { area: "Memory", counter: "\\Memory\\Available MBytes", watch: "Less than about 10% of physical RAM", metric: "system.memory.utilization" }, + { area: "Memory", counter: "\\Memory\\Committed Bytes and \\Memory\\Commit Limit", watch: "Committed bytes approaching the limit", metric: "windows.memory.committed, windows.memory.commit_limit" }, + { area: "Memory", counter: "\\Memory\\Pages/sec", watch: "High and sustained while available memory is low", metric: "windows.memory.pages_per_sec" }, + { area: "Memory", counter: "\\Paging File(_Total)\\% Usage", watch: "Steady growth week over week", metric: "system.paging.usage" }, + { area: "Disk", counter: "\\PhysicalDisk(*)\\Avg. Disk sec/Read and sec/Write", watch: "Above about 20 ms, sustained", metric: "windows.disk.latency" }, + { area: "Disk", counter: "\\PhysicalDisk(*)\\Current Disk Queue Length", watch: "A queue that does not drain between samples", metric: "windows.disk.queue_length" }, + { area: "Disk", counter: "\\LogicalDisk(*)\\% Free Space", watch: "Below 10 to 15% on any volume", metric: "system.filesystem.utilization" }, + { area: "Network", counter: "\\Network Interface(*)\\Bytes Total/sec", watch: "Close to the adapter's Current Bandwidth", metric: "system.network.io" }, + { area: "Network", counter: "\\Network Interface(*)\\Packets Received Errors", watch: "Any count that keeps rising", metric: "system.network.errors" }, + { area: "Process", counter: "\\Process(*)\\Working Set and Handle Count", watch: "One process that grows and never gives memory or handles back", metric: "process.memory.usage, process.handles" }, + { area: "IIS", counter: "\\Web Service(_Total)\\Current Connections", watch: "A jump with no matching rise in requests", metric: "iis.connection.active" }, +]; + +const builtInTools: [string, string][] = [ + ["Task Manager", "Live CPU, memory, disk and network per process, and a Services tab. Nothing is kept once you close it."], + ["Resource Monitor", "Per-process disk I/O with file names and response times, network activity per process, and handle searches. Run resmon.exe."], + ["Performance Monitor", "Live charts of any counter, plus data collector sets that log counters to disk on a schedule. Run perfmon.exe."], + ["Event Viewer", "The System, Application and Security logs with filters on level, source and event ID. Custom views save a filter."], + ["Windows Admin Center", "A browser-based console for one server or many, with performance charts, events, services and processes."], + ["PowerShell", "Get-Counter, Get-WinEvent and Get-Service return the same data as objects you can script, schedule and compare."], +]; + +const toolTypes: { type: string; examples: string; fits: string; gaps: string }[] = [ + { + type: "Built into Windows", + examples: "Performance Monitor, Event Viewer, Resource Monitor, Windows Admin Center", + fits: "Investigating one server while you are logged in to it", + gaps: "No alerting across servers, little history unless you set up collector sets", + }, + { + type: "Microsoft management products", + examples: "System Center Operations Manager, Azure Monitor", + fits: "Estates already standardized on System Center or Azure", + gaps: "Agents, management packs or workspaces to operate and license", + }, + { + type: "Open-source agents", + examples: "OpenTelemetry Collector, windows_exporter, Telegraf, Zabbix agent, NSClient++", + fits: "Sending Windows data to the backend you already run for other systems", + gaps: "You choose the counters, the storage and the alert rules yourself", + }, + { + type: "Commercial monitoring suites", + examples: "SolarWinds Server & Application Monitor, Paessler PRTG, ManageEngine Applications Manager, Datadog", + fits: "Teams that want packaged templates for Windows roles and a vendor to call", + gaps: "Pricing per server, sensor or host; check the vendor's current terms", + }, +]; + +const eventIds: [string, string, string][] = [ + ["System", "41, 6008", "The server restarted without a clean shutdown"], + ["System", "1074", "A user or process started a restart or shutdown"], + ["System", "7031, 7034", "A service terminated unexpectedly"], + ["System", "2004", "Resource Exhaustion Detector: the system is low on virtual memory"], + ["Application", "1000", "An application crashed"], + ["Security", "4625", "A logon attempt failed"], + ["Security", "4740", "An account was locked out"], + ["Security", "1102", "The audit log was cleared"], +]; + +const getCounterExample = `# One counter, sampled every 5 seconds for a minute +Get-Counter '\\Processor(_Total)\\% Processor Time' -SampleInterval 5 -MaxSamples 12 + +# Commit charge against the commit limit +Get-Counter '\\Memory\\Committed Bytes', '\\Memory\\Commit Limit', '\\Memory\\% Committed Bytes In Use' + +# Disk latency for every physical disk +Get-Counter '\\PhysicalDisk(*)\\Avg. Disk sec/Read', '\\PhysicalDisk(*)\\Avg. Disk sec/Write' + +# Every counter in a set +(Get-Counter -ListSet Memory).Counter`; + +const logmanExample = `logman create counter Baseline -f bin -si 00:00:15 -o C:\\PerfLogs\\Baseline ^ + -c "\\Processor(_Total)\\% Processor Time" "\\Memory\\Available MBytes" ^ + "\\Memory\\Committed Bytes" "\\PhysicalDisk(*)\\Avg. Disk sec/Transfer" +logman start Baseline`; + +const eventsAndServicesExample = `# Critical and error events in the System log, last 24 hours +Get-WinEvent -FilterHashtable @{ LogName = 'System'; Level = 1, 2; StartTime = (Get-Date).AddDays(-1) } + +# Automatic services that are not running +Get-Service | Where-Object { $_.StartType -eq 'Automatic' -and $_.Status -ne 'Running' } + +# The ten processes that have used the most CPU time +Get-Process | Sort-Object CPU -Descending | Select-Object -First 10 Name, Id, CPU, WS`; + +const installExample = `$v = '0.163.1' +$msi = "$env:TEMP\\otelcol-contrib.msi" +Invoke-WebRequest -OutFile $msi -Uri "https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v$v/otelcol-contrib_\${v}_windows_x64.msi" +Start-Process msiexec.exe -ArgumentList "/i \`"$msi\`" /qn" -Wait +Get-Service otelcol-contrib`; + +const collectorConfig = `extensions: + file_storage: + directory: C:\\ProgramData\\OpenTelemetry Collector\\storage + create_directory: true + +receivers: + hostmetrics: + collection_interval: 30s + scrapers: + cpu: + metrics: + system.cpu.utilization: + enabled: true + memory: + metrics: + system.memory.utilization: + enabled: true + filesystem: + metrics: + system.filesystem.utilization: + enabled: true + load: {} + disk: {} + network: {} + paging: {} + process: + include: + names: [w3wp.exe, sqlservr.exe] # the processes you care about + match_type: strict + mute_process_name_error: true + metrics: + process.handles: + enabled: true + + windows_perf_counters: + collection_interval: 30s + metrics: + windows.memory.committed: + description: Memory\\Committed Bytes + unit: By + gauge: + windows.memory.commit_limit: + description: Memory\\Commit Limit + unit: By + gauge: + windows.memory.pages_per_sec: + description: Memory\\Pages/sec + unit: "{page}/s" + gauge: + windows.disk.latency: + description: PhysicalDisk\\Avg. Disk sec/Read and sec/Write + unit: s + gauge: + windows.disk.queue_length: + description: PhysicalDisk\\Current Disk Queue Length + unit: "{request}" + gauge: + windows.system.processor_queue_length: + description: System\\Processor Queue Length + unit: "{thread}" + gauge: + perfcounters: + - object: Memory + counters: + - name: Committed Bytes + metric: windows.memory.committed + - name: Commit Limit + metric: windows.memory.commit_limit + - name: Pages/sec + metric: windows.memory.pages_per_sec + - object: PhysicalDisk + instances: "*" + counters: + - name: Avg. Disk sec/Read + metric: windows.disk.latency + attributes: + direction: read + - name: Avg. Disk sec/Write + metric: windows.disk.latency + attributes: + direction: write + - name: Current Disk Queue Length + metric: windows.disk.queue_length + - object: System + counters: + - name: Processor Queue Length + metric: windows.system.processor_queue_length + + windows_service: + collection_interval: 1m + include_services: [W3SVC, WinRM] # the services this server must keep running + + iis: # remove on servers without IIS + collection_interval: 30s + + windows_event_log/system: + channel: system + storage: file_storage + resource: + service.name: windows-eventlog + + windows_event_log/application: + channel: application + storage: file_storage + exclude_providers: [otelcol-contrib] + resource: + service.name: windows-eventlog + + windows_event_log/security: + storage: file_storage + query: | + + + + + + resource: + service.name: windows-eventlog + +processors: + resourcedetection: + detectors: [env, system] + batch: {} + +exporters: + otlphttp/maple: + endpoint: https://ingest.maple.dev + compression: gzip + headers: + x-maple-ingest-key: \${env:MAPLE_INGEST_KEY} + +service: + extensions: [file_storage] + pipelines: + metrics: + receivers: [hostmetrics, windows_perf_counters, windows_service, iis] + processors: [resourcedetection, batch] + exporters: [otlphttp/maple] + logs: + receivers: [windows_event_log/system, windows_event_log/application, windows_event_log/security] + processors: [resourcedetection, batch] + exporters: [otlphttp/maple]`; + +const serviceEnvExample = `$dir = 'C:\\Program Files\\OpenTelemetry Collector' +$svc = 'HKLM:\\SYSTEM\\CurrentControlSet\\Services\\otelcol-contrib' +New-ItemProperty -Path $svc -Name Environment -PropertyType MultiString -Value 'MAPLE_INGEST_KEY=maple_sk_your_key' -Force + +# Check the file before restarting the service +$env:MAPLE_INGEST_KEY = 'maple_sk_your_key' +& "$dir\\otelcol-contrib.exe" validate --config "$dir\\config.yaml" +Restart-Service otelcol-contrib`; + +const perHostCpuSql = `SELECT $__timeGroup(TimeUnix) AS bucket, + ResourceAttributes['host.name'] AS group, + avg(Value) AS value, + count() AS samples +FROM metrics_gauge +WHERE $__orgFilter AND $__timeFilter(TimeUnix) + AND MetricName = 'system.cpu.utilization' + AND Attributes['state'] = 'idle' +GROUP BY bucket, group +ORDER BY bucket`; + +const toc = [ + ["counters", "Counters to watch"], + ["built-in", "Built-in tools"], + ["tool-types", "Types of tools"], + ["collector", "OpenTelemetry setup"], + ["event-logs", "Event logs"], + ["maple", "In Maple"], +] as const; +const tocHeadings = toc.map(([slug, text]) => ({ depth: 2, slug, text })); + +const faqs = [ + { + question: "How do I monitor a Windows server?", + answer: + "Decide on a short list of signals: CPU and processor queue, available memory and commit charge, disk latency and free space, network errors, the services the server must keep running, and the System and Application event logs. Check them by hand with Performance Monitor and Event Viewer. To keep history and get alerts, run an agent such as the OpenTelemetry Collector as a Windows service and send the data to a monitoring backend.", + }, + { + question: "What are the best free Windows server monitoring tools?", + answer: + "Windows itself includes Performance Monitor, Resource Monitor, Event Viewer and PowerShell's Get-Counter, and Windows Admin Center is a free download for Windows Server. Open-source agents include the OpenTelemetry Collector, the Prometheus windows_exporter, Telegraf and the Zabbix agent. The built-in tools cover one server at a time; the agents need a backend that stores the data and runs alerts.", + }, + { + question: "Which Performance Monitor counters matter most on Windows Server?", + answer: + "Start with Processor(_Total)\\% Processor Time and System\\Processor Queue Length for CPU, Memory\\Available MBytes and Memory\\Committed Bytes against Memory\\Commit Limit for memory, PhysicalDisk\\Avg. Disk sec/Read and sec/Write for disk latency, LogicalDisk\\% Free Space, and Network Interface\\Bytes Total/sec. Add role counters such as Web Service\\Current Connections for IIS.", + }, + { + question: "How do I monitor Windows event logs?", + answer: + "Event Viewer and Get-WinEvent read the logs on one machine. To collect them centrally, use Windows Event Forwarding or an agent. The OpenTelemetry Collector's windows_event_log receiver reads a channel such as System or Application, or an XML query that selects specific event IDs, and sends each event as an OpenTelemetry log record with its level mapped to a severity.", + }, + { + question: "How do I get an alert when CPU is high on a Windows server?", + answer: + "Collect system.cpu.utilization with the OpenTelemetry Collector's hostmetrics receiver, then alert on its idle share: when the average for state idle stays below 0.1, the CPU has been more than 90% busy. In Maple, open the metric on the Metrics page, filter attr.state to idle, choose Create alert, set the condition to below 0.1 over a 10-minute window, and lower Min samples so a window with 20 data points is evaluated.", + }, + { + question: "Does the OpenTelemetry Collector run on Windows Server?", + answer: + "Yes. The opentelemetry-collector-releases project publishes MSI installers for the contrib distribution. The installer registers a Windows service named otelcol-contrib that runs as LocalSystem and reads config.yaml from C:\\Program Files\\OpenTelemetry Collector.", + }, +]; + +const siteUrl = Astro.site?.toString().replace(/\/$/, "") ?? "https://maple.dev"; +const articleJsonLd = { + "@context": "https://schema.org", + "@type": "TechArticle", + headline: "Windows server monitoring tools and setup with OpenTelemetry", + description: pageDescription, + datePublished: published, + dateModified: published, + author: { "@type": "Person", name: author.name, url: authorUrl }, + publisher: { + "@type": "Organization", + name: "Maple", + logo: { "@type": "ImageObject", url: `${siteUrl}/logo192.png` }, + }, + mainEntityOfPage: `${siteUrl}/guides/windows-server-monitoring`, +}; +--- + + + + +