mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-17 18:19:04 +02:00
366 lines
8.5 KiB
Go
366 lines
8.5 KiB
Go
// 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.
|
|
|
|
package metriccache_test
|
|
|
|
import (
|
|
"errors"
|
|
"strconv"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/prometheus-community/windows_exporter/internal/metriccache"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
//nolint:gochecknoglobals
|
|
var desc = prometheus.NewDesc("test_state", "test", []string{"name", "state"}, nil)
|
|
|
|
func build(name, state string) []prometheus.Metric {
|
|
return []prometheus.Metric{
|
|
prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, 1, name, state),
|
|
}
|
|
}
|
|
|
|
func TestCache(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var cache metriccache.Cache[string, string]
|
|
|
|
scrape := cache.Begin()
|
|
_, ok := scrape.Load("a", "running")
|
|
require.False(t, ok, "empty cache")
|
|
|
|
a := build("a", "running")
|
|
scrape.Store("a", "running", a)
|
|
scrape.Store("b", "stopped", build("b", "stopped"))
|
|
|
|
_, ok = scrape.Load("a", "running")
|
|
require.False(t, ok, "entries are visible after Commit")
|
|
|
|
scrape.Commit()
|
|
|
|
scrape = cache.Begin()
|
|
metrics, ok := scrape.Load("a", "running")
|
|
require.True(t, ok)
|
|
require.Same(t, a[0], metrics[0])
|
|
|
|
_, ok = scrape.Load("a", "stopped")
|
|
require.False(t, ok, "changed value")
|
|
|
|
// b is neither loaded nor stored in this scrape.
|
|
scrape.Commit()
|
|
|
|
scrape = cache.Begin()
|
|
_, ok = scrape.Load("b", "stopped")
|
|
require.False(t, ok, "keys not seen in the last scrape are dropped")
|
|
|
|
metrics, ok = scrape.Load("a", "running")
|
|
require.True(t, ok, "loaded keys are kept")
|
|
require.Same(t, a[0], metrics[0])
|
|
scrape.Commit()
|
|
|
|
cache.Reset()
|
|
|
|
scrape = cache.Begin()
|
|
_, ok = scrape.Load("a", "running")
|
|
require.False(t, ok, "reset")
|
|
}
|
|
|
|
func TestCacheResetDuringScrape(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var cache metriccache.Cache[string, string]
|
|
|
|
// A scrape that is still running when the collector is closed or rebuilt
|
|
// must not put its metrics back into the cache.
|
|
scrape := cache.Begin()
|
|
scrape.Store("a", "running", build("a", "running"))
|
|
cache.Reset()
|
|
scrape.Commit()
|
|
|
|
scrape = cache.Begin()
|
|
_, ok := scrape.Load("a", "running")
|
|
require.False(t, ok)
|
|
|
|
// Scrapes begun after the Reset commit as usual.
|
|
scrape.Store("a", "running", build("a", "running"))
|
|
scrape.Commit()
|
|
|
|
scrape = cache.Begin()
|
|
_, ok = scrape.Load("a", "running")
|
|
require.True(t, ok)
|
|
}
|
|
|
|
func TestCacheConcurrentScrapes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var (
|
|
cache metriccache.Cache[string, int]
|
|
wg sync.WaitGroup
|
|
)
|
|
|
|
for i := range 8 {
|
|
wg.Go(func() {
|
|
for j := range 100 {
|
|
scrape := cache.Begin()
|
|
|
|
for k := range 10 {
|
|
name := strconv.Itoa(k)
|
|
value := (i + j) % 3
|
|
|
|
metrics, ok := scrape.Load(name, value)
|
|
if !ok {
|
|
metrics = build(name, strconv.Itoa(value))
|
|
scrape.Store(name, value, metrics)
|
|
}
|
|
|
|
// require must not be called outside the test goroutine.
|
|
assert.Len(t, metrics, 1)
|
|
}
|
|
|
|
scrape.Commit()
|
|
}
|
|
})
|
|
}
|
|
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestScrapeGet(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var (
|
|
cache metriccache.Cache[string, string]
|
|
builds int
|
|
)
|
|
|
|
get := func(scrape *metriccache.Scrape[string, string], value string) []prometheus.Metric {
|
|
return scrape.Get("a", value, func() []prometheus.Metric {
|
|
builds++
|
|
|
|
return build("a", value)
|
|
})
|
|
}
|
|
|
|
scrape := cache.Begin()
|
|
a := get(scrape, "running")
|
|
scrape.Commit()
|
|
|
|
scrape = cache.Begin()
|
|
require.Same(t, a[0], get(scrape, "running")[0])
|
|
require.Equal(t, 1, builds, "unchanged value is reused")
|
|
|
|
require.NotSame(t, a[0], get(scrape, "stopped")[0])
|
|
require.Equal(t, 2, builds, "changed value is rebuilt")
|
|
scrape.Commit()
|
|
}
|
|
|
|
func TestScrapeAfterCommit(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var cache metriccache.Cache[string, string]
|
|
|
|
scrape := cache.Begin()
|
|
scrape.Store("a", "running", build("a", "running"))
|
|
scrape.Commit()
|
|
|
|
// A late call must not modify the committed generation.
|
|
scrape.Store("b", "stopped", build("b", "stopped"))
|
|
scrape.Commit()
|
|
|
|
next := cache.Begin()
|
|
_, ok := next.Load("a", "running")
|
|
require.True(t, ok)
|
|
|
|
_, ok = next.Load("b", "stopped")
|
|
require.False(t, ok, "stored after Commit")
|
|
}
|
|
|
|
func TestScrapeConcurrentWorkers(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var cache metriccache.Cache[string, int]
|
|
|
|
for range 10 {
|
|
scrape := cache.Begin()
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
// Several workers of one scrape share it, as in the service collector.
|
|
for worker := range 4 {
|
|
wg.Go(func() {
|
|
for k := range 100 {
|
|
name := strconv.Itoa(worker*100 + k)
|
|
|
|
metrics := scrape.Get(name, k%3, func() []prometheus.Metric {
|
|
return build(name, strconv.Itoa(k%3))
|
|
})
|
|
|
|
// require must not be called outside the test goroutine.
|
|
assert.Len(t, metrics, 1)
|
|
}
|
|
})
|
|
}
|
|
|
|
wg.Wait()
|
|
scrape.Commit()
|
|
}
|
|
}
|
|
|
|
func TestNilScrape(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var cache metriccache.Cache[string, string]
|
|
|
|
scrape := cache.Scrape()
|
|
require.Nil(t, scrape, "no scrape outside of BeginScrape")
|
|
|
|
built := 0
|
|
buildCounted := func() []prometheus.Metric {
|
|
built++
|
|
|
|
return build("a", "up")
|
|
}
|
|
|
|
metrics := scrape.Get("a", "up", buildCounted)
|
|
require.Len(t, metrics, 1)
|
|
|
|
_, ok := scrape.Load("a", "up")
|
|
require.False(t, ok)
|
|
scrape.Store("a", "up", metrics)
|
|
scrape.Commit()
|
|
|
|
scrape.Get("a", "up", buildCounted)
|
|
require.Equal(t, 2, built, "a nil scrape caches nothing")
|
|
}
|
|
|
|
// scrapeWith runs one scrape of keys the way the collection does around a
|
|
// Collect call that returns err.
|
|
func scrapeWith(cache *metriccache.Cache[string, string], err error, keys ...string) []prometheus.Metric {
|
|
cache.BeginScrape()
|
|
defer cache.EndScrape(err)
|
|
|
|
scrape := cache.Scrape()
|
|
metrics := make([]prometheus.Metric, 0, len(keys))
|
|
|
|
for _, key := range keys {
|
|
metrics = append(metrics, scrape.Get(key, "up", func() []prometheus.Metric {
|
|
return build(key, "up")
|
|
})...)
|
|
}
|
|
|
|
return metrics
|
|
}
|
|
|
|
func TestBeginEndScrape(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var cache metriccache.Cache[string, string]
|
|
|
|
first := scrapeWith(&cache, nil, "a", "b")
|
|
require.Nil(t, cache.Scrape(), "EndScrape ends the scrape")
|
|
|
|
second := scrapeWith(&cache, nil, "a", "b")
|
|
require.Same(t, first[0], second[0], "metrics reused")
|
|
require.Same(t, first[1], second[1])
|
|
|
|
// A Collect call that never asked for the scrape leaves the cache unchanged.
|
|
cache.BeginScrape()
|
|
cache.EndScrape(nil)
|
|
|
|
// A failed Collect that used no entry keeps the previous ones.
|
|
scrapeWith(&cache, errors.New("read failed"))
|
|
|
|
third := scrapeWith(&cache, nil, "a", "b")
|
|
require.Same(t, first[0], third[0], "failed empty scrape kept the entries")
|
|
|
|
// A failed Collect that used entries commits them.
|
|
scrapeWith(&cache, errors.New("partial"), "a")
|
|
|
|
fourth := scrapeWith(&cache, nil, "a", "b")
|
|
require.Same(t, first[0], fourth[0])
|
|
require.NotSame(t, first[1], fourth[1], "partial scrape dropped b")
|
|
|
|
// A successful empty scrape drops every entry.
|
|
scrapeWith(&cache, nil)
|
|
|
|
fifth := scrapeWith(&cache, nil, "a")
|
|
require.NotSame(t, fourth[0], fifth[0])
|
|
|
|
// Reset drops the entries, also those of a running scrape.
|
|
cache.BeginScrape()
|
|
cache.Scrape().Get("a", "up", func() []prometheus.Metric { return build("a", "up") })
|
|
cache.Reset()
|
|
cache.EndScrape(nil)
|
|
|
|
sixth := scrapeWith(&cache, nil, "a")
|
|
require.NotSame(t, fifth[0], sixth[0])
|
|
|
|
// EndScrape without a scrape does nothing.
|
|
cache.EndScrape(nil)
|
|
}
|
|
|
|
func TestScrapeCreatedOnce(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var (
|
|
cache metriccache.Cache[string, string]
|
|
wg sync.WaitGroup
|
|
scrapes [8]*metriccache.Scrape[string, string]
|
|
)
|
|
|
|
cache.BeginScrape()
|
|
|
|
for i := range scrapes {
|
|
wg.Go(func() {
|
|
scrapes[i] = cache.Scrape()
|
|
})
|
|
}
|
|
|
|
wg.Wait()
|
|
cache.EndScrape(nil)
|
|
|
|
for _, scrape := range scrapes {
|
|
require.NotNil(t, scrape)
|
|
require.Same(t, scrapes[0], scrape, "the workers of a scrape share one scrape")
|
|
}
|
|
}
|
|
|
|
func BenchmarkScrape(b *testing.B) {
|
|
var cache metriccache.Cache[string, int]
|
|
|
|
names := make([]string, 1000)
|
|
for i := range names {
|
|
names[i] = strconv.Itoa(i)
|
|
}
|
|
|
|
b.ReportAllocs()
|
|
|
|
for b.Loop() {
|
|
scrape := cache.Begin()
|
|
|
|
for _, name := range names {
|
|
if _, ok := scrape.Load(name, 0); !ok {
|
|
scrape.Store(name, 0, build(name, "0"))
|
|
}
|
|
}
|
|
|
|
scrape.Commit()
|
|
}
|
|
}
|