mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-17 10:09:05 +02:00
scheduled_task: expose result statuses and deprecate last_result (#2532)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
88034b818e
commit
92483242ba
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
+16
-2
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user