mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-17 01:59:04 +02:00
284 lines
6.7 KiB
Go
284 lines
6.7 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.
|
|
|
|
//go:build windows
|
|
|
|
package collector
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"runtime/debug"
|
|
"sync"
|
|
|
|
"github.com/prometheus-community/windows_exporter/internal/mi"
|
|
"github.com/prometheus-community/windows_exporter/internal/scrapecache"
|
|
)
|
|
|
|
var (
|
|
errCollectorBusy = errors.New("collector is still running")
|
|
errCollectionClosed = errors.New("collection is closed")
|
|
)
|
|
|
|
// collectionState is the state of a Collection that its filtered views share.
|
|
type collectionState struct {
|
|
mu sync.Mutex
|
|
collectors map[string]*collectorState
|
|
miApp *mi.Application
|
|
miSession *mi.Session
|
|
|
|
// scrapeSlot admits one scrape at a time to the collection and its filtered views.
|
|
// Independent collections don't share collector instances, so they don't wait for each other.
|
|
scrapeSlot chan struct{}
|
|
|
|
// closeMu serializes Close calls.
|
|
closeMu sync.Mutex
|
|
}
|
|
|
|
func newCollectionState() *collectionState {
|
|
return &collectionState{
|
|
collectors: make(map[string]*collectorState),
|
|
scrapeSlot: make(chan struct{}, 1),
|
|
}
|
|
}
|
|
|
|
// acquireScrapeSlot waits for the scrape slot until ctx is done.
|
|
func (s *collectionState) acquireScrapeSlot(ctx context.Context) error {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
|
|
select {
|
|
case s.scrapeSlot <- struct{}{}:
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
|
|
// Both cases may have been ready. Don't start a scrape that nobody waits for anymore.
|
|
if err := ctx.Err(); err != nil {
|
|
<-s.scrapeSlot
|
|
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// releaseScrapeSlot releases the slot acquired by acquireScrapeSlot.
|
|
func (s *collectionState) releaseScrapeSlot() {
|
|
<-s.scrapeSlot
|
|
}
|
|
|
|
// collector returns the state of the named collector, creating it on first use.
|
|
func (s *collectionState) collector(name string) *collectorState {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
state, ok := s.collectors[name]
|
|
if !ok {
|
|
state = &collectorState{slot: make(chan struct{}, 1)}
|
|
s.collectors[name] = state
|
|
}
|
|
|
|
return state
|
|
}
|
|
|
|
func (s *collectionState) session() *mi.Session {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
return s.miSession
|
|
}
|
|
|
|
func (s *collectionState) setMI(app *mi.Application, session *mi.Session) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.miApp = app
|
|
s.miSession = session
|
|
}
|
|
|
|
// closeMI closes the MI session and application. Later calls do nothing.
|
|
func (s *collectionState) closeMI() []error {
|
|
s.mu.Lock()
|
|
app, session := s.miApp, s.miSession
|
|
s.miApp, s.miSession = nil, nil
|
|
s.mu.Unlock()
|
|
|
|
var errs []error
|
|
|
|
if session != nil {
|
|
if err := session.Close(); err != nil && !errors.Is(err, mi.ErrNotInitialized) {
|
|
errs = append(errs, fmt.Errorf("error from close MI session: %w", err))
|
|
}
|
|
}
|
|
|
|
if app != nil {
|
|
if err := app.Close(); err != nil && !errors.Is(err, mi.ErrNotInitialized) {
|
|
errs = append(errs, fmt.Errorf("error from close MI application: %w", err))
|
|
}
|
|
}
|
|
|
|
return errs
|
|
}
|
|
|
|
type startResult int
|
|
|
|
const (
|
|
started startResult = iota
|
|
busy
|
|
notAvailable
|
|
)
|
|
|
|
// collectorState tracks the lifecycle of one collector instance.
|
|
type collectorState struct {
|
|
// slot is held while Build, Collect or Close of the collector runs.
|
|
// A Collect call that outlives its scrape keeps the slot until it returns,
|
|
// so no scrape starts a second call on the same instance.
|
|
slot chan struct{}
|
|
|
|
mu sync.Mutex
|
|
// buildErr is the error of the last Build. A collector that failed to build is not collected.
|
|
buildErr error
|
|
// built is true once Build was called, so Close only closes collectors that may hold resources.
|
|
built bool
|
|
// closing is true once Close was called. No Build or Collect starts afterwards.
|
|
closing bool
|
|
// closed is true once Collector.Close was called.
|
|
closed bool
|
|
// busyLogged is true once a scrape logged that it skipped the current run.
|
|
busyLogged bool
|
|
}
|
|
|
|
// tryCollect acquires the slot for a Collect call without waiting.
|
|
func (s *collectorState) tryCollect() startResult {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if s.closing || s.buildErr != nil {
|
|
return notAvailable
|
|
}
|
|
|
|
select {
|
|
case s.slot <- struct{}{}:
|
|
return started
|
|
default:
|
|
return busy
|
|
}
|
|
}
|
|
|
|
// release releases the slot acquired by tryCollect.
|
|
func (s *collectorState) release() {
|
|
s.mu.Lock()
|
|
s.busyLogged = false
|
|
s.mu.Unlock()
|
|
|
|
<-s.slot
|
|
}
|
|
|
|
// logBusy reports whether a skipped scrape is the first one of the current run.
|
|
func (s *collectorState) logBusy() bool {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
first := !s.busyLogged
|
|
s.busyLogged = true
|
|
|
|
return first
|
|
}
|
|
|
|
// build runs Collector.Build while it holds the slot and records the result.
|
|
// A panic in Collector.Build is returned as an error.
|
|
func (s *collectorState) build(ctx context.Context, logger *slog.Logger, miSession *mi.Session, collector Collector) (err error) {
|
|
select {
|
|
case s.slot <- struct{}{}:
|
|
case <-ctx.Done():
|
|
err = context.Cause(ctx)
|
|
|
|
s.mu.Lock()
|
|
s.buildErr = err
|
|
s.mu.Unlock()
|
|
|
|
return err
|
|
}
|
|
|
|
defer func() {
|
|
s.mu.Lock()
|
|
s.buildErr = err
|
|
s.mu.Unlock()
|
|
|
|
<-s.slot
|
|
}()
|
|
|
|
s.mu.Lock()
|
|
closing := s.closing
|
|
s.built = !closing
|
|
s.mu.Unlock()
|
|
|
|
if closing {
|
|
return errCollectionClosed
|
|
}
|
|
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = fmt.Errorf("panic: %v. stack: %s", r, string(debug.Stack()))
|
|
}
|
|
}()
|
|
|
|
// Cached metrics refer to the descriptors of an earlier Build.
|
|
scrapecache.Reset(collector)
|
|
|
|
return collector.Build(logger, miSession)
|
|
}
|
|
|
|
// close calls Collector.Close once the collector is idle. It waits until ctx is done for a running
|
|
// Build or Collect call to return. If the call does not return in time, the collector is left open,
|
|
// because closing it would release resources that are still in use.
|
|
// After close, the collector is never built or collected again.
|
|
func (s *collectorState) close(ctx context.Context, collector Collector) (err error) {
|
|
s.mu.Lock()
|
|
s.closing = true
|
|
done := s.closed || !s.built
|
|
s.mu.Unlock()
|
|
|
|
if done {
|
|
return nil
|
|
}
|
|
|
|
select {
|
|
case s.slot <- struct{}{}:
|
|
// The slot is never released, so no call starts after Close.
|
|
case <-ctx.Done():
|
|
return errCollectorBusy
|
|
}
|
|
|
|
s.mu.Lock()
|
|
s.closed = true
|
|
s.mu.Unlock()
|
|
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = fmt.Errorf("panic: %v. stack: %s", r, string(debug.Stack()))
|
|
}
|
|
}()
|
|
|
|
// Release the cached metrics, also if Close panics.
|
|
defer scrapecache.Reset(collector)
|
|
|
|
return collector.Close()
|
|
}
|