From 92483242ba375bd287ea3718236de4886e61aea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Wed, 7 Oct 2026 23:26:47 +0200 Subject: [PATCH] scheduled_task: expose result statuses and deprecate last_result (#2532) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Claude Opus 5.5 Signed-off-by: Jan-Otto Kröpke --- docs/collector.scheduled_task.md | 91 +++++- .../scheduled_task/scheduled_task.go | 98 +++++- .../scheduled_task_internal_test.go | 304 ++++++++++++++++++ tools/e2e-output.txt | 18 +- 4 files changed, 492 insertions(+), 19 deletions(-) create mode 100644 internal/collector/scheduled_task/scheduled_task_internal_test.go diff --git a/docs/collector.scheduled_task.md b/docs/collector.scheduled_task.md index e86eac4d..9384575c 100644 --- a/docs/collector.scheduled_task.md +++ b/docs/collector.scheduled_task.md @@ -12,13 +12,13 @@ Enabled by default? | No ### `--collector.scheduled_task.include` -If given, the path of the task needs to match the include regexp in order for the corresponding metrics to be reported. +If given, the path of the task needs to match the include regular expression in order for the corresponding metrics to be reported. E.G. `--collector.scheduled_task.include="Firefox.*"` ### `--collector.scheduled_task.exclude` -If given, the path of the task needs to *not* match the exclude regexp in order for the corresponding metrics to be reported. +If given, the path of the task needs to *not* match the exclude regular expression in order for the corresponding metrics to be reported. E.G. `--collector.scheduled_task.exclude="/Microsoft/.+"` @@ -26,11 +26,53 @@ E.G. `--collector.scheduled_task.exclude="/Microsoft/.+"` Name | Description | Type | Labels -----|-------------|------|------- -`windows_scheduled_task_last_result` | The result that was returned the last time the registered task was run | gauge | task +`windows_scheduled_task_last_result` | **Deprecated:** use `windows_scheduled_task_last_result_status`. 1 if the last result code is zero, 0 otherwise; omitted for tasks that have never run | gauge | task +`windows_scheduled_task_last_result_status` | The last result status of a scheduled task, 1 if the current status, 0 otherwise | gauge | task, status `windows_scheduled_task_missed_runs` | The number of times the registered task missed a scheduled run | gauge | task `windows_scheduled_task_state` | The current state of a scheduled task | gauge | task, state -For the values of the `state` label, see below. +For the values of the `state` and `status` labels, see below. + +### Last result + +`windows_scheduled_task_last_result_status` uses the same enum convention as +`windows_service_state`: every status is exported for each task, with exactly one +status set to 1 and all others set to 0. The status metric is exported even when a +task has never run. + +Status | Result code (decimal) | Result code (hexadecimal) | Meaning +-------|-----------------------|---------------------------|-------- +`success` | 0 | `0x00000000` | The task completed successfully +`ready` | 267008 | `0x00041300` | The task is ready to run +`running` | 267009 | `0x00041301` | The task is currently running +`disabled` | 267010 | `0x00041302` | The task is disabled +`has_not_run` | 267011 | `0x00041303` | The task has never run +`no_more_runs` | 267012 | `0x00041304` | No more runs are scheduled +`not_scheduled` | 267013 | `0x00041305` | Required scheduling properties are missing +`terminated` | 267014 | `0x00041306` | The last run was terminated by the user +`no_valid_triggers` | 267015 | `0x00041307` | No triggers exist or all triggers are disabled +`event_trigger` | 267016 | `0x00041308` | Event triggers do not have set run times +`queued` | 267045 | `0x00041325` | The task has been queued to run +`unknown` | Any other code | Any other code | The result code has no named status mapping + +Application exit codes, scheduler errors, and unrecognized results are grouped +under `unknown`. This status means the result code has no named mapping; it does +not establish whether the task succeeded or failed. +See [Microsoft's Task Scheduler result codes](https://learn.microsoft.com/en-us/windows/win32/taskschd/task-scheduler-error-and-success-constants) +for details. + +### Deprecated metric + +`windows_scheduled_task_last_result` is deprecated. Use +`windows_scheduled_task_last_result_status` to distinguish success, running, +tasks that have never run, and other scheduler statuses. For example, use +`windows_scheduled_task_last_result_status{status="success"} == 1` to select +successful results. + +The deprecated metric remains available with its existing labels and values for +compatibility. A running result code still produces 0, which does not necessarily +mean the task failed. `windows_scheduled_task_last_result` and +`windows_scheduled_task_missed_runs` are omitted when the result is `has_not_run`. ### State @@ -46,6 +88,18 @@ A task can be in the following states: ``` windows_scheduled_task_last_result{task="/Microsoft/Windows/Chkdsk/SyspartRepair"} 1 +windows_scheduled_task_last_result_status{status="success",task="/Microsoft/Windows/Chkdsk/SyspartRepair"} 1 +windows_scheduled_task_last_result_status{status="ready",task="/Microsoft/Windows/Chkdsk/SyspartRepair"} 0 +windows_scheduled_task_last_result_status{status="running",task="/Microsoft/Windows/Chkdsk/SyspartRepair"} 0 +windows_scheduled_task_last_result_status{status="disabled",task="/Microsoft/Windows/Chkdsk/SyspartRepair"} 0 +windows_scheduled_task_last_result_status{status="has_not_run",task="/Microsoft/Windows/Chkdsk/SyspartRepair"} 0 +windows_scheduled_task_last_result_status{status="no_more_runs",task="/Microsoft/Windows/Chkdsk/SyspartRepair"} 0 +windows_scheduled_task_last_result_status{status="not_scheduled",task="/Microsoft/Windows/Chkdsk/SyspartRepair"} 0 +windows_scheduled_task_last_result_status{status="terminated",task="/Microsoft/Windows/Chkdsk/SyspartRepair"} 0 +windows_scheduled_task_last_result_status{status="no_valid_triggers",task="/Microsoft/Windows/Chkdsk/SyspartRepair"} 0 +windows_scheduled_task_last_result_status{status="event_trigger",task="/Microsoft/Windows/Chkdsk/SyspartRepair"} 0 +windows_scheduled_task_last_result_status{status="queued",task="/Microsoft/Windows/Chkdsk/SyspartRepair"} 0 +windows_scheduled_task_last_result_status{status="unknown",task="/Microsoft/Windows/Chkdsk/SyspartRepair"} 0 windows_scheduled_task_missed_runs{task="/Microsoft/Windows/Chkdsk/SyspartRepair"} 0 windows_scheduled_task_state{state="disabled",task="/Microsoft/Windows/Chkdsk/SyspartRepair"} 1 windows_scheduled_task_state{state="queued",task="/Microsoft/Windows/Chkdsk/SyspartRepair"} 0 @@ -55,13 +109,38 @@ windows_scheduled_task_state{state="unknown",task="/Microsoft/Windows/Chkdsk/Sys ``` ## Useful queries -_This collector does not yet have any useful queries added, we would appreciate your help adding them!_ + +Current result status for each task: + +```promql +windows_scheduled_task_last_result_status == 1 +``` + +Tasks that have never run: + +```promql +windows_scheduled_task_last_result_status{status="has_not_run"} == 1 +``` + +For dashboards still using the deprecated success/failure metric, exclude running tasks: + +```promql +(windows_scheduled_task_last_result == 0) +unless on (job, instance, task) +(windows_scheduled_task_state{state="running"} == 1) +``` ## Alerting examples + +This example treats unknown or terminated results as failures. An `unknown` +status only means that the result code has no named mapping; adjust the alert +selection for your task's exit-code conventions. Running tasks report the +`running` status, so they do not match this alert. + **prometheus.rules** ```yaml - alert: "WindowsScheduledTaskFailure" - expr: "windows_scheduled_task_last_result == 0" + expr: 'windows_scheduled_task_last_result_status{status=~"unknown|terminated"} == 1' for: "1d" labels: severity: "high" diff --git a/internal/collector/scheduled_task/scheduled_task.go b/internal/collector/scheduled_task/scheduled_task.go index 4e47fdd8..685af589 100644 --- a/internal/collector/scheduled_task/scheduled_task.go +++ b/internal/collector/scheduled_task/scheduled_task.go @@ -50,16 +50,19 @@ var ConfigDefaults = Config{ type Collector struct { config Config - lastResult *prometheus.Desc - missedRuns *prometheus.Desc - state *prometheus.Desc + lastResult *prometheus.Desc + lastResultStatus *prometheus.Desc + missedRuns *prometheus.Desc + state *prometheus.Desc } // TaskState ... // https://docs.microsoft.com/en-us/windows/desktop/api/taskschd/ne-taskschd-task_state type TaskState uint -type TaskResult uint +// TaskResult preserves the unsigned 32-bit representation of a result code, +// including HRESULTs returned as signed LONG values by Task Scheduler. +type TaskResult uint32 const ( TASK_STATE_UNKNOWN TaskState = iota @@ -70,8 +73,17 @@ const ( ) const ( - SCHED_S_SUCCESS TaskResult = 0x0 - SCHED_S_TASK_HAS_NOT_RUN TaskResult = 0x00041303 + SCHED_S_SUCCESS TaskResult = 0x0 + SCHED_S_TASK_READY TaskResult = 0x00041300 + SCHED_S_TASK_RUNNING TaskResult = 0x00041301 + SCHED_S_TASK_DISABLED TaskResult = 0x00041302 + SCHED_S_TASK_HAS_NOT_RUN TaskResult = 0x00041303 + SCHED_S_TASK_NO_MORE_RUNS TaskResult = 0x00041304 + SCHED_S_TASK_NOT_SCHEDULED TaskResult = 0x00041305 + SCHED_S_TASK_TERMINATED TaskResult = 0x00041306 + SCHED_S_TASK_NO_VALID_TRIGGERS TaskResult = 0x00041307 + SCHED_S_EVENT_TRIGGER TaskResult = 0x00041308 + SCHED_S_TASK_QUEUED TaskResult = 0x00041325 ) type ScheduledTask struct { @@ -152,11 +164,19 @@ func (c *Collector) Close() error { func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { c.lastResult = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "last_result"), - "The result that was returned the last time the registered task was run", + "DEPRECATED: use windows_scheduled_task_last_result_status. "+ + "1 if the last result code of the registered task is zero, 0 otherwise", []string{"task"}, nil, ) + c.lastResultStatus = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "last_result_status"), + "The last result status of a scheduled task, 1 if the current status, 0 otherwise", + []string{"task", "status"}, + nil, + ) + c.missedRuns = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "missed_runs"), "The number of times the registered task missed a scheduled run", @@ -181,12 +201,24 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric, _ time.Duration) error //nolint:gochecknoglobals var TASK_STATES = []string{"disabled", "queued", "ready", "running", "unknown"} +//nolint:gochecknoglobals +var TASK_RESULT_STATUSES = []string{ + "success", "ready", "running", "disabled", "has_not_run", "no_more_runs", + "not_scheduled", "terminated", "no_valid_triggers", "event_trigger", "queued", "unknown", +} + func (c *Collector) collect(ch chan<- prometheus.Metric) error { scheduledTasks, err := getScheduledTasks() if err != nil { return fmt.Errorf("get scheduled tasks: %w", err) } + c.collectMetrics(ch, scheduledTasks) + + return nil +} + +func (c *Collector) collectMetrics(ch chan<- prometheus.Metric, scheduledTasks ScheduledTasks) { for _, task := range scheduledTasks { if c.config.TaskExclude.MatchString(task.Path) || !c.config.TaskInclude.MatchString(task.Path) { @@ -209,6 +241,24 @@ func (c *Collector) collect(ch chan<- prometheus.Metric) error { ) } + resultStatus := task.LastTaskResult.String() + + for _, status := range TASK_RESULT_STATUSES { + var statusValue float64 + + if resultStatus == status { + statusValue = 1 + } + + ch <- prometheus.MustNewConstMetric( + c.lastResultStatus, + prometheus.GaugeValue, + statusValue, + task.Path, + status, + ) + } + if task.LastTaskResult == SCHED_S_TASK_HAS_NOT_RUN { continue } @@ -232,8 +282,6 @@ func (c *Collector) collect(ch chan<- prometheus.Metric) error { task.Path, ) } - - return nil } const SCHEDULED_TASK_PROGRAM_ID = "Schedule.Service.1" @@ -253,8 +301,7 @@ func getScheduledTasks() (ScheduledTasks, error) { defer runtime.UnlockOSThread() if err := ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED|ole.COINIT_DISABLE_OLE1DDE); err != nil { - var oleCode *ole.OleError - if errors.As(err, &oleCode) && oleCode.Code() != ole.S_OK && oleCode.Code() != S_FALSE { + if oleCode, ok := errors.AsType[*ole.OleError](err); ok && oleCode.Code() != ole.S_OK && oleCode.Code() != S_FALSE { return nil, err } } @@ -442,3 +489,32 @@ func (t TaskState) String() string { return "" } } + +func (t TaskResult) String() string { + switch t { + case SCHED_S_SUCCESS: + return "success" + case SCHED_S_TASK_READY: + return "ready" + case SCHED_S_TASK_RUNNING: + return "running" + case SCHED_S_TASK_DISABLED: + return "disabled" + case SCHED_S_TASK_HAS_NOT_RUN: + return "has_not_run" + case SCHED_S_TASK_NO_MORE_RUNS: + return "no_more_runs" + case SCHED_S_TASK_NOT_SCHEDULED: + return "not_scheduled" + case SCHED_S_TASK_TERMINATED: + return "terminated" + case SCHED_S_TASK_NO_VALID_TRIGGERS: + return "no_valid_triggers" + case SCHED_S_EVENT_TRIGGER: + return "event_trigger" + case SCHED_S_TASK_QUEUED: + return "queued" + default: + return "unknown" + } +} diff --git a/internal/collector/scheduled_task/scheduled_task_internal_test.go b/internal/collector/scheduled_task/scheduled_task_internal_test.go new file mode 100644 index 00000000..4bba5f92 --- /dev/null +++ b/internal/collector/scheduled_task/scheduled_task_internal_test.go @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build windows + +package scheduled_task + +import ( + "regexp" + "testing" + + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" + "github.com/stretchr/testify/require" +) + +func TestCollectMetrics(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + name string + result int64 + state TaskState + stateLabel string + status string + }{ + { + name: "success", result: 0, + state: TASK_STATE_READY, stateLabel: "ready", status: "success", + }, + { + name: "application error", result: 1, + state: TASK_STATE_READY, stateLabel: "ready", status: "unknown", + }, + { + name: "ready", result: 0x41300, + state: TASK_STATE_READY, stateLabel: "ready", status: "ready", + }, + { + name: "running", result: 0x41301, + state: TASK_STATE_RUNNING, stateLabel: "running", status: "running", + }, + { + name: "disabled", result: 0x41302, + state: TASK_STATE_DISABLED, stateLabel: "disabled", status: "disabled", + }, + { + name: "never run", result: 0x41303, + state: TASK_STATE_UNKNOWN, stateLabel: "unknown", status: "has_not_run", + }, + { + name: "no more runs", result: 0x41304, + state: TASK_STATE_READY, stateLabel: "ready", status: "no_more_runs", + }, + { + name: "not scheduled", result: 0x41305, + state: TASK_STATE_READY, stateLabel: "ready", status: "not_scheduled", + }, + { + name: "terminated", result: 0x41306, + state: TASK_STATE_DISABLED, stateLabel: "disabled", status: "terminated", + }, + { + name: "no valid triggers", result: 0x41307, + state: TASK_STATE_READY, stateLabel: "ready", status: "no_valid_triggers", + }, + { + name: "event trigger", result: 0x41308, + state: TASK_STATE_READY, stateLabel: "ready", status: "event_trigger", + }, + { + name: "queued", result: 0x41325, + state: TASK_STATE_QUEUED, stateLabel: "queued", status: "queued", + }, + { + name: "signed HRESULT", result: -2147216629, + state: TASK_STATE_READY, stateLabel: "ready", status: "unknown", + }, + { + name: "unsigned HRESULT", result: 0x8004130B, + state: TASK_STATE_READY, stateLabel: "ready", status: "unknown", + }, + { + name: "unrecognized result", result: -559038737, + state: TASK_STATE_READY, stateLabel: "ready", status: "unknown", + }, + { + name: "maximum code", result: -1, + state: TASK_STATE_READY, stateLabel: "ready", status: "unknown", + }, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + task := ScheduledTask{ + Path: "/Test/Task", + State: tc.state, + MissedRunsCount: 3, + LastTaskResult: TaskResult(tc.result), + } + families := gatherTaskMetrics(t, nil, ScheduledTasks{task}) + + states := families["windows_scheduled_task_state"] + require.NotNil(t, states) + require.Equal(t, dto.MetricType_GAUGE, states.GetType()) + require.Len(t, states.GetMetric(), 5) + + expectedStates := map[string]float64{"disabled": 0, "queued": 0, "ready": 0, "running": 0, "unknown": 0} + expectedStates[tc.stateLabel] = 1 + + for _, metric := range states.GetMetric() { + require.Len(t, metric.GetLabel(), 2) + require.Equal(t, "state", metric.GetLabel()[0].GetName()) + require.Equal(t, "task", metric.GetLabel()[1].GetName()) + require.Equal(t, task.Path, metric.GetLabel()[1].GetValue()) + state := metric.GetLabel()[0].GetValue() + want, ok := expectedStates[state] + require.True(t, ok, "unexpected or duplicate state %s", state) + require.InDelta(t, want, metric.GetGauge().GetValue(), 0) + delete(expectedStates, state) + } + + require.Empty(t, expectedStates) + + statuses := families["windows_scheduled_task_last_result_status"] + require.NotNil(t, statuses) + require.Equal(t, dto.MetricType_GAUGE, statuses.GetType()) + require.Len(t, statuses.GetMetric(), 12) + + expectedStatuses := map[string]float64{ + "success": 0, "ready": 0, "running": 0, "disabled": 0, + "has_not_run": 0, "no_more_runs": 0, "not_scheduled": 0, "terminated": 0, + "no_valid_triggers": 0, "event_trigger": 0, "queued": 0, "unknown": 0, + } + expectedStatuses[tc.status] = 1 + + for _, metric := range statuses.GetMetric() { + require.Len(t, metric.GetLabel(), 2) + require.Equal(t, "status", metric.GetLabel()[0].GetName()) + require.Equal(t, "task", metric.GetLabel()[1].GetName()) + require.Equal(t, task.Path, metric.GetLabel()[1].GetValue()) + status := metric.GetLabel()[0].GetValue() + want, ok := expectedStatuses[status] + require.True(t, ok, "unexpected or duplicate status %s", status) + require.InDelta(t, want, metric.GetGauge().GetValue(), 0) + delete(expectedStatuses, status) + } + + require.Empty(t, expectedStatuses) + + if tc.result == 0x41303 { + require.Len(t, families, 2) + require.NotContains(t, families, "windows_scheduled_task_last_result") + require.NotContains(t, families, "windows_scheduled_task_missed_runs") + + return + } + + require.Len(t, families, 4) + lastResult := families["windows_scheduled_task_last_result"] + require.NotNil(t, lastResult) + require.Len(t, lastResult.GetMetric(), 1) + + wantSuccess := 0.0 + if tc.result == 0 { + wantSuccess = 1 + } + + require.InDelta(t, wantSuccess, lastResult.GetMetric()[0].GetGauge().GetValue(), 0) + + missedRuns := families["windows_scheduled_task_missed_runs"] + require.NotNil(t, missedRuns) + require.Len(t, missedRuns.GetMetric(), 1) + require.InDelta(t, task.MissedRunsCount, missedRuns.GetMetric()[0].GetGauge().GetValue(), 0) + }) + } +} + +func TestCollectMetricsFilters(t *testing.T) { + t.Parallel() + + tasks := ScheduledTasks{ + {Path: "/Included/Task", State: TASK_STATE_READY}, + {Path: "/Included/Excluded", State: TASK_STATE_READY}, + {Path: "/Other/Task", State: TASK_STATE_READY}, + } + + for _, tc := range []struct { + name string + config *Config + tasks ScheduledTasks + paths []string + }{ + {name: "empty", tasks: ScheduledTasks{}, paths: []string{}}, + {name: "defaults", tasks: tasks, paths: []string{"/Included/Task", "/Included/Excluded", "/Other/Task"}}, + { + name: "include", + config: &Config{TaskInclude: regexp.MustCompile(`^/Included/`)}, + tasks: tasks, + paths: []string{"/Included/Task", "/Included/Excluded"}, + }, + { + name: "exclude takes precedence", + config: &Config{ + TaskInclude: regexp.MustCompile(`^/Included/`), + TaskExclude: regexp.MustCompile(`Excluded$`), + }, + tasks: tasks, + paths: []string{"/Included/Task"}, + }, + { + name: "all excluded", + config: &Config{TaskExclude: regexp.MustCompile(`.*`)}, + tasks: tasks, + paths: []string{}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + families := gatherTaskMetrics(t, tc.config, tc.tasks) + if len(tc.paths) == 0 { + require.Empty(t, families) + + return + } + + require.Len(t, families, 4) + + for _, family := range families { + paths := make([]string, 0, len(family.GetMetric())) + + for _, metric := range family.GetMetric() { + for _, label := range metric.GetLabel() { + if label.GetName() == "task" { + paths = append(paths, label.GetValue()) + } + } + } + + samplesPerTask := 1 + if family.GetName() == "windows_scheduled_task_state" { + samplesPerTask = 5 + } + + if family.GetName() == "windows_scheduled_task_last_result_status" { + samplesPerTask = 12 + } + + expected := make([]string, 0, samplesPerTask*len(tc.paths)) + for range samplesPerTask { + expected = append(expected, tc.paths...) + } + + require.ElementsMatch(t, expected, paths, "%s", family.GetName()) + } + }) + } +} + +func gatherTaskMetrics(t *testing.T, config *Config, tasks ScheduledTasks) map[string]*dto.MetricFamily { + t.Helper() + + c := New(config) + require.NoError(t, c.Build(nil, nil)) + + registry := prometheus.NewPedanticRegistry() + require.NoError(t, registry.Register(taskMetricCollector{collector: c, tasks: tasks})) + families, err := registry.Gather() + require.NoError(t, err) + + result := make(map[string]*dto.MetricFamily, len(families)) + + for _, family := range families { + result[family.GetName()] = family + } + + return result +} + +type taskMetricCollector struct { + collector *Collector + tasks ScheduledTasks +} + +func (c taskMetricCollector) Describe(ch chan<- *prometheus.Desc) { + prometheus.DescribeByCollect(c, ch) +} + +func (c taskMetricCollector) Collect(ch chan<- prometheus.Metric) { + c.collector.collectMetrics(ch, c.tasks) +} diff --git a/tools/e2e-output.txt b/tools/e2e-output.txt index 19f62d6a..162435c5 100644 --- a/tools/e2e-output.txt +++ b/tools/e2e-output.txt @@ -1,4 +1,4 @@ -# HELP test_alpha_total Some random metric. +# HELP test_alpha_total Some random metric. # TYPE test_alpha_total counter test_alpha_total 42 # HELP windows_cache_async_copy_reads_total (AsyncCopyReadsTotal) @@ -325,9 +325,23 @@ windows_exporter_collector_timeout{collector="udp"} 0 # TYPE windows_physical_disk_write_seconds_total counter # HELP windows_physical_disk_writes_total The number of write operations on the disk (PhysicalDisk.DiskWritesPerSec) # TYPE windows_physical_disk_writes_total counter -# HELP windows_scheduled_task_last_result The result that was returned the last time the registered task was run +# HELP windows_scheduled_task_last_result DEPRECATED: use windows_scheduled_task_last_result_status. 1 if the last result code of the registered task is zero, 0 otherwise # TYPE windows_scheduled_task_last_result gauge windows_scheduled_task_last_result{task="/Microsoft/Windows/PLA/GAEvents"} 0 +# HELP windows_scheduled_task_last_result_status The last result status of a scheduled task, 1 if the current status, 0 otherwise +# TYPE windows_scheduled_task_last_result_status gauge +windows_scheduled_task_last_result_status{status="disabled",task="/Microsoft/Windows/PLA/GAEvents"} 0 +windows_scheduled_task_last_result_status{status="event_trigger",task="/Microsoft/Windows/PLA/GAEvents"} 0 +windows_scheduled_task_last_result_status{status="has_not_run",task="/Microsoft/Windows/PLA/GAEvents"} 0 +windows_scheduled_task_last_result_status{status="no_more_runs",task="/Microsoft/Windows/PLA/GAEvents"} 0 +windows_scheduled_task_last_result_status{status="no_valid_triggers",task="/Microsoft/Windows/PLA/GAEvents"} 0 +windows_scheduled_task_last_result_status{status="not_scheduled",task="/Microsoft/Windows/PLA/GAEvents"} 0 +windows_scheduled_task_last_result_status{status="queued",task="/Microsoft/Windows/PLA/GAEvents"} 0 +windows_scheduled_task_last_result_status{status="ready",task="/Microsoft/Windows/PLA/GAEvents"} 0 +windows_scheduled_task_last_result_status{status="running",task="/Microsoft/Windows/PLA/GAEvents"} 1 +windows_scheduled_task_last_result_status{status="success",task="/Microsoft/Windows/PLA/GAEvents"} 0 +windows_scheduled_task_last_result_status{status="terminated",task="/Microsoft/Windows/PLA/GAEvents"} 0 +windows_scheduled_task_last_result_status{status="unknown",task="/Microsoft/Windows/PLA/GAEvents"} 0 # HELP windows_scheduled_task_missed_runs The number of times the registered task missed a scheduled run # TYPE windows_scheduled_task_missed_runs gauge windows_scheduled_task_missed_runs{task="/Microsoft/Windows/PLA/GAEvents"} 0