Initial QSfera import
This commit is contained in:
+30
@@ -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);
|
||||
}
|
||||
+86
@@ -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
|
||||
}
|
||||
+36
@@ -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
|
||||
}
|
||||
+85
@@ -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
|
||||
}
|
||||
+125
@@ -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
@@ -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
|
||||
}
|
||||
+36
@@ -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
|
||||
}
|
||||
+36
@@ -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
|
||||
}
|
||||
+25
@@ -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
|
||||
}
|
||||
+45
@@ -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
@@ -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
|
||||
}
|
||||
+296
@@ -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
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user