|
|
|
@@ -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,
|
|
|
|
|