get.pme.sh/pnats@v0.0.0-20240304004023-26bb5a137ed0/server/pse/pse_windows.go (about)

     1  // Copyright 2015-2018 The NATS Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  //go:build windows
    15  // +build windows
    16  
    17  package pse
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"path/filepath"
    23  	"strings"
    24  	"sync"
    25  	"syscall"
    26  	"time"
    27  	"unsafe"
    28  )
    29  
    30  var (
    31  	pdh                            = syscall.NewLazyDLL("pdh.dll")
    32  	winPdhOpenQuery                = pdh.NewProc("PdhOpenQuery")
    33  	winPdhAddCounter               = pdh.NewProc("PdhAddCounterW")
    34  	winPdhCollectQueryData         = pdh.NewProc("PdhCollectQueryData")
    35  	winPdhGetFormattedCounterValue = pdh.NewProc("PdhGetFormattedCounterValue")
    36  	winPdhGetFormattedCounterArray = pdh.NewProc("PdhGetFormattedCounterArrayW")
    37  )
    38  
    39  // global performance counter query handle and counters
    40  var (
    41  	pcHandle                                       PDH_HQUERY
    42  	pidCounter, cpuCounter, rssCounter, vssCounter PDH_HCOUNTER
    43  	prevCPU                                        float64
    44  	prevRss                                        int64
    45  	prevVss                                        int64
    46  	lastSampleTime                                 time.Time
    47  	processPid                                     int
    48  	pcQueryLock                                    sync.Mutex
    49  	initialSample                                  = true
    50  )
    51  
    52  // maxQuerySize is the number of values to return from a query.
    53  // It represents the maximum # of servers that can be queried
    54  // simultaneously running on a machine.
    55  const maxQuerySize = 512
    56  
    57  // Keep static memory around to reuse; this works best for passing
    58  // into the pdh API.
    59  var counterResults [maxQuerySize]PDH_FMT_COUNTERVALUE_ITEM_DOUBLE
    60  
    61  // PDH Types
    62  type (
    63  	PDH_HQUERY   syscall.Handle
    64  	PDH_HCOUNTER syscall.Handle
    65  )
    66  
    67  // PDH constants used here
    68  const (
    69  	PDH_FMT_DOUBLE   = 0x00000200
    70  	PDH_INVALID_DATA = 0xC0000BC6
    71  	PDH_MORE_DATA    = 0x800007D2
    72  )
    73  
    74  // PDH_FMT_COUNTERVALUE_DOUBLE - double value
    75  type PDH_FMT_COUNTERVALUE_DOUBLE struct {
    76  	CStatus     uint32
    77  	DoubleValue float64
    78  }
    79  
    80  // PDH_FMT_COUNTERVALUE_ITEM_DOUBLE is an array
    81  // element of a double value
    82  type PDH_FMT_COUNTERVALUE_ITEM_DOUBLE struct {
    83  	SzName   *uint16 // pointer to a string
    84  	FmtValue PDH_FMT_COUNTERVALUE_DOUBLE
    85  }
    86  
    87  func pdhAddCounter(hQuery PDH_HQUERY, szFullCounterPath string, dwUserData uintptr, phCounter *PDH_HCOUNTER) error {
    88  	ptxt, _ := syscall.UTF16PtrFromString(szFullCounterPath)
    89  	r0, _, _ := winPdhAddCounter.Call(
    90  		uintptr(hQuery),
    91  		uintptr(unsafe.Pointer(ptxt)),
    92  		dwUserData,
    93  		uintptr(unsafe.Pointer(phCounter)))
    94  
    95  	if r0 != 0 {
    96  		return fmt.Errorf("pdhAddCounter failed. %d", r0)
    97  	}
    98  	return nil
    99  }
   100  
   101  func pdhOpenQuery(datasrc *uint16, userdata uint32, query *PDH_HQUERY) error {
   102  	r0, _, _ := syscall.Syscall(winPdhOpenQuery.Addr(), 3, 0, uintptr(userdata), uintptr(unsafe.Pointer(query)))
   103  	if r0 != 0 {
   104  		return fmt.Errorf("pdhOpenQuery failed - %d", r0)
   105  	}
   106  	return nil
   107  }
   108  
   109  func pdhCollectQueryData(hQuery PDH_HQUERY) error {
   110  	r0, _, _ := winPdhCollectQueryData.Call(uintptr(hQuery))
   111  	if r0 != 0 {
   112  		return fmt.Errorf("pdhCollectQueryData failed - %d", r0)
   113  	}
   114  	return nil
   115  }
   116  
   117  // pdhGetFormattedCounterArrayDouble returns the value of return code
   118  // rather than error, to easily check return codes
   119  func pdhGetFormattedCounterArrayDouble(hCounter PDH_HCOUNTER, lpdwBufferSize *uint32, lpdwBufferCount *uint32, itemBuffer *PDH_FMT_COUNTERVALUE_ITEM_DOUBLE) uint32 {
   120  	ret, _, _ := winPdhGetFormattedCounterArray.Call(
   121  		uintptr(hCounter),
   122  		uintptr(PDH_FMT_DOUBLE),
   123  		uintptr(unsafe.Pointer(lpdwBufferSize)),
   124  		uintptr(unsafe.Pointer(lpdwBufferCount)),
   125  		uintptr(unsafe.Pointer(itemBuffer)))
   126  
   127  	return uint32(ret)
   128  }
   129  
   130  func getCounterArrayData(counter PDH_HCOUNTER) ([]float64, error) {
   131  	var bufSize uint32
   132  	var bufCount uint32
   133  
   134  	// Retrieving array data requires two calls, the first which
   135  	// requires an addressable empty buffer, and sets size fields.
   136  	// The second call returns the data.
   137  	initialBuf := make([]PDH_FMT_COUNTERVALUE_ITEM_DOUBLE, 1)
   138  	ret := pdhGetFormattedCounterArrayDouble(counter, &bufSize, &bufCount, &initialBuf[0])
   139  	if ret == PDH_MORE_DATA {
   140  		// we'll likely never get here, but be safe.
   141  		if bufCount > maxQuerySize {
   142  			bufCount = maxQuerySize
   143  		}
   144  		ret = pdhGetFormattedCounterArrayDouble(counter, &bufSize, &bufCount, &counterResults[0])
   145  		if ret == 0 {
   146  			rv := make([]float64, bufCount)
   147  			for i := 0; i < int(bufCount); i++ {
   148  				rv[i] = counterResults[i].FmtValue.DoubleValue
   149  			}
   150  			return rv, nil
   151  		}
   152  	}
   153  	if ret != 0 {
   154  		return nil, fmt.Errorf("getCounterArrayData failed - %d", ret)
   155  	}
   156  
   157  	return nil, nil
   158  }
   159  
   160  // getProcessImageName returns the name of the process image, as expected by
   161  // the performance counter API.
   162  func getProcessImageName() (name string) {
   163  	name = filepath.Base(os.Args[0])
   164  	name = strings.TrimSuffix(name, ".exe")
   165  	return
   166  }
   167  
   168  // initialize our counters
   169  func initCounters() (err error) {
   170  
   171  	processPid = os.Getpid()
   172  	// require an addressible nil pointer
   173  	var source uint16
   174  	if err := pdhOpenQuery(&source, 0, &pcHandle); err != nil {
   175  		return err
   176  	}
   177  
   178  	// setup the performance counters, search for all server instances
   179  	name := fmt.Sprintf("%s*", getProcessImageName())
   180  	pidQuery := fmt.Sprintf("\\Process(%s)\\ID Process", name)
   181  	cpuQuery := fmt.Sprintf("\\Process(%s)\\%% Processor Time", name)
   182  	rssQuery := fmt.Sprintf("\\Process(%s)\\Working Set - Private", name)
   183  	vssQuery := fmt.Sprintf("\\Process(%s)\\Virtual Bytes", name)
   184  
   185  	if err = pdhAddCounter(pcHandle, pidQuery, 0, &pidCounter); err != nil {
   186  		return err
   187  	}
   188  	if err = pdhAddCounter(pcHandle, cpuQuery, 0, &cpuCounter); err != nil {
   189  		return err
   190  	}
   191  	if err = pdhAddCounter(pcHandle, rssQuery, 0, &rssCounter); err != nil {
   192  		return err
   193  	}
   194  	if err = pdhAddCounter(pcHandle, vssQuery, 0, &vssCounter); err != nil {
   195  		return err
   196  	}
   197  
   198  	// prime the counters by collecting once, and sleep to get somewhat
   199  	// useful information the first request.  Counters for the CPU require
   200  	// at least two collect calls.
   201  	if err = pdhCollectQueryData(pcHandle); err != nil {
   202  		return err
   203  	}
   204  	time.Sleep(50)
   205  
   206  	return nil
   207  }
   208  
   209  // ProcUsage returns process CPU and memory statistics
   210  func ProcUsage(pcpu *float64, rss, vss *int64) error {
   211  	var err error
   212  
   213  	// For simplicity, protect the entire call.
   214  	// Most simultaneous requests will immediately return
   215  	// with cached values.
   216  	pcQueryLock.Lock()
   217  	defer pcQueryLock.Unlock()
   218  
   219  	// First time through, initialize counters.
   220  	if initialSample {
   221  		if err = initCounters(); err != nil {
   222  			return err
   223  		}
   224  		initialSample = false
   225  	} else if time.Since(lastSampleTime) < (2 * time.Second) {
   226  		// only refresh every two seconds as to minimize impact
   227  		// on the server.
   228  		*pcpu = prevCPU
   229  		*rss = prevRss
   230  		*vss = prevVss
   231  		return nil
   232  	}
   233  
   234  	// always save the sample time, even on errors.
   235  	defer func() {
   236  		lastSampleTime = time.Now()
   237  	}()
   238  
   239  	// refresh the performance counter data
   240  	if err = pdhCollectQueryData(pcHandle); err != nil {
   241  		return err
   242  	}
   243  
   244  	// retrieve the data
   245  	var pidAry, cpuAry, rssAry, vssAry []float64
   246  	if pidAry, err = getCounterArrayData(pidCounter); err != nil {
   247  		return err
   248  	}
   249  	if cpuAry, err = getCounterArrayData(cpuCounter); err != nil {
   250  		return err
   251  	}
   252  	if rssAry, err = getCounterArrayData(rssCounter); err != nil {
   253  		return err
   254  	}
   255  	if vssAry, err = getCounterArrayData(vssCounter); err != nil {
   256  		return err
   257  	}
   258  	// find the index of the entry for this process
   259  	idx := int(-1)
   260  	for i := range pidAry {
   261  		if int(pidAry[i]) == processPid {
   262  			idx = i
   263  			break
   264  		}
   265  	}
   266  	// no pid found...
   267  	if idx < 0 {
   268  		return fmt.Errorf("could not find pid in performance counter results")
   269  	}
   270  	// assign values from the performance counters
   271  	*pcpu = cpuAry[idx]
   272  	*rss = int64(rssAry[idx])
   273  	*vss = int64(vssAry[idx])
   274  
   275  	// save off cache values
   276  	prevCPU = *pcpu
   277  	prevRss = *rss
   278  	prevVss = *vss
   279  
   280  	return nil
   281  }