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:
Jan-Otto Kröpke
2026-10-09 01:48:17 +02:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 301eef493b
commit 64046fb91f
9 changed files with 241 additions and 41 deletions
+27 -20
View File
@@ -26,26 +26,33 @@ If the endpoint is not available, for example on hosts without Kubernetes, the H
## Metrics
| Name | Description | Type | Labels |
|------------------------------------------------------------|----------------------------------------|---------|------------------------------------------------------------|
| `windows_container_available` | Available | gauge | `container_id`,`namespace`,`pod`,`container`,`hostprocess` |
| `windows_container_count` | Number of running HCS containers | gauge | None |
| `windows_container_cpu_usage_seconds_kernelmode` | Runtime in Kernel mode in Seconds | counter | `container_id`,`namespace`,`pod`,`container` |
| `windows_container_cpu_usage_seconds_usermode` | Runtime in User mode in Seconds | counter | `container_id`,`namespace`,`pod`,`container` |
| `windows_container_cpu_usage_seconds_total` | Total Runtime in Seconds | counter | `container_id`,`namespace`,`pod`,`container` |
| `windows_container_memory_usage_commit_bytes` | Memory Usage Commit Bytes | gauge | `container_id`,`namespace`,`pod`,`container` |
| `windows_container_memory_usage_commit_peak_bytes` | Memory Usage Commit Peak Bytes | gauge | `container_id`,`namespace`,`pod`,`container` |
| `windows_container_memory_usage_private_working_set_bytes` | Memory Usage Private Working Set Bytes | gauge | `container_id`,`namespace`,`pod`,`container` |
| `windows_container_network_receive_bytes_total` | Bytes Received on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` |
| `windows_container_network_receive_packets_total` | Packets Received on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` |
| `windows_container_network_receive_packets_dropped_total` | Dropped Incoming Packets on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` |
| `windows_container_network_transmit_bytes_total` | Bytes Sent on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` |
| `windows_container_network_transmit_packets_total` | Packets Sent on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` |
| `windows_container_network_transmit_packets_dropped_total` | Dropped Outgoing Packets on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` |
| `windows_container_storage_read_count_normalized_total` | Read Count Normalized | counter | `container_id`,`namespace`,`pod`,`container` |
| `windows_container_storage_read_size_bytes_total` | Read Size Bytes | counter | `container_id`,`namespace`,`pod`,`container` |
| `windows_container_storage_write_count_normalized_total` | Write Count Normalized | counter | `container_id`,`namespace`,`pod`,`container` |
| `windows_container_storage_write_size_bytes_total` | Write Size Bytes | counter | `container_id`,`namespace`,`pod`,`container` |
| Name | Description | Type | Labels |
|------------------------------------------------------------|-----------------------------------------------------|---------|------------------------------------------------------------|
| `windows_container_available` | Available | gauge | `container_id`,`namespace`,`pod`,`container`,`hostprocess` |
| `windows_container_count` | Number of running HCS containers | gauge | None |
| `windows_container_cpu_usage_seconds_kernelmode` | Runtime in Kernel mode in Seconds | counter | `container_id`,`namespace`,`pod`,`container` |
| `windows_container_cpu_usage_seconds_usermode` | Runtime in User mode in Seconds | counter | `container_id`,`namespace`,`pod`,`container` |
| `windows_container_cpu_usage_seconds_total` | Total Runtime in Seconds | counter | `container_id`,`namespace`,`pod`,`container` |
| `windows_container_memory_page_faults_total` | Total number of page faults (HostProcess only) | counter | `container_id`,`namespace`,`pod`,`container` |
| `windows_container_memory_usage_commit_bytes` | Memory Usage Commit Bytes | gauge | `container_id`,`namespace`,`pod`,`container` |
| `windows_container_memory_usage_commit_peak_bytes` | Memory Usage Commit Peak Bytes | gauge | `container_id`,`namespace`,`pod`,`container` |
| `windows_container_memory_usage_private_working_set_bytes` | Memory Usage Private Working Set Bytes | gauge | `container_id`,`namespace`,`pod`,`container` |
| `windows_container_network_receive_bytes_total` | Bytes Received on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` |
| `windows_container_network_receive_packets_total` | Packets Received on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` |
| `windows_container_network_receive_packets_dropped_total` | Dropped Incoming Packets on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` |
| `windows_container_network_transmit_bytes_total` | Bytes Sent on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` |
| `windows_container_network_transmit_packets_total` | Packets Sent on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` |
| `windows_container_network_transmit_packets_dropped_total` | Dropped Outgoing Packets on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` |
| `windows_container_processes` | Number of processes running in the container | gauge | `container_id`,`namespace`,`pod`,`container` |
| `windows_container_start_time_seconds` | Start time of the container since Unix epoch | gauge | `container_id`,`namespace`,`pod`,`container` |
| `windows_container_storage_read_count_normalized_total` | Read Count Normalized | counter | `container_id`,`namespace`,`pod`,`container` |
| `windows_container_storage_read_size_bytes_total` | Read Size Bytes | counter | `container_id`,`namespace`,`pod`,`container` |
| `windows_container_storage_write_count_normalized_total` | Write Count Normalized | counter | `container_id`,`namespace`,`pod`,`container` |
| `windows_container_storage_write_size_bytes_total` | Write Size Bytes | counter | `container_id`,`namespace`,`pod`,`container` |
`windows_container_start_time_seconds` is the start time reported by HCS. HostProcess containers have no start time, they report their creation time from the CRI endpoint instead.
`windows_container_memory_page_faults_total` is only available for HostProcess containers, because HCS doesn't report page faults. The job object counts page faults in 32 bits, so the counter wraps around after 2^32 page faults.
### Example metric
_windows_container_network_receive_bytes_total{container_id="docker://1bd30e8b8ac28cbd76a9b697b4d7bb9d760267b0733d1bc55c60024e98d1e43e",interface="822179E7-002C-4280-ABBA-28BCFE401826"} 9.3305343e+07_
+79 -1
View File
@@ -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"})
}
+6 -2
View File
@@ -23,6 +23,7 @@ import (
"net/http"
"strings"
"testing"
"time"
"github.com/stretchr/testify/require"
"golang.org/x/sys/windows"
@@ -69,7 +70,7 @@ func containersResponse() message {
message(4, message{}.string(1, "mcr.microsoft.com/windows/nanoserver")).
string(5, "sha256:1234").
varint(6, uint64(ContainerRunning)).
varint(7, 1700000000000000000)
varint(7, 1700000000123456789)
nanoserver = append(nanoserver, labels(8, "io.kubernetes.pod.name", "pod")...)
nanoserver = append(nanoserver, labels(9, "io.kubernetes.container.restartCount", "2")...)
@@ -100,9 +101,12 @@ func TestDecodeListContainersResponse(t *testing.T) {
containers, err := decodeListContainersResponse(containersResponse())
require.NoError(t, err)
require.Equal(t, []Container{
{ID: "abc", PodSandboxID: "sandbox-1", Name: "nanoserver", State: ContainerRunning},
{ID: "abc", PodSandboxID: "sandbox-1", Name: "nanoserver", State: ContainerRunning, CreatedAt: time.Unix(0, 1700000000123456789)},
{ID: "def", Name: "other", State: ContainerCreated},
}, containers)
require.Equal(t, int64(1700000000123456789), containers[0].CreatedAt.UnixNano())
require.True(t, containers[1].CreatedAt.IsZero())
}
func TestDecodeListPodSandboxResponse(t *testing.T) {
+8
View File
@@ -18,6 +18,8 @@
package cri
import (
"time"
"google.golang.org/protobuf/encoding/protowire"
)
@@ -54,6 +56,8 @@ type Container struct {
// Name is the container name from the Kubernetes pod spec.
Name string
State ContainerState
// CreatedAt is the creation time of the container. It is zero if the runtime did not set it.
CreatedAt time.Time
}
// PodSandbox is the subset of runtime.v1.PodSandbox used by the exporter.
@@ -170,6 +174,10 @@ func decodeContainer(b []byte) (Container, error) {
})
case f.is(6, protowire.VarintType):
container.State = ContainerState(f.value)
case f.is(7, protowire.VarintType): // created_at, int64 in nanoseconds since the Unix epoch
if f.value != 0 {
container.CreatedAt = time.Unix(0, int64(f.value))
}
}
return nil
+18 -11
View File
@@ -27,8 +27,9 @@ import (
//nolint:gochecknoglobals
var (
ContainerQuery = utils.Must(windows.UTF16PtrFromString(`{"Types":["Container"]}`))
StatisticsQuery = utils.Must(windows.UTF16PtrFromString(`{"PropertyTypes":["Statistics"]}`))
ContainerQuery = utils.Must(windows.UTF16PtrFromString(`{"Types":["Container"]}`))
// StatisticsQuery queries the statistics and the process list in one call.
StatisticsQuery = utils.Must(windows.UTF16PtrFromString(`{"PropertyTypes":["Statistics","ProcessList"]}`))
)
func GetContainers() ([]Properties, error) {
@@ -58,40 +59,46 @@ func GetContainers() ([]Properties, error) {
return computeSystems, nil
}
func GetContainerStatistics(containerID string) (Statistics, error) {
// GetContainerStatistics returns the statistics and the process list of a container.
// The Statistics field is never nil if no error is returned.
func GetContainerStatistics(containerID string) (Properties, error) {
computeSystem, err := OpenComputeSystem(containerID)
if err != nil {
return Statistics{}, fmt.Errorf("failed to open compute system: %w", err)
return Properties{}, 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)
return Properties{}, 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)
return Properties{}, 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)
return Properties{}, fmt.Errorf("failed to get compute system properties: %w", err)
} else if resultDocument == "" {
return Statistics{}, ErrEmptyResultDocument
return Properties{}, ErrEmptyResultDocument
}
return parseStatistics(containerID, resultDocument)
}
func parseStatistics(containerID, resultDocument string) (Properties, error) {
var properties Properties
if err := json.Unmarshal([]byte(resultDocument), &properties); err != nil {
return Statistics{}, fmt.Errorf("failed to unmarshal system properties: %w", err)
return Properties{}, 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{}, fmt.Errorf("no statistics found for container %s", containerID)
}
return *properties.Statistics, nil
return properties, nil
}
+75
View File
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build windows
package hcs
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestParseStatistics(t *testing.T) {
t.Parallel()
// A result document of the Statistics and ProcessList query, as HCS returns it for a process-isolated container.
properties, err := parseStatistics("abc", `{
"Id": "abc",
"SystemType": "Container",
"Statistics": {
"Timestamp": "2026-10-08T10:00:05.1234567Z",
"ContainerStartTime": "2026-10-08T10:00:00.5Z",
"Uptime100ns": 50000000,
"Processor": {"TotalRuntime100ns": 30000000, "RuntimeUser100ns": 20000000, "RuntimeKernel100ns": 10000000},
"Memory": {"MemoryUsageCommitBytes": 1, "MemoryUsageCommitPeakBytes": 2, "MemoryUsagePrivateWorkingSetBytes": 3},
"Storage": {"ReadCountNormalized": 4, "ReadSizeBytes": 5, "WriteCountNormalized": 6, "WriteSizeBytes": 7}
},
"ProcessList": [
{"CreateTimestamp": "2026-10-08T10:00:00.6Z", "ImageName": "smss.exe", "ProcessId": 4096},
{
"CreateTimestamp": "2026-10-08T10:00:01Z",
"ImageName": "app.exe",
"KernelTime100ns": 9000000000,
"MemoryCommitBytes": 8589934592,
"MemoryWorkingSetPrivateBytes": 4294967296,
"MemoryWorkingSetSharedBytes": 4096,
"ProcessId": 4294967292,
"UserTime100ns": 9000000000
}
]
}`)
require.NoError(t, err)
require.NotNil(t, properties.Statistics)
require.Equal(t, time.Date(2026, 10, 8, 10, 0, 0, 500_000_000, time.UTC), properties.Statistics.ContainerStartTime)
require.Equal(t, uint64(30000000), properties.Statistics.Processor.TotalRuntime100ns)
require.Equal(t, uint64(7), properties.Statistics.Storage.WriteSizeBytes)
require.Len(t, properties.ProcessList, 2)
require.Equal(t, uint32(4294967292), properties.ProcessList[1].ProcessId)
require.Equal(t, uint64(8589934592), properties.ProcessList[1].MemoryCommitBytes)
require.Equal(t, uint64(9000000000), properties.ProcessList[1].UserTime100ns)
}
func TestParseStatisticsMissing(t *testing.T) {
t.Parallel()
_, err := parseStatistics("abc", `{"Id": "abc", "ProcessList": []}`)
require.ErrorContains(t, err, "no statistics found for container abc")
_, err = parseStatistics("abc", `{"Statistics": `)
require.Error(t, err)
}
+8 -6
View File
@@ -48,15 +48,17 @@ type Properties struct {
ProcessList []ProcessDetails `json:"ProcessList,omitempty"`
}
// ProcessDetails is an entry of the ProcessList property, see ProcessListItem in hcsshim's schema1.
// The times and sizes are 64-bit, e.g. a process using more than 2 GiB of memory would overflow an int32.
type ProcessDetails struct {
ProcessId int32 `json:"ProcessId,omitempty"`
ProcessId uint32 `json:"ProcessId,omitempty"`
ImageName string `json:"ImageName,omitempty"`
CreateTimestamp time.Time `json:"CreateTimestamp"`
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"`
UserTime100ns uint64 `json:"UserTime100ns,omitempty"`
KernelTime100ns uint64 `json:"KernelTime100ns,omitempty"`
MemoryCommitBytes uint64 `json:"MemoryCommitBytes,omitempty"`
MemoryWorkingSetPrivateBytes uint64 `json:"MemoryWorkingSetPrivateBytes,omitempty"`
MemoryWorkingSetSharedBytes uint64 `json:"MemoryWorkingSetSharedBytes,omitempty"`
}
type Statistics struct {