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

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

View File

@@ -0,0 +1,97 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build windows
package hcs
import (
"encoding/json"
"fmt"
"github.com/prometheus-community/windows_exporter/internal/utils"
"golang.org/x/sys/windows"
)
//nolint:gochecknoglobals
var (
ContainerQuery = utils.Must(windows.UTF16PtrFromString(`{"Types":["Container"]}`))
StatisticsQuery = utils.Must(windows.UTF16PtrFromString(`{"PropertyTypes":["Statistics"]}`))
)
func GetContainers() ([]Properties, error) {
operation, err := CreateOperation()
if err != nil {
return nil, fmt.Errorf("failed to create operation: %w", err)
}
defer CloseOperation(operation)
if err := EnumerateComputeSystems(ContainerQuery, operation); err != nil {
return nil, fmt.Errorf("failed to enumerate compute systems: %w", err)
}
resultDocument, err := WaitForOperationResult(operation, 1000)
if err != nil {
return nil, fmt.Errorf("failed to wait and get for operation result: %w - %s", err, resultDocument)
} else if resultDocument == "" {
return nil, ErrEmptyResultDocument
}
var computeSystems []Properties
if err := json.Unmarshal([]byte(resultDocument), &computeSystems); err != nil {
return nil, fmt.Errorf("failed to unmarshal compute systems: %w", err)
}
return computeSystems, nil
}
func GetContainerStatistics(containerID string) (Statistics, error) {
computeSystem, err := OpenComputeSystem(containerID)
if err != nil {
return Statistics{}, fmt.Errorf("failed to open compute system: %w", err)
}
defer CloseComputeSystem(computeSystem)
operation, err := CreateOperation()
if err != nil {
return Statistics{}, fmt.Errorf("failed to create operation: %w", err)
}
defer CloseOperation(operation)
if err := GetComputeSystemProperties(computeSystem, operation, StatisticsQuery); err != nil {
return Statistics{}, fmt.Errorf("failed to enumerate compute systems: %w", err)
}
resultDocument, err := WaitForOperationResult(operation, 1000)
if err != nil {
return Statistics{}, fmt.Errorf("failed to get compute system properties: %w", err)
} else if resultDocument == "" {
return Statistics{}, ErrEmptyResultDocument
}
var properties Properties
if err := json.Unmarshal([]byte(resultDocument), &properties); err != nil {
return Statistics{}, fmt.Errorf("failed to unmarshal system properties: %w", err)
}
if properties.Statistics == nil {
return Statistics{}, fmt.Errorf("no statistics found for container %s", containerID)
}
return *properties.Statistics, nil
}

View File

@@ -0,0 +1,55 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build windows
package hcs_test
import (
"testing"
"github.com/prometheus-community/windows_exporter/internal/headers/hcs"
"github.com/stretchr/testify/require"
)
func TestGetContainers(t *testing.T) {
t.Parallel()
containers, err := hcs.GetContainers()
require.NoError(t, err)
require.NotNil(t, containers)
}
func TestOpenContainer(t *testing.T) {
t.Parallel()
containers, err := hcs.GetContainers()
require.NoError(t, err)
if len(containers) == 0 {
t.Skip("No containers found")
}
statistics, err := hcs.GetContainerStatistics(containers[0].ID)
require.NoError(t, err)
require.NotNil(t, statistics)
}
func TestOpenContainerNotFound(t *testing.T) {
t.Parallel()
_, err := hcs.GetContainerStatistics("f3056b79b36ddfe203376473e2aeb4922a8ca7c5d8100764e5829eb5552fe09b")
require.ErrorIs(t, err, hcs.ErrIDNotFound)
}

View File

@@ -0,0 +1,130 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build windows
package hcs
import (
"fmt"
"unsafe"
"golang.org/x/sys/windows"
)
//nolint:gochecknoglobals
var (
modComputeCore = windows.NewLazySystemDLL("computecore.dll")
procHcsCreateOperation = modComputeCore.NewProc("HcsCreateOperation")
procHcsWaitForOperationResult = modComputeCore.NewProc("HcsWaitForOperationResult")
procHcsCloseOperation = modComputeCore.NewProc("HcsCloseOperation")
procHcsEnumerateComputeSystems = modComputeCore.NewProc("HcsEnumerateComputeSystems")
procHcsOpenComputeSystem = modComputeCore.NewProc("HcsOpenComputeSystem")
procHcsGetComputeSystemProperties = modComputeCore.NewProc("HcsGetComputeSystemProperties")
procHcsCloseComputeSystem = modComputeCore.NewProc("HcsCloseComputeSystem")
)
// CreateOperation creates a new operation.
func CreateOperation() (Operation, error) {
r1, r2, _ := procHcsCreateOperation.Call(0, 0)
if r2 != 0 {
return 0, fmt.Errorf("HcsCreateOperation failed: HRESULT 0x%X: %w", r2, Win32FromHResult(r2))
}
return Operation(r1), nil
}
func WaitForOperationResult(operation Operation, timeout uint32) (string, error) {
var resultDocument *uint16
r1, _, _ := procHcsWaitForOperationResult.Call(uintptr(operation), uintptr(timeout), uintptr(unsafe.Pointer(&resultDocument)))
if r1 != 0 {
return "", fmt.Errorf("HcsWaitForOperationResult failed: HRESULT 0x%X: %w", r1, Win32FromHResult(r1))
}
result := windows.UTF16PtrToString(resultDocument)
windows.CoTaskMemFree(unsafe.Pointer(resultDocument))
return result, nil
}
// CloseOperation closes an operation.
func CloseOperation(operation Operation) {
_, _, _ = procHcsCloseOperation.Call(uintptr(operation))
}
// EnumerateComputeSystems enumerates compute systems.
//
// https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsenumeratecomputesystems
func EnumerateComputeSystems(query *uint16, operation Operation) error {
r1, _, _ := procHcsEnumerateComputeSystems.Call(uintptr(unsafe.Pointer(query)), uintptr(operation))
if r1 != 0 {
return fmt.Errorf("HcsEnumerateComputeSystems failed: HRESULT 0x%X: %w", r1, Win32FromHResult(r1))
}
return nil
}
// OpenComputeSystem opens a handle to an existing compute system.
//
// https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsopencomputesystem
func OpenComputeSystem(id string) (ComputeSystem, error) {
idPtr, err := windows.UTF16PtrFromString(id)
if err != nil {
return 0, err
}
var system ComputeSystem
r1, _, _ := procHcsOpenComputeSystem.Call(
uintptr(unsafe.Pointer(idPtr)),
uintptr(windows.GENERIC_ALL),
uintptr(unsafe.Pointer(&system)),
)
if r1 != 0 {
return 0, fmt.Errorf("HcsOpenComputeSystem failed: HRESULT 0x%X: %w", r1, Win32FromHResult(r1))
}
return system, nil
}
func GetComputeSystemProperties(system ComputeSystem, operation Operation, propertyQuery *uint16) error {
r1, _, err := procHcsGetComputeSystemProperties.Call(
uintptr(system),
uintptr(operation),
uintptr(unsafe.Pointer(propertyQuery)),
)
if r1 != 0 {
return fmt.Errorf("HcsGetComputeSystemProperties failed: HRESULT 0x%X: %w", r1, err)
}
return nil
}
// CloseComputeSystem closes a handle to a compute system.
//
// https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsclosecomputesystem
func CloseComputeSystem(system ComputeSystem) {
_, _, _ = procHcsCloseComputeSystem.Call(uintptr(system))
}
func Win32FromHResult(hr uintptr) windows.Errno {
if hr&0x1fff0000 == 0x00070000 {
return windows.Errno(hr & 0xffff)
}
return windows.Errno(hr)
}

View File

@@ -0,0 +1,83 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build windows
package hcs
import (
"errors"
"time"
"golang.org/x/sys/windows"
)
var (
ErrEmptyResultDocument = errors.New("empty result document")
ErrIDNotFound = windows.Errno(2151088398)
)
type (
Operation = windows.Handle
ComputeSystem = windows.Handle
)
type Properties struct {
ID string `json:"Id,omitempty"`
SystemType string `json:"SystemType,omitempty"`
Owner string `json:"Owner,omitempty"`
State string `json:"State,omitempty"`
Statistics *Statistics `json:"Statistics,omitempty"`
ProcessList []ProcessDetails `json:"ProcessList,omitempty"`
}
type ProcessDetails struct {
ProcessId int32 `json:"ProcessId,omitempty"`
ImageName string `json:"ImageName,omitempty"`
CreateTimestamp time.Time `json:"CreateTimestamp,omitempty"`
UserTime100ns int32 `json:"UserTime100ns,omitempty"`
KernelTime100ns int32 `json:"KernelTime100ns,omitempty"`
MemoryCommitBytes int32 `json:"MemoryCommitBytes,omitempty"`
MemoryWorkingSetPrivateBytes int32 `json:"MemoryWorkingSetPrivateBytes,omitempty"`
MemoryWorkingSetSharedBytes int32 `json:"MemoryWorkingSetSharedBytes,omitempty"`
}
type Statistics struct {
Timestamp time.Time `json:"Timestamp,omitempty"`
ContainerStartTime time.Time `json:"ContainerStartTime,omitempty"`
Uptime100ns uint64 `json:"Uptime100ns,omitempty"`
Processor *ProcessorStats `json:"Processor,omitempty"`
Memory *MemoryStats `json:"Memory,omitempty"`
Storage *StorageStats `json:"Storage,omitempty"`
}
type ProcessorStats struct {
TotalRuntime100ns uint64 `json:"TotalRuntime100ns,omitempty"`
RuntimeUser100ns uint64 `json:"RuntimeUser100ns,omitempty"`
RuntimeKernel100ns uint64 `json:"RuntimeKernel100ns,omitempty"`
}
type MemoryStats struct {
MemoryUsageCommitBytes uint64 `json:"MemoryUsageCommitBytes,omitempty"`
MemoryUsageCommitPeakBytes uint64 `json:"MemoryUsageCommitPeakBytes,omitempty"`
MemoryUsagePrivateWorkingSetBytes uint64 `json:"MemoryUsagePrivateWorkingSetBytes,omitempty"`
}
type StorageStats struct {
ReadCountNormalized uint64 `json:"ReadCountNormalized,omitempty"`
ReadSizeBytes uint64 `json:"ReadSizeBytes,omitempty"`
WriteCountNormalized uint64 `json:"WriteCountNormalized,omitempty"`
WriteSizeBytes uint64 `json:"WriteSizeBytes,omitempty"`
}