Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
/*
* Compile and run this as a C program to get the kinfo_proc offsets
* for your architecture.
* While FreeBSD works hard at binary-compatibility within an ABI, various
* we can't say for sure that these are right for _all_ use on a hardware
* platform. The LP64 ifdef affects the offsets considerably.
*
* We use these offsets in hardware-specific files for FreeBSD, to avoid a cgo
* compilation-time dependency, allowing us to cross-compile for FreeBSD from
* other hardware platforms, letting us distribute binaries for FreeBSD.
*/
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/user.h>
#define SHOW_OFFSET(FIELD) printf(" KIP_OFF_%s = %zu\n", #FIELD, offsetof(struct kinfo_proc, ki_ ## FIELD))
int main(int argc, char *argv[]) {
/* Uncomment these if you want some extra debugging aids:
SHOW_OFFSET(pid);
SHOW_OFFSET(ppid);
SHOW_OFFSET(uid);
*/
SHOW_OFFSET(size);
SHOW_OFFSET(rssize);
SHOW_OFFSET(pctcpu);
}
@@ -0,0 +1,86 @@
// Copyright 2015-2021 The NATS 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.
package pse
// On macs after some studying it seems that typical tools like ps and activity monitor report MaxRss and not
// current RSS. I wrote some C code to pull the real RSS and although it does not go down very often, when it does
// that is not reflected in the typical tooling one might compare us to, so we can skip cgo and just use rusage imo.
// We also do not use virtual memory in the upper layers at all, so ok to skip since rusage does not report vss.
import (
"math"
"sync"
"syscall"
"time"
)
type lastUsage struct {
sync.Mutex
last time.Time
cpu time.Duration
rss int64
pcpu float64
}
// To hold the last usage and call time.
var lu lastUsage
func init() {
updateUsage()
periodic()
}
// Get our usage.
func getUsage() (now time.Time, cpu time.Duration, rss int64) {
var ru syscall.Rusage
syscall.Getrusage(syscall.RUSAGE_SELF, &ru)
now = time.Now()
cpu = time.Duration(ru.Utime.Sec)*time.Second + time.Duration(ru.Utime.Usec)*time.Microsecond
cpu += time.Duration(ru.Stime.Sec)*time.Second + time.Duration(ru.Stime.Usec)*time.Microsecond
return now, cpu, ru.Maxrss
}
// Update last usage.
// We need to have a prior sample to compute pcpu.
func updateUsage() (pcpu float64, rss int64) {
lu.Lock()
defer lu.Unlock()
now, cpu, rss := getUsage()
// Don't skew pcpu by sampling too close to last sample.
if elapsed := now.Sub(lu.last); elapsed < 500*time.Millisecond {
// Always update rss.
lu.rss = rss
} else {
tcpu := float64(cpu - lu.cpu)
lu.last, lu.cpu, lu.rss = now, cpu, rss
// Want to make this one decimal place and not count on upper layers.
// Cores already taken into account via cpu time measurements.
lu.pcpu = math.Round(tcpu/float64(elapsed)*1000) / 10
}
return lu.pcpu, lu.rss
}
// Sampling function to keep pcpu relevant.
func periodic() {
updateUsage()
time.AfterFunc(time.Second, periodic)
}
// ProcUsage returns CPU and memory usage.
// Note upper layers do not use virtual memory size, so ok that it is not filled in here.
func ProcUsage(pcpu *float64, rss, vss *int64) error {
*pcpu, *rss = updateUsage()
return nil
}
@@ -0,0 +1,36 @@
// Copyright 2015-2023 The NATS 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.
//
// Copied from pse_openbsd.go
package pse
import (
"fmt"
"os"
"os/exec"
)
// ProcUsage returns CPU usage
func ProcUsage(pcpu *float64, rss, vss *int64) error {
pidStr := fmt.Sprintf("%d", os.Getpid())
out, err := exec.Command("ps", "o", "pcpu=,rss=,vsz=", "-p", pidStr).Output()
if err != nil {
*rss, *vss = -1, -1
return fmt.Errorf("ps call failed:%v", err)
}
fmt.Sscanf(string(out), "%f %d %d", pcpu, rss, vss)
*rss *= 1024 // 1k blocks, want bytes.
*vss *= 1024 // 1k blocks, want bytes.
return nil
}
@@ -0,0 +1,85 @@
// Copyright 2015-2025 The NATS 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 cgo && freebsd
package pse
/*
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/user.h>
#include <stddef.h>
#include <unistd.h>
long pagetok(long size)
{
int pageshift, pagesize;
pagesize = getpagesize();
pageshift = 0;
while (pagesize > 1) {
pageshift++;
pagesize >>= 1;
}
return (size << pageshift);
}
int getusage(double *pcpu, unsigned int *rss, unsigned int *vss)
{
int mib[4], ret;
size_t len;
struct kinfo_proc kp;
len = 4;
sysctlnametomib("kern.proc.pid", mib, &len);
mib[3] = getpid();
len = sizeof(kp);
ret = sysctl(mib, 4, &kp, &len, NULL, 0);
if (ret != 0) {
return (errno);
}
*rss = pagetok(kp.ki_rssize);
*vss = kp.ki_size;
*pcpu = (double)kp.ki_pctcpu / FSCALE;
return 0;
}
*/
import "C"
import (
"syscall"
)
// This is a placeholder for now.
func ProcUsage(pcpu *float64, rss, vss *int64) error {
var r, v C.uint
var c C.double
if ret := C.getusage(&c, &r, &v); ret != 0 {
return syscall.Errno(ret)
}
*pcpu = float64(c)
*rss = int64(r)
*vss = int64(v)
return nil
}
@@ -0,0 +1,125 @@
// Copyright 2015-2025 The NATS 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.
// There are two FreeBSD implementations; one which uses cgo and should build
// locally on any FreeBSD, and this one which uses sysctl but needs us to know
// the offset constants for the fields we care about.
//
// The advantage of this one is that without cgo, it is much easier to
// cross-compile to a target. The official releases are all built with
// cross-compilation.
//
// We've switched the other implementation to include '_cgo' in the filename,
// to show that it's not the default. This isn't an os or arch build tag,
// so we have to use explicit build-tags within.
// If lacking CGO support and targeting an unsupported arch, then before the
// change you would have a compile failure for not being able to cross-compile.
// After the change, you have a compile failure for not having the symbols
// because no source file satisfies them.
// Thus we are no worse off, and it's now much easier to extend support for
// non-CGO to new architectures, just by editing this file.
//
// To generate for other architectures:
// 1. Copy `freebsd.txt` to have a .c filename on a box running the target
// architecture, compile and run it.
// 2. Update the init() function below to include a case for this architecture
// 3. Update the build-tags in this file.
//go:build !cgo && freebsd && (amd64 || arm64)
package pse
import (
"encoding/binary"
"runtime"
"syscall"
"golang.org/x/sys/unix"
)
// On FreeBSD, to get proc information we read it out of the kernel using a
// binary sysctl. The endianness of integers is thus explicitly "host", rather
// than little or big endian.
var nativeEndian = binary.LittleEndian
var pageshift int // derived from getpagesize(3) in init() below
var (
// These are populated in the init function, based on the current architecture.
// (It's less file-count explosion than having one small file for each
// FreeBSD architecture).
KIP_OFF_size int
KIP_OFF_rssize int
KIP_OFF_pctcpu int
)
func init() {
switch runtime.GOARCH {
// These are the values which come from compiling and running
// freebsd.txt as a C program.
// Most recently validated: 2025-04 with FreeBSD 14.2R in AWS.
case "amd64":
KIP_OFF_size = 256
KIP_OFF_rssize = 264
KIP_OFF_pctcpu = 308
case "arm64":
KIP_OFF_size = 256
KIP_OFF_rssize = 264
KIP_OFF_pctcpu = 308
default:
panic("code bug: server/pse FreeBSD support missing case for '" + runtime.GOARCH + "' but build-tags allowed us to build anyway?")
}
// To get the physical page size, the C library checks two places:
// process ELF auxiliary info, AT_PAGESZ
// as a fallback, the hw.pagesize sysctl
// In looking closely, I found that the Go runtime support is handling
// this for us, and exposing that as syscall.Getpagesize, having checked
// both in the same ways, at process start, so a call to that should return
// a memory value without even a syscall bounce.
pagesize := syscall.Getpagesize()
pageshift = 0
for pagesize > 1 {
pageshift += 1
pagesize >>= 1
}
}
func ProcUsage(pcpu *float64, rss, vss *int64) error {
rawdata, err := unix.SysctlRaw("kern.proc.pid", unix.Getpid())
if err != nil {
return err
}
r_vss_bytes := nativeEndian.Uint32(rawdata[KIP_OFF_size:])
r_rss_pages := nativeEndian.Uint32(rawdata[KIP_OFF_rssize:])
rss_bytes := r_rss_pages << pageshift
// In C: fixpt_t ki_pctcpu
// Doc: %cpu for process during ki_swtime
// fixpt_t is __uint32_t
// usr.bin/top uses pctdouble to convert to a double (float64)
// define pctdouble(p) ((double)(p) / FIXED_PCTCPU)
// FIXED_PCTCPU is _usually_ FSCALE (some architectures are special)
// <sys/param.h> has:
// #define FSHIFT 11 /* bits to right of fixed binary point */
// #define FSCALE (1<<FSHIFT)
r_pcpu := nativeEndian.Uint32(rawdata[KIP_OFF_pctcpu:])
f_pcpu := float64(r_pcpu) / float64(2048)
*rss = int64(rss_bytes)
*vss = int64(r_vss_bytes)
*pcpu = f_pcpu
return nil
}
+128
View File
@@ -0,0 +1,128 @@
// Copyright 2015-2025 The NATS 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.
package pse
import (
"bytes"
"fmt"
"os"
"sync/atomic"
"syscall"
"time"
)
var (
procStatFile string
ticks int64
lastTotal int64
lastSeconds int64
ipcpu int64
pageSize int64
)
const (
utimePos = 13
stimePos = 14
startPos = 21
vssPos = 22
rssPos = 23
)
func init() {
// Avoiding to generate docker image without CGO
ticks = 100 // int64(C.sysconf(C._SC_CLK_TCK))
procStatFile = fmt.Sprintf("/proc/%d/stat", os.Getpid())
pageSize = int64(os.Getpagesize())
periodic()
}
// Sampling function to keep pcpu relevant.
func periodic() {
contents, err := os.ReadFile(procStatFile)
if err != nil {
return
}
fields := bytes.Fields(contents)
// PCPU
pstart := parseInt64(fields[startPos])
utime := parseInt64(fields[utimePos])
stime := parseInt64(fields[stimePos])
total := utime + stime
var sysinfo syscall.Sysinfo_t
if err := syscall.Sysinfo(&sysinfo); err != nil {
return
}
seconds := int64(sysinfo.Uptime) - (pstart / ticks)
// Save off temps
lt := lastTotal
ls := lastSeconds
// Update last sample
lastTotal = total
lastSeconds = seconds
// Adjust to current time window
total -= lt
seconds -= ls
if seconds > 0 {
atomic.StoreInt64(&ipcpu, (total*1000/ticks)/seconds)
}
time.AfterFunc(1*time.Second, periodic)
}
// ProcUsage returns CPU usage
func ProcUsage(pcpu *float64, rss, vss *int64) error {
contents, err := os.ReadFile(procStatFile)
if err != nil {
return err
}
fields := bytes.Fields(contents)
// Memory
*rss = (parseInt64(fields[rssPos])) * pageSize
*vss = parseInt64(fields[vssPos])
// PCPU
// We track this with periodic sampling, so just load and go.
*pcpu = float64(atomic.LoadInt64(&ipcpu)) / 10.0
return nil
}
// Ascii numbers 0-9
const (
asciiZero = 48
asciiNine = 57
)
// parseInt64 expects decimal positive numbers. We
// return -1 to signal error
func parseInt64(d []byte) (n int64) {
if len(d) == 0 {
return -1
}
for _, dec := range d {
if dec < asciiZero || dec > asciiNine {
return -1
}
n = n*10 + (int64(dec) - asciiZero)
}
return n
}
@@ -0,0 +1,36 @@
// Copyright 2022 The NATS 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.
//
// Copied from pse_openbsd.go
package pse
import (
"fmt"
"os"
"os/exec"
)
// ProcUsage returns CPU usage
func ProcUsage(pcpu *float64, rss, vss *int64) error {
pidStr := fmt.Sprintf("%d", os.Getpid())
out, err := exec.Command("ps", "o", "pcpu=,rss=,vsz=", "-p", pidStr).Output()
if err != nil {
*rss, *vss = -1, -1
return fmt.Errorf("ps call failed:%v", err)
}
fmt.Sscanf(string(out), "%f %d %d", pcpu, rss, vss)
*rss *= 1024 // 1k blocks, want bytes.
*vss *= 1024 // 1k blocks, want bytes.
return nil
}
@@ -0,0 +1,36 @@
// Copyright 2015-2018 The NATS 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.
//
// Copied from pse_darwin.go
package pse
import (
"fmt"
"os"
"os/exec"
)
// ProcUsage returns CPU usage
func ProcUsage(pcpu *float64, rss, vss *int64) error {
pidStr := fmt.Sprintf("%d", os.Getpid())
out, err := exec.Command("ps", "o", "pcpu=,rss=,vsz=", "-p", pidStr).Output()
if err != nil {
*rss, *vss = -1, -1
return fmt.Errorf("ps call failed:%v", err)
}
fmt.Sscanf(string(out), "%f %d %d", pcpu, rss, vss)
*rss *= 1024 // 1k blocks, want bytes.
*vss *= 1024 // 1k blocks, want bytes.
return nil
}
@@ -0,0 +1,25 @@
// Copyright 2015-2025 The NATS 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 rumprun
package pse
// This is a placeholder for now.
func ProcUsage(pcpu *float64, rss, vss *int64) error {
*pcpu = 0.0
*rss = 0
*vss = 0
return nil
}
@@ -0,0 +1,45 @@
// Copyright 2015-2025 The NATS 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.
//
// Copied from pse_openbsd.go
//go:build illumos || solaris
package pse
import (
"fmt"
"os"
"os/exec"
"strings"
)
// ProcUsage returns CPU usage
func ProcUsage(pcpu *float64, rss, vss *int64) error {
pidStr := fmt.Sprintf("%d", os.Getpid())
out, err := exec.Command("ps", "-o", "pcpu,rss,vsz", "-p", pidStr).Output()
if err != nil {
*rss, *vss = -1, -1
return fmt.Errorf("ps call failed:%v", err)
}
lines := strings.Split(string(out), "\n")
if len(lines) < 2 {
*rss, *vss = -1, -1
return fmt.Errorf("no ps output")
}
output := lines[1]
fmt.Sscanf(output, "%f %d %d", pcpu, rss, vss)
*rss *= 1024 // 1k blocks, want bytes.
*vss *= 1024 // 1k blocks, want bytes.
return nil
}
+25
View File
@@ -0,0 +1,25 @@
// Copyright 2022-2025 The NATS 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 wasm
package pse
// This is a placeholder for now.
func ProcUsage(pcpu *float64, rss, vss *int64) error {
*pcpu = 0.0
*rss = 0
*vss = 0
return nil
}
@@ -0,0 +1,296 @@
// Copyright 2015-2025 The NATS 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 pse
import (
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"syscall"
"time"
"unsafe"
"golang.org/x/sys/windows"
)
var (
pdh = windows.NewLazySystemDLL("pdh.dll")
winPdhOpenQuery = pdh.NewProc("PdhOpenQuery")
winPdhAddCounter = pdh.NewProc("PdhAddCounterW")
winPdhCollectQueryData = pdh.NewProc("PdhCollectQueryData")
winPdhGetFormattedCounterValue = pdh.NewProc("PdhGetFormattedCounterValue")
winPdhGetFormattedCounterArray = pdh.NewProc("PdhGetFormattedCounterArrayW")
)
func init() {
if err := pdh.Load(); err != nil {
panic(err)
}
for _, p := range []*windows.LazyProc{
winPdhOpenQuery, winPdhAddCounter, winPdhCollectQueryData,
winPdhGetFormattedCounterValue, winPdhGetFormattedCounterArray,
} {
if err := p.Find(); err != nil {
panic(err)
}
}
}
// global performance counter query handle and counters
var (
pcHandle PDH_HQUERY
pidCounter, cpuCounter, rssCounter, vssCounter PDH_HCOUNTER
prevCPU float64
prevRss int64
prevVss int64
lastSampleTime time.Time
processPid int
pcQueryLock sync.Mutex
initialSample = true
)
// maxQuerySize is the number of values to return from a query.
// It represents the maximum # of servers that can be queried
// simultaneously running on a machine.
const maxQuerySize = 512
// Keep static memory around to reuse; this works best for passing
// into the pdh API.
var counterResults [maxQuerySize]PDH_FMT_COUNTERVALUE_ITEM_DOUBLE
// PDH Types
type (
PDH_HQUERY syscall.Handle
PDH_HCOUNTER syscall.Handle
)
// PDH constants used here
const (
PDH_FMT_DOUBLE = 0x00000200
PDH_INVALID_DATA = 0xC0000BC6
PDH_MORE_DATA = 0x800007D2
)
// PDH_FMT_COUNTERVALUE_DOUBLE - double value
type PDH_FMT_COUNTERVALUE_DOUBLE struct {
CStatus uint32
DoubleValue float64
}
// PDH_FMT_COUNTERVALUE_ITEM_DOUBLE is an array
// element of a double value
type PDH_FMT_COUNTERVALUE_ITEM_DOUBLE struct {
SzName *uint16 // pointer to a string
FmtValue PDH_FMT_COUNTERVALUE_DOUBLE
}
func pdhAddCounter(hQuery PDH_HQUERY, szFullCounterPath string, dwUserData uintptr, phCounter *PDH_HCOUNTER) error {
ptxt, _ := syscall.UTF16PtrFromString(szFullCounterPath)
r0, _, _ := winPdhAddCounter.Call(
uintptr(hQuery),
uintptr(unsafe.Pointer(ptxt)),
dwUserData,
uintptr(unsafe.Pointer(phCounter)))
if r0 != 0 {
return fmt.Errorf("pdhAddCounter failed. %d", r0)
}
return nil
}
func pdhOpenQuery(datasrc *uint16, userdata uint32, query *PDH_HQUERY) error {
r0, _, _ := syscall.Syscall(winPdhOpenQuery.Addr(), 3, 0, uintptr(userdata), uintptr(unsafe.Pointer(query)))
if r0 != 0 {
return fmt.Errorf("pdhOpenQuery failed - %d", r0)
}
return nil
}
func pdhCollectQueryData(hQuery PDH_HQUERY) error {
r0, _, _ := winPdhCollectQueryData.Call(uintptr(hQuery))
if r0 != 0 {
return fmt.Errorf("pdhCollectQueryData failed - %d", r0)
}
return nil
}
// pdhGetFormattedCounterArrayDouble returns the value of return code
// rather than error, to easily check return codes
func pdhGetFormattedCounterArrayDouble(hCounter PDH_HCOUNTER, lpdwBufferSize *uint32, lpdwBufferCount *uint32, itemBuffer *PDH_FMT_COUNTERVALUE_ITEM_DOUBLE) uint32 {
ret, _, _ := winPdhGetFormattedCounterArray.Call(
uintptr(hCounter),
uintptr(PDH_FMT_DOUBLE),
uintptr(unsafe.Pointer(lpdwBufferSize)),
uintptr(unsafe.Pointer(lpdwBufferCount)),
uintptr(unsafe.Pointer(itemBuffer)))
return uint32(ret)
}
func getCounterArrayData(counter PDH_HCOUNTER) ([]float64, error) {
var bufSize uint32
var bufCount uint32
// Retrieving array data requires two calls, the first which
// requires an addressable empty buffer, and sets size fields.
// The second call returns the data.
initialBuf := make([]PDH_FMT_COUNTERVALUE_ITEM_DOUBLE, 1)
ret := pdhGetFormattedCounterArrayDouble(counter, &bufSize, &bufCount, &initialBuf[0])
if ret == PDH_MORE_DATA {
// we'll likely never get here, but be safe.
if bufCount > maxQuerySize {
bufCount = maxQuerySize
}
ret = pdhGetFormattedCounterArrayDouble(counter, &bufSize, &bufCount, &counterResults[0])
if ret == 0 {
rv := make([]float64, bufCount)
for i := 0; i < int(bufCount); i++ {
rv[i] = counterResults[i].FmtValue.DoubleValue
}
return rv, nil
}
}
if ret != 0 {
return nil, fmt.Errorf("getCounterArrayData failed - %d", ret)
}
return nil, nil
}
// getProcessImageName returns the name of the process image, as expected by
// the performance counter API.
func getProcessImageName() (name string) {
name = filepath.Base(os.Args[0])
name = strings.TrimSuffix(name, ".exe")
return
}
// initialize our counters
func initCounters() (err error) {
processPid = os.Getpid()
// require an addressible nil pointer
var source uint16
if err := pdhOpenQuery(&source, 0, &pcHandle); err != nil {
return err
}
// setup the performance counters, search for all server instances
name := fmt.Sprintf("%s*", getProcessImageName())
pidQuery := fmt.Sprintf("\\Process(%s)\\ID Process", name)
cpuQuery := fmt.Sprintf("\\Process(%s)\\%% Processor Time", name)
rssQuery := fmt.Sprintf("\\Process(%s)\\Working Set - Private", name)
vssQuery := fmt.Sprintf("\\Process(%s)\\Virtual Bytes", name)
if err = pdhAddCounter(pcHandle, pidQuery, 0, &pidCounter); err != nil {
return err
}
if err = pdhAddCounter(pcHandle, cpuQuery, 0, &cpuCounter); err != nil {
return err
}
if err = pdhAddCounter(pcHandle, rssQuery, 0, &rssCounter); err != nil {
return err
}
if err = pdhAddCounter(pcHandle, vssQuery, 0, &vssCounter); err != nil {
return err
}
// prime the counters by collecting once, and sleep to get somewhat
// useful information the first request. Counters for the CPU require
// at least two collect calls.
if err = pdhCollectQueryData(pcHandle); err != nil {
return err
}
time.Sleep(50)
return nil
}
// ProcUsage returns process CPU and memory statistics
func ProcUsage(pcpu *float64, rss, vss *int64) error {
var err error
// For simplicity, protect the entire call.
// Most simultaneous requests will immediately return
// with cached values.
pcQueryLock.Lock()
defer pcQueryLock.Unlock()
// First time through, initialize counters.
if initialSample {
if err = initCounters(); err != nil {
return err
}
initialSample = false
} else if time.Since(lastSampleTime) < (2 * time.Second) {
// only refresh every two seconds as to minimize impact
// on the server.
*pcpu = prevCPU
*rss = prevRss
*vss = prevVss
return nil
}
// always save the sample time, even on errors.
defer func() {
lastSampleTime = time.Now()
}()
// refresh the performance counter data
if err = pdhCollectQueryData(pcHandle); err != nil {
return err
}
// retrieve the data
var pidAry, cpuAry, rssAry, vssAry []float64
if pidAry, err = getCounterArrayData(pidCounter); err != nil {
return err
}
if cpuAry, err = getCounterArrayData(cpuCounter); err != nil {
return err
}
if rssAry, err = getCounterArrayData(rssCounter); err != nil {
return err
}
if vssAry, err = getCounterArrayData(vssCounter); err != nil {
return err
}
// find the index of the entry for this process
idx := int(-1)
for i := range pidAry {
if int(pidAry[i]) == processPid {
idx = i
break
}
}
// no pid found...
if idx < 0 {
return fmt.Errorf("could not find pid in performance counter results")
}
// assign values from the performance counters
*pcpu = cpuAry[idx]
*rss = int64(rssAry[idx])
*vss = int64(vssAry[idx])
// save off cache values
prevCPU = *pcpu
prevRss = *rss
prevVss = *vss
return nil
}
+25
View File
@@ -0,0 +1,25 @@
// Copyright 2023-2025 The NATS 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 zos
package pse
// This is a placeholder for now.
func ProcUsage(pcpu *float64, rss, vss *int64) error {
*pcpu = 0.0
*rss = 0
*vss = 0
return nil
}