mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-02-14 08:56:36 +00:00
container: support hostprocess containers and expose kubernetes labels (#1911)
This commit is contained in:
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"`
|
||||
}
|
||||
Reference in New Issue
Block a user