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 }