mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-02-11 15:36:37 +00:00
container: support hostprocess containers and expose kubernetes labels (#1911)
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
96
internal/headers/guid/guid.go
Normal file
96
internal/headers/guid/guid.go
Normal 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:])
|
||||
}
|
||||
47
internal/headers/hcn/hcn.go
Normal file
47
internal/headers/hcn/hcn.go
Normal 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
|
||||
}
|
||||
47
internal/headers/hcn/hcn_test.go
Normal file
47
internal/headers/hcn/hcn_test.go
Normal 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)
|
||||
}
|
||||
134
internal/headers/hcn/syscall.go
Normal file
134
internal/headers/hcn/syscall.go
Normal 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))
|
||||
}
|
||||
53
internal/headers/hcn/types.go
Normal file
53
internal/headers/hcn/types.go
Normal 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"`
|
||||
}
|
||||
97
internal/headers/hcs/hcs.go
Normal file
97
internal/headers/hcs/hcs.go
Normal 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
|
||||
}
|
||||
55
internal/headers/hcs/hcs_test.go
Normal file
55
internal/headers/hcs/hcs_test.go
Normal 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)
|
||||
}
|
||||
130
internal/headers/hcs/syscall.go
Normal file
130
internal/headers/hcs/syscall.go
Normal 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)
|
||||
}
|
||||
83
internal/headers/hcs/types.go
Normal file
83
internal/headers/hcs/types.go
Normal 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"`
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
53
internal/headers/kernel32/job.go
Normal file
53
internal/headers/kernel32/job.go
Normal 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
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
75
internal/headers/kernel32/types.go
Normal file
75
internal/headers/kernel32/types.go
Normal 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
|
||||
}
|
||||
65
internal/osversion/osversion_windows.go
Normal file
65
internal/osversion/osversion_windows.go
Normal 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)
|
||||
}
|
||||
36
internal/osversion/osversion_windows_test.go
Normal file
36
internal/osversion/osversion_windows_test.go
Normal 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))
|
||||
}
|
||||
101
internal/osversion/windowsbuilds.go
Normal file
101
internal/osversion/windowsbuilds.go
Normal 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
|
||||
)
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user