container: support hostprocess containers and expose kubernetes labels (#1911)

This commit is contained in:
Jan-Otto Kröpke
2025-05-18 09:39:52 +02:00
committed by GitHub
parent 6b87441729
commit 898e16bcb1
43 changed files with 1800 additions and 296 deletions

View File

@@ -18,25 +18,48 @@
package container
import (
"encoding/json"
"errors"
"fmt"
"io/fs"
"log/slog"
"os"
"slices"
"strings"
"unsafe"
"github.com/Microsoft/hcsshim"
"github.com/alecthomas/kingpin/v2"
"github.com/prometheus-community/windows_exporter/internal/headers/guid"
"github.com/prometheus-community/windows_exporter/internal/headers/hcn"
"github.com/prometheus-community/windows_exporter/internal/headers/hcs"
"github.com/prometheus-community/windows_exporter/internal/headers/iphlpapi"
"github.com/prometheus-community/windows_exporter/internal/headers/kernel32"
"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/client_golang/prometheus"
"golang.org/x/sys/windows"
)
const Name = "container"
const (
Name = "container"
type Config struct{}
subCollectorHCS = "hcs"
subCollectorHostprocess = "hostprocess"
containerDStateDir = `C:\ProgramData\containerd\state\io.containerd.runtime.v2.task\k8s.io\`
)
type Config struct {
CollectorsEnabled []string `yaml:"collectors_enabled"`
}
//nolint:gochecknoglobals
var ConfigDefaults = Config{}
var ConfigDefaults = Config{
CollectorsEnabled: []string{
subCollectorHCS,
subCollectorHostprocess,
},
}
// A Collector is a Prometheus Collector for containers metrics.
type Collector struct {
@@ -44,6 +67,9 @@ type Collector struct {
logger *slog.Logger
annotationsCacheHCS map[string]containerInfo
annotationsCacheJob map[string]containerInfo
// Presence
containerAvailable *prometheus.Desc
@@ -75,12 +101,27 @@ type Collector struct {
writeSizeBytes *prometheus.Desc
}
type containerInfo struct {
id string
namespace string
pod string
container string
}
type ociSpec struct {
Annotations map[string]string `json:"annotations"`
}
// New constructs a new Collector.
func New(config *Config) *Collector {
if config == nil {
config = &ConfigDefaults
}
if config.CollectorsEnabled == nil {
config.CollectorsEnabled = ConfigDefaults.CollectorsEnabled
}
c := &Collector{
config: *config,
}
@@ -88,8 +129,26 @@ func New(config *Config) *Collector {
return c
}
func NewWithFlags(_ *kingpin.Application) *Collector {
return &Collector{}
func NewWithFlags(app *kingpin.Application) *Collector {
c := &Collector{
config: ConfigDefaults,
}
c.config.CollectorsEnabled = make([]string, 0)
var collectorsEnabled string
app.Flag(
"collector.container.enabled",
"Comma-separated list of collectors to use. Defaults to all, if not specified.",
).Default(strings.Join(ConfigDefaults.CollectorsEnabled, ",")).StringVar(&collectorsEnabled)
app.Action(func(*kingpin.ParseContext) error {
c.config.CollectorsEnabled = strings.Split(collectorsEnabled, ",")
return nil
})
return c
}
func (c *Collector) GetName() string {
@@ -103,10 +162,16 @@ func (c *Collector) Close() error {
func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
c.logger = logger.With(slog.String("collector", Name))
for _, collector := range c.config.CollectorsEnabled {
if !slices.Contains([]string{subCollectorHCS, subCollectorHostprocess}, collector) {
return fmt.Errorf("unknown collector: %s", collector)
}
}
c.containerAvailable = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "available"),
"Available",
[]string{"container_id"},
[]string{"container_id", "namespace", "pod", "container"},
nil,
)
c.containersCount = prometheus.NewDesc(
@@ -118,97 +183,97 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
c.usageCommitBytes = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "memory_usage_commit_bytes"),
"Memory Usage Commit Bytes",
[]string{"container_id"},
[]string{"container_id", "namespace", "pod", "container"},
nil,
)
c.usageCommitPeakBytes = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "memory_usage_commit_peak_bytes"),
"Memory Usage Commit Peak Bytes",
[]string{"container_id"},
[]string{"container_id", "namespace", "pod", "container"},
nil,
)
c.usagePrivateWorkingSetBytes = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "memory_usage_private_working_set_bytes"),
"Memory Usage Private Working Set Bytes",
[]string{"container_id"},
[]string{"container_id", "namespace", "pod", "container"},
nil,
)
c.runtimeTotal = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "cpu_usage_seconds_total"),
"Total Run time in Seconds",
[]string{"container_id"},
[]string{"container_id", "namespace", "pod", "container"},
nil,
)
c.runtimeUser = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "cpu_usage_seconds_usermode"),
"Run Time in User mode in Seconds",
[]string{"container_id"},
[]string{"container_id", "namespace", "pod", "container"},
nil,
)
c.runtimeKernel = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "cpu_usage_seconds_kernelmode"),
"Run time in Kernel mode in Seconds",
[]string{"container_id"},
[]string{"container_id", "namespace", "pod", "container"},
nil,
)
c.bytesReceived = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "network_receive_bytes_total"),
"Bytes Received on Interface",
[]string{"container_id", "interface"},
[]string{"container_id", "namespace", "pod", "container", "interface"},
nil,
)
c.bytesSent = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "network_transmit_bytes_total"),
"Bytes Sent on Interface",
[]string{"container_id", "interface"},
[]string{"container_id", "namespace", "pod", "container", "interface"},
nil,
)
c.packetsReceived = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "network_receive_packets_total"),
"Packets Received on Interface",
[]string{"container_id", "interface"},
[]string{"container_id", "namespace", "pod", "container", "interface"},
nil,
)
c.packetsSent = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "network_transmit_packets_total"),
"Packets Sent on Interface",
[]string{"container_id", "interface"},
[]string{"container_id", "namespace", "pod", "container", "interface"},
nil,
)
c.droppedPacketsIncoming = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "network_receive_packets_dropped_total"),
"Dropped Incoming Packets on Interface",
[]string{"container_id", "interface"},
[]string{"container_id", "namespace", "pod", "container", "interface"},
nil,
)
c.droppedPacketsOutgoing = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "network_transmit_packets_dropped_total"),
"Dropped Outgoing Packets on Interface",
[]string{"container_id", "interface"},
[]string{"container_id", "namespace", "pod", "container", "interface"},
nil,
)
c.readCountNormalized = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "storage_read_count_normalized_total"),
"Read Count Normalized",
[]string{"container_id"},
[]string{"container_id", "namespace", "pod", "container"},
nil,
)
c.readSizeBytes = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "storage_read_size_bytes_total"),
"Read Size Bytes",
[]string{"container_id"},
[]string{"container_id", "namespace", "pod", "container"},
nil,
)
c.writeCountNormalized = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "storage_write_count_normalized_total"),
"Write Count Normalized",
[]string{"container_id"},
[]string{"container_id", "namespace", "pod", "container"},
nil,
)
c.writeSizeBytes = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "storage_write_size_bytes_total"),
"Write Size Bytes",
[]string{"container_id"},
[]string{"container_id", "namespace", "pod", "container"},
nil,
)
@@ -218,39 +283,85 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
errs := make([]error, 0)
if slices.Contains(c.config.CollectorsEnabled, subCollectorHCS) {
if err := c.collectHCS(ch); err != nil {
errs = append(errs, err)
}
}
if slices.Contains(c.config.CollectorsEnabled, subCollectorHostprocess) {
if err := c.collectJobContainers(ch); err != nil {
errs = append(errs, err)
}
}
return errors.Join(errs...)
}
func (c *Collector) collectHCS(ch chan<- prometheus.Metric) error {
// Types Container is passed to get the containers compute systems only
containers, err := hcsshim.GetContainers(hcsshim.ComputeSystemQuery{Types: []string{"Container"}})
containers, err := hcs.GetContainers()
if err != nil {
return fmt.Errorf("error in fetching containers: %w", err)
}
count := len(containers)
ch <- prometheus.MustNewConstMetric(
c.containersCount,
prometheus.GaugeValue,
float64(count),
)
if count == 0 {
ch <- prometheus.MustNewConstMetric(
c.containersCount,
prometheus.GaugeValue,
0,
)
return nil
}
containerPrefixes := make(map[string]string)
var countersCount float64
containerIDs := make([]string, 0, len(containers))
collectErrors := make([]error, 0, len(containers))
for _, containerDetails := range containers {
containerIdWithPrefix := getContainerIdWithPrefix(containerDetails)
for _, container := range containers {
if container.State != "Running" {
continue
}
if err = c.collectContainer(ch, containerDetails, containerIdWithPrefix); err != nil {
if hcsshim.IsNotExist(err) {
containerIDs = append(containerIDs, container.ID)
countersCount++
var (
namespace string
podName string
containerName string
)
if _, ok := c.annotationsCacheHCS[container.ID]; !ok {
if spec, err := getContainerAnnotations(container.ID); err == nil {
namespace = spec.Annotations["io.kubernetes.cri.sandbox-namespace"]
podName = spec.Annotations["io.kubernetes.cri.sandbox-name"]
containerName = spec.Annotations["io.kubernetes.cri.container-name"]
}
c.annotationsCacheHCS[container.ID] = containerInfo{
id: getContainerIdWithPrefix(container),
namespace: namespace,
pod: podName,
container: containerName,
}
}
if err = c.collectHCSContainer(ch, container, c.annotationsCacheHCS[container.ID]); err != nil {
if errors.Is(err, hcs.ErrIDNotFound) {
c.logger.Debug("err in fetching container statistics",
slog.String("container_id", containerDetails.ID),
slog.String("container_id", container.ID),
slog.Any("err", err),
)
} else {
c.logger.Error("err in fetching container statistics",
slog.String("container_id", containerDetails.ID),
slog.String("container_id", container.ID),
slog.Any("err", err),
)
@@ -259,14 +370,25 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
continue
}
containerPrefixes[containerDetails.ID] = containerIdWithPrefix
}
if err = c.collectNetworkMetrics(ch, containerPrefixes); err != nil {
ch <- prometheus.MustNewConstMetric(
c.containersCount,
prometheus.GaugeValue,
countersCount,
)
if err = c.collectNetworkMetrics(ch); err != nil {
return fmt.Errorf("error in fetching container network statistics: %w", err)
}
// Remove containers that are no longer running
for _, containerID := range c.annotationsCacheHCS {
if !slices.Contains(containerIDs, containerID.id) {
delete(c.annotationsCacheHCS, containerID.id)
}
}
if len(collectErrors) > 0 {
return fmt.Errorf("errors while fetching container statistics: %w", errors.Join(collectErrors...))
}
@@ -274,94 +396,87 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
return nil
}
func (c *Collector) collectContainer(ch chan<- prometheus.Metric, containerDetails hcsshim.ContainerProperties, containerIdWithPrefix string) error {
container, err := hcsshim.OpenContainer(containerDetails.ID)
func (c *Collector) collectHCSContainer(ch chan<- prometheus.Metric, containerDetails hcs.Properties, containerInfo containerInfo) error {
containerStats, err := hcs.GetContainerStatistics(containerDetails.ID)
if err != nil {
return fmt.Errorf("error in opening container: %w", err)
}
defer func() {
if container == nil {
return
}
if err := container.Close(); err != nil {
c.logger.Error("error in closing container",
slog.Any("err", err),
)
}
}()
containerStats, err := container.Statistics()
if err != nil {
return fmt.Errorf("error in fetching container statistics: %w", err)
return fmt.Errorf("error fetching container statistics: %w", err)
}
ch <- prometheus.MustNewConstMetric(
c.containerAvailable,
prometheus.CounterValue,
1,
containerIdWithPrefix,
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
ch <- prometheus.MustNewConstMetric(
c.usageCommitBytes,
prometheus.GaugeValue,
float64(containerStats.Memory.UsageCommitBytes),
containerIdWithPrefix,
float64(containerStats.Memory.MemoryUsageCommitBytes),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
ch <- prometheus.MustNewConstMetric(
c.usageCommitPeakBytes,
prometheus.GaugeValue,
float64(containerStats.Memory.UsageCommitPeakBytes),
containerIdWithPrefix,
float64(containerStats.Memory.MemoryUsageCommitPeakBytes),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
ch <- prometheus.MustNewConstMetric(
c.usagePrivateWorkingSetBytes,
prometheus.GaugeValue,
float64(containerStats.Memory.UsagePrivateWorkingSetBytes),
containerIdWithPrefix,
float64(containerStats.Memory.MemoryUsagePrivateWorkingSetBytes),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
ch <- prometheus.MustNewConstMetric(
c.runtimeTotal,
prometheus.CounterValue,
float64(containerStats.Processor.TotalRuntime100ns)*pdh.TicksToSecondScaleFactor,
containerIdWithPrefix,
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
ch <- prometheus.MustNewConstMetric(
c.runtimeUser,
prometheus.CounterValue,
float64(containerStats.Processor.RuntimeUser100ns)*pdh.TicksToSecondScaleFactor,
containerIdWithPrefix,
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
ch <- prometheus.MustNewConstMetric(
c.runtimeKernel,
prometheus.CounterValue,
float64(containerStats.Processor.RuntimeKernel100ns)*pdh.TicksToSecondScaleFactor,
containerIdWithPrefix,
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
ch <- prometheus.MustNewConstMetric(
c.readCountNormalized,
prometheus.CounterValue,
float64(containerStats.Storage.ReadCountNormalized),
containerIdWithPrefix,
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
ch <- prometheus.MustNewConstMetric(
c.readSizeBytes,
prometheus.CounterValue,
float64(containerStats.Storage.ReadSizeBytes),
containerIdWithPrefix,
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
ch <- prometheus.MustNewConstMetric(
c.writeCountNormalized,
prometheus.CounterValue,
float64(containerStats.Storage.WriteCountNormalized),
containerIdWithPrefix,
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
ch <- prometheus.MustNewConstMetric(
c.writeSizeBytes,
prometheus.CounterValue,
float64(containerStats.Storage.WriteSizeBytes),
containerIdWithPrefix,
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
return nil
@@ -371,73 +486,105 @@ func (c *Collector) collectContainer(ch chan<- prometheus.Metric, containerDetai
// With HNSv2, the network stats must be collected from hcsshim.HNSListEndpointRequest.
// Network statistics from the container.Statistics() are providing data only, if HNSv1 is used.
// Ref: https://github.com/prometheus-community/windows_exporter/pull/1218
func (c *Collector) collectNetworkMetrics(ch chan<- prometheus.Metric, containerPrefixes map[string]string) error {
hnsEndpoints, err := hcsshim.HNSListEndpointRequest()
func (c *Collector) collectNetworkMetrics(ch chan<- prometheus.Metric) error {
endpoints, err := hcn.EnumerateEndpoints()
if err != nil {
return fmt.Errorf("error in fetching HNS endpoints: %w", err)
return fmt.Errorf("error in fetching HCN endpoints: %w", err)
}
if len(hnsEndpoints) == 0 {
return errors.New("no network stats for containers to collect")
if len(endpoints) == 0 {
return nil
}
for _, endpoint := range hnsEndpoints {
endpointStats, err := hcsshim.GetHNSEndpointStats(endpoint.Id)
for _, endpoint := range endpoints {
properties, err := hcn.GetEndpointProperties(endpoint)
if err != nil {
c.logger.Warn("Failed to collect network stats for interface "+endpoint.Id,
c.logger.Warn("Failed to collect properties for interface "+endpoint.String(),
slog.Any("err", err),
)
continue
}
for _, containerId := range endpoint.SharedContainers {
containerIdWithPrefix, ok := containerPrefixes[containerId]
var nicGUID *guid.GUID
for _, allocator := range properties.Resources.Allocators {
if allocator.AdapterNetCfgInstanceId != nil {
nicGUID = allocator.AdapterNetCfgInstanceId
break
}
}
if nicGUID == nil {
c.logger.Warn("Failed to get nic GUID for endpoint " + endpoint.String())
continue
}
luid, err := iphlpapi.ConvertInterfaceGUIDToLUID(*nicGUID)
if err != nil {
return fmt.Errorf("error in converting interface GUID to LUID: %w", err)
}
var endpointStats iphlpapi.MIB_IF_ROW2
endpointStats.InterfaceLuid = luid
if err := iphlpapi.GetIfEntry2Ex(&endpointStats); err != nil {
c.logger.Warn("Failed to get interface entry for endpoint "+endpoint.String(),
slog.Any("err", err),
)
continue
}
for _, containerId := range properties.SharedContainers {
containerInfo, ok := c.annotationsCacheHCS[containerId]
if !ok {
c.logger.Debug("Failed to collect network stats for container " + containerId)
c.logger.Debug("Unknown container " + containerId + " for endpoint " + endpoint.String())
continue
}
endpointId := strings.ToUpper(endpoint.Id)
endpointId := strings.ToUpper(endpoint.String())
ch <- prometheus.MustNewConstMetric(
c.bytesReceived,
prometheus.CounterValue,
float64(endpointStats.BytesReceived),
containerIdWithPrefix, endpointId,
float64(endpointStats.InOctets),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId,
)
ch <- prometheus.MustNewConstMetric(
c.bytesSent,
prometheus.CounterValue,
float64(endpointStats.BytesSent),
containerIdWithPrefix, endpointId,
float64(endpointStats.OutOctets),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId,
)
ch <- prometheus.MustNewConstMetric(
c.packetsReceived,
prometheus.CounterValue,
float64(endpointStats.PacketsReceived),
containerIdWithPrefix, endpointId,
float64(endpointStats.InUcastPkts+endpointStats.InNUcastPkts),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId,
)
ch <- prometheus.MustNewConstMetric(
c.packetsSent,
prometheus.CounterValue,
float64(endpointStats.PacketsSent),
containerIdWithPrefix, endpointId,
float64(endpointStats.OutUcastPkts+endpointStats.OutNUcastPkts),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId,
)
ch <- prometheus.MustNewConstMetric(
c.droppedPacketsIncoming,
prometheus.CounterValue,
float64(endpointStats.DroppedPacketsIncoming),
containerIdWithPrefix, endpointId,
float64(endpointStats.InDiscards+endpointStats.InErrors),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId,
)
ch <- prometheus.MustNewConstMetric(
c.droppedPacketsOutgoing,
prometheus.CounterValue,
float64(endpointStats.DroppedPacketsOutgoing),
containerIdWithPrefix, endpointId,
float64(endpointStats.OutDiscards+endpointStats.OutErrors),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId,
)
}
}
@@ -445,12 +592,286 @@ func (c *Collector) collectNetworkMetrics(ch chan<- prometheus.Metric, container
return nil
}
func getContainerIdWithPrefix(containerDetails hcsshim.ContainerProperties) string {
switch containerDetails.Owner {
// collectJobContainers collects container metrics for job containers.
// Job container based on Win32 Job objects.
// https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects
//
// Job containers are containers that aren't managed by HCS, e.g host process containers.
func (c *Collector) collectJobContainers(ch chan<- prometheus.Metric) error {
containerDStateFS := os.DirFS(containerDStateDir)
allContainerIDs := make([]string, 0, len(c.annotationsCacheJob)+len(c.annotationsCacheHCS))
jobContainerIDs := make([]string, 0, len(allContainerIDs))
if err := fs.WalkDir(containerDStateFS, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
c.logger.Warn("containerd state directory does not exist",
slog.String("path", containerDStateDir),
slog.Any("err", err),
)
return nil
}
return err
}
if !d.IsDir() {
return nil
}
if _, err := os.Stat(path + "\\config.json"); err != nil {
containerID := strings.TrimPrefix(strings.Replace(path, containerDStateDir, "", 1), `\`)
allContainerIDs = append(allContainerIDs, containerID)
}
// Skip the directory content
return fs.SkipDir
}); err != nil {
return fmt.Errorf("error in walking containerd state directory: %w", err)
}
errs := make([]error, 0)
for _, containerID := range allContainerIDs {
if err := c.collectJobContainer(ch, containerID); err != nil {
errs = append(errs, err)
} else {
jobContainerIDs = append(jobContainerIDs, containerID)
}
}
// Remove containers that are no longer running
for _, containerID := range c.annotationsCacheJob {
if !slices.Contains(jobContainerIDs, containerID.id) {
delete(c.annotationsCacheJob, containerID.id)
}
}
return errors.Join(errs...)
}
func (c *Collector) collectJobContainer(ch chan<- prometheus.Metric, containerID string) error {
jobObjectHandle, err := kernel32.OpenJobObject("JobContainer_" + containerID)
if err != nil {
if errors.Is(err, windows.ERROR_FILE_NOT_FOUND) {
return nil
}
return fmt.Errorf("error in opening job object: %w", err)
}
defer func(fd windows.Handle) {
_ = windows.Close(fd)
}(jobObjectHandle)
if _, ok := c.annotationsCacheJob[containerID]; !ok {
var (
namespace string
podName string
containerName string
)
if spec, err := getContainerAnnotations(containerID); err == nil {
namespace = spec.Annotations["io.kubernetes.cri.sandbox-namespace"]
podName = spec.Annotations["io.kubernetes.cri.sandbox-name"]
containerName = spec.Annotations["io.kubernetes.cri.container-name"]
}
c.annotationsCacheJob[containerID] = containerInfo{
id: "containerd://" + containerID,
namespace: namespace,
pod: podName,
container: containerName,
}
}
var jobInfo kernel32.JobObjectExtendedLimitInformation
retLen := uint32(unsafe.Sizeof(jobInfo))
if err := windows.QueryInformationJobObject(
jobObjectHandle,
windows.JobObjectExtendedLimitInformation,
uintptr(unsafe.Pointer(&jobInfo)),
retLen, &retLen); err != nil {
return err
}
privateWorkingSetBytes, err := calculatePrivateWorkingSetBytes(jobObjectHandle)
if err != nil {
c.logger.Debug("error in calculating private working set bytes", slog.Any("err", err))
}
containerInfo := c.annotationsCacheJob[containerID]
ch <- prometheus.MustNewConstMetric(
c.containerAvailable,
prometheus.CounterValue,
1,
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
ch <- prometheus.MustNewConstMetric(
c.usageCommitBytes,
prometheus.GaugeValue,
float64(jobInfo.JobMemoryLimit),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
ch <- prometheus.MustNewConstMetric(
c.usageCommitPeakBytes,
prometheus.GaugeValue,
float64(jobInfo.PeakProcessMemoryUsed),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
ch <- prometheus.MustNewConstMetric(
c.usagePrivateWorkingSetBytes,
prometheus.GaugeValue,
float64(privateWorkingSetBytes),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
ch <- prometheus.MustNewConstMetric(
c.runtimeTotal,
prometheus.CounterValue,
(float64(jobInfo.BasicInfo.ThisPeriodTotalKernelTime)+float64(jobInfo.BasicInfo.ThisPeriodTotalUserTime))*pdh.TicksToSecondScaleFactor,
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
ch <- prometheus.MustNewConstMetric(
c.runtimeUser,
prometheus.CounterValue,
float64(jobInfo.BasicInfo.ThisPeriodTotalUserTime)*pdh.TicksToSecondScaleFactor,
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
ch <- prometheus.MustNewConstMetric(
c.runtimeKernel,
prometheus.CounterValue,
float64(jobInfo.BasicInfo.ThisPeriodTotalKernelTime)*pdh.TicksToSecondScaleFactor,
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
ch <- prometheus.MustNewConstMetric(
c.readCountNormalized,
prometheus.CounterValue,
float64(jobInfo.IoInfo.ReadOperationCount),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
ch <- prometheus.MustNewConstMetric(
c.readSizeBytes,
prometheus.CounterValue,
float64(jobInfo.IoInfo.ReadTransferCount),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
ch <- prometheus.MustNewConstMetric(
c.writeCountNormalized,
prometheus.CounterValue,
float64(jobInfo.IoInfo.WriteOperationCount),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
ch <- prometheus.MustNewConstMetric(
c.writeSizeBytes,
prometheus.CounterValue,
float64(jobInfo.IoInfo.WriteTransferCount),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
return nil
}
func getContainerIdWithPrefix(container hcs.Properties) string {
switch container.Owner {
case "containerd-shim-runhcs-v1.exe":
return "containerd://" + containerDetails.ID
return "containerd://" + container.ID
default:
// default to docker or if owner is not set
return "docker://" + containerDetails.ID
return "docker://" + container.ID
}
}
func getContainerAnnotations(containerID string) (ociSpec, error) {
configJSON, err := os.OpenFile(containerDStateDir+containerID+`\config.json`, os.O_RDONLY, 0)
if err != nil {
return ociSpec{}, fmt.Errorf("error in opening config.json file: %w", err)
}
var annotations ociSpec
if err = json.NewDecoder(configJSON).Decode(&annotations); err != nil {
return ociSpec{}, fmt.Errorf("error in decoding config.json file: %w", err)
}
return annotations, nil
}
func calculatePrivateWorkingSetBytes(jobObjectHandle windows.Handle) (uint64, error) {
var pidList kernel32.JobObjectBasicProcessIDList
retLen := uint32(unsafe.Sizeof(pidList))
if err := windows.QueryInformationJobObject(
jobObjectHandle,
windows.JobObjectBasicProcessIdList,
uintptr(unsafe.Pointer(&pidList)),
retLen, &retLen); err != nil {
return 0, err
}
var (
privateWorkingSetBytes uint64
vmCounters kernel32.PROCESS_VM_COUNTERS
)
retLen = uint32(unsafe.Sizeof(vmCounters))
getPrivateWorkingSetBytes := func(pid uint32) (uint64, error) {
processHandle, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
if err != nil {
return 0, fmt.Errorf("error in opening process: %w", err)
}
defer func(fd windows.Handle) {
_ = windows.Close(fd)
}(processHandle)
var isInJob bool
if err := kernel32.IsProcessInJob(processHandle, jobObjectHandle, &isInJob); err != nil {
return 0, fmt.Errorf("error in checking if process is in job: %w", err)
}
if !isInJob {
return 0, nil
}
if err := windows.NtQueryInformationProcess(
processHandle,
windows.ProcessVmCounters,
unsafe.Pointer(&vmCounters),
retLen,
&retLen,
); err != nil {
return 0, fmt.Errorf("error in querying process information: %w", err)
}
return uint64(vmCounters.PrivateWorkingSetSize), nil
}
for _, pid := range pidList.PIDs() {
privateWorkingSetSize, err := getPrivateWorkingSetBytes(pid)
if err != nil {
return 0, fmt.Errorf("error in getting private working set bytes: %w", err)
}
privateWorkingSetBytes += privateWorkingSetSize
}
return privateWorkingSetBytes, nil
}

View File

@@ -25,9 +25,9 @@ import (
"strings"
"sync"
"github.com/Microsoft/hcsshim/osversion"
"github.com/alecthomas/kingpin/v2"
"github.com/prometheus-community/windows_exporter/internal/mi"
"github.com/prometheus-community/windows_exporter/internal/osversion"
"github.com/prometheus/client_golang/prometheus"
)

View File

@@ -20,7 +20,7 @@ package hyperv
import (
"fmt"
"github.com/Microsoft/hcsshim/osversion"
"github.com/prometheus-community/windows_exporter/internal/osversion"
"github.com/prometheus-community/windows_exporter/internal/pdh"
"github.com/prometheus-community/windows_exporter/internal/types"
"github.com/prometheus-community/windows_exporter/internal/utils"

View File

@@ -20,7 +20,7 @@ package hyperv
import (
"fmt"
"github.com/Microsoft/hcsshim/osversion"
"github.com/prometheus-community/windows_exporter/internal/osversion"
"github.com/prometheus-community/windows_exporter/internal/pdh"
"github.com/prometheus-community/windows_exporter/internal/types"
"github.com/prometheus-community/windows_exporter/internal/utils"

View File

@@ -20,8 +20,8 @@ package mscluster
import (
"fmt"
"github.com/Microsoft/hcsshim/osversion"
"github.com/prometheus-community/windows_exporter/internal/mi"
"github.com/prometheus-community/windows_exporter/internal/osversion"
"github.com/prometheus-community/windows_exporter/internal/types"
"github.com/prometheus/client_golang/prometheus"
)

View File

@@ -20,8 +20,8 @@ package mscluster
import (
"fmt"
"github.com/Microsoft/hcsshim/osversion"
"github.com/prometheus-community/windows_exporter/internal/mi"
"github.com/prometheus-community/windows_exporter/internal/osversion"
"github.com/prometheus-community/windows_exporter/internal/types"
"github.com/prometheus/client_golang/prometheus"
)

View File

@@ -30,6 +30,7 @@ import (
"github.com/prometheus-community/windows_exporter/internal/headers/netapi32"
"github.com/prometheus-community/windows_exporter/internal/headers/sysinfoapi"
"github.com/prometheus-community/windows_exporter/internal/mi"
"github.com/prometheus-community/windows_exporter/internal/osversion"
"github.com/prometheus-community/windows_exporter/internal/types"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/sys/windows"
@@ -118,10 +119,10 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
return fmt.Errorf("failed to get Windows version: %w", err)
}
version := windows.RtlGetVersion()
version := osversion.Get()
// Microsoft has decided to keep the major version as "10" for Windows 11, including the product name.
if version.BuildNumber >= 22000 {
if version.Build >= osversion.V21H2Win11 {
productName = strings.Replace(productName, " 10 ", " 11 ", 1)
}
@@ -131,10 +132,10 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
nil,
prometheus.Labels{
"product": productName,
"version": fmt.Sprintf("%d.%d.%d", version.MajorVersion, version.MinorVersion, version.BuildNumber),
"version": version.String(),
"major_version": strconv.FormatUint(uint64(version.MajorVersion), 10),
"minor_version": strconv.FormatUint(uint64(version.MinorVersion), 10),
"build_number": strconv.FormatUint(uint64(version.BuildNumber), 10),
"build_number": strconv.FormatUint(uint64(version.Build), 10),
"revision": revision,
},
)
@@ -365,7 +366,9 @@ func (c *Collector) getWindowsVersion() (string, string, error) {
return "", "", fmt.Errorf("failed to open registry key: %w", err)
}
defer ntKey.Close()
defer func(ntKey registry.Key) {
_ = ntKey.Close()
}(ntKey)
productName, _, err := ntKey.GetStringValue("ProductName")
if err != nil {

View File

@@ -30,7 +30,6 @@ import (
"github.com/prometheus-community/windows_exporter/internal/pdh"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -208,9 +207,9 @@ windows_performancecounter_processor_information_processor_time\{core="0,0",stat
promhttp.HandlerFor(registry, promhttp.HandlerOpts{ErrorHandling: promhttp.ContinueOnError}).ServeHTTP(rw, &http.Request{})
got := rw.Body.String()
assert.NotEmpty(t, got)
require.NotEmpty(t, got)
require.NotEmpty(t, tc.expectedMetrics)
assert.Regexp(t, tc.expectedMetrics, got)
require.Regexp(t, tc.expectedMetrics, got)
})
}
}

View File

@@ -28,7 +28,7 @@ type Object struct {
Type pdh.CounterType `json:"type" yaml:"type"`
Instances []string `json:"instances" yaml:"instances"`
Counters []Counter `json:"counters" yaml:"counters"`
InstanceLabel string `json:"instance_label" yaml:"instance_label"` //nolint:tagliatelle
InstanceLabel string `json:"instance_label" yaml:"instance_label"`
collector *pdh.Collector
perfDataObject any

View File

@@ -28,7 +28,6 @@ import (
"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"
)
@@ -70,7 +69,7 @@ func TestMultipleDirectories(t *testing.T) {
require.NoError(t, <-errCh)
for _, f := range []string{"dir1", "dir2", "dir3", "dir3sub"} {
assert.Contains(t, got, f)
require.Contains(t, got, f)
}
}
@@ -106,6 +105,6 @@ func TestDuplicateFileName(t *testing.T) {
require.ErrorContains(t, <-errCh, "duplicate filename detected")
assert.Contains(t, got, "file")
assert.NotContains(t, got, "sub_file")
require.Contains(t, got, "file")
require.NotContains(t, got, "sub_file")
}

View File

@@ -25,10 +25,10 @@ import (
"strings"
"time"
"github.com/Microsoft/hcsshim/osversion"
"github.com/alecthomas/kingpin/v2"
"github.com/prometheus-community/windows_exporter/internal/headers/kernel32"
"github.com/prometheus-community/windows_exporter/internal/mi"
"github.com/prometheus-community/windows_exporter/internal/osversion"
"github.com/prometheus-community/windows_exporter/internal/pdh"
"github.com/prometheus-community/windows_exporter/internal/types"
"github.com/prometheus/client_golang/prometheus"

View File

@@ -0,0 +1,96 @@
// 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 guid
import (
"fmt"
"strconv"
"strings"
"golang.org/x/sys/windows"
)
type GUID windows.GUID
// FromString parses a string containing a GUID and returns the GUID. The only
// format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
// format.
func FromString(s string) (GUID, error) {
if len(s) != 36 {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
var g GUID
data1, err := strconv.ParseUint(s[0:8], 16, 32)
if err != nil {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
g.Data1 = uint32(data1)
data2, err := strconv.ParseUint(s[9:13], 16, 16)
if err != nil {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
g.Data2 = uint16(data2)
data3, err := strconv.ParseUint(s[14:18], 16, 16)
if err != nil {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
g.Data3 = uint16(data3)
for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} {
v, err := strconv.ParseUint(s[x:x+2], 16, 8)
if err != nil {
return GUID{}, fmt.Errorf("invalid GUID %q", s)
}
g.Data4[i] = uint8(v)
}
return g, nil
}
func (g *GUID) UnmarshalJSON(b []byte) error {
guid, err := FromString(strings.Trim(strings.Trim(string(b), `"`), `{}`))
if err != nil {
return err
}
*g = guid
return nil
}
func (g *GUID) String() string {
return fmt.Sprintf(
"%08x-%04x-%04x-%04x-%012x",
g.Data1,
g.Data2,
g.Data3,
g.Data4[:2],
g.Data4[2:])
}

View File

@@ -0,0 +1,47 @@
// 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 hcn
import (
"fmt"
"github.com/prometheus-community/windows_exporter/internal/headers/guid"
"github.com/prometheus-community/windows_exporter/internal/utils"
"golang.org/x/sys/windows"
)
//nolint:gochecknoglobals
var (
defaultQuery = utils.Must(windows.UTF16PtrFromString(`{"SchemaVersion":{"Major": 2,"Minor": 0},"Flags":"None"}`))
)
func GetEndpointProperties(endpointID guid.GUID) (EndpointProperties, error) {
endpoint, err := OpenEndpoint(endpointID)
if err != nil {
return EndpointProperties{}, fmt.Errorf("failed to open endpoint: %w", err)
}
defer CloseEndpoint(endpoint)
result, err := QueryEndpointProperties(endpoint, defaultQuery)
if err != nil {
return EndpointProperties{}, fmt.Errorf("failed to query endpoint properties: %w", err)
}
return result, nil
}

View File

@@ -0,0 +1,47 @@
// 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 hcn_test
import (
"testing"
"github.com/prometheus-community/windows_exporter/internal/headers/hcn"
"github.com/stretchr/testify/require"
)
func TestEnumerateEndpoints(t *testing.T) {
t.Parallel()
endpoints, err := hcn.EnumerateEndpoints()
require.NoError(t, err)
require.NotNil(t, endpoints)
}
func TestQueryEndpointProperties(t *testing.T) {
t.Parallel()
endpoints, err := hcn.EnumerateEndpoints()
require.NoError(t, err)
if len(endpoints) == 0 {
t.Skip("No endpoints found")
}
_, err = hcn.GetEndpointProperties(endpoints[0])
require.NoError(t, err)
}

View File

@@ -0,0 +1,134 @@
// 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 hcn
import (
"encoding/json"
"fmt"
"unsafe"
"github.com/prometheus-community/windows_exporter/internal/headers/guid"
"github.com/prometheus-community/windows_exporter/internal/headers/hcs"
"golang.org/x/sys/windows"
)
//nolint:gochecknoglobals
var (
modComputeNetwork = windows.NewLazySystemDLL("computenetwork.dll")
procHcnEnumerateEndpoints = modComputeNetwork.NewProc("HcnEnumerateEndpoints")
procHcnOpenEndpoint = modComputeNetwork.NewProc("HcnOpenEndpoint")
procHcnQueryEndpointProperties = modComputeNetwork.NewProc("HcnQueryEndpointProperties")
procHcnCloseEndpoint = modComputeNetwork.NewProc("HcnCloseEndpoint")
)
// EnumerateEndpoints enumerates the endpoints.
//
// https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcnenumerateendpoints
func EnumerateEndpoints() ([]guid.GUID, error) {
var (
endpointsJSON *uint16
errorRecord *uint16
)
r1, _, _ := procHcnEnumerateEndpoints.Call(
0,
uintptr(unsafe.Pointer(&endpointsJSON)),
uintptr(unsafe.Pointer(&errorRecord)),
)
windows.CoTaskMemFree(unsafe.Pointer(errorRecord))
result := windows.UTF16PtrToString(endpointsJSON)
if r1 != 0 {
return nil, fmt.Errorf("HcnEnumerateEndpoints failed: HRESULT 0x%X: %w", r1, hcs.Win32FromHResult(r1))
}
var endpoints []guid.GUID
if err := json.Unmarshal([]byte(result), &endpoints); err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
}
return endpoints, nil
}
// OpenEndpoint opens an endpoint.
//
// https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcnopenendpoint
func OpenEndpoint(id guid.GUID) (Endpoint, error) {
var (
endpoint Endpoint
errorRecord *uint16
)
r1, _, _ := procHcnOpenEndpoint.Call(
uintptr(unsafe.Pointer(&id)),
uintptr(unsafe.Pointer(&endpoint)),
uintptr(unsafe.Pointer(&errorRecord)),
)
windows.CoTaskMemFree(unsafe.Pointer(errorRecord))
if r1 != 0 {
return 0, fmt.Errorf("HcnOpenEndpoint failed: HRESULT 0x%X: %w", r1, hcs.Win32FromHResult(r1))
}
return endpoint, nil
}
// QueryEndpointProperties queries the properties of an endpoint.
//
// https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcnqueryendpointproperties
func QueryEndpointProperties(endpoint Endpoint, propertyQuery *uint16) (EndpointProperties, error) {
var (
resultDocument *uint16
errorRecord *uint16
)
r1, _, _ := procHcnQueryEndpointProperties.Call(
uintptr(endpoint),
uintptr(unsafe.Pointer(&propertyQuery)),
uintptr(unsafe.Pointer(&resultDocument)),
uintptr(unsafe.Pointer(&errorRecord)),
)
windows.CoTaskMemFree(unsafe.Pointer(errorRecord))
result := windows.UTF16PtrToString(resultDocument)
windows.CoTaskMemFree(unsafe.Pointer(resultDocument))
if r1 != 0 {
return EndpointProperties{}, fmt.Errorf("HcsGetComputeSystemProperties failed: HRESULT 0x%X: %w", r1, hcs.Win32FromHResult(r1))
}
var properties EndpointProperties
if err := json.Unmarshal([]byte(result), &properties); err != nil {
return EndpointProperties{}, fmt.Errorf("failed to unmarshal JSON: %w", err)
}
return properties, nil
}
// CloseEndpoint close a handle to an Endpoint.
//
// https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcncloseendpoint
func CloseEndpoint(endpoint Endpoint) {
_, _, _ = procHcnCloseEndpoint.Call(uintptr(endpoint))
}

View File

@@ -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 hcn
import (
"github.com/prometheus-community/windows_exporter/internal/headers/guid"
"golang.org/x/sys/windows"
)
type Endpoint = windows.Handle
// EndpointProperties contains the properties of an HCN endpoint.
//
// https://learn.microsoft.com/en-us/virtualization/api/hcn/hns_schema#HostComputeEndpoint
type EndpointProperties struct {
ID string `json:"ID"`
State int `json:"State"`
SharedContainers []string `json:"SharedContainers"`
Resources EndpointPropertiesResources `json:"Resources"`
}
type EndpointPropertiesResources struct {
Allocators []EndpointPropertiesAllocators `json:"Allocators"`
}
type EndpointPropertiesAllocators struct {
AdapterNetCfgInstanceId *guid.GUID `json:"AdapterNetCfgInstanceId"`
}
type EndpointStats struct {
BytesReceived uint64 `json:"BytesReceived"`
BytesSent uint64 `json:"BytesSent"`
DroppedPacketsIncoming uint64 `json:"DroppedPacketsIncoming"`
DroppedPacketsOutgoing uint64 `json:"DroppedPacketsOutgoing"`
EndpointID string `json:"EndpointId"`
InstanceID string `json:"InstanceId"`
PacketsReceived uint64 `json:"PacketsReceived"`
PacketsSent uint64 `json:"PacketsSent"`
}

View File

@@ -0,0 +1,97 @@
// 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 hcs
import (
"encoding/json"
"fmt"
"github.com/prometheus-community/windows_exporter/internal/utils"
"golang.org/x/sys/windows"
)
//nolint:gochecknoglobals
var (
ContainerQuery = utils.Must(windows.UTF16PtrFromString(`{"Types":["Container"]}`))
StatisticsQuery = utils.Must(windows.UTF16PtrFromString(`{"PropertyTypes":["Statistics"]}`))
)
func GetContainers() ([]Properties, error) {
operation, err := CreateOperation()
if err != nil {
return nil, fmt.Errorf("failed to create operation: %w", err)
}
defer CloseOperation(operation)
if err := EnumerateComputeSystems(ContainerQuery, operation); err != nil {
return nil, fmt.Errorf("failed to enumerate compute systems: %w", err)
}
resultDocument, err := WaitForOperationResult(operation, 1000)
if err != nil {
return nil, fmt.Errorf("failed to wait and get for operation result: %w - %s", err, resultDocument)
} else if resultDocument == "" {
return nil, ErrEmptyResultDocument
}
var computeSystems []Properties
if err := json.Unmarshal([]byte(resultDocument), &computeSystems); err != nil {
return nil, fmt.Errorf("failed to unmarshal compute systems: %w", err)
}
return computeSystems, nil
}
func GetContainerStatistics(containerID string) (Statistics, error) {
computeSystem, err := OpenComputeSystem(containerID)
if err != nil {
return Statistics{}, fmt.Errorf("failed to open compute system: %w", err)
}
defer CloseComputeSystem(computeSystem)
operation, err := CreateOperation()
if err != nil {
return Statistics{}, fmt.Errorf("failed to create operation: %w", err)
}
defer CloseOperation(operation)
if err := GetComputeSystemProperties(computeSystem, operation, StatisticsQuery); err != nil {
return Statistics{}, fmt.Errorf("failed to enumerate compute systems: %w", err)
}
resultDocument, err := WaitForOperationResult(operation, 1000)
if err != nil {
return Statistics{}, fmt.Errorf("failed to get compute system properties: %w", err)
} else if resultDocument == "" {
return Statistics{}, ErrEmptyResultDocument
}
var properties Properties
if err := json.Unmarshal([]byte(resultDocument), &properties); err != nil {
return Statistics{}, fmt.Errorf("failed to unmarshal system properties: %w", err)
}
if properties.Statistics == nil {
return Statistics{}, fmt.Errorf("no statistics found for container %s", containerID)
}
return *properties.Statistics, nil
}

View File

@@ -0,0 +1,55 @@
// 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 hcs_test
import (
"testing"
"github.com/prometheus-community/windows_exporter/internal/headers/hcs"
"github.com/stretchr/testify/require"
)
func TestGetContainers(t *testing.T) {
t.Parallel()
containers, err := hcs.GetContainers()
require.NoError(t, err)
require.NotNil(t, containers)
}
func TestOpenContainer(t *testing.T) {
t.Parallel()
containers, err := hcs.GetContainers()
require.NoError(t, err)
if len(containers) == 0 {
t.Skip("No containers found")
}
statistics, err := hcs.GetContainerStatistics(containers[0].ID)
require.NoError(t, err)
require.NotNil(t, statistics)
}
func TestOpenContainerNotFound(t *testing.T) {
t.Parallel()
_, err := hcs.GetContainerStatistics("f3056b79b36ddfe203376473e2aeb4922a8ca7c5d8100764e5829eb5552fe09b")
require.ErrorIs(t, err, hcs.ErrIDNotFound)
}

View File

@@ -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 hcs
import (
"fmt"
"unsafe"
"golang.org/x/sys/windows"
)
//nolint:gochecknoglobals
var (
modComputeCore = windows.NewLazySystemDLL("computecore.dll")
procHcsCreateOperation = modComputeCore.NewProc("HcsCreateOperation")
procHcsWaitForOperationResult = modComputeCore.NewProc("HcsWaitForOperationResult")
procHcsCloseOperation = modComputeCore.NewProc("HcsCloseOperation")
procHcsEnumerateComputeSystems = modComputeCore.NewProc("HcsEnumerateComputeSystems")
procHcsOpenComputeSystem = modComputeCore.NewProc("HcsOpenComputeSystem")
procHcsGetComputeSystemProperties = modComputeCore.NewProc("HcsGetComputeSystemProperties")
procHcsCloseComputeSystem = modComputeCore.NewProc("HcsCloseComputeSystem")
)
// CreateOperation creates a new operation.
func CreateOperation() (Operation, error) {
r1, r2, _ := procHcsCreateOperation.Call(0, 0)
if r2 != 0 {
return 0, fmt.Errorf("HcsCreateOperation failed: HRESULT 0x%X: %w", r2, Win32FromHResult(r2))
}
return Operation(r1), nil
}
func WaitForOperationResult(operation Operation, timeout uint32) (string, error) {
var resultDocument *uint16
r1, _, _ := procHcsWaitForOperationResult.Call(uintptr(operation), uintptr(timeout), uintptr(unsafe.Pointer(&resultDocument)))
if r1 != 0 {
return "", fmt.Errorf("HcsWaitForOperationResult failed: HRESULT 0x%X: %w", r1, Win32FromHResult(r1))
}
result := windows.UTF16PtrToString(resultDocument)
windows.CoTaskMemFree(unsafe.Pointer(resultDocument))
return result, nil
}
// CloseOperation closes an operation.
func CloseOperation(operation Operation) {
_, _, _ = procHcsCloseOperation.Call(uintptr(operation))
}
// EnumerateComputeSystems enumerates compute systems.
//
// https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsenumeratecomputesystems
func EnumerateComputeSystems(query *uint16, operation Operation) error {
r1, _, _ := procHcsEnumerateComputeSystems.Call(uintptr(unsafe.Pointer(query)), uintptr(operation))
if r1 != 0 {
return fmt.Errorf("HcsEnumerateComputeSystems failed: HRESULT 0x%X: %w", r1, Win32FromHResult(r1))
}
return nil
}
// OpenComputeSystem opens a handle to an existing compute system.
//
// https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsopencomputesystem
func OpenComputeSystem(id string) (ComputeSystem, error) {
idPtr, err := windows.UTF16PtrFromString(id)
if err != nil {
return 0, err
}
var system ComputeSystem
r1, _, _ := procHcsOpenComputeSystem.Call(
uintptr(unsafe.Pointer(idPtr)),
uintptr(windows.GENERIC_ALL),
uintptr(unsafe.Pointer(&system)),
)
if r1 != 0 {
return 0, fmt.Errorf("HcsOpenComputeSystem failed: HRESULT 0x%X: %w", r1, Win32FromHResult(r1))
}
return system, nil
}
func GetComputeSystemProperties(system ComputeSystem, operation Operation, propertyQuery *uint16) error {
r1, _, err := procHcsGetComputeSystemProperties.Call(
uintptr(system),
uintptr(operation),
uintptr(unsafe.Pointer(propertyQuery)),
)
if r1 != 0 {
return fmt.Errorf("HcsGetComputeSystemProperties failed: HRESULT 0x%X: %w", r1, err)
}
return nil
}
// CloseComputeSystem closes a handle to a compute system.
//
// https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsclosecomputesystem
func CloseComputeSystem(system ComputeSystem) {
_, _, _ = procHcsCloseComputeSystem.Call(uintptr(system))
}
func Win32FromHResult(hr uintptr) windows.Errno {
if hr&0x1fff0000 == 0x00070000 {
return windows.Errno(hr & 0xffff)
}
return windows.Errno(hr)
}

View File

@@ -0,0 +1,83 @@
// 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 hcs
import (
"errors"
"time"
"golang.org/x/sys/windows"
)
var (
ErrEmptyResultDocument = errors.New("empty result document")
ErrIDNotFound = windows.Errno(2151088398)
)
type (
Operation = windows.Handle
ComputeSystem = windows.Handle
)
type Properties struct {
ID string `json:"Id,omitempty"`
SystemType string `json:"SystemType,omitempty"`
Owner string `json:"Owner,omitempty"`
State string `json:"State,omitempty"`
Statistics *Statistics `json:"Statistics,omitempty"`
ProcessList []ProcessDetails `json:"ProcessList,omitempty"`
}
type ProcessDetails struct {
ProcessId int32 `json:"ProcessId,omitempty"`
ImageName string `json:"ImageName,omitempty"`
CreateTimestamp time.Time `json:"CreateTimestamp,omitempty"`
UserTime100ns int32 `json:"UserTime100ns,omitempty"`
KernelTime100ns int32 `json:"KernelTime100ns,omitempty"`
MemoryCommitBytes int32 `json:"MemoryCommitBytes,omitempty"`
MemoryWorkingSetPrivateBytes int32 `json:"MemoryWorkingSetPrivateBytes,omitempty"`
MemoryWorkingSetSharedBytes int32 `json:"MemoryWorkingSetSharedBytes,omitempty"`
}
type Statistics struct {
Timestamp time.Time `json:"Timestamp,omitempty"`
ContainerStartTime time.Time `json:"ContainerStartTime,omitempty"`
Uptime100ns uint64 `json:"Uptime100ns,omitempty"`
Processor *ProcessorStats `json:"Processor,omitempty"`
Memory *MemoryStats `json:"Memory,omitempty"`
Storage *StorageStats `json:"Storage,omitempty"`
}
type ProcessorStats struct {
TotalRuntime100ns uint64 `json:"TotalRuntime100ns,omitempty"`
RuntimeUser100ns uint64 `json:"RuntimeUser100ns,omitempty"`
RuntimeKernel100ns uint64 `json:"RuntimeKernel100ns,omitempty"`
}
type MemoryStats struct {
MemoryUsageCommitBytes uint64 `json:"MemoryUsageCommitBytes,omitempty"`
MemoryUsageCommitPeakBytes uint64 `json:"MemoryUsageCommitPeakBytes,omitempty"`
MemoryUsagePrivateWorkingSetBytes uint64 `json:"MemoryUsagePrivateWorkingSetBytes,omitempty"`
}
type StorageStats struct {
ReadCountNormalized uint64 `json:"ReadCountNormalized,omitempty"`
ReadSizeBytes uint64 `json:"ReadSizeBytes,omitempty"`
WriteCountNormalized uint64 `json:"WriteCountNormalized,omitempty"`
WriteSizeBytes uint64 `json:"WriteSizeBytes,omitempty"`
}

View File

@@ -22,13 +22,16 @@ import (
"fmt"
"unsafe"
"github.com/prometheus-community/windows_exporter/internal/headers/guid"
"golang.org/x/sys/windows"
)
//nolint:gochecknoglobals
var (
modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
procGetExtendedTcpTable = modiphlpapi.NewProc("GetExtendedTcpTable")
modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
procGetExtendedTcpTable = modiphlpapi.NewProc("GetExtendedTcpTable")
procGetIfEntry2Ex = modiphlpapi.NewProc("GetIfEntry2Ex")
procConvertInterfaceGuidToLuid = modiphlpapi.NewProc("ConvertInterfaceGuidToLuid")
)
func GetTCPConnectionStates(family uint32) (map[MIB_TCP_STATE]uint32, error) {
@@ -128,3 +131,37 @@ func getExtendedTcpTable[T any](ulAf uint32, tableClass uint32) ([]T, error) {
return unsafe.Slice((*T)(unsafe.Pointer(&buf[4])), binary.LittleEndian.Uint32(buf)), nil
}
// GetIfEntry2Ex function retrieves the specified level of information for the specified interface on the local computer.
//
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-getifentry2ex
func GetIfEntry2Ex(row *MIB_IF_ROW2) error {
ret, _, _ := procGetIfEntry2Ex.Call(
uintptr(0),
uintptr(unsafe.Pointer(row)),
)
if ret != 0 {
return fmt.Errorf("GetIfEntry2Ex failed with code %d: %w", ret, windows.Errno(ret))
}
return nil
}
// ConvertInterfaceGUIDToLUID function converts a globally unique identifier (GUID) for a network interface to the
// locally unique identifier (LUID) for the interface.
//
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-convertinterfaceguidtoluid
func ConvertInterfaceGUIDToLUID(guid guid.GUID) (uint64, error) {
var luid uint64
ret, _, _ := procConvertInterfaceGuidToLuid.Call(
uintptr(unsafe.Pointer(&guid)),
uintptr(unsafe.Pointer(&luid)),
)
if ret != 0 {
return 0, fmt.Errorf("ConvertInterfaceGUIDToLUID failed with code %d: %w", ret, windows.Errno(ret))
}
return luid, nil
}

View File

@@ -20,6 +20,8 @@ package iphlpapi
import (
"encoding/binary"
"fmt"
"github.com/prometheus-community/windows_exporter/internal/headers/guid"
)
// MIB_TCPROW_OWNER_PID structure for IPv4.
@@ -107,3 +109,54 @@ func (b BigEndianUint32) uint16() uint16 {
return binary.LittleEndian.Uint16(data)
}
// Constants from Windows headers
const (
IF_MAX_STRING_SIZE = 256
IF_MAX_PHYS_ADDRESS_LENGTH = 32
)
// MIB_IF_ROW2 represents network interface statistics
type MIB_IF_ROW2 struct {
InterfaceLuid uint64
InterfaceIndex uint32
InterfaceGuid guid.GUID
Alias [IF_MAX_STRING_SIZE + 1]uint16
Description [IF_MAX_STRING_SIZE + 1]uint16
PhysicalAddressLength uint32
PhysicalAddress [IF_MAX_PHYS_ADDRESS_LENGTH]byte
PermanentPhysicalAddress [IF_MAX_PHYS_ADDRESS_LENGTH]byte
Mtu uint32
Type uint32
TunnelType uint32
MediaType uint32
PhysicalMediumType uint32
AccessType uint32
DirectionType uint32
InterfaceAndOperStatusFlags uint8
OperStatus uint32
AdminStatus uint32
MediaConnectState uint32
NetworkGuid [16]byte
ConnectionType uint32
TransmitLinkSpeed uint64
ReceiveLinkSpeed uint64
InOctets uint64
InUcastPkts uint64
InNUcastPkts uint64
InDiscards uint64
InErrors uint64
InUnknownProtos uint64
InUcastOctets uint64
InMulticastOctets uint64
InBroadcastOctets uint64
OutOctets uint64
OutUcastPkts uint64
OutNUcastPkts uint64
OutDiscards uint64
OutErrors uint64
OutUcastOctets uint64
OutMulticastOctets uint64
OutBroadcastOctets uint64
OutQLen uint64
}

View File

@@ -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 kernel32
import (
"unsafe"
"golang.org/x/sys/windows"
)
const (
// JobObjectQuery is required to retrieve certain information about a job object,
// such as attributes and accounting information (see QueryInformationJobObject and IsProcessInJob).
// https://learn.microsoft.com/en-us/windows/win32/procthread/job-object-security-and-access-rights
JobObjectQuery = 0x0004
)
func OpenJobObject(name string) (windows.Handle, error) {
handle, _, err := procOpenJobObject.Call(JobObjectQuery, 0, uintptr(unsafe.Pointer(&name)))
if handle == 0 {
return 0, err
}
return windows.Handle(handle), nil
}
func IsProcessInJob(process windows.Handle, job windows.Handle, result *bool) error {
ret, _, err := procIsProcessInJob.Call(
uintptr(process),
uintptr(job),
uintptr(unsafe.Pointer(&result)),
)
if ret == 0 {
return err
}
return nil
}

View File

@@ -30,6 +30,8 @@ var (
procGetDynamicTimeZoneInformationSys = modkernel32.NewProc("GetDynamicTimeZoneInformation")
procKernelLocalFileTimeToFileTime = modkernel32.NewProc("LocalFileTimeToFileTime")
procGetTickCount = modkernel32.NewProc("GetTickCount64")
procOpenJobObject = modkernel32.NewProc("OpenJobObjectW")
procIsProcessInJob = modkernel32.NewProc("IsProcessInJob")
)
// SYSTEMTIME contains a date and time.

View File

@@ -0,0 +1,75 @@
// 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 kernel32
import "unsafe"
type JobObjectBasicAccountingInformation struct {
TotalUserTime uint64
TotalKernelTime uint64
ThisPeriodTotalUserTime uint64
ThisPeriodTotalKernelTime uint64
TotalPageFaultCount uint32
TotalProcesses uint32
ActiveProcesses uint32
TotalTerminatedProcesses uint32
}
type IOCounters struct {
ReadOperationCount uint64
WriteOperationCount uint64
OtherOperationCount uint64
ReadTransferCount uint64
WriteTransferCount uint64
OtherTransferCount uint64
}
type JobObjectExtendedLimitInformation struct {
BasicInfo JobObjectBasicAccountingInformation
IoInfo IOCounters
ProcessMemoryLimit uint64
JobMemoryLimit uint64
PeakProcessMemoryUsed uint64
PeakJobMemoryUsed uint64
}
type JobObjectBasicProcessIDList struct {
NumberOfAssignedProcesses uint32
NumberOfProcessIdsInList uint32
ProcessIdList [1]uintptr
}
// PIDs returns all the process Ids in the job object.
func (p *JobObjectBasicProcessIDList) PIDs() []uint32 {
return unsafe.Slice((*uint32)(unsafe.Pointer(&p.ProcessIdList[0])), int(p.NumberOfProcessIdsInList))
}
type PROCESS_VM_COUNTERS struct {
PeakVirtualSize uintptr
VirtualSize uintptr
PageFaultCount uint32
PeakWorkingSetSize uintptr
WorkingSetSize uintptr
QuotaPeakPagedPoolUsage uintptr
QuotaPagedPoolUsage uintptr
QuotaPeakNonPagedPoolUsage uintptr
QuotaNonPagedPoolUsage uintptr
PagefileUsage uintptr
PeakPagefileUsage uintptr
PrivateWorkingSetSize uintptr
}

View File

@@ -0,0 +1,65 @@
// 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 osversion
import (
"fmt"
"sync"
"golang.org/x/sys/windows"
)
// OSVersion is a wrapper for Windows version information
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724439(v=vs.85).aspx
type OSVersion struct {
Version uint32
MajorVersion uint8
MinorVersion uint8
Build uint16
}
//nolint:gochecknoglobals
var osv = sync.OnceValue(func() OSVersion {
v := *windows.RtlGetVersion()
return OSVersion{
MajorVersion: uint8(v.MajorVersion),
MinorVersion: uint8(v.MinorVersion),
Build: uint16(v.BuildNumber),
// Fill version value so that existing clients don't break
Version: v.BuildNumber<<16 | (v.MinorVersion << 8) | v.MajorVersion,
}
})
// Get gets the operating system version on Windows.
// The calling application must be manifested to get the correct version information.
func Get() OSVersion {
return osv()
}
// Build gets the build-number on Windows
// The calling application must be manifested to get the correct version information.
func Build() uint16 {
return Get().Build
}
// String returns the OSVersion formatted as a string. It implements the
// [fmt.Stringer] interface.
func (osv OSVersion) String() string {
return fmt.Sprintf("%d.%d.%d", osv.MajorVersion, osv.MinorVersion, osv.Build)
}

View File

@@ -0,0 +1,36 @@
// 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 osversion
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
)
func TestOSVersionString(t *testing.T) {
v := OSVersion{
Version: 809042555,
MajorVersion: 123,
MinorVersion: 2,
Build: 12345,
}
require.Equal(t, "the version is: 123.2.12345", fmt.Sprintf("the version is: %s", v))
}

View File

@@ -0,0 +1,101 @@
// 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 osversion
// Windows Client and Server build numbers.
//
// See:
// https://learn.microsoft.com/en-us/windows/release-health/release-information
// https://learn.microsoft.com/en-us/windows/release-health/windows-server-release-info
// https://learn.microsoft.com/en-us/windows/release-health/windows11-release-information
const (
// RS1 (version 1607, codename "Redstone 1") corresponds to Windows Server
// 2016 (ltsc2016) and Windows 10 (Anniversary Update).
RS1 = 14393
// V1607 (version 1607, codename "Redstone 1") is an alias for [RS1].
V1607 = RS1
// LTSC2016 (Windows Server 2016) is an alias for [RS1].
LTSC2016 = RS1
// RS2 (version 1703, codename "Redstone 2") was a client-only update, and
// corresponds to Windows 10 (Creators Update).
RS2 = 15063
// V1703 (version 1703, codename "Redstone 2") is an alias for [RS2].
V1703 = RS2
// RS3 (version 1709, codename "Redstone 3") corresponds to Windows Server
// 1709 (Semi-Annual Channel (SAC)), and Windows 10 (Fall Creators Update).
RS3 = 16299
// V1709 (version 1709, codename "Redstone 3") is an alias for [RS3].
V1709 = RS3
// RS4 (version 1803, codename "Redstone 4") corresponds to Windows Server
// 1803 (Semi-Annual Channel (SAC)), and Windows 10 (April 2018 Update).
RS4 = 17134
// V1803 (version 1803, codename "Redstone 4") is an alias for [RS4].
V1803 = RS4
// RS5 (version 1809, codename "Redstone 5") corresponds to Windows Server
// 2019 (ltsc2019), and Windows 10 (October 2018 Update).
RS5 = 17763
// V1809 (version 1809, codename "Redstone 5") is an alias for [RS5].
V1809 = RS5
// LTSC2019 (Windows Server 2019) is an alias for [RS5].
LTSC2019 = RS5
// V19H1 (version 1903, codename 19H1) corresponds to Windows Server 1903 (semi-annual
// channel).
V19H1 = 18362
// V1903 (version 1903) is an alias for [V19H1].
V1903 = V19H1
// V19H2 (version 1909, codename 19H2) corresponds to Windows Server 1909 (semi-annual
// channel).
V19H2 = 18363
// V1909 (version 1909) is an alias for [V19H2].
V1909 = V19H2
// V20H1 (version 2004, codename 20H1) corresponds to Windows Server 2004 (semi-annual
// channel).
V20H1 = 19041
// V2004 (version 2004) is an alias for [V20H1].
V2004 = V20H1
// V20H2 corresponds to Windows Server 20H2 (semi-annual channel).
V20H2 = 19042
// V21H1 corresponds to Windows Server 21H1 (semi-annual channel).
V21H1 = 19043
// V21H2Win10 corresponds to Windows 10 (November 2021 Update).
V21H2Win10 = 19044
// V21H2Server corresponds to Windows Server 2022 (ltsc2022).
V21H2Server = 20348
// LTSC2022 (Windows Server 2022) is an alias for [V21H2Server]
LTSC2022 = V21H2Server
// V21H2Win11 corresponds to Windows 11 (original release).
V21H2Win11 = 22000
// V22H2Win10 corresponds to Windows 10 (2022 Update).
V22H2Win10 = 19045
// V22H2Win11 corresponds to Windows 11 (2022 Update).
V22H2Win11 = 22621
)

View File

@@ -27,8 +27,8 @@ import (
"sync"
"unsafe"
"github.com/Microsoft/hcsshim/osversion"
"github.com/prometheus-community/windows_exporter/internal/mi"
"github.com/prometheus-community/windows_exporter/internal/osversion"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/sys/windows"
)

View File

@@ -22,7 +22,6 @@ import (
"time"
"github.com/prometheus-community/windows_exporter/internal/pdh"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -66,7 +65,7 @@ func TestCollector(t *testing.T) {
continue
}
assert.NotZerof(t, instance.ThreadCount, "object: %s, instance: %s, counter: %s", tc.object, instance, instance.ThreadCount)
require.NotZerof(t, instance.ThreadCount, "object: %s, instance: %s, counter: %s", tc.object, instance, instance.ThreadCount)
}
})
}

View File

@@ -19,7 +19,6 @@ package registry
import (
"bytes"
"fmt"
"strconv"
"sync"
)
@@ -90,10 +89,6 @@ func (t *NameTable) initialize() {
break
}
if err != nil {
panic(fmt.Sprint("Invalid index ", index))
}
indexInt, _ := strconv.Atoi(index)
t.table.index[uint32(indexInt)] = desc