mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-20 19:49:03 +02:00
mscluster: collect metrics through ClusAPI (#2634)
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
f292a6fbcf
commit
1302cf6899
@@ -0,0 +1,171 @@
|
||||
// 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 clusapi
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
resourceGetROPrivateProperties = 0x0100007d
|
||||
resourceGetPrivateProperties = 0x01000081
|
||||
resourceTypeGetROCommonProperties = 0x02000055
|
||||
resourceTypeGetCommonProperties = 0x02000059
|
||||
resourceTypeGetPrivateProperties = 0x02000081
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var resourceTypeControl = dll.NewProc("ClusterResourceTypeControl")
|
||||
|
||||
// TestDumpPropertyLists logs raw property lists from a live cluster as hex, so
|
||||
// parser fixtures can be built from real cluster service output. Set
|
||||
// WINDOWS_EXPORTER_TEST_CLUSAPI_DUMP=1 on a cluster node to enable it.
|
||||
func TestDumpPropertyLists(t *testing.T) {
|
||||
if os.Getenv("WINDOWS_EXPORTER_TEST_CLUSAPI_DUMP") == "" {
|
||||
t.Skip("WINDOWS_EXPORTER_TEST_CLUSAPI_DUMP is not set")
|
||||
}
|
||||
|
||||
cluster, _, err := openCluster.Call(0, windows.GENERIC_READ, 0)
|
||||
if cluster == 0 {
|
||||
t.Fatalf("OpenClusterEx: %v", err)
|
||||
}
|
||||
|
||||
defer closeCluster.Call(cluster) //nolint:errcheck
|
||||
|
||||
deadline := time.Now().Add(time.Minute)
|
||||
|
||||
for _, code := range []uint32{objectCluster<<24 | ctlGetROCommonProperties, objectCluster<<24 | ctlGetCommonProperties} {
|
||||
data, err := controlBuffer(deadline, 0, func(buffer []byte) (uint32, error) {
|
||||
var (
|
||||
size uint32
|
||||
pointer *byte
|
||||
)
|
||||
if len(buffer) != 0 {
|
||||
pointer = &buffer[0]
|
||||
}
|
||||
|
||||
status, _, _ := clusterControl.Call(cluster, 0, uintptr(code), 0, 0, uintptr(unsafe.Pointer(pointer)), uintptr(len(buffer)), uintptr(unsafe.Pointer(&size)))
|
||||
if status != 0 {
|
||||
return size, windows.Errno(status)
|
||||
}
|
||||
|
||||
return size, nil
|
||||
})
|
||||
logPropertyList(t, fmt.Sprintf("cluster %#x", code), data, err)
|
||||
}
|
||||
|
||||
// Resource type lists carry EXPAND_SZ values such as DllName, which the
|
||||
// cluster service returns as an EXPAND_SZ and EXPANDED_SZ value pair.
|
||||
for _, typeName := range []string{"Generic Service", "IP Address"} {
|
||||
typePtr, err := windows.UTF16PtrFromString(typeName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, code := range []uint32{resourceTypeGetROCommonProperties, resourceTypeGetCommonProperties, resourceTypeGetPrivateProperties} {
|
||||
data, err := controlBuffer(deadline, 0, func(buffer []byte) (uint32, error) {
|
||||
var (
|
||||
size uint32
|
||||
pointer *byte
|
||||
)
|
||||
if len(buffer) != 0 {
|
||||
pointer = &buffer[0]
|
||||
}
|
||||
|
||||
status, _, _ := resourceTypeControl.Call(cluster, uintptr(unsafe.Pointer(typePtr)), 0, uintptr(code), 0, 0, uintptr(unsafe.Pointer(pointer)), uintptr(len(buffer)), uintptr(unsafe.Pointer(&size)))
|
||||
if status != 0 {
|
||||
return size, windows.Errno(status)
|
||||
}
|
||||
|
||||
return size, nil
|
||||
})
|
||||
logPropertyList(t, fmt.Sprintf("resource type %q %#x", typeName, code), data, err)
|
||||
}
|
||||
}
|
||||
|
||||
enum, _, err := openEnum.Call(cluster, 4)
|
||||
if enum == 0 {
|
||||
t.Fatalf("ClusterOpenEnum: %v", err)
|
||||
}
|
||||
|
||||
defer closeEnum.Call(enum) //nolint:errcheck
|
||||
|
||||
for index := uint32(0); ; index++ {
|
||||
name, err := enumName(enum, index, enumResource, deadline)
|
||||
if errors.Is(err, windows.ERROR_NO_MORE_ITEMS) {
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("ClusterEnum: %v", err)
|
||||
}
|
||||
|
||||
handle, _, err := openResource.Call(cluster, uintptr(unsafe.Pointer(&name.units[0])), windows.GENERIC_READ, 0)
|
||||
if handle == 0 {
|
||||
t.Errorf("OpenClusterResourceEx %q: %v", name.name, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
for _, code := range []uint32{resourceGetROCommonProperties, resourceGetCommonProperties, resourceGetROPrivateProperties, resourceGetPrivateProperties} {
|
||||
data, err := resourceBuffer(handle, code, 0, deadline)
|
||||
logPropertyList(t, fmt.Sprintf("resource %q %#x", name.name, code), data, err)
|
||||
}
|
||||
|
||||
data, err := resourceBuffer(handle, resourceGetType, 0, deadline)
|
||||
t.Logf("resource %q GET_RESOURCE_TYPE: %s err=%v", name.name, hex.EncodeToString(data), err)
|
||||
|
||||
closeResource.Call(handle) //nolint:errcheck
|
||||
}
|
||||
}
|
||||
|
||||
func logPropertyList(t *testing.T, label string, data []byte, err error) {
|
||||
t.Helper()
|
||||
|
||||
if err != nil {
|
||||
t.Logf("%s: %v", label, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
properties, parseErr := ParseProperties(data)
|
||||
|
||||
formats := make([]string, 0, len(properties))
|
||||
for name, values := range properties {
|
||||
valueFormats := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
valueFormats = append(valueFormats, fmt.Sprintf("%d/%d", value.Format, len(value.Data)))
|
||||
}
|
||||
|
||||
formats = append(formats, name+"="+strings.Join(valueFormats, "+"))
|
||||
}
|
||||
|
||||
slices.Sort(formats)
|
||||
|
||||
t.Logf("%s: parse error=%v formats=[%s]\n%s", label, parseErr, strings.Join(formats, " "), hex.EncodeToString(data))
|
||||
}
|
||||
Reference in New Issue
Block a user