mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-17 10:09:05 +02:00
mscluster: reuse control buffers and cut per-property allocations (#2673)
Co-authored-by: Claude Opus 5.5 (1M context) <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
a8adb6d7bc
commit
930609b0e5
@@ -38,6 +38,7 @@ const (
|
||||
resourceGetType = 0x0100002d
|
||||
resourceGetClassInfo = 0x0100000d
|
||||
maxBufferSize = 64 << 20
|
||||
maxScratchSize = 64 << 10
|
||||
stateUnknown = ^uint32(0) // ClusterResourceStateUnknown
|
||||
propertyListBufferSize = 4 << 10
|
||||
typeBufferSize = 512
|
||||
@@ -74,7 +75,7 @@ type nativeAPI struct {
|
||||
enumName func(enum uintptr, index, enumType uint32, deadline time.Time) (objectName, error)
|
||||
openResource func(cluster uintptr, name objectName) (uintptr, error)
|
||||
closeResource func(resource uintptr) error
|
||||
readResource func(resource uintptr, out *Resource, deadline time.Time) error
|
||||
readResource func(resource uintptr, out *Resource, scratch *controlScratch, deadline time.Time) error
|
||||
}
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
@@ -158,6 +159,46 @@ type Cluster struct {
|
||||
handle uintptr
|
||||
closed bool
|
||||
running bool
|
||||
|
||||
// scratch belongs to the running call, like handle.
|
||||
scratch controlScratch
|
||||
}
|
||||
|
||||
// controlScratch is the reusable control buffer of a Cluster. Its owner runs
|
||||
// one control at a time, so no pool is needed. Values parsed from a result,
|
||||
// such as Property.Data, point into the buffer and are only valid until the
|
||||
// next control; callers copy them out first.
|
||||
type controlScratch struct {
|
||||
buffer []byte
|
||||
}
|
||||
|
||||
// get returns n zeroed bytes, the same contents as a new buffer.
|
||||
func (s *controlScratch) get(n int) []byte {
|
||||
if cap(s.buffer) < n {
|
||||
s.buffer = make([]byte, max(n, propertyListBufferSize))
|
||||
}
|
||||
|
||||
buffer := s.buffer[:n]
|
||||
clear(buffer)
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
// keep holds on to a buffer a control had to grow, up to maxScratchSize, so
|
||||
// the next object starts with the larger size.
|
||||
func (s *controlScratch) keep(data []byte) {
|
||||
if cap(data) > cap(s.buffer) && cap(data) <= maxScratchSize {
|
||||
s.buffer = data[:cap(data)]
|
||||
}
|
||||
}
|
||||
|
||||
// resourceControl calls ClusterResourceControl starting with n bytes of the
|
||||
// scratch buffer. The result is only valid until the next control.
|
||||
func (s *controlScratch) resourceControl(handle uintptr, code uint32, n int, deadline time.Time) ([]byte, error) {
|
||||
data, err := resourceBufferIn(handle, code, s.get(n), deadline)
|
||||
s.keep(data)
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
type Resource struct {
|
||||
@@ -391,7 +432,7 @@ func (c *Cluster) Resources(deadline time.Time) (_ []Resource, resultErr error)
|
||||
return fmt.Errorf("OpenClusterResourceEx: %w", err)
|
||||
}
|
||||
|
||||
err = api.readResource(handle, &resource, deadline)
|
||||
err = api.readResource(handle, &resource, &c.scratch, deadline)
|
||||
if closeErr := api.closeResource(handle); closeErr != nil {
|
||||
err = errors.Join(err, fmt.Errorf("CloseClusterResource: %w", closeErr))
|
||||
}
|
||||
@@ -459,13 +500,13 @@ func enumName(enum uintptr, index, enumType uint32, deadline time.Time) (objectN
|
||||
|
||||
// readResource fills the properties of an open resource. Failed properties are
|
||||
// omitted; the identity is only valid once the state and owner are known.
|
||||
func readResource(handle uintptr, resource *Resource, deadline time.Time) error {
|
||||
func readResource(handle uintptr, resource *Resource, scratch *controlScratch, deadline time.Time) error {
|
||||
var resultErr error
|
||||
|
||||
hasType := false
|
||||
|
||||
for _, code := range []uint32{resourceGetROCommonProperties, resourceGetCommonProperties} {
|
||||
data, err := resourceBuffer(handle, code, propertyListBufferSize, deadline)
|
||||
data, err := scratch.resourceControl(handle, code, propertyListBufferSize, deadline)
|
||||
if err != nil {
|
||||
resultErr = errors.Join(resultErr, fmt.Errorf("ClusterResourceControl %#x: %w", code, err))
|
||||
|
||||
@@ -479,23 +520,8 @@ func readResource(handle uintptr, resource *Resource, deadline time.Time) error
|
||||
continue
|
||||
}
|
||||
|
||||
for name, values := range properties {
|
||||
if len(values) != 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
if value, err := values[0].DWORD(); err == nil {
|
||||
resource.Values[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
// The read-only common Type property is the same resource type name
|
||||
// that CLUSCTL_RESOURCE_GET_RESOURCE_TYPE returns.
|
||||
if values := properties["Type"]; len(values) == 1 {
|
||||
if value, err := values[0].String(); err == nil {
|
||||
resource.Type = value
|
||||
hasType = true
|
||||
}
|
||||
if copyResourceProperties(resource, properties) {
|
||||
hasType = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,7 +529,7 @@ func readResource(handle uintptr, resource *Resource, deadline time.Time) error
|
||||
name string
|
||||
code uint32
|
||||
}{{"Characteristics", resourceGetCharacteristics}, {"Flags", resourceGetFlags}} {
|
||||
data, err := resourceBuffer(handle, field.code, 4, deadline)
|
||||
data, err := scratch.resourceControl(handle, field.code, 4, deadline)
|
||||
switch {
|
||||
case err != nil:
|
||||
resultErr = errors.Join(resultErr, fmt.Errorf("%s: %w", field.name, err))
|
||||
@@ -514,7 +540,7 @@ func readResource(handle uintptr, resource *Resource, deadline time.Time) error
|
||||
}
|
||||
}
|
||||
|
||||
data, err := resourceBuffer(handle, resourceGetClassInfo, 8, deadline)
|
||||
data, err := scratch.resourceControl(handle, resourceGetClassInfo, 8, deadline)
|
||||
if err != nil {
|
||||
resultErr = errors.Join(resultErr, fmt.Errorf("class information: %w", err))
|
||||
} else {
|
||||
@@ -523,7 +549,7 @@ func readResource(handle uintptr, resource *Resource, deadline time.Time) error
|
||||
|
||||
// The property lists normally carry the type; only ask separately without it.
|
||||
if !hasType {
|
||||
data, err = resourceBuffer(handle, resourceGetType, typeBufferSize, deadline)
|
||||
data, err = scratch.resourceControl(handle, resourceGetType, typeBufferSize, deadline)
|
||||
if err != nil {
|
||||
return errors.Join(resultErr, fmt.Errorf("resource type: %w", err))
|
||||
}
|
||||
@@ -544,6 +570,33 @@ func readResource(handle uintptr, resource *Resource, deadline time.Time) error
|
||||
return resultErr
|
||||
}
|
||||
|
||||
// copyResourceProperties copies the single DWORD values and the type name out
|
||||
// of a parsed property list, so the list's buffer can be reused. It reports
|
||||
// whether the list had the type.
|
||||
func copyResourceProperties(resource *Resource, properties map[string][]Property) bool {
|
||||
for name, values := range properties {
|
||||
if len(values) != 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
if value, ok := values[0].dword(); ok {
|
||||
resource.Values[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
// The read-only common Type property is the same resource type name that
|
||||
// CLUSCTL_RESOURCE_GET_RESOURCE_TYPE returns.
|
||||
if values := properties["Type"]; len(values) == 1 {
|
||||
if value, err := values[0].String(); err == nil {
|
||||
resource.Type = value
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// parseResourceClassInfo decodes CLUS_RESOURCE_CLASS_INFO. Some resource DLLs,
|
||||
// such as the Storage Spaces Direct Health Service, succeed but return only
|
||||
// the class DWORD without the subclass; the WMI provider reports their
|
||||
@@ -563,8 +616,10 @@ func parseResourceClassInfo(data []byte) (uint32, uint32) {
|
||||
return class, subclass
|
||||
}
|
||||
|
||||
func resourceBuffer(handle uintptr, code uint32, size int, deadline time.Time) ([]byte, error) {
|
||||
return controlBuffer(deadline, size, func(buffer []byte) (uint32, error) {
|
||||
// resourceBufferIn calls ClusterResourceControl starting with buffer. The
|
||||
// result may share buffer's memory.
|
||||
func resourceBufferIn(handle uintptr, code uint32, buffer []byte, deadline time.Time) ([]byte, error) {
|
||||
return controlBufferIn(deadline, buffer, func(buffer []byte) (uint32, error) {
|
||||
var (
|
||||
size uint32
|
||||
pointer *byte
|
||||
@@ -597,7 +652,8 @@ func readResourceState(handle uintptr, deadline time.Time) (uint32, string, stri
|
||||
}
|
||||
|
||||
func stateBuffers(deadline time.Time, call func([]uint16, []uint16) (uint32, uint32, uint32, error)) (uint32, string, string, error) {
|
||||
node, group := make([]uint16, 256), make([]uint16, 256)
|
||||
units := make([]uint16, 512)
|
||||
node, group := units[:256:256], units[256:]
|
||||
|
||||
for range 16 {
|
||||
if err := checkDeadline(deadline); err != nil {
|
||||
@@ -655,15 +711,20 @@ func checkDeadline(deadline time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// controlBuffer starts with a buffer of the expected size, so most controls need
|
||||
// one RPC. ERROR_MORE_DATA still grows the buffer to the reported size; size 0
|
||||
// starts with a NULL size probe.
|
||||
func controlBuffer(deadline time.Time, size int, call func([]byte) (uint32, error)) ([]byte, error) {
|
||||
var buffer []byte
|
||||
if size > 0 {
|
||||
buffer = make([]byte, size)
|
||||
// newControlBuffer returns a buffer of the expected size, so most controls
|
||||
// need one RPC; size 0 starts with a NULL size probe.
|
||||
func newControlBuffer(size int) []byte {
|
||||
if size <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return make([]byte, size)
|
||||
}
|
||||
|
||||
// controlBufferIn calls a control starting with buffer; an empty buffer is a
|
||||
// NULL size probe. ERROR_MORE_DATA grows the buffer to the reported size,
|
||||
// within buffer's capacity when it fits. The result may share buffer's memory.
|
||||
func controlBufferIn(deadline time.Time, buffer []byte, call func([]byte) (uint32, error)) ([]byte, error) {
|
||||
for range 16 {
|
||||
if err := checkDeadline(deadline); err != nil {
|
||||
return nil, err
|
||||
@@ -675,7 +736,7 @@ func controlBuffer(deadline time.Time, size int, call func([]byte) (uint32, erro
|
||||
return nil, errors.New("invalid ClusterResourceControl buffer size")
|
||||
}
|
||||
|
||||
buffer = make([]byte, size)
|
||||
buffer = growBuffer(buffer, int(size))
|
||||
|
||||
continue
|
||||
}
|
||||
@@ -689,7 +750,7 @@ func controlBuffer(deadline time.Time, size int, call func([]byte) (uint32, erro
|
||||
return nil, errors.New("invalid ClusterResourceControl buffer size")
|
||||
}
|
||||
|
||||
buffer = make([]byte, size)
|
||||
buffer = growBuffer(buffer, int(size))
|
||||
|
||||
continue
|
||||
}
|
||||
@@ -703,3 +764,15 @@ func controlBuffer(deadline time.Time, size int, call func([]byte) (uint32, erro
|
||||
|
||||
return nil, errors.New("ClusterResourceControl buffer did not stabilize")
|
||||
}
|
||||
|
||||
// growBuffer returns size zeroed bytes, reusing buffer's capacity when it fits.
|
||||
func growBuffer(buffer []byte, size int) []byte {
|
||||
if size > cap(buffer) {
|
||||
return make([]byte, size)
|
||||
}
|
||||
|
||||
buffer = buffer[:size]
|
||||
clear(buffer)
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user