mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-06 20:59:04 +02:00
chore: consolidate CI and expand Windows test coverage (#2498)
Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
[codespell]
|
||||
ignore-words-list = notin
|
||||
@@ -0,0 +1,5 @@
|
||||
rules:
|
||||
cache-poisoning:
|
||||
disable: true
|
||||
secrets-outside-env:
|
||||
disable: true
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,98 +0,0 @@
|
||||
name: Linting
|
||||
|
||||
# Trigger on pull requests and pushes to master branch where Go-related files
|
||||
# have been changed.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- next
|
||||
- main
|
||||
- "0.*"
|
||||
- "1.*"
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
VERSION_PROMU: '0.14.0'
|
||||
PROMTOOL_VER: '2.43.0'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: windows-2025
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
# renovate: golang=go
|
||||
go-version: '1.27.1'
|
||||
|
||||
- name: Test
|
||||
run: make test
|
||||
|
||||
- name: Install e2e deps
|
||||
run: |
|
||||
Invoke-WebRequest -Uri https://github.com/prometheus/promu/releases/download/v$($Env:VERSION_PROMU)/promu-$($Env:VERSION_PROMU).windows-amd64.zip -OutFile promu-$($Env:VERSION_PROMU).windows-amd64.zip
|
||||
Expand-Archive -Path promu-$($Env:VERSION_PROMU).windows-amd64.zip -DestinationPath .
|
||||
Copy-Item -Path promu-$($Env:VERSION_PROMU).windows-amd64\promu.exe -Destination "$(go env GOPATH)\bin"
|
||||
|
||||
# GOPATH\bin dir must be appended to PATH else the `promu` command won't be found
|
||||
echo "$(go env GOPATH)\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
|
||||
- name: e2e Test
|
||||
run: make e2e-test
|
||||
|
||||
promtool:
|
||||
runs-on: windows-2025
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
# renovate: golang=go
|
||||
go-version: '1.27.1'
|
||||
|
||||
- name: Install promtool
|
||||
run: |
|
||||
Invoke-WebRequest -Uri https://github.com/prometheus/prometheus/releases/download/v$($Env:PROMTOOL_VER)/prometheus-$($Env:PROMTOOL_VER).windows-amd64.zip -OutFile prometheus-$($Env:PROMTOOL_VER).windows-amd64.zip
|
||||
Expand-Archive -Path prometheus-$($Env:PROMTOOL_VER).windows-amd64.zip -DestinationPath .
|
||||
Copy-Item -Path prometheus-$($Env:PROMTOOL_VER).windows-amd64\promtool.exe -Destination "$(go env GOPATH)\bin"
|
||||
|
||||
Invoke-WebRequest -Uri https://github.com/prometheus/promu/releases/download/v$($Env:VERSION_PROMU)/promu-$($Env:VERSION_PROMU).windows-amd64.zip -OutFile promu-$($Env:VERSION_PROMU).windows-amd64.zip
|
||||
Expand-Archive -Path promu-$($Env:VERSION_PROMU).windows-amd64.zip -DestinationPath .
|
||||
Copy-Item -Path promu-$($Env:VERSION_PROMU).windows-amd64\promu.exe -Destination "$(go env GOPATH)\bin"
|
||||
|
||||
# GOPATH\bin dir must be appended to PATH else the `promu` command won't be found
|
||||
echo "$(go env GOPATH)\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
|
||||
- name: Promtool
|
||||
run: make promtool
|
||||
|
||||
- name: Upload windows_exporter.exe
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
if: always()
|
||||
with:
|
||||
name: windows_exporter.amd64.exe
|
||||
path: 'windows_exporter.exe'
|
||||
retention-days: 7
|
||||
if-no-files-found: error
|
||||
|
||||
lint:
|
||||
runs-on: windows-2025
|
||||
steps:
|
||||
# `gofmt` linter run by golangci-lint fails on CRLF line endings (the default for Windows)
|
||||
- name: Set git to use LF
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
# renovate: golang=go
|
||||
go-version: '1.27.1'
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0
|
||||
with:
|
||||
# renovate: github=golangci/golangci-lint
|
||||
version: v2.13.2
|
||||
args: "--max-same-issues=0"
|
||||
@@ -8,10 +8,12 @@ on:
|
||||
- labeled
|
||||
- unlabeled
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
required-labels-missing:
|
||||
name: required labels missing
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- name: check
|
||||
if: >-
|
||||
@@ -31,9 +33,14 @@ jobs:
|
||||
&& exit 1
|
||||
title:
|
||||
name: check title prefix
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: 'false'
|
||||
|
||||
- name: check
|
||||
run: |
|
||||
PR_TITLE_PREFIX=$(echo "$PR_TITLE" | cut -d':' -f1)
|
||||
|
||||
@@ -1,244 +0,0 @@
|
||||
name: Releases
|
||||
|
||||
# Trigger on releases.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
env:
|
||||
VERSION_PROMU: '0.17.0'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-2025
|
||||
environment: build
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: '0'
|
||||
|
||||
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
# renovate: golang=go
|
||||
go-version: '1.27.1'
|
||||
|
||||
- name: Install WiX
|
||||
run: |
|
||||
dotnet tool install --global wix --version 5.0.2
|
||||
|
||||
- name: Install WiX extensions
|
||||
run: |
|
||||
wix extension add -g WixToolset.Util.wixext/5.0.2
|
||||
wix extension add -g WixToolset.Ui.wixext/5.0.2
|
||||
wix extension add -g WixToolset.Firewall.wixext/5.0.2
|
||||
|
||||
- name: Install Build deps
|
||||
run: |
|
||||
Invoke-WebRequest -Uri https://github.com/prometheus/promu/releases/download/v$($Env:VERSION_PROMU)/promu-$($Env:VERSION_PROMU).windows-amd64.zip -OutFile promu-$($Env:VERSION_PROMU).windows-amd64.zip
|
||||
Expand-Archive -Path promu-$($Env:VERSION_PROMU).windows-amd64.zip -DestinationPath .
|
||||
Copy-Item -Path promu-$($Env:VERSION_PROMU).windows-amd64\promu.exe -Destination "$(go env GOPATH)\bin"
|
||||
|
||||
# GOPATH\bin dir must be added to PATH else the `promu` commands won't be found
|
||||
echo "$(go env GOPATH)\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$Version = git describe --tags --always
|
||||
$Version = $Version -replace 'v', ''
|
||||
# '+' symbols are invalid characters in image tags
|
||||
$Version = $Version -replace '\+', '_'
|
||||
$Version | Set-Content VERSION -PassThru
|
||||
|
||||
make build-all
|
||||
|
||||
# GH requires all files to have different names, so add version/arch to differentiate
|
||||
foreach($Arch in "amd64", "arm64") {
|
||||
Move-Item output\$Arch\windows_exporter.exe output\windows_exporter-$Version-$Arch.exe
|
||||
}
|
||||
|
||||
Get-ChildItem -Path output
|
||||
|
||||
- name: Sign build artifacts
|
||||
if: ${{ (github.event_name != 'pull_request' && github.repository == 'prometheus-community/windows_exporter') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'prometheus-community/windows_exporter') }}
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
$Version = Get-Content VERSION
|
||||
|
||||
$b64 = $env:CODE_SIGN_KEY
|
||||
$filename = 'windows_exporter_CodeSign.pfx'
|
||||
|
||||
$bytes = [Convert]::FromBase64String($b64)
|
||||
[IO.File]::WriteAllBytes($filename, $bytes)
|
||||
|
||||
$basePath = "C:\Program Files (x86)\Windows Kits\10\bin"
|
||||
$latestSigntool = Get-ChildItem -Path $basePath -Directory |
|
||||
Where-Object { $_.Name -match "^\d+\.\d+\.\d+\.\d+$" } |
|
||||
Sort-Object { [Version]$_.Name } -Descending |
|
||||
Select-Object -First 1 |
|
||||
ForEach-Object { Join-Path $_.FullName "x64\signtool.exe" }
|
||||
|
||||
if (Test-Path $latestSigntool) {
|
||||
Write-Output $latestSigntool
|
||||
} else {
|
||||
Write-Output "signtool.exe not found"
|
||||
}
|
||||
|
||||
foreach($Arch in "amd64", "arm64") {
|
||||
& $latestSigntool sign /v /tr "http://timestamp.digicert.com" /d "Prometheus exporter for Windows machines" /td SHA256 /fd SHA256 /a /f "windows_exporter_CodeSign.pfx" /p $env:CODE_SIGN_PASSWORD "output\windows_exporter-$Version-$Arch.exe"
|
||||
}
|
||||
|
||||
rm windows_exporter_CodeSign.pfx
|
||||
env:
|
||||
CODE_SIGN_KEY: ${{ secrets.CODE_SIGN_KEY }}
|
||||
CODE_SIGN_PASSWORD: ${{ secrets.CODE_SIGN_PASSWORD }}
|
||||
|
||||
- name: Build Release Artifacts
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
$Version = Get-Content VERSION
|
||||
|
||||
foreach($Arch in "amd64", "arm64") {
|
||||
Write-Host "Building windows_exporter $Version msi for $Arch"
|
||||
.\installer\build.ps1 -PathToExecutable .\output\windows_exporter-$Version-$Arch.exe -Version $Version -Arch "$Arch"
|
||||
}
|
||||
|
||||
Move-Item installer\*.msi output\
|
||||
Get-ChildItem -Path output\ g
|
||||
|
||||
- name: Sign installer artifacts
|
||||
if: ${{ (github.event_name != 'pull_request' && github.repository == 'prometheus-community/windows_exporter') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'prometheus-community/windows_exporter') }}
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
$Version = Get-Content VERSION
|
||||
|
||||
$b64 = $env:CODE_SIGN_KEY
|
||||
$filename = 'windows_exporter_CodeSign.pfx'
|
||||
|
||||
$bytes = [Convert]::FromBase64String($b64)
|
||||
[IO.File]::WriteAllBytes($filename, $bytes)
|
||||
|
||||
$basePath = "C:\Program Files (x86)\Windows Kits\10\bin"
|
||||
$latestSigntool = Get-ChildItem -Path $basePath -Directory |
|
||||
Where-Object { $_.Name -match "^\d+\.\d+\.\d+\.\d+$" } |
|
||||
Sort-Object { [Version]$_.Name } -Descending |
|
||||
Select-Object -First 1 |
|
||||
ForEach-Object { Join-Path $_.FullName "x64\signtool.exe" }
|
||||
|
||||
if (Test-Path $latestSigntool) {
|
||||
Write-Output $latestSigntool
|
||||
} else {
|
||||
Write-Output "signtool.exe not found"
|
||||
}
|
||||
|
||||
foreach($Arch in "amd64", "arm64") {
|
||||
& $latestSigntool sign /v /tr "http://timestamp.digicert.com" /d "Prometheus exporter for Windows machines" /td SHA256 /fd SHA256 /a /f "windows_exporter_CodeSign.pfx" /p $env:CODE_SIGN_PASSWORD "output\windows_exporter-$Version-$Arch.msi"
|
||||
}
|
||||
|
||||
rm windows_exporter_CodeSign.pfx
|
||||
env:
|
||||
CODE_SIGN_KEY: ${{ secrets.CODE_SIGN_KEY }}
|
||||
CODE_SIGN_PASSWORD: ${{ secrets.CODE_SIGN_PASSWORD }}
|
||||
|
||||
- name: Generate checksums
|
||||
run: |
|
||||
promu checksum output
|
||||
cat output\sha256sums.txt
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: windows_exporter_binaries
|
||||
path: |
|
||||
output\windows_exporter-*.exe
|
||||
output\windows_exporter-*.msi
|
||||
|
||||
- name: Release
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
$TagName = $env:GITHUB_REF -replace 'refs/tags/', ''
|
||||
Get-ChildItem -Path output\* -Include @('windows_exporter*.msi', 'windows_exporter*.exe', 'sha256sums.txt') | Foreach-Object {gh release upload $TagName $_}
|
||||
docker:
|
||||
name: Build docker images
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: '0'
|
||||
|
||||
- name: Download Artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: windows_exporter_binaries
|
||||
|
||||
- name: Login to Docker Hub
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_LOGIN }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
|
||||
- name: Login to quay.io
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ${{ secrets.QUAY_IO_LOGIN }}
|
||||
password: ${{ secrets.QUAY_IO_PASSWORD }}
|
||||
|
||||
- name: Login to GitHub container registry
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/prometheus-community/windows-exporter
|
||||
docker.io/prometheuscommunity/windows-exporter
|
||||
quay.io/prometheuscommunity/windows-exporter
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
labels: |
|
||||
org.opencontainers.image.title=windows_exporter
|
||||
org.opencontainers.image.description=A Prometheus exporter for Windows machines.
|
||||
org.opencontainers.image.vendor=The Prometheus Community
|
||||
org.opencontainers.image.licenses=MIT
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: windows/amd64
|
||||
annotations: ${{ steps.meta.outputs.labels }}
|
||||
@@ -1,26 +0,0 @@
|
||||
name: Spell checking
|
||||
|
||||
# Trigger on pull requests, and pushes to master branch.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
env:
|
||||
VERSION_PROMU: 'v0.14.0'
|
||||
|
||||
jobs:
|
||||
codespell:
|
||||
name: Check for spelling errors
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: codespell-project/actions-codespell@e3258cca84ce02b0bb36a3d41f2c18f719a0cc1a # master
|
||||
with:
|
||||
check_filenames: true
|
||||
# When using this Action in other repos, the --skip option below can be removed
|
||||
skip: ./.git,go.mod,go.sum
|
||||
ignore_words_list: calle,Entires
|
||||
@@ -3,6 +3,7 @@ VERSION
|
||||
*.swp
|
||||
*.un~
|
||||
output/
|
||||
dist/
|
||||
.vscode
|
||||
*.syso
|
||||
installer/*.msi
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
|
||||
version: 2
|
||||
|
||||
builds:
|
||||
- id: windows_exporter
|
||||
main: ./cmd/windows_exporter
|
||||
binary: windows_exporter-{{ .Arch }}
|
||||
no_unique_dist_dir: true
|
||||
goos:
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
flags:
|
||||
- -trimpath
|
||||
tags:
|
||||
- trimpath
|
||||
ldflags:
|
||||
- >-
|
||||
-X github.com/prometheus/common/version.Version={{ .Version }}
|
||||
-X github.com/prometheus/common/version.Revision={{ .FullCommit }}
|
||||
-X github.com/prometheus/common/version.Branch={{ .Branch }}
|
||||
-X github.com/prometheus/common/version.BuildUser=github-actions
|
||||
-X github.com/prometheus/common/version.BuildDate={{ .Now.Format "20060102-15:04:05" }}
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
go:
|
||||
# Whenever the Go version is updated here,
|
||||
# .github/workflows should also be updated.
|
||||
version: 1.23
|
||||
cgo: false
|
||||
repository:
|
||||
path: github.com/prometheus-community/windows_exporter
|
||||
build:
|
||||
binaries:
|
||||
- name: windows_exporter
|
||||
path: ./cmd/windows_exporter
|
||||
tags:
|
||||
all:
|
||||
- trimpath
|
||||
ldflags: |
|
||||
-X github.com/prometheus/common/version.Version={{.Version}}
|
||||
-X github.com/prometheus/common/version.Revision={{.Revision}}
|
||||
-X github.com/prometheus/common/version.Branch={{.Branch}}
|
||||
-X github.com/prometheus/common/version.BuildUser={{user}}@{{host}}
|
||||
-X github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}}
|
||||
tarball:
|
||||
files:
|
||||
- LICENSE
|
||||
crossbuild:
|
||||
platforms:
|
||||
- windows/amd64
|
||||
- windows/arm64
|
||||
@@ -16,10 +16,10 @@ ALL_OS ?= ltsc2019 ltsc2022
|
||||
BASE_IMAGE ?= mcr.microsoft.com/windows/nanoserver
|
||||
|
||||
.PHONY: build
|
||||
build: generate windows_exporter.exe
|
||||
build: windows_exporter.exe
|
||||
|
||||
windows_exporter.exe: pkg/**/*.go
|
||||
promu build -v
|
||||
windows_exporter.exe: generate
|
||||
CGO_ENABLED=0 go build -trimpath -tags=trimpath -o $@ ./cmd/windows_exporter
|
||||
|
||||
.PHONY: generate
|
||||
generate:
|
||||
@@ -38,18 +38,13 @@ lint:
|
||||
e2e-test: windows_exporter.exe
|
||||
powershell -NonInteractive -ExecutionPolicy Bypass -File .\tools\end-to-end-test.ps1
|
||||
|
||||
.PHONY: promtool
|
||||
promtool: windows_exporter.exe
|
||||
pwsh -NonInteractive -ExecutionPolicy Bypass -File .\tools\promtool.ps1
|
||||
|
||||
fmt:
|
||||
gofmt -l -w -s .
|
||||
|
||||
crossbuild: generate
|
||||
# The prometheus/golang-builder image for promu crossbuild doesn't exist
|
||||
# on Windows, so for now, we'll just build twice
|
||||
GOARCH=amd64 promu build --prefix=output/amd64
|
||||
GOARCH=arm64 promu build --prefix=output/arm64
|
||||
mkdir -p output/amd64 output/arm64
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -tags=trimpath -o output/amd64/windows_exporter.exe ./cmd/windows_exporter
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -trimpath -tags=trimpath -o output/arm64/windows_exporter.exe ./cmd/windows_exporter
|
||||
|
||||
.PHONY: package
|
||||
package: crossbuild
|
||||
|
||||
@@ -96,13 +96,8 @@ func TestRun(t *testing.T) {
|
||||
|
||||
exitCodeCh := make(chan int)
|
||||
|
||||
var stdout string
|
||||
|
||||
go func() {
|
||||
stdout = captureOutput(t, func() {
|
||||
// Simulate the service control manager signaling that we are done.
|
||||
exitCodeCh <- run(ctx, tc.args)
|
||||
})
|
||||
exitCodeCh <- run(ctx, tc.args)
|
||||
}()
|
||||
|
||||
t.Cleanup(func() {
|
||||
@@ -122,13 +117,13 @@ func TestRun(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
err = waitUntilListening(t, "tcp", uri.Host)
|
||||
require.NoError(t, err, "LOGS:\n%s", stdout)
|
||||
require.NoError(t, err)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, tc.metricsEndpoint, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err, "LOGS:\n%s", stdout)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
@@ -145,24 +140,6 @@ func TestRun(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func captureOutput(tb testing.TB, f func()) string {
|
||||
tb.Helper()
|
||||
|
||||
orig := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
f()
|
||||
|
||||
os.Stdout = orig
|
||||
|
||||
_ = w.Close()
|
||||
|
||||
out, _ := io.ReadAll(r)
|
||||
|
||||
return string(out)
|
||||
}
|
||||
|
||||
func waitUntilListening(tb testing.TB, network, address string) error {
|
||||
tb.Helper()
|
||||
|
||||
@@ -173,7 +150,9 @@ func waitUntilListening(tb testing.TB, network, address string) error {
|
||||
|
||||
dialer := &net.Dialer{Timeout: 100 * time.Millisecond}
|
||||
|
||||
for range 20 {
|
||||
deadline := time.Now().Add(30 * time.Second)
|
||||
|
||||
for time.Now().Before(deadline) {
|
||||
conn, err = dialer.DialContext(tb.Context(), network, address)
|
||||
if err == nil {
|
||||
_ = conn.Close()
|
||||
|
||||
@@ -85,6 +85,19 @@ func (c *Collector) Close() error {
|
||||
}
|
||||
|
||||
func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
||||
c.buildDescriptors()
|
||||
|
||||
var err error
|
||||
|
||||
c.perfDataCollector, err = pdh.NewCollector[perfDataCounterValues](logger.With(slog.String("collector", Name)), pdh.CounterTypeRaw, "Certification Authority", pdh.InstancesAll)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Certification Authority collector: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) buildDescriptors() {
|
||||
c.requestsPerSecond = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "requests_total"),
|
||||
"Total certificate requests processed",
|
||||
@@ -163,15 +176,6 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
||||
[]string{"cert_template"},
|
||||
nil,
|
||||
)
|
||||
|
||||
var err error
|
||||
|
||||
c.perfDataCollector, err = pdh.NewCollector[perfDataCounterValues](logger.With(slog.String("collector", Name)), pdh.CounterTypeRaw, "Certification Authority", pdh.InstancesAll)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Certification Authority collector: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) Collect(ch chan<- prometheus.Metric, _ time.Duration) error {
|
||||
@@ -180,7 +184,13 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric, _ time.Duration) error
|
||||
return fmt.Errorf("failed to collect Certification Authority (ADCS) metrics: %w", err)
|
||||
}
|
||||
|
||||
for _, data := range c.perfDataObject {
|
||||
c.collectMetrics(ch, c.perfDataObject)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) collectMetrics(ch chan<- prometheus.Metric, rows []perfDataCounterValues) {
|
||||
for _, data := range rows {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.requestsPerSecond,
|
||||
prometheus.CounterValue,
|
||||
@@ -272,6 +282,4 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric, _ time.Duration) error
|
||||
data.Name,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
// 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 adcs
|
||||
|
||||
import (
|
||||
"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()
|
||||
|
||||
values := perfDataCounterValues{
|
||||
Name: "WebServer",
|
||||
RequestsPerSecond: 17,
|
||||
RequestProcessingTime: 1500,
|
||||
RetrievalsPerSecond: 19,
|
||||
RetrievalProcessingTime: 2250,
|
||||
FailedRequestsPerSecond: 3,
|
||||
IssuedRequestsPerSecond: 11,
|
||||
PendingRequestsPerSecond: 2,
|
||||
RequestCryptographicSigningTime: 500,
|
||||
RequestPolicyModuleProcessingTime: 750,
|
||||
ChallengeResponsesPerSecond: 13,
|
||||
ChallengeResponseProcessingTime: 1250,
|
||||
SignedCertificateTimestampListsPerSecond: 23,
|
||||
SignedCertificateTimestampListProcessingTime: 1750,
|
||||
}
|
||||
expected := map[string]struct {
|
||||
metricType dto.MetricType
|
||||
value float64
|
||||
}{
|
||||
"windows_adcs_requests_total": {metricType: dto.MetricType_COUNTER, value: 17},
|
||||
"windows_adcs_request_processing_time_seconds": {metricType: dto.MetricType_GAUGE, value: 1.5},
|
||||
"windows_adcs_retrievals_total": {metricType: dto.MetricType_COUNTER, value: 19},
|
||||
"windows_adcs_retrievals_processing_time_seconds": {metricType: dto.MetricType_GAUGE, value: 2.25},
|
||||
"windows_adcs_failed_requests_total": {metricType: dto.MetricType_COUNTER, value: 3},
|
||||
"windows_adcs_issued_requests_total": {metricType: dto.MetricType_COUNTER, value: 11},
|
||||
"windows_adcs_pending_requests_total": {metricType: dto.MetricType_COUNTER, value: 2},
|
||||
"windows_adcs_request_cryptographic_signing_time_seconds": {metricType: dto.MetricType_GAUGE, value: 0.5},
|
||||
"windows_adcs_request_policy_module_processing_time_seconds": {metricType: dto.MetricType_GAUGE, value: 0.75},
|
||||
"windows_adcs_challenge_responses_total": {metricType: dto.MetricType_COUNTER, value: 13},
|
||||
"windows_adcs_challenge_response_processing_time_seconds": {metricType: dto.MetricType_GAUGE, value: 1.25},
|
||||
"windows_adcs_signed_certificate_timestamp_lists_total": {metricType: dto.MetricType_COUNTER, value: 23},
|
||||
"windows_adcs_signed_certificate_timestamp_list_processing_time_seconds": {metricType: dto.MetricType_GAUGE, value: 1.75},
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
rows []perfDataCounterValues
|
||||
}{
|
||||
{name: "no templates"},
|
||||
{name: "zero counters", rows: []perfDataCounterValues{{Name: "ZeroTemplate"}}},
|
||||
{name: "independent templates", rows: []perfDataCounterValues{values, {Name: "ZeroTemplate"}}},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c := New(nil)
|
||||
c.buildDescriptors()
|
||||
|
||||
registry := prometheus.NewPedanticRegistry()
|
||||
require.NoError(t, registry.Register(metricCollector{collector: c, rows: tc.rows}))
|
||||
families, err := registry.Gather()
|
||||
require.NoError(t, err)
|
||||
|
||||
if len(tc.rows) == 0 {
|
||||
require.Empty(t, families)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
require.Len(t, families, len(expected))
|
||||
|
||||
for _, family := range families {
|
||||
want, ok := expected[family.GetName()]
|
||||
require.True(t, ok, "unexpected metric %s", family.GetName())
|
||||
require.Equal(t, want.metricType, family.GetType())
|
||||
require.Len(t, family.GetMetric(), len(tc.rows))
|
||||
templates := make(map[string]bool, len(tc.rows))
|
||||
|
||||
for _, metric := range family.GetMetric() {
|
||||
require.Len(t, metric.GetLabel(), 1)
|
||||
label := metric.GetLabel()[0]
|
||||
require.Equal(t, "cert_template", label.GetName())
|
||||
require.False(t, templates[label.GetValue()], "duplicate template sample")
|
||||
templates[label.GetValue()] = true
|
||||
|
||||
expectedValue := 0.0
|
||||
if label.GetValue() == values.Name {
|
||||
expectedValue = want.value
|
||||
} else {
|
||||
require.Equal(t, "ZeroTemplate", label.GetValue())
|
||||
}
|
||||
|
||||
actual := metric.GetGauge().GetValue()
|
||||
if want.metricType == dto.MetricType_COUNTER {
|
||||
actual = metric.GetCounter().GetValue()
|
||||
}
|
||||
|
||||
require.InDelta(t, expectedValue, actual, 0, "%s for template %s", family.GetName(), label.GetValue())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type metricCollector struct {
|
||||
collector *Collector
|
||||
rows []perfDataCounterValues
|
||||
}
|
||||
|
||||
func (c metricCollector) Describe(ch chan<- *prometheus.Desc) {
|
||||
prometheus.DescribeByCollect(c, ch)
|
||||
}
|
||||
|
||||
func (c metricCollector) Collect(ch chan<- prometheus.Metric) {
|
||||
c.collector.collectMetrics(ch, c.rows)
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/container"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils/testutils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func BenchmarkCollector(b *testing.B) {
|
||||
@@ -29,5 +30,7 @@ func BenchmarkCollector(b *testing.B) {
|
||||
}
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
testutils.TestCollector(t, container.New, nil)
|
||||
metrics := testutils.TestCollector(t, container.New, nil)
|
||||
testutils.RequireFixtureMetric(t, metrics, container.Name, "windows_container_available", prometheus.Labels{"container": "hostprocess", "hostprocess": "true"})
|
||||
testutils.RequireFixtureMetric(t, metrics, container.Name, "windows_container_available", prometheus.Labels{"container": "nanoserver", "hostprocess": "false"})
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/dhcp"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils/testutils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func BenchmarkCollector(b *testing.B) {
|
||||
@@ -29,5 +30,6 @@ func BenchmarkCollector(b *testing.B) {
|
||||
}
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
testutils.TestCollector(t, dhcp.New, nil)
|
||||
metrics := testutils.TestCollector(t, dhcp.New, nil)
|
||||
testutils.RequireFixtureMetric(t, metrics, dhcp.Name, "windows_dhcp_scope_info", prometheus.Labels{"name": "GitHubActions", "scope": "192.0.2.0/24"})
|
||||
}
|
||||
|
||||
@@ -18,10 +18,13 @@
|
||||
package file_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/file"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils/testutils"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func BenchmarkCollector(b *testing.B) {
|
||||
@@ -29,7 +32,51 @@ func BenchmarkCollector(b *testing.B) {
|
||||
}
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
testutils.TestCollector(t, file.New, &file.Config{
|
||||
FilePatterns: []string{"*.*"},
|
||||
path := filepath.Join(t.TempDir(), "test.txt")
|
||||
content := []byte("windows_exporter test")
|
||||
require.NoError(t, os.WriteFile(path, content, 0o600))
|
||||
|
||||
metrics := testutils.TestCollector(t, file.New, &file.Config{
|
||||
FilePatterns: []string{path},
|
||||
})
|
||||
require.Contains(t, metrics, "windows_file_size_bytes")
|
||||
require.Len(t, metrics["windows_file_size_bytes"].GetMetric(), 1)
|
||||
require.InDelta(t, len(content), metrics["windows_file_size_bytes"].GetMetric()[0].GetGauge().GetValue(), 0)
|
||||
require.Contains(t, metrics, "windows_file_mtime_timestamp_seconds")
|
||||
require.Positive(t, metrics["windows_file_mtime_timestamp_seconds"].GetMetric()[0].GetGauge().GetValue())
|
||||
}
|
||||
|
||||
func TestCollectorPatterns(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
directory := t.TempDir()
|
||||
require.NoError(t, os.WriteFile(filepath.Join(directory, "first.txt"), []byte("first"), 0o600))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(directory, "second.log"), []byte("second"), 0o600))
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
pattern string
|
||||
count int
|
||||
}{
|
||||
{name: "all files", pattern: "*", count: 2},
|
||||
{name: "extension", pattern: "*.txt", count: 1},
|
||||
{name: "case insensitive", pattern: "FIRST.TXT", count: 1},
|
||||
{name: "missing file", pattern: "missing.txt", count: 0},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
metrics := testutils.TestCollector(t, file.New, &file.Config{
|
||||
FilePatterns: []string{filepath.Join(directory, tc.pattern)},
|
||||
})
|
||||
if tc.count == 0 {
|
||||
require.Empty(t, metrics)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
require.Contains(t, metrics, "windows_file_size_bytes")
|
||||
require.Len(t, metrics["windows_file_size_bytes"].GetMetric(), tc.count)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/fsrmquota"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils/testutils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func BenchmarkCollector(b *testing.B) {
|
||||
@@ -29,5 +30,6 @@ func BenchmarkCollector(b *testing.B) {
|
||||
}
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
testutils.TestCollector(t, fsrmquota.New, nil)
|
||||
metrics := testutils.TestCollector(t, fsrmquota.New, nil)
|
||||
testutils.RequireFixtureMetric(t, metrics, fsrmquota.Name, "windows_fsrmquota_size_bytes", prometheus.Labels{"path": `C:\fsrm-ci`})
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/hyperv"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils/testutils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func BenchmarkCollector(b *testing.B) {
|
||||
@@ -29,5 +30,6 @@ func BenchmarkCollector(b *testing.B) {
|
||||
}
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
testutils.TestCollector(t, hyperv.New, nil)
|
||||
metrics := testutils.TestCollector(t, hyperv.New, nil)
|
||||
testutils.RequireFixtureMetric(t, metrics, hyperv.Name, "windows_hyperv_dynamic_memory_vm_physical_bytes", prometheus.Labels{"vm": "GitHubActions"})
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/iis"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils/testutils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func BenchmarkCollector(b *testing.B) {
|
||||
@@ -29,5 +30,6 @@ func BenchmarkCollector(b *testing.B) {
|
||||
}
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
testutils.TestCollector(t, iis.New, nil)
|
||||
metrics := testutils.TestCollector(t, iis.New, nil)
|
||||
testutils.RequireFixtureMetric(t, metrics, iis.Name, "windows_iis_requests_total", prometheus.Labels{"site": "Default Web Site", "method": "get"})
|
||||
}
|
||||
|
||||
@@ -18,12 +18,15 @@
|
||||
package logical_disk_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/logical_disk"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils/testutils"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func BenchmarkCollector(b *testing.B) {
|
||||
@@ -37,6 +40,46 @@ func BenchmarkCollector(b *testing.B) {
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
testutils.TestCollector(t, logical_disk.New, &logical_disk.Config{
|
||||
VolumeInclude: types.RegExpAny,
|
||||
CollectorsEnabled: logical_disk.ConfigDefaults.CollectorsEnabled,
|
||||
VolumeInclude: types.RegExpAny,
|
||||
})
|
||||
}
|
||||
|
||||
func TestCollectorVolumeFilters(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
systemDrive := os.Getenv("SystemDrive")
|
||||
require.NotEmpty(t, systemDrive)
|
||||
matchDrive := regexp.MustCompile("^" + regexp.QuoteMeta(systemDrive) + "$")
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
config logical_disk.Config
|
||||
included bool
|
||||
}{
|
||||
{name: "include system drive", config: logical_disk.Config{CollectorsEnabled: logical_disk.ConfigDefaults.CollectorsEnabled, VolumeInclude: matchDrive}, included: true},
|
||||
{name: "exclude system drive", config: logical_disk.Config{CollectorsEnabled: logical_disk.ConfigDefaults.CollectorsEnabled, VolumeInclude: types.RegExpAny, VolumeExclude: matchDrive}},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
metrics := testutils.TestCollector(t, logical_disk.New, &tc.config)
|
||||
if tc.included {
|
||||
require.Contains(t, metrics, "windows_logical_disk_size_bytes")
|
||||
require.Len(t, metrics["windows_logical_disk_size_bytes"].GetMetric(), 1)
|
||||
}
|
||||
|
||||
for _, metric := range metrics["windows_logical_disk_size_bytes"].GetMetric() {
|
||||
for _, label := range metric.GetLabel() {
|
||||
if label.GetName() == "volume" {
|
||||
if tc.included {
|
||||
require.Equal(t, systemDrive, label.GetValue())
|
||||
} else {
|
||||
require.NotEqual(t, systemDrive, label.GetValue())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/mscluster"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils/testutils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func BenchmarkCollector(b *testing.B) {
|
||||
@@ -29,5 +30,6 @@ func BenchmarkCollector(b *testing.B) {
|
||||
}
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
testutils.TestCollector(t, mscluster.New, nil)
|
||||
metrics := testutils.TestCollector(t, mscluster.New, nil)
|
||||
testutils.RequireFixtureMetric(t, metrics, mscluster.Name, "windows_mscluster_virtualdisk_size_bytes", prometheus.Labels{"name": "CIVirtualDisk"})
|
||||
}
|
||||
|
||||
@@ -30,7 +30,5 @@ func BenchmarkCollector(b *testing.B) {
|
||||
}
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
t.Skip("Skipping test as it requires WMI data")
|
||||
|
||||
testutils.TestCollector(t, netframework.New, nil)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,5 @@ func BenchmarkCollector(b *testing.B) {
|
||||
}
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
t.Skip()
|
||||
|
||||
testutils.TestCollector(t, os.New, nil)
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ func (c *Collector) GetName() string {
|
||||
}
|
||||
|
||||
func (c *Collector) Close() error {
|
||||
for _, object := range c.config.Objects {
|
||||
for _, object := range c.objects {
|
||||
object.collector.Close()
|
||||
}
|
||||
|
||||
|
||||
@@ -188,6 +188,8 @@ windows_performancecounter_processor_information_processor_time\{core="0,0",stat
|
||||
},
|
||||
})
|
||||
|
||||
t.Cleanup(func() { require.NoError(t, perfDataCollector.Close()) })
|
||||
|
||||
logger := slog.New(slog.DiscardHandler)
|
||||
err := perfDataCollector.Build(logger, nil)
|
||||
|
||||
@@ -203,7 +205,8 @@ windows_performancecounter_processor_information_processor_time\{core="0,0",stat
|
||||
registry.MustRegister(collectorAdapter{*perfDataCollector})
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
promhttp.HandlerFor(registry, promhttp.HandlerOpts{ErrorHandling: promhttp.ContinueOnError}).ServeHTTP(rw, &http.Request{})
|
||||
promhttp.HandlerFor(registry, promhttp.HandlerOpts{}).ServeHTTP(rw, &http.Request{})
|
||||
require.Equal(t, http.StatusOK, rw.Code)
|
||||
got := rw.Body.String()
|
||||
|
||||
require.NotEmpty(t, got)
|
||||
@@ -212,3 +215,23 @@ windows_performancecounter_processor_information_processor_time\{core="0,0",stat
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectorClose(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c := performancecounter.New(&performancecounter.Config{
|
||||
Objects: []performancecounter.Object{{
|
||||
Name: "memory", Object: "Memory",
|
||||
Counters: []performancecounter.Counter{{Name: "Available Bytes", Type: "gauge"}},
|
||||
}},
|
||||
})
|
||||
require.NoError(t, c.Build(slog.New(slog.DiscardHandler), nil))
|
||||
t.Cleanup(func() { require.NoError(t, c.Close()) })
|
||||
|
||||
metrics := make(chan prometheus.Metric, 10)
|
||||
require.NoError(t, c.Collect(metrics, 0))
|
||||
require.NoError(t, c.Close())
|
||||
|
||||
// A closed collector must no longer hold usable native counter queries.
|
||||
require.ErrorIs(t, c.Collect(metrics, 0), pdh.ErrPerformanceCounterNotInitialized)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/printer"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils/testutils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func BenchmarkCollector(b *testing.B) {
|
||||
@@ -35,5 +36,6 @@ func BenchmarkCollector(b *testing.B) {
|
||||
}
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
testutils.TestCollector(t, printer.New, nil)
|
||||
metrics := testutils.TestCollector(t, printer.New, nil)
|
||||
testutils.RequireFixtureMetric(t, metrics, printer.Name, "windows_printer_job_count", prometheus.Labels{"printer": "CIPrinter"})
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/smb"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils/testutils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func BenchmarkCollector(b *testing.B) {
|
||||
@@ -29,5 +30,6 @@ func BenchmarkCollector(b *testing.B) {
|
||||
}
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
testutils.TestCollector(t, smb.New, nil)
|
||||
metrics := testutils.TestCollector(t, smb.New, nil)
|
||||
testutils.RequireFixtureMetric(t, metrics, smb.Name, "windows_smb_server_shares_received_bytes_total", prometheus.Labels{"share": `\\*\CIShare`})
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/smbclient"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils/testutils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func BenchmarkCollector(b *testing.B) {
|
||||
@@ -29,5 +30,6 @@ func BenchmarkCollector(b *testing.B) {
|
||||
}
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
testutils.TestCollector(t, smbclient.New, nil)
|
||||
metrics := testutils.TestCollector(t, smbclient.New, nil)
|
||||
testutils.RequireFixtureMetric(t, metrics, smbclient.Name, "windows_smbclient_data_queue_seconds_total", prometheus.Labels{"share": "CIShare"})
|
||||
}
|
||||
|
||||
@@ -111,17 +111,6 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
||||
|
||||
c.logger.Info("update collector is in an experimental state! The configuration and metrics may change in future. Please report any issues.")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
initErrCh := make(chan error, 1)
|
||||
go c.scheduleUpdateStatus(ctx, logger, initErrCh, c.config.Online)
|
||||
|
||||
c.ctxCancelFn = cancel
|
||||
|
||||
if err := <-initErrCh; err != nil {
|
||||
return fmt.Errorf("failed to initialize Windows Update collector: %w", err)
|
||||
}
|
||||
|
||||
c.pendingUpdate = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "pending_info"),
|
||||
"Expose information for a single pending update item",
|
||||
@@ -150,6 +139,17 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
||||
nil,
|
||||
)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
initErrCh := make(chan error, 1)
|
||||
go c.scheduleUpdateStatus(ctx, logger, initErrCh, c.config.Online)
|
||||
|
||||
c.ctxCancelFn = cancel
|
||||
|
||||
if err := <-initErrCh; err != nil {
|
||||
return fmt.Errorf("failed to initialize Windows Update collector: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -279,14 +279,13 @@ func (c *Collector) scheduleUpdateStatus(ctx context.Context, logger *slog.Logge
|
||||
c.mu.Lock()
|
||||
c.metricsBuf = nil
|
||||
c.mu.Unlock()
|
||||
|
||||
continue
|
||||
} else {
|
||||
c.mu.Lock()
|
||||
c.metricsBuf = metricsBuf
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
c.metricsBuf = metricsBuf
|
||||
c.mu.Unlock()
|
||||
|
||||
// Failed searches also observe the interval and cancellation.
|
||||
select {
|
||||
case <-time.After(c.config.ScrapeInterval):
|
||||
case <-ctx.Done():
|
||||
|
||||
@@ -104,12 +104,12 @@ func ParseConfigFile(args []string) string {
|
||||
return configFile
|
||||
}
|
||||
|
||||
if strings.HasSuffix(cliFlag, "-config.file") {
|
||||
if len(os.Args) <= i+1 {
|
||||
if cliFlag == "--config.file" || cliFlag == "-config.file" {
|
||||
if len(args) <= i+1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return os.Args[i+1]
|
||||
return args[i+1]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
// 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 config_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
"github.com/prometheus-community/windows_exporter/internal/config"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseConfigFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
args []string
|
||||
want string
|
||||
}{
|
||||
{name: "absent"},
|
||||
{name: "long joined", args: []string{"--config.file=test.yaml"}, want: "test.yaml"},
|
||||
{name: "short joined", args: []string{"-config.file=test.yaml"}, want: "test.yaml"},
|
||||
{name: "long separate", args: []string{"--config.file", "test.yaml"}, want: "test.yaml"},
|
||||
{name: "short separate", args: []string{"-config.file", "test.yaml"}, want: "test.yaml"},
|
||||
{name: "after other flags", args: []string{"--log.level=debug", "--config.file", "test.yaml"}, want: "test.yaml"},
|
||||
{name: "missing value", args: []string{"--config.file"}},
|
||||
{name: "empty value", args: []string{"--config.file="}},
|
||||
{name: "unrelated suffix", args: []string{"--other-config.file", "test.yaml"}},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Equal(t, tc.want, config.ParseConfigFile(tc.args))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewConfigFileResolver(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
contents string
|
||||
errorText string
|
||||
}{
|
||||
{name: "empty"},
|
||||
{name: "valid", contents: "log:\n level: debug\nweb:\n listen-address: ['127.0.0.1:9182', '[::1]:9182']\n"},
|
||||
{name: "unknown key", contents: "unknown: value", errorText: "configuration file validation error"},
|
||||
{name: "unknown collector option", contents: "collector:\n cpu:\n unknown: true\n", errorText: "configuration file validation error"},
|
||||
{name: "malformed YAML", contents: "log: [", errorText: "configuration file validation error"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
path := filepath.Join(t.TempDir(), "config.yaml")
|
||||
require.NoError(t, os.WriteFile(path, []byte(tc.contents), 0o600))
|
||||
|
||||
resolver, err := config.NewConfigFileResolver(path)
|
||||
if tc.errorText != "" {
|
||||
require.ErrorContains(t, err, tc.errorText)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resolver)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("missing file", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := config.NewConfigFileResolver(filepath.Join(t.TempDir(), "missing.yaml"))
|
||||
require.ErrorContains(t, err, "failed to open configuration file")
|
||||
})
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
extraArgs []string
|
||||
want string
|
||||
errorText string
|
||||
}{
|
||||
{name: "config defaults", want: "debug"},
|
||||
{name: "CLI overrides config", extraArgs: []string{"--log.level=error"}, want: "error"},
|
||||
{name: "invalid CLI flag", extraArgs: []string{"--unknown"}, errorText: "failed to bind configuration"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
path := filepath.Join(t.TempDir(), "config.yaml")
|
||||
require.NoError(t, os.WriteFile(path, []byte("log:\n level: debug\n"), 0o600))
|
||||
|
||||
app := kingpin.New("test", "test")
|
||||
app.Flag("config.file", "config").String()
|
||||
level := app.Flag("log.level", "level").Default("info").String()
|
||||
|
||||
args := append([]string{"--config.file=" + path}, tc.extraArgs...)
|
||||
|
||||
err := config.Parse(app, args)
|
||||
if tc.errorText != "" {
|
||||
require.ErrorContains(t, err, tc.errorText)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.want, *level)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// 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 httphandler_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/httphandler"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHealthHandler(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
response := httptest.NewRecorder()
|
||||
httphandler.NewHealthHandler().ServeHTTP(response, httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/health", nil))
|
||||
require.Equal(t, http.StatusOK, response.Code)
|
||||
require.Equal(t, "application/json", response.Header().Get("Content-Type"))
|
||||
require.JSONEq(t, `{"status":"ok"}`, response.Body.String())
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
// 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 httphandler_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/httphandler"
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/prometheus-community/windows_exporter/pkg/collector"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMetricsHTTPHandler(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
query string
|
||||
options *httphandler.Options
|
||||
status int
|
||||
included []string
|
||||
excluded []string
|
||||
}{
|
||||
{name: "default", status: http.StatusOK, included: []string{"windows_test_first 42", "windows_test_second 42", "go_goroutines", "windows_exporter_build_info"}},
|
||||
{name: "disable exporter metrics", options: &httphandler.Options{DisableExporterMetrics: true, TimeoutMargin: 0.5}, status: http.StatusOK, included: []string{"windows_test_first 42", "windows_exporter_build_info"}, excluded: []string{"go_goroutines", "process_cpu_seconds_total"}},
|
||||
{name: "filter", query: "?collect[]=first", status: http.StatusOK, included: []string{"windows_test_first 42"}, excluded: []string{"windows_test_second"}},
|
||||
{name: "multiple collectors", query: "?collect[]=first&collect[]=second", status: http.StatusOK, included: []string{"windows_test_first 42", "windows_test_second 42"}},
|
||||
{name: "unknown collector", query: "?collect[]=missing", status: http.StatusBadRequest, included: []string{"unknown collector missing"}},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := collector.New(collector.Map{
|
||||
"first": &testCollector{name: "first"},
|
||||
"second": &testCollector{name: "second"},
|
||||
})
|
||||
handler := httphandler.New(slog.New(slog.DiscardHandler), collection, tc.options)
|
||||
request := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/metrics"+tc.query, nil)
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
|
||||
require.Equal(t, tc.status, response.Code)
|
||||
|
||||
for _, value := range tc.included {
|
||||
require.Contains(t, response.Body.String(), value)
|
||||
}
|
||||
|
||||
for _, value := range tc.excluded {
|
||||
require.NotContains(t, response.Body.String(), value)
|
||||
}
|
||||
|
||||
// Filtering one request must not remove collectors from later requests.
|
||||
if tc.query == "?collect[]=first" {
|
||||
response = httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/metrics", nil))
|
||||
require.Equal(t, http.StatusOK, response.Code)
|
||||
require.Contains(t, response.Body.String(), "windows_test_second 42")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricsHTTPHandlerCollectorStatus(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
err error
|
||||
panicValue bool
|
||||
success string
|
||||
}{
|
||||
{name: "success", success: "1"},
|
||||
{name: "failure", err: errors.New("collector failed"), success: "0"},
|
||||
{name: "no data", err: types.ErrNoData, success: "1"},
|
||||
{name: "panic", panicValue: true, success: "0"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := collector.New(collector.Map{"test": &testCollector{name: "test", err: tc.err, panicValue: tc.panicValue}})
|
||||
handler := httphandler.New(slog.New(slog.DiscardHandler), collection, &httphandler.Options{DisableExporterMetrics: true})
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/metrics", nil))
|
||||
|
||||
require.Equal(t, http.StatusOK, response.Code)
|
||||
require.Contains(t, response.Body.String(), `windows_exporter_collector_success{collector="test"} `+tc.success)
|
||||
require.Contains(t, response.Body.String(), `windows_exporter_collector_timeout{collector="test"} 0`)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricsHTTPHandlerScrapeTimeout(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
header string
|
||||
timeout bool
|
||||
}{
|
||||
{name: "missing header"},
|
||||
{name: "invalid header", header: "invalid"},
|
||||
{name: "zero header", header: "0"},
|
||||
{name: "sufficient timeout", header: "2"},
|
||||
{name: "timeout", header: "0.1", timeout: true},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// The collector's delay and deadline use synthetic time.
|
||||
synctest.Test(t, func(t *testing.T) {
|
||||
collection := collector.New(collector.Map{"test": &testCollector{name: "test", delay: time.Second}})
|
||||
handler := httphandler.New(slog.New(slog.DiscardHandler), collection, &httphandler.Options{DisableExporterMetrics: true})
|
||||
request := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/metrics", nil)
|
||||
request.Header.Set("X-Prometheus-Scrape-Timeout-Seconds", tc.header)
|
||||
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
|
||||
require.Equal(t, http.StatusOK, response.Code)
|
||||
|
||||
if tc.timeout {
|
||||
require.Contains(t, response.Body.String(), `windows_exporter_collector_timeout{collector="test"} 1`)
|
||||
require.NotContains(t, response.Body.String(), "windows_test_test 42")
|
||||
|
||||
// Finish the delayed collector and its drain goroutine before leaving the bubble.
|
||||
time.Sleep(time.Second)
|
||||
synctest.Wait()
|
||||
} else {
|
||||
require.Contains(t, response.Body.String(), `windows_exporter_collector_timeout{collector="test"} 0`)
|
||||
require.Contains(t, response.Body.String(), "windows_test_test 42")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type testCollector struct {
|
||||
name string
|
||||
err error
|
||||
panicValue bool
|
||||
delay time.Duration
|
||||
}
|
||||
|
||||
func (c *testCollector) GetName() string { return c.name }
|
||||
func (c *testCollector) Build(_ *slog.Logger, _ *mi.Session) error { return nil }
|
||||
func (c *testCollector) Close() error { return nil }
|
||||
func (c *testCollector) Collect(ch chan<- prometheus.Metric, _ time.Duration) error {
|
||||
if c.panicValue {
|
||||
panic("test collector panic")
|
||||
}
|
||||
|
||||
time.Sleep(c.delay)
|
||||
|
||||
if c.err != nil {
|
||||
return c.err
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(prometheus.NewDesc("windows_test_"+c.name, "Test metric", nil, nil), prometheus.GaugeValue, 42)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// 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 httphandler_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/httphandler"
|
||||
"github.com/prometheus/common/version"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestVersionHandler(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
response := httptest.NewRecorder()
|
||||
httphandler.NewVersionHandler().ServeHTTP(response, httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/version", nil))
|
||||
require.Equal(t, http.StatusOK, response.Code)
|
||||
|
||||
var got map[string]string
|
||||
require.NoError(t, json.Unmarshal(response.Body.Bytes(), &got))
|
||||
require.Equal(t, map[string]string{
|
||||
"version": version.Version, "revision": version.Revision,
|
||||
"branch": version.Branch, "buildUser": version.BuildUser,
|
||||
"buildDate": version.BuildDate, "goVersion": version.GoVersion,
|
||||
}, got)
|
||||
}
|
||||
@@ -21,6 +21,8 @@ import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -29,8 +31,11 @@ import (
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/update"
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/pdh"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/prometheus-community/windows_exporter/pkg/collector"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
@@ -52,81 +57,206 @@ func FuncBenchmarkCollector[C collector.Collector](b *testing.B, name string, co
|
||||
|
||||
metrics := make(chan prometheus.Metric)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
<-metrics
|
||||
var wg sync.WaitGroup
|
||||
wg.Go(func() {
|
||||
for range metrics {
|
||||
}
|
||||
}()
|
||||
})
|
||||
b.Cleanup(func() {
|
||||
close(metrics)
|
||||
wg.Wait()
|
||||
assert.NoError(b, collectors.Close())
|
||||
})
|
||||
|
||||
for b.Loop() {
|
||||
require.NoError(b, c.Collect(metrics, 0))
|
||||
}
|
||||
|
||||
require.NoError(b, collectors.Close())
|
||||
}
|
||||
|
||||
func TestCollector[C collector.Collector, V any](t *testing.T, fn func(*V) C, conf *V) {
|
||||
// TestCollector validates real Windows collector output. CI lists provisioned
|
||||
// collectors in WINDOWS_EXPORTER_TEST_COLLECTORS so setup failures cannot skip.
|
||||
func TestCollector[C collector.Collector, V any](t *testing.T, fn func(*V) C, conf *V) map[string]*dto.MetricFamily {
|
||||
t.Helper()
|
||||
|
||||
var (
|
||||
metrics []prometheus.Metric
|
||||
err error
|
||||
)
|
||||
|
||||
logger := slog.New(slog.DiscardHandler)
|
||||
c := fn(conf)
|
||||
ch := make(chan prometheus.Metric, 10000)
|
||||
required := slices.Contains(strings.Split(os.Getenv("WINDOWS_EXPORTER_TEST_COLLECTORS"), ","), c.GetName())
|
||||
|
||||
miApp, err := mi.ApplicationInitialize()
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { assert.NoError(t, miApp.Close()) })
|
||||
|
||||
miSession, err := miApp.NewSession(nil)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { assert.NoError(t, miSession.Close()) })
|
||||
t.Cleanup(func() { assert.NoError(t, c.Close()) })
|
||||
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, c.Close())
|
||||
require.NoError(t, miSession.Close())
|
||||
require.NoError(t, miApp.Close())
|
||||
})
|
||||
if err := c.Build(logger, miSession); err != nil {
|
||||
if !required && unsupportedCollector(err) {
|
||||
t.Skipf("collector %s is not supported: %v", c.GetName(), err)
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
require.NoError(t, err, "build %s", c.GetName())
|
||||
}
|
||||
|
||||
// PDH rate counters need a second sample after initialization.
|
||||
time.Sleep(time.Second)
|
||||
|
||||
var families map[string]*dto.MetricFamily
|
||||
for scrape := range 2 {
|
||||
families = collectMetrics(t, c, required)
|
||||
t.Logf("%s scrape %d: %d metric families", c.GetName(), scrape+1, len(families))
|
||||
}
|
||||
|
||||
return families
|
||||
}
|
||||
|
||||
func unsupportedCollector(err error) bool {
|
||||
return errors.Is(err, mi.MI_RESULT_INVALID_NAMESPACE) ||
|
||||
errors.Is(err, mi.MI_RESULT_INVALID_QUERY) ||
|
||||
errors.Is(err, pdh.NewPdhError(pdh.CstatusNoCounter)) ||
|
||||
errors.Is(err, pdh.NewPdhError(pdh.CstatusNoObject)) ||
|
||||
errors.Is(err, pdh.ErrPerformanceCounterNotInitialized) ||
|
||||
errors.Is(err, pdh.ErrNoData) ||
|
||||
errors.Is(err, update.ErrUpdateServiceDisabled) ||
|
||||
errors.Is(err, os.ErrNotExist) ||
|
||||
errors.Is(err, windows.Errno(2151088411))
|
||||
}
|
||||
|
||||
func collectMetrics(t *testing.T, c collector.Collector, required bool) map[string]*dto.MetricFamily {
|
||||
t.Helper()
|
||||
|
||||
var (
|
||||
metrics collectedMetrics
|
||||
collectErr error
|
||||
)
|
||||
|
||||
ch := make(chan prometheus.Metric)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Go(func() {
|
||||
for metric := range ch {
|
||||
metrics = append(metrics, metric)
|
||||
}
|
||||
})
|
||||
|
||||
err = c.Build(logger, miSession)
|
||||
// Stop the receiver even if Collect panics or an assertion ends the test.
|
||||
func() {
|
||||
defer func() {
|
||||
close(ch)
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
switch {
|
||||
case err == nil:
|
||||
case errors.Is(err, mi.MI_RESULT_INVALID_NAMESPACE),
|
||||
errors.Is(err, pdh.NewPdhError(pdh.CstatusNoCounter)),
|
||||
errors.Is(err, pdh.NewPdhError(pdh.CstatusNoObject)),
|
||||
errors.Is(err, update.ErrUpdateServiceDisabled),
|
||||
errors.Is(err, os.ErrNotExist):
|
||||
default:
|
||||
require.NoError(t, err)
|
||||
err := c.Collect(ch, 30*time.Second)
|
||||
if errors.Is(err, update.ErrNoUpdates) && required {
|
||||
deadline := time.Now().Add(time.Minute)
|
||||
for errors.Is(err, update.ErrNoUpdates) && time.Now().Before(deadline) {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
err = c.Collect(ch, 30*time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
collectErr = err
|
||||
}()
|
||||
|
||||
// Empty optional instance groups may report no data alongside valid metrics.
|
||||
// Inspect every joined error so an unrelated failure cannot be hidden.
|
||||
if noDataOnly(collectErr) && len(metrics) > 0 {
|
||||
t.Logf("collector %s has empty optional instance groups: %v", c.GetName(), collectErr)
|
||||
collectErr = nil
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
err = c.Collect(ch, 0)
|
||||
|
||||
switch {
|
||||
// container collector
|
||||
case errors.Is(err, windows.Errno(2151088411)),
|
||||
errors.Is(err, pdh.ErrPerformanceCounterNotInitialized),
|
||||
errors.Is(err, pdh.ErrNoData),
|
||||
errors.Is(err, mi.MI_RESULT_INVALID_NAMESPACE),
|
||||
errors.Is(err, mi.MI_RESULT_INVALID_QUERY),
|
||||
errors.Is(err, update.ErrNoUpdates):
|
||||
t.Skip("collector not supported on this system")
|
||||
default:
|
||||
require.NoError(t, err)
|
||||
if !required && (unsupportedCollector(collectErr) || errors.Is(collectErr, update.ErrNoUpdates)) {
|
||||
t.Skipf("collector %s is not supported: %v", c.GetName(), collectErr)
|
||||
}
|
||||
|
||||
close(ch)
|
||||
require.NoError(t, collectErr, "collect %s", c.GetName())
|
||||
|
||||
wg.Wait()
|
||||
if required {
|
||||
require.NotEmpty(t, metrics, "provisioned collector %s emitted no metrics", c.GetName())
|
||||
}
|
||||
|
||||
registry := prometheus.NewPedanticRegistry()
|
||||
require.NoError(t, registry.Register(metrics))
|
||||
gathered, err := registry.Gather()
|
||||
require.NoError(t, err, "invalid metrics from %s", c.GetName())
|
||||
|
||||
families := make(map[string]*dto.MetricFamily, len(gathered))
|
||||
for _, family := range gathered {
|
||||
families[family.GetName()] = family
|
||||
if strings.HasSuffix(family.GetName(), "_collector_success") {
|
||||
for _, metric := range family.GetMetric() {
|
||||
require.InDelta(t, 1, metric.GetGauge().GetValue(), 0, "failed child collector: %s", metric)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return families
|
||||
}
|
||||
|
||||
type collectedMetrics []prometheus.Metric
|
||||
|
||||
func (m collectedMetrics) Describe(ch chan<- *prometheus.Desc) {
|
||||
prometheus.DescribeByCollect(m, ch)
|
||||
}
|
||||
|
||||
func (m collectedMetrics) Collect(ch chan<- prometheus.Metric) {
|
||||
for _, metric := range m {
|
||||
ch <- metric
|
||||
}
|
||||
}
|
||||
|
||||
// RequireFixtureMetric checks a known CI fixture without requiring it on a
|
||||
// developer's machine. Labels match exactly, ignoring case for Windows names.
|
||||
func RequireFixtureMetric(t *testing.T, families map[string]*dto.MetricFamily, collectorName, metricName string, labels prometheus.Labels) {
|
||||
t.Helper()
|
||||
|
||||
if !slices.Contains(strings.Split(os.Getenv("WINDOWS_EXPORTER_TEST_COLLECTORS"), ","), collectorName) {
|
||||
return
|
||||
}
|
||||
|
||||
require.Contains(t, families, metricName)
|
||||
|
||||
for _, metric := range families[metricName].GetMetric() {
|
||||
matched := true
|
||||
|
||||
for name, value := range labels {
|
||||
found := false
|
||||
|
||||
for _, label := range metric.GetLabel() {
|
||||
if label.GetName() == name && strings.EqualFold(label.GetValue(), value) {
|
||||
found = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
matched = matched && found
|
||||
}
|
||||
|
||||
if matched {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
t.Fatalf("metric %s with fixture labels %v was not emitted; got %s", metricName, labels, families[metricName])
|
||||
}
|
||||
|
||||
func noDataOnly(err error) bool {
|
||||
if joined, ok := err.(interface{ Unwrap() []error }); ok {
|
||||
for _, cause := range joined.Unwrap() {
|
||||
if !noDataOnly(cause) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if wrapped, ok := err.(interface{ Unwrap() error }); ok {
|
||||
return noDataOnly(wrapped.Unwrap())
|
||||
}
|
||||
|
||||
return errors.Is(err, pdh.ErrNoData) || errors.Is(err, types.ErrNoData)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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 testutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/pdh"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNoDataOnly(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
failure := errors.New("collector failed")
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
err error
|
||||
want bool
|
||||
}{
|
||||
{name: "nil"},
|
||||
{name: "PDH no data", err: pdh.ErrNoData, want: true},
|
||||
{name: "collector no data", err: types.ErrNoData, want: true},
|
||||
{name: "wrapped", err: fmt.Errorf("empty instances: %w", pdh.ErrNoData), want: true},
|
||||
{name: "joined empty groups", err: errors.Join(pdh.ErrNoData, types.ErrNoData), want: true},
|
||||
{name: "failure", err: failure},
|
||||
{name: "mixed failures", err: errors.Join(pdh.ErrNoData, failure)},
|
||||
{name: "nested mixed failures", err: fmt.Errorf("child: %w", errors.Join(pdh.ErrNoData, failure))},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Equal(t, tc.want, noDataOnly(tc.err))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// 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 collector_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/pdh"
|
||||
"github.com/prometheus-community/windows_exporter/pkg/collector"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCollectionConstructors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("config includes every collector", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collection := collector.NewWithConfig(collector.Config{})
|
||||
_, err := collection.NewHandler(time.Second, slog.New(slog.DiscardHandler), collector.Available())
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("flags include every collector", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app := kingpin.New("test", "test")
|
||||
collection := collector.NewWithFlags(app)
|
||||
_, err := app.Parse(nil)
|
||||
require.NoError(t, err)
|
||||
_, err = collection.NewHandler(time.Second, slog.New(slog.DiscardHandler), collector.Available())
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCollectionBuildAndClose(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
buildFailure := errors.New("build failed")
|
||||
|
||||
closeFailure := errors.New("close failed")
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
buildErr error
|
||||
closeErr error
|
||||
wantBuildErr error
|
||||
}{
|
||||
{name: "success"},
|
||||
{name: "build failure", buildErr: buildFailure, wantBuildErr: buildFailure},
|
||||
{name: "optional counter missing", buildErr: pdh.ErrNoData},
|
||||
{name: "close failure", closeErr: closeFailure},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
test := &lifecycleCollector{name: "test", buildErr: tc.buildErr, closeErr: tc.closeErr}
|
||||
collection := collector.New(collector.Map{"test": test})
|
||||
err := collection.Build(t.Context(), slog.New(slog.DiscardHandler))
|
||||
t.Cleanup(func() {
|
||||
err := collection.Close()
|
||||
if tc.closeErr != nil {
|
||||
require.ErrorIs(t, err, tc.closeErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
require.True(t, test.closed)
|
||||
})
|
||||
|
||||
if tc.wantBuildErr != nil {
|
||||
require.ErrorIs(t, err, tc.wantBuildErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type lifecycleCollector struct {
|
||||
name string
|
||||
buildErr error
|
||||
closeErr error
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (c *lifecycleCollector) GetName() string { return c.name }
|
||||
|
||||
func (c *lifecycleCollector) Build(_ *slog.Logger, _ *mi.Session) error { return c.buildErr }
|
||||
|
||||
func (c *lifecycleCollector) Collect(_ chan<- prometheus.Metric, _ time.Duration) error { return nil }
|
||||
|
||||
func (c *lifecycleCollector) Close() error {
|
||||
c.closed = true
|
||||
|
||||
return c.closeErr
|
||||
}
|
||||
+91
-45
@@ -15,23 +15,108 @@
|
||||
"ignorePaths": ["**/workflows/container_description.yml","**/workflows/stale.yml","**/workflows/govulncheck.yml"],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchPackageNames": ["https://github.com/yaml/go-yaml"],
|
||||
"matchUpdateTypes": ["major"],
|
||||
"enabled": true,
|
||||
"matchDatasources": ["golang-version"],
|
||||
"rangeStrategy": "bump"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"matchDepNames": ["go", "golang"],
|
||||
"groupName": "go version"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"matchCategories": ["go"],
|
||||
"matchUpdateTypes": ["digest"]
|
||||
},
|
||||
{
|
||||
"groupName": "golangci-lint",
|
||||
"matchPackageNames": [
|
||||
"github.com/golangci/golangci-lint/cmd/golangci-lint",
|
||||
"golangci/golangci-lint",
|
||||
"golangci/golangci-lint-action"
|
||||
],
|
||||
"addLabels": ["chore"]
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["gopkg.in/yaml"],
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"matchManagers": ["gomod"],
|
||||
"matchDepTypes": ["indirect"],
|
||||
"major": {"enabled": false},
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"groupName": "GitHub Actions",
|
||||
"matchManagers": ["github-actions"],
|
||||
"matchDatasources": ["!/github-runners$/"],
|
||||
"semanticCommitScope": "github-actions",
|
||||
"separateMajorMinor": false,
|
||||
"addLabels": ["chore"]
|
||||
},
|
||||
{
|
||||
"groupName": "GitHub Runners",
|
||||
"matchManagers": ["github-actions"],
|
||||
"matchDatasources": ["github-runners"],
|
||||
"semanticCommitScope": "github-actions",
|
||||
"separateMajorMinor": false,
|
||||
"addLabels": ["chore"]
|
||||
},
|
||||
{
|
||||
"groupName": "moby monorepo",
|
||||
"matchManagers": ["gomod"],
|
||||
"matchPackageNames": ["github.com/moby/moby", "github.com/moby/moby/**"]
|
||||
},
|
||||
{
|
||||
"groupName": "golang.org/x",
|
||||
"matchPackageNames": ["golang.org/x/**"]
|
||||
},
|
||||
{
|
||||
"groupName": "googleapis",
|
||||
"matchPackageNames": ["google.golang.org/genproto/googleapis/**"]
|
||||
},
|
||||
{
|
||||
"groupName": "Go tools",
|
||||
"matchFileNames": ["tools/**", "Makefile"],
|
||||
"matchPackageNames": ["*"],
|
||||
"addLabels": ["chore"]
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["golang.org/x/net"],
|
||||
"sourceUrl": "https://github.com/golang/net"
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["golang.org/x/oauth2"],
|
||||
"sourceUrl": "https://github.com/golang/oauth2"
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["golang.org/x/sys"],
|
||||
"sourceUrl": "https://github.com/golang/sys"
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["golang.org/x/text"],
|
||||
"sourceUrl": "https://github.com/golang/text"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"matchPackageNames": ["https://github.com/yaml/go-yaml"],
|
||||
"matchUpdateTypes": ["major"]
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"matchBaseBranches": ["/^[0-9]\\.[0-9][0-9]$/"],
|
||||
"matchUpdateTypes": [
|
||||
"major"
|
||||
],
|
||||
"enabled": false
|
||||
]
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"matchBaseBranches": ["/^[0-9]\\.[0-9][0-9]$/"],
|
||||
"matchManagers": [
|
||||
"github-actions"
|
||||
],
|
||||
"enabled": false
|
||||
]
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
@@ -41,45 +126,6 @@
|
||||
"golangci/golangci-lint",
|
||||
"golangci/golangci-lint-action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"groupName": "golangci-lint",
|
||||
"matchPackageNames": [
|
||||
"github.com/golangci/golangci-lint/cmd/golangci-lint",
|
||||
"golangci/golangci-lint",
|
||||
"golangci/golangci-lint-action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"matchManagers": [
|
||||
"gomod"
|
||||
],
|
||||
"matchDepTypes": [
|
||||
"indirect"
|
||||
],
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"groupName": "GitHub Actions",
|
||||
"matchManagers": [
|
||||
"github-actions"
|
||||
]
|
||||
},
|
||||
{
|
||||
"groupName": "golang.org/x/",
|
||||
"matchPackageNames": [
|
||||
"golang.org/x/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"groupName": "Go tools",
|
||||
"matchFileNames": [
|
||||
"tools/**",
|
||||
"Makefile"
|
||||
],
|
||||
"matchPackageNames": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"postUpdateOptions": [
|
||||
|
||||
+33
-77
@@ -1,85 +1,41 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version 3
|
||||
|
||||
# cd to location of script
|
||||
$script_path = $MyInvocation.MyCommand.Path
|
||||
$working_dir = Split-Path $script_path
|
||||
Push-Location $working_dir
|
||||
$binary = Join-Path $PSScriptRoot '..\windows_exporter.exe'
|
||||
$logDir = if ($env:RUNNER_TEMP) { $env:RUNNER_TEMP } else { $env:TEMP }
|
||||
$stdout = Join-Path $logDir 'windows_exporter.stdout.log'
|
||||
$stderr = Join-Path $logDir 'windows_exporter.stderr.log'
|
||||
$exporter = Start-Process -FilePath $binary -PassThru `
|
||||
-ArgumentList '--web.listen-address=127.0.0.1:9182' `
|
||||
-RedirectStandardOutput $stdout -RedirectStandardError $stderr
|
||||
|
||||
if (-not (Test-Path -Path '..\windows_exporter.exe')) {
|
||||
Write-Output "..\windows_exporter.exe not found. Consider running \`go build\` first"
|
||||
}
|
||||
try {
|
||||
$deadline = (Get-Date).AddSeconds(60)
|
||||
do {
|
||||
if ($exporter.HasExited) {
|
||||
throw "windows_exporter exited with code $($exporter.ExitCode)"
|
||||
}
|
||||
|
||||
$temp_dir = Join-Path $env:TEMP $([guid]::newguid()) | ForEach-Object { mkdir $_ }
|
||||
$client = [System.Net.Sockets.TcpClient]::new()
|
||||
try {
|
||||
$client.Connect('127.0.0.1', 9182)
|
||||
break
|
||||
} catch {
|
||||
if ((Get-Date) -ge $deadline) { throw 'windows_exporter did not start within 60 seconds' }
|
||||
Start-Sleep -Milliseconds 200
|
||||
} finally {
|
||||
$client.Dispose()
|
||||
}
|
||||
} while ($true)
|
||||
|
||||
# Create temporary directory for textfile collector
|
||||
$textfile_dir = "$($temp_dir)/textfile"
|
||||
mkdir $textfile_dir | Out-Null
|
||||
Copy-Item 'e2e-textfile.prom' -Destination "$($textfile_dir)/e2e-textfile.prom"
|
||||
|
||||
# Omit dynamic collector information that will change after each run
|
||||
$skip_re = "^(go_|windows_exporter_build_info|windows_exporter_collector_duration_seconds|windows_exporter_scrape_duration_seconds|process_|windows_textfile_mtime_seconds|windows_cpu|windows_cache|windows_pagefile|windows_logical_disk|windows_physical_disk|windows_memory|windows_net|windows_os|windows_process|windows_service_process|windows_printer|windows_udp|windows_tcp|windows_system|windows_time|windows_session|windows_performancecounter|windows_performancecounter|windows_textfile_mtime_seconds)"
|
||||
|
||||
# Start process in background, awaiting HTTP requests.
|
||||
# Use default collectors, port and address: http://localhost:9182/metrics
|
||||
$exporter_proc = Start-Process `
|
||||
-PassThru `
|
||||
-FilePath ..\windows_exporter.exe `
|
||||
-ArgumentList "--log.level=debug","--web.disable-exporter-metrics","--collectors.enabled=[defaults],cpu_info,textfile,process,pagefile,performancecounter,scheduled_task,tcp,udp,time,system,service,logical_disk,os,net,memory,cache","--collector.process.include=explorer.exe","--collector.scheduled_task.include=.*GAEvents","--collector.service.include=Themes","--collector.textfile.directories=$($textfile_dir)",@"
|
||||
--collector.performancecounter.objects="[{\"name\":\"cpu\",\"object\":\"Processor Information\",\"instances\":[\"*\"],\"instance_label\":\"core\",\"counters\":[{\"name\":\"% Processor Time\",\"metric\":\"windows_performancecounter_processor_information_processor_time\",\"labels\":{\"state\":\"active\"}},{\"name\":\"% Idle Time\",\"metric\":\"windows_performancecounter_processor_information_processor_time\",\"labels\":{\"state\":\"idle\"}}]},{\"name\":\"memory\",\"object\":\"Memory\",\"counters\":[{\"name\":\"Cache Faults/sec\",\"type\":\"counter\"}]}]"
|
||||
"@ `
|
||||
-WindowStyle Hidden `
|
||||
-RedirectStandardOutput "$($temp_dir)/windows_exporter.log" `
|
||||
-RedirectStandardError "$($temp_dir)/windows_exporter_error.log"
|
||||
|
||||
# Exporter can take some time to start
|
||||
for ($i=1; $i -le 1; $i++) {
|
||||
Start-Sleep 10
|
||||
|
||||
$netstat_output = netstat -anp tcp | Select-String 'listening'
|
||||
if ($netstat_output -like '*:9182*') {
|
||||
break
|
||||
# Metric validation belongs in Go tests. This is one binary smoke request.
|
||||
$response = Invoke-WebRequest -UseBasicParsing -Uri 'http://127.0.0.1:9182/metrics' -TimeoutSec 30
|
||||
if ($response.StatusCode -ne 200 -or [string]::IsNullOrWhiteSpace($response.Content)) {
|
||||
throw 'windows_exporter returned an unsuccessful or empty metrics response'
|
||||
}
|
||||
Write-Host "Waiting for exporter to start"
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Invoke-WebRequest -UseBasicParsing -URI http://127.0.0.1:9182/metrics
|
||||
} catch {
|
||||
Write-Host "STDOUT"
|
||||
Get-Content "$($temp_dir)/windows_exporter.log"
|
||||
Write-Host "STDERR"
|
||||
Get-Content "$($temp_dir)/windows_exporter_error.log"
|
||||
|
||||
throw $_
|
||||
}
|
||||
# Response output must be split and saved as UTF-8.
|
||||
$response.content -split "[`r`n]"| Select-String -NotMatch $skip_re | Set-Content -Encoding utf8 "$($temp_dir)/e2e-output.txt"
|
||||
try {
|
||||
Stop-Process -Id $exporter_proc.Id
|
||||
} catch {
|
||||
Write-Host "STDOUT"
|
||||
Get-Content "$($temp_dir)/windows_exporter.log"
|
||||
Write-Host "STDERR"
|
||||
Get-Content "$($temp_dir)/windows_exporter_error.log"
|
||||
|
||||
throw $_
|
||||
}
|
||||
|
||||
# Compare the expected and actual output
|
||||
$output_diff = Compare-Object (Get-Content 'e2e-output.txt' | Where-Object { $_ -ne "" }) (Get-Content "$($temp_dir)/e2e-output.txt" | Where-Object { $_ -ne "" })
|
||||
|
||||
# Fail if differences in output are detected
|
||||
if (-not ($null -eq $output_diff)) {
|
||||
$output_diff | Format-Table -AutoSize | Out-String -Width 10000
|
||||
|
||||
Write-Host "STDOUT"
|
||||
Get-Content "$($temp_dir)/windows_exporter.log"
|
||||
Write-Host "----------------------------------------"
|
||||
Write-Host "STDERR"
|
||||
Get-Content "$($temp_dir)/windows_exporter_error.log"
|
||||
|
||||
(Get-Content "$($temp_dir)/e2e-output.txt") | Set-Content -Encoding utf8 "e2e-output.txt"
|
||||
|
||||
exit 1
|
||||
Write-Host "windows_exporter returned HTTP $($response.StatusCode)"
|
||||
} finally {
|
||||
if (-not $exporter.HasExited) { Stop-Process -Id $exporter.Id }
|
||||
Get-Content $stdout -ErrorAction SilentlyContinue
|
||||
Get-Content $stderr -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version 3
|
||||
|
||||
if (-not (Test-Path -Path '.\windows_exporter.exe')) {
|
||||
Write-Output ".\windows_exporter.exe not found. Consider running \`go build\` first"
|
||||
}
|
||||
|
||||
# Powershell pipes & Get-Content command rather unhelpfully add a carriage return at the end of the string, so
|
||||
# passing the string as bytes is a messy but necessary workaround for processes that are sensitive to
|
||||
# line endings, like promtool.
|
||||
function Start-RawProcess {
|
||||
param(
|
||||
# String to pass to $CommandName via STDIN
|
||||
[Parameter(Mandatory=$true)][String]$InputVar,
|
||||
# Command to run
|
||||
[Parameter(Mandatory=$true)][String]$CommandName,
|
||||
# Arguments provided to $CommandName
|
||||
[Parameter(Mandatory=$false)][String[]]$CommandArgs
|
||||
)
|
||||
# Buffer & initial size of MemoryStream
|
||||
$BufferSize = 4096
|
||||
|
||||
# Convert text to bytes and write to MemoryStream
|
||||
[byte[]]$InputBytes = [Text.Encoding]::UTF8.GetBytes($InputVar)
|
||||
$MemStream = New-Object -TypeName System.IO.MemoryStream -ArgumentList $BufferSize
|
||||
$MemStream.Write($InputBytes, 0, $InputBytes.Length)
|
||||
[Void]$MemStream.Seek(0, 'Begin')
|
||||
|
||||
# Setup stdin\stdout redirection for our process
|
||||
if ($CommandArgs) {
|
||||
$StartInfo = New-Object -TypeName System.Diagnostics.ProcessStartInfo -Property @{
|
||||
FileName = $CommandName
|
||||
UseShellExecute = $false
|
||||
RedirectStandardInput = $true
|
||||
RedirectStandardError = $true
|
||||
Arguments = $CommandArgs
|
||||
}
|
||||
} else {
|
||||
$StartInfo = New-Object -TypeName System.Diagnostics.ProcessStartInfo -Property @{
|
||||
FileName = $CommandName
|
||||
UseShellExecute = $false
|
||||
RedirectStandardInput = $true
|
||||
RedirectStandardError = $true
|
||||
}
|
||||
}
|
||||
|
||||
# Create new process
|
||||
$Process = New-Object -TypeName System.Diagnostics.Process
|
||||
|
||||
# Assign previously created StartInfo properties
|
||||
$Process.StartInfo = $StartInfo
|
||||
# Start process
|
||||
[void]$Process.Start()
|
||||
|
||||
# Pipe data
|
||||
$Buffer = New-Object -TypeName byte[] -ArgumentList $BufferSize
|
||||
$StdinStream = $Process.StandardInput.BaseStream
|
||||
|
||||
try {
|
||||
do {
|
||||
$ReadCount = $MemStream.Read($Buffer, 0, $Buffer.Length)
|
||||
$StdinStream.Write($Buffer, 0, $ReadCount)
|
||||
$StdinStream.Flush()
|
||||
}
|
||||
while($ReadCount -gt 0)
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw 'Error streaming buffer to STDIN'
|
||||
} finally {
|
||||
# Close streams
|
||||
$StdinStream.Close()
|
||||
$MemStream.Close()
|
||||
}
|
||||
$Process.WaitForExit()
|
||||
if ($Process.ExitCode -ne 0) {
|
||||
Write-Host $Process.StandardError.ReadToEnd()
|
||||
}
|
||||
|
||||
return $Process.ExitCode
|
||||
}
|
||||
|
||||
# cd to location of script
|
||||
$script_path = $MyInvocation.MyCommand.Path
|
||||
$working_dir = Split-Path $script_path
|
||||
Push-Location $working_dir
|
||||
|
||||
$temp_dir = Join-Path $env:TEMP $([guid]::newguid()) | ForEach-Object { mkdir $_ }
|
||||
|
||||
# Start process in background, awaiting HTTP requests.
|
||||
# Listen on 9183/TCP, preventing conflicts with 9182/TCP used by end-to-end-test.ps1
|
||||
# Not an issue when run individually, but will cause failures when run concurrently in CI.
|
||||
$exporter_proc = Start-Process `
|
||||
-PassThru `
|
||||
-FilePath ..\windows_exporter.exe `
|
||||
-ArgumentList '--web.listen-address="127.0.0.1:9183" --log.level=debug --collectors.enabled=[defaults],cpu_info,textfile,process,scheduled_task'`
|
||||
-WindowStyle Hidden `
|
||||
-RedirectStandardOutput "$($temp_dir)/windows_exporter.log" `
|
||||
-RedirectStandardError "$($temp_dir)/windows_exporter_error.log"
|
||||
|
||||
# Exporter can take some time to start
|
||||
for ($i=1; $i -le 5; $i++) {
|
||||
Start-Sleep 10
|
||||
|
||||
$netstat_output = netstat -anp tcp | Select-String 'listening'
|
||||
if ($netstat_output -like '*:9183*') {
|
||||
break
|
||||
}
|
||||
Write-Host "Waiting for exporter to start"
|
||||
}
|
||||
|
||||
# Omit metrics from client_golang library; we're not responsible for these
|
||||
# windows_memory_pool_nonpaged_allocs_total is wrong for years. It's not a gauge, but a counter.
|
||||
$skip_re = "^([#]?\s*(HELP|TYPE)?\s*go_|windows_memory_pool_nonpaged_allocs_total)"
|
||||
|
||||
try {
|
||||
# Need to remove carriage returns, as promtool expects LF line endings
|
||||
$output = ((Invoke-WebRequest -UseBasicParsing -URI http://127.0.0.1:9183/metrics).Content) -Split "`r?`n" | Select-String -NotMatch $skip_re | Join-String -Separator "`n"
|
||||
# $output = (((Invoke-WebRequest -UseBasicParsing -URI http://127.0.0.1:9183/metrics).Content) -Split "`r?`n" | Select-String -NotMatch $skip_re) -join "`n"
|
||||
# Join the split lines back to a single String (with LF line endings!)
|
||||
$output = $output -Join "`n"
|
||||
|
||||
Stop-Process -Id $exporter_proc.Id
|
||||
} catch {
|
||||
Write-Host "STDOUT"
|
||||
Get-Content "$($temp_dir)/windows_exporter.log"
|
||||
Write-Host "STDERR"
|
||||
Get-Content "$($temp_dir)/windows_exporter_error.log"
|
||||
|
||||
throw $_
|
||||
}
|
||||
|
||||
$ExitCode = Start-RawProcess -InputVar $output -CommandName promtool.exe -CommandArgs @("check metrics")
|
||||
if ($ExitCode -ne 0) {
|
||||
Write-Host "OUTPUT"
|
||||
|
||||
Write-Host $output
|
||||
|
||||
Write-Host "Promtool command returned exit code $($ExitCode). See output for details."
|
||||
EXIT 1
|
||||
}
|
||||
Reference in New Issue
Block a user