terminal_services: refactor collector (#1729)
This commit is contained in:
parent
b4f50c542c
commit
f332361723
|
@ -0,0 +1,23 @@
|
|||
package terminal_services
|
||||
|
||||
const (
|
||||
HandleCount = "Handle Count"
|
||||
PageFaultsPersec = "Page Faults/sec"
|
||||
PageFileBytes = "Page File Bytes"
|
||||
PageFileBytesPeak = "Page File Bytes Peak"
|
||||
PercentPrivilegedTime = "% Privileged Time"
|
||||
PercentProcessorTime = "% Processor Time"
|
||||
PercentUserTime = "% User Time"
|
||||
PoolNonpagedBytes = "Pool Nonpaged Bytes"
|
||||
PoolPagedBytes = "Pool Paged Bytes"
|
||||
PrivateBytes = "Private Bytes"
|
||||
ThreadCount = "Thread Count"
|
||||
VirtualBytes = "Virtual Bytes"
|
||||
VirtualBytesPeak = "Virtual Bytes Peak"
|
||||
WorkingSet = "Working Set"
|
||||
WorkingSetPeak = "Working Set Peak"
|
||||
|
||||
SuccessfulConnections = "Successful Connections"
|
||||
PendingConnections = "Pending Connections"
|
||||
FailedConnections = "Failed Connections"
|
||||
)
|
|
@ -12,7 +12,8 @@ import (
|
|||
"github.com/alecthomas/kingpin/v2"
|
||||
"github.com/prometheus-community/windows_exporter/internal/headers/wtsapi32"
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1"
|
||||
"github.com/prometheus-community/windows_exporter/internal/perfdata"
|
||||
"github.com/prometheus-community/windows_exporter/internal/perfdata/perftypes"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
@ -32,7 +33,7 @@ type Win32_ServerFeature struct {
|
|||
ID uint32
|
||||
}
|
||||
|
||||
func isConnectionBrokerServer(logger *slog.Logger, miSession *mi.Session) bool {
|
||||
func isConnectionBrokerServer(miSession *mi.Session) bool {
|
||||
var dst []Win32_ServerFeature
|
||||
if err := miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * FROM Win32_ServerFeature"))); err != nil {
|
||||
return false
|
||||
|
@ -44,8 +45,6 @@ func isConnectionBrokerServer(logger *slog.Logger, miSession *mi.Session) bool {
|
|||
}
|
||||
}
|
||||
|
||||
logger.Debug("host is not a connection broker skipping Connection Broker performance metrics.")
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -58,6 +57,9 @@ type Collector struct {
|
|||
|
||||
connectionBrokerEnabled bool
|
||||
|
||||
perfDataCollectorTerminalServicesSession perfdata.Collector
|
||||
perfDataCollectorBroker perfdata.Collector
|
||||
|
||||
hServer windows.Handle
|
||||
|
||||
sessionInfo *prometheus.Desc
|
||||
|
@ -98,10 +100,7 @@ func (c *Collector) GetName() string {
|
|||
}
|
||||
|
||||
func (c *Collector) GetPerfCounter(_ *slog.Logger) ([]string, error) {
|
||||
return []string{
|
||||
"Terminal Services Session",
|
||||
"Remote Desktop Connection Broker Counterset",
|
||||
}, nil
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
func (c *Collector) Close(_ *slog.Logger) error {
|
||||
|
@ -110,6 +109,12 @@ func (c *Collector) Close(_ *slog.Logger) error {
|
|||
return fmt.Errorf("failed to close WTS server: %w", err)
|
||||
}
|
||||
|
||||
c.perfDataCollectorTerminalServicesSession.Close()
|
||||
|
||||
if c.connectionBrokerEnabled {
|
||||
c.perfDataCollectorBroker.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -120,7 +125,49 @@ func (c *Collector) Build(logger *slog.Logger, miSession *mi.Session) error {
|
|||
|
||||
logger = logger.With(slog.String("collector", Name))
|
||||
|
||||
c.connectionBrokerEnabled = isConnectionBrokerServer(logger, miSession)
|
||||
counters := []string{
|
||||
HandleCount,
|
||||
PageFaultsPersec,
|
||||
PageFileBytes,
|
||||
PageFileBytesPeak,
|
||||
PercentPrivilegedTime,
|
||||
PercentProcessorTime,
|
||||
PercentUserTime,
|
||||
PoolNonpagedBytes,
|
||||
PoolPagedBytes,
|
||||
PrivateBytes,
|
||||
ThreadCount,
|
||||
VirtualBytes,
|
||||
VirtualBytesPeak,
|
||||
WorkingSet,
|
||||
WorkingSetPeak,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
c.perfDataCollectorTerminalServicesSession, err = perfdata.NewCollector(perfdata.V2, "Terminal Services Session", perfdata.AllInstances, counters)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Terminal Services Session collector: %w", err)
|
||||
}
|
||||
|
||||
c.connectionBrokerEnabled = isConnectionBrokerServer(miSession)
|
||||
|
||||
if c.connectionBrokerEnabled {
|
||||
counters = []string{
|
||||
SuccessfulConnections,
|
||||
PendingConnections,
|
||||
FailedConnections,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
c.perfDataCollectorBroker, err = perfdata.NewCollector(perfdata.V2, "Remote Desktop Connection Broker Counterset", perfdata.AllInstances, counters)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Remote Desktop Connection Broker Counterset collector: %w", err)
|
||||
}
|
||||
} else {
|
||||
logger.Debug("host is not a connection broker skipping Connection Broker performance metrics.")
|
||||
}
|
||||
|
||||
c.sessionInfo = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "session_info"),
|
||||
|
@ -213,8 +260,6 @@ func (c *Collector) Build(logger *slog.Logger, miSession *mi.Session) error {
|
|||
nil,
|
||||
)
|
||||
|
||||
var err error
|
||||
|
||||
c.hServer, err = wtsapi32.WTSOpenServer("")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open WTS server: %w", err)
|
||||
|
@ -225,71 +270,40 @@ func (c *Collector) Build(logger *slog.Logger, miSession *mi.Session) error {
|
|||
|
||||
// Collect sends the metric values for each metric
|
||||
// to the provided prometheus Metric channel.
|
||||
func (c *Collector) Collect(ctx *types.ScrapeContext, logger *slog.Logger, ch chan<- prometheus.Metric) error {
|
||||
func (c *Collector) Collect(_ *types.ScrapeContext, logger *slog.Logger, ch chan<- prometheus.Metric) error {
|
||||
logger = logger.With(slog.String("collector", Name))
|
||||
if err := c.collectWTSSessions(logger, ch); err != nil {
|
||||
logger.Error("failed collecting terminal services session infos",
|
||||
slog.Any("err", err),
|
||||
)
|
||||
|
||||
return err
|
||||
errs := make([]error, 0, 3)
|
||||
|
||||
if err := c.collectWTSSessions(logger, ch); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed collecting terminal services session infos: %w", err))
|
||||
}
|
||||
|
||||
if err := c.collectTSSessionCounters(ctx, logger, ch); err != nil {
|
||||
logger.Error("failed collecting terminal services session count metrics",
|
||||
slog.Any("err", err),
|
||||
)
|
||||
|
||||
return err
|
||||
if err := c.collectTSSessionCounters(ch); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed collecting terminal services session count metrics: %w", err))
|
||||
}
|
||||
|
||||
// only collect CollectionBrokerPerformance if host is a Connection Broker
|
||||
if c.connectionBrokerEnabled {
|
||||
if err := c.collectCollectionBrokerPerformanceCounter(ctx, logger, ch); err != nil {
|
||||
logger.Error("failed collecting Connection Broker performance metrics",
|
||||
slog.Any("err", err),
|
||||
)
|
||||
|
||||
return err
|
||||
if err := c.collectCollectionBrokerPerformanceCounter(ch); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed collecting Connection Broker performance metrics: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
type perflibTerminalServicesSession struct {
|
||||
Name string
|
||||
HandleCount float64 `perflib:"Handle Count"`
|
||||
PageFaultsPersec float64 `perflib:"Page Faults/sec"`
|
||||
PageFileBytes float64 `perflib:"Page File Bytes"`
|
||||
PageFileBytesPeak float64 `perflib:"Page File Bytes Peak"`
|
||||
PercentPrivilegedTime float64 `perflib:"% Privileged Time"`
|
||||
PercentProcessorTime float64 `perflib:"% Processor Time"`
|
||||
PercentUserTime float64 `perflib:"% User Time"`
|
||||
PoolNonpagedBytes float64 `perflib:"Pool Nonpaged Bytes"`
|
||||
PoolPagedBytes float64 `perflib:"Pool Paged Bytes"`
|
||||
PrivateBytes float64 `perflib:"Private Bytes"`
|
||||
ThreadCount float64 `perflib:"Thread Count"`
|
||||
VirtualBytes float64 `perflib:"Virtual Bytes"`
|
||||
VirtualBytesPeak float64 `perflib:"Virtual Bytes Peak"`
|
||||
WorkingSet float64 `perflib:"Working Set"`
|
||||
WorkingSetPeak float64 `perflib:"Working Set Peak"`
|
||||
}
|
||||
|
||||
func (c *Collector) collectTSSessionCounters(ctx *types.ScrapeContext, logger *slog.Logger, ch chan<- prometheus.Metric) error {
|
||||
logger = logger.With(slog.String("collector", Name))
|
||||
dst := make([]perflibTerminalServicesSession, 0)
|
||||
|
||||
err := v1.UnmarshalObject(ctx.PerfObjects["Terminal Services Session"], &dst, logger)
|
||||
func (c *Collector) collectTSSessionCounters(ch chan<- prometheus.Metric) error {
|
||||
perfData, err := c.perfDataCollectorTerminalServicesSession.Collect()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to collect Terminal Services Session metrics: %w", err)
|
||||
}
|
||||
|
||||
names := make(map[string]bool)
|
||||
|
||||
for _, d := range dst {
|
||||
for name, data := range perfData {
|
||||
// only connect metrics for remote named sessions
|
||||
n := strings.ToLower(d.Name)
|
||||
n := strings.ToLower(name)
|
||||
if n == "" || n == "services" || n == "console" {
|
||||
continue
|
||||
}
|
||||
|
@ -303,138 +317,130 @@ func (c *Collector) collectTSSessionCounters(ctx *types.ScrapeContext, logger *s
|
|||
ch <- prometheus.MustNewConstMetric(
|
||||
c.handleCount,
|
||||
prometheus.GaugeValue,
|
||||
d.HandleCount,
|
||||
d.Name,
|
||||
data[HandleCount].FirstValue,
|
||||
name,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.pageFaultsPerSec,
|
||||
prometheus.CounterValue,
|
||||
d.PageFaultsPersec,
|
||||
d.Name,
|
||||
data[PageFaultsPersec].FirstValue,
|
||||
name,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.pageFileBytes,
|
||||
prometheus.GaugeValue,
|
||||
d.PageFileBytes,
|
||||
d.Name,
|
||||
data[PageFileBytes].FirstValue,
|
||||
name,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.pageFileBytesPeak,
|
||||
prometheus.GaugeValue,
|
||||
d.PageFileBytesPeak,
|
||||
d.Name,
|
||||
data[PageFileBytesPeak].FirstValue,
|
||||
name,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.percentCPUTime,
|
||||
prometheus.CounterValue,
|
||||
d.PercentPrivilegedTime,
|
||||
d.Name,
|
||||
data[PercentPrivilegedTime].FirstValue,
|
||||
name,
|
||||
"privileged",
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.percentCPUTime,
|
||||
prometheus.CounterValue,
|
||||
d.PercentProcessorTime,
|
||||
d.Name,
|
||||
data[PercentProcessorTime].FirstValue,
|
||||
name,
|
||||
"processor",
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.percentCPUTime,
|
||||
prometheus.CounterValue,
|
||||
d.PercentUserTime,
|
||||
d.Name,
|
||||
data[PercentUserTime].FirstValue,
|
||||
name,
|
||||
"user",
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.poolNonPagedBytes,
|
||||
prometheus.GaugeValue,
|
||||
d.PoolNonpagedBytes,
|
||||
d.Name,
|
||||
data[PoolNonpagedBytes].FirstValue,
|
||||
name,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.poolPagedBytes,
|
||||
prometheus.GaugeValue,
|
||||
d.PoolPagedBytes,
|
||||
d.Name,
|
||||
data[PoolPagedBytes].FirstValue,
|
||||
name,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.privateBytes,
|
||||
prometheus.GaugeValue,
|
||||
d.PrivateBytes,
|
||||
d.Name,
|
||||
data[PrivateBytes].FirstValue,
|
||||
name,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.threadCount,
|
||||
prometheus.GaugeValue,
|
||||
d.ThreadCount,
|
||||
d.Name,
|
||||
data[ThreadCount].FirstValue,
|
||||
name,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.virtualBytes,
|
||||
prometheus.GaugeValue,
|
||||
d.VirtualBytes,
|
||||
d.Name,
|
||||
data[VirtualBytes].FirstValue,
|
||||
name,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.virtualBytesPeak,
|
||||
prometheus.GaugeValue,
|
||||
d.VirtualBytesPeak,
|
||||
d.Name,
|
||||
data[VirtualBytesPeak].FirstValue,
|
||||
name,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.workingSet,
|
||||
prometheus.GaugeValue,
|
||||
d.WorkingSet,
|
||||
d.Name,
|
||||
data[WorkingSet].FirstValue,
|
||||
name,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.workingSetPeak,
|
||||
prometheus.GaugeValue,
|
||||
d.WorkingSetPeak,
|
||||
d.Name,
|
||||
data[WorkingSetPeak].FirstValue,
|
||||
name,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type perflibRemoteDesktopConnectionBrokerCounterset struct {
|
||||
SuccessfulConnections float64 `perflib:"Successful Connections"`
|
||||
PendingConnections float64 `perflib:"Pending Connections"`
|
||||
FailedConnections float64 `perflib:"Failed Connections"`
|
||||
}
|
||||
|
||||
func (c *Collector) collectCollectionBrokerPerformanceCounter(ctx *types.ScrapeContext, logger *slog.Logger, ch chan<- prometheus.Metric) error {
|
||||
logger = logger.With(slog.String("collector", Name))
|
||||
dst := make([]perflibRemoteDesktopConnectionBrokerCounterset, 0)
|
||||
|
||||
err := v1.UnmarshalObject(ctx.PerfObjects["Remote Desktop Connection Broker Counterset"], &dst, logger)
|
||||
func (c *Collector) collectCollectionBrokerPerformanceCounter(ch chan<- prometheus.Metric) error {
|
||||
perfData, err := c.perfDataCollectorBroker.Collect()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to collect Remote Desktop Connection Broker Counterset metrics: %w", err)
|
||||
}
|
||||
|
||||
if len(dst) == 0 {
|
||||
return errors.New("WMI query returned empty result set")
|
||||
data, ok := perfData[perftypes.EmptyInstance]
|
||||
if !ok {
|
||||
return errors.New("query for Remote Desktop Connection Broker Counterset returned empty result set")
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.connectionBrokerPerformance,
|
||||
prometheus.CounterValue,
|
||||
dst[0].SuccessfulConnections,
|
||||
data[SuccessfulConnections].FirstValue,
|
||||
"Successful",
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.connectionBrokerPerformance,
|
||||
prometheus.CounterValue,
|
||||
dst[0].PendingConnections,
|
||||
data[PendingConnections].FirstValue,
|
||||
"Pending",
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.connectionBrokerPerformance,
|
||||
prometheus.CounterValue,
|
||||
dst[0].FailedConnections,
|
||||
data[FailedConnections].FirstValue,
|
||||
"Failed",
|
||||
)
|
||||
|
||||
|
@ -448,6 +454,12 @@ func (c *Collector) collectWTSSessions(logger *slog.Logger, ch chan<- prometheus
|
|||
}
|
||||
|
||||
for _, session := range sessions {
|
||||
// only connect metrics for remote named sessions
|
||||
n := strings.ReplaceAll(session.SessionName, "#", " ")
|
||||
if n == "" || n == "Services" || n == "Console" {
|
||||
continue
|
||||
}
|
||||
|
||||
userName := session.UserName
|
||||
if session.DomainName != "" {
|
||||
userName = fmt.Sprintf("%s\\%s", session.DomainName, session.UserName)
|
||||
|
@ -458,12 +470,11 @@ func (c *Collector) collectWTSSessions(logger *slog.Logger, ch chan<- prometheus
|
|||
if session.State == stateID {
|
||||
isState = 1.0
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.sessionInfo,
|
||||
prometheus.GaugeValue,
|
||||
isState,
|
||||
strings.ReplaceAll(session.SessionName, "#", " "),
|
||||
n,
|
||||
userName,
|
||||
session.HostName,
|
||||
stateName,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package wtsapi32
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"unsafe"
|
||||
|
@ -129,7 +130,7 @@ func WTSOpenServer(server string) (windows.Handle, error) {
|
|||
func WTSCloseServer(server windows.Handle) error {
|
||||
r1, _, err := procWTSCloseServer.Call(uintptr(server))
|
||||
|
||||
if r1 != 1 {
|
||||
if r1 != 1 && !errors.Is(err, windows.ERROR_SUCCESS) {
|
||||
return fmt.Errorf("failed to close server: %w", err)
|
||||
}
|
||||
|
||||
|
@ -170,8 +171,7 @@ func WTSEnumerateSessionsEx(server windows.Handle, logger *slog.Logger) ([]WTSSe
|
|||
|
||||
if sessionInfoPointer != 0 {
|
||||
defer func(class WTSTypeClass, pMemory uintptr, NumberOfEntries uint32) {
|
||||
err := WTSFreeMemoryEx(class, pMemory, NumberOfEntries)
|
||||
if err != nil {
|
||||
if err := WTSFreeMemoryEx(class, pMemory, NumberOfEntries); err != nil {
|
||||
logger.Warn("failed to free memory", "err", fmt.Errorf("WTSEnumerateSessionsEx: %w", err))
|
||||
}
|
||||
}(WTSTypeSessionInfoLevel1, sessionInfoPointer, count)
|
||||
|
|
|
@ -5,8 +5,10 @@ package mi
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
@ -14,6 +16,10 @@ import (
|
|||
|
||||
// We have to registry a global callback function, since the amount of callbacks is limited.
|
||||
var operationUnmarshalCallbacksInstanceResult = sync.OnceValue[uintptr](func() uintptr {
|
||||
// Workaround for a deadlock issue in go.
|
||||
// Ref: https://github.com/golang/go/issues/55015
|
||||
go time.Sleep(time.Duration(math.MaxInt64))
|
||||
|
||||
return windows.NewCallback(func(
|
||||
operation *Operation,
|
||||
callbacks *OperationUnmarshalCallbacks,
|
||||
|
|
|
@ -212,9 +212,16 @@ func (s *Session) QueryUnmarshal(dst any,
|
|||
|
||||
errs := make([]error, 0)
|
||||
|
||||
for err := range errCh {
|
||||
if err != nil {
|
||||
// We need an active go routine to prevent a
|
||||
// fatal error: all goroutines are asleep - deadlock!
|
||||
// ref: https://github.com/golang/go/issues/55015
|
||||
// go time.Sleep(5 * time.Second)
|
||||
|
||||
for {
|
||||
if err, ok := <-errCh; err != nil {
|
||||
errs = append(errs, err)
|
||||
} else if !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue