mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-20 19:49:03 +02:00
container: add start time, process count and page fault metrics (#2566)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
301eef493b
commit
64046fb91f
@@ -94,10 +94,15 @@ type Collector struct {
|
||||
// Number of containers
|
||||
containersCount *prometheus.Desc
|
||||
|
||||
// Lifecycle
|
||||
startTime *prometheus.Desc
|
||||
processes *prometheus.Desc
|
||||
|
||||
// Memory
|
||||
usageCommitBytes *prometheus.Desc
|
||||
usageCommitPeakBytes *prometheus.Desc
|
||||
usagePrivateWorkingSetBytes *prometheus.Desc
|
||||
pageFaults *prometheus.Desc
|
||||
|
||||
// CPU
|
||||
runtimeTotal *prometheus.Desc
|
||||
@@ -124,6 +129,8 @@ type containerInfo struct {
|
||||
namespace string
|
||||
pod string
|
||||
container string
|
||||
// createdAt is the creation time reported by the CRI endpoint. It is zero for containers not managed by Kubernetes.
|
||||
createdAt time.Time
|
||||
}
|
||||
|
||||
// kubernetesContainers holds the Kubernetes metadata of the running containers, read from the CRI endpoint.
|
||||
@@ -159,6 +166,7 @@ func newKubernetesContainers(runtimeName string, sandboxes []cri.PodSandbox, con
|
||||
namespace: pod.Namespace,
|
||||
pod: pod.Name,
|
||||
container: container.Name,
|
||||
createdAt: container.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,6 +276,18 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
c.startTime = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "start_time_seconds"),
|
||||
"Start time of the container since Unix epoch in seconds. HostProcess containers report their creation time.",
|
||||
[]string{"container_id", "namespace", "pod", "container"},
|
||||
nil,
|
||||
)
|
||||
c.processes = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "processes"),
|
||||
"Number of processes running in the container",
|
||||
[]string{"container_id", "namespace", "pod", "container"},
|
||||
nil,
|
||||
)
|
||||
c.usageCommitBytes = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "memory_usage_commit_bytes"),
|
||||
"Memory Usage Commit Bytes",
|
||||
@@ -286,6 +306,12 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
||||
[]string{"container_id", "namespace", "pod", "container"},
|
||||
nil,
|
||||
)
|
||||
c.pageFaults = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "memory_page_faults_total"),
|
||||
"Total number of page faults of the container processes. Only available for HostProcess containers.",
|
||||
[]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",
|
||||
@@ -547,11 +573,13 @@ func (c *Collector) collectHCS(ch chan<- prometheus.Metric, kubernetes kubernete
|
||||
}
|
||||
|
||||
func (c *Collector) collectHCSContainer(ch chan<- prometheus.Metric, containerDetails hcs.Properties, containerInfo containerInfo) error {
|
||||
containerStats, err := hcs.GetContainerStatistics(containerDetails.ID)
|
||||
properties, err := hcs.GetContainerStatistics(containerDetails.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error fetching container statistics: %w", err)
|
||||
}
|
||||
|
||||
containerStats := properties.Statistics
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.containerAvailable,
|
||||
prometheus.GaugeValue,
|
||||
@@ -559,6 +587,29 @@ func (c *Collector) collectHCSContainer(ch chan<- prometheus.Metric, containerDe
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, "false",
|
||||
)
|
||||
|
||||
startTime := containerStats.ContainerStartTime
|
||||
if startTime.IsZero() {
|
||||
startTime = containerInfo.createdAt
|
||||
}
|
||||
|
||||
if !startTime.IsZero() {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.startTime,
|
||||
prometheus.GaugeValue,
|
||||
float64(startTime.UnixNano())/1e9,
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.processes,
|
||||
prometheus.GaugeValue,
|
||||
float64(len(properties.ProcessList)),
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.usageCommitBytes,
|
||||
prometheus.GaugeValue,
|
||||
@@ -793,6 +844,33 @@ func (c *Collector) collectJobContainer(ch chan<- prometheus.Metric, containerID
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, "true",
|
||||
)
|
||||
|
||||
// Job objects have no start time, so the creation time reported by the CRI endpoint is used.
|
||||
if !containerInfo.createdAt.IsZero() {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.startTime,
|
||||
prometheus.GaugeValue,
|
||||
float64(containerInfo.createdAt.UnixNano())/1e9,
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.processes,
|
||||
prometheus.GaugeValue,
|
||||
float64(jobInfo.BasicInfo.ActiveProcesses),
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.pageFaults,
|
||||
prometheus.CounterValue,
|
||||
float64(jobInfo.BasicInfo.TotalPageFaultCount),
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.usageCommitBytes,
|
||||
prometheus.GaugeValue,
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/headers/cri"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
@@ -38,7 +39,7 @@ func TestNewKubernetesContainers(t *testing.T) {
|
||||
{ID: "sandbox-2", Name: "stopped", Namespace: "kube-system", State: cri.SandboxNotReady},
|
||||
},
|
||||
[]cri.Container{
|
||||
{ID: "abc", PodSandboxID: "sandbox-1", Name: "nanoserver", State: cri.ContainerRunning},
|
||||
{ID: "abc", PodSandboxID: "sandbox-1", Name: "nanoserver", State: cri.ContainerRunning, CreatedAt: time.Unix(1700000000, 0)},
|
||||
{ID: "orphan", PodSandboxID: "unknown", Name: "orphan", State: cri.ContainerRunning},
|
||||
{ID: "exited", PodSandboxID: "sandbox-1", Name: "init", State: cri.ContainerExited},
|
||||
},
|
||||
@@ -50,6 +51,7 @@ func TestNewKubernetesContainers(t *testing.T) {
|
||||
namespace: "default",
|
||||
pod: "pod",
|
||||
container: "nanoserver",
|
||||
createdAt: time.Unix(1700000000, 0),
|
||||
},
|
||||
"orphan": {
|
||||
id: "containerd://orphan",
|
||||
|
||||
@@ -19,10 +19,12 @@ package container_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/container"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils/testutils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func BenchmarkCollector(b *testing.B) {
|
||||
@@ -33,4 +35,19 @@ func TestCollector(t *testing.T) {
|
||||
metrics := testutils.TestCollector(t, container.New, nil)
|
||||
testutils.RequireFixtureMetric(t, metrics, container.Name, "windows_container_available", prometheus.Labels{"container": "hostprocess", "namespace": "default", "hostprocess": "true"})
|
||||
testutils.RequireFixtureMetric(t, metrics, container.Name, "windows_container_available", prometheus.Labels{"container": "nanoserver", "namespace": "default", "hostprocess": "false"})
|
||||
|
||||
for _, name := range []string{"hostprocess", "nanoserver"} {
|
||||
labels := prometheus.Labels{"container": name, "namespace": "default"}
|
||||
|
||||
if metric := testutils.RequireFixtureMetric(t, metrics, container.Name, "windows_container_processes", labels); metric != nil {
|
||||
require.Positive(t, metric.GetGauge().GetValue(), "container %s has no processes", name)
|
||||
}
|
||||
|
||||
if metric := testutils.RequireFixtureMetric(t, metrics, container.Name, "windows_container_start_time_seconds", labels); metric != nil {
|
||||
require.InDelta(t, float64(time.Now().Unix()), metric.GetGauge().GetValue(), float64(24*time.Hour/time.Second),
|
||||
"container %s start time is not within the last day", name)
|
||||
}
|
||||
}
|
||||
|
||||
testutils.RequireFixtureMetric(t, metrics, container.Name, "windows_container_memory_page_faults_total", prometheus.Labels{"container": "hostprocess", "namespace": "default"})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user