mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-09-12 15:51:01 +00:00
Merge pull request #1859 from seebs/seebs/serverinfo
add server stats to /info endpoint
This commit is contained in:
commit
4955dff22f
4 changed files with 187 additions and 8 deletions
19
api.go
19
api.go
|
|
@ -1177,8 +1177,18 @@ func (api *API) Version() string {
|
|||
|
||||
// Info returns information about this server instance
|
||||
func (api *API) Info() serverInfo {
|
||||
si := api.server.systemInfo
|
||||
// we don't report errors on failures to get this information
|
||||
physicalCores, logicalCores, _ := si.CPUCores()
|
||||
mhz, _ := si.CPUMHz()
|
||||
mem, _ := si.MemTotal()
|
||||
return serverInfo{
|
||||
ShardWidth: ShardWidth,
|
||||
ShardWidth: ShardWidth,
|
||||
CPUPhysicalCores: physicalCores,
|
||||
CPULogicalCores: logicalCores,
|
||||
CPUMHz: mhz,
|
||||
CPUType: si.CPUModel(),
|
||||
Memory: mem,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1213,7 +1223,12 @@ func (api *API) TranslateKeys(body io.Reader) ([]byte, error) {
|
|||
}
|
||||
|
||||
type serverInfo struct {
|
||||
ShardWidth uint64 `json:"shardWidth"`
|
||||
ShardWidth uint64 `json:"shardWidth"`
|
||||
Memory uint64 `json:"memory"`
|
||||
CPUType string `json:"cpuType"`
|
||||
CPUPhysicalCores int `json:"cpuPhysicalCores"`
|
||||
CPULogicalCores int `json:"cpuLogicalCores"`
|
||||
CPUMHz int `json:"cpuMHz"`
|
||||
}
|
||||
|
||||
type apiMethod int
|
||||
|
|
|
|||
|
|
@ -271,6 +271,9 @@ type SystemInfo interface {
|
|||
MemFree() (uint64, error)
|
||||
MemTotal() (uint64, error)
|
||||
MemUsed() (uint64, error)
|
||||
CPUModel() string
|
||||
CPUCores() (physical int, logical int, err error)
|
||||
CPUMHz() (int, error)
|
||||
CPUArch() string
|
||||
}
|
||||
|
||||
|
|
@ -327,3 +330,18 @@ func (n *nopSystemInfo) MemUsed() (uint64, error) {
|
|||
func (n *nopSystemInfo) CPUArch() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// CPUModel returns the CPU model string
|
||||
func (n *nopSystemInfo) CPUModel() string {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// CPUMHz returns the CPU clock speed
|
||||
func (n *nopSystemInfo) CPUMHz() (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// CPUCores returns the number of CPU cores (physical or logical)
|
||||
func (n *nopSystemInfo) CPUCores() (physical, logical int, err error) {
|
||||
return 0, 0, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,10 @@ package gopsutil
|
|||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/pilosa/pilosa"
|
||||
"github.com/shirou/gopsutil/cpu"
|
||||
"github.com/shirou/gopsutil/host"
|
||||
"github.com/shirou/gopsutil/mem"
|
||||
)
|
||||
|
|
@ -26,9 +28,13 @@ var _ pilosa.SystemInfo = NewSystemInfo()
|
|||
|
||||
// systemInfo is an implementation of pilosa.systemInfo that uses gopsutil to collect information about the host OS.
|
||||
type systemInfo struct {
|
||||
platform string
|
||||
family string
|
||||
osVersion string
|
||||
platform string
|
||||
family string
|
||||
osVersion string
|
||||
cpuModel string
|
||||
cpuPhysicalCores int
|
||||
cpuLogicalCores int
|
||||
cpuMHz int
|
||||
}
|
||||
|
||||
// Uptime returns the system uptime in seconds.
|
||||
|
|
@ -40,6 +46,55 @@ func (s *systemInfo) Uptime() (uptime uint64, err error) {
|
|||
return hostInfo.Uptime, nil
|
||||
}
|
||||
|
||||
// cpuFrequencyMultipliers is a lookup table for prefixes on clock speeds.
|
||||
// This is probably overkill.
|
||||
var cpuFrequencyMultipliers = [256]int{
|
||||
'M': 1,
|
||||
'G': 1000,
|
||||
'T': 1000 * 1000,
|
||||
}
|
||||
|
||||
// computeHz determines the official rated speed of a CPU from its brand
|
||||
// string. This insanity is *actually the official documented way to do
|
||||
// this according to Intel*. There is also a cpuid leaf for the frequency,
|
||||
// but I am not sure how supported it is, so.
|
||||
func computeMHz(brandString string) int {
|
||||
hz := strings.LastIndex(brandString, "Hz")
|
||||
// ' 1MHz'
|
||||
if hz < 3 {
|
||||
return -1
|
||||
}
|
||||
multiplier := cpuFrequencyMultipliers[brandString[hz-1]]
|
||||
if multiplier == 0 {
|
||||
return -1
|
||||
}
|
||||
freq := 0
|
||||
divisor := 0
|
||||
decimalShift := 1
|
||||
var i int
|
||||
for i = hz - 2; i >= 0 && brandString[i] != ' '; i-- {
|
||||
if brandString[i] >= '0' && brandString[i] <= '9' {
|
||||
freq += int(brandString[i]-'0') * decimalShift
|
||||
decimalShift *= 10
|
||||
} else if brandString[i] == '.' {
|
||||
if divisor != 0 {
|
||||
return -1
|
||||
}
|
||||
divisor = decimalShift
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
// we didn't find a space
|
||||
if i < 0 {
|
||||
return -1
|
||||
}
|
||||
if divisor != 0 {
|
||||
return (freq * multiplier) / divisor
|
||||
}
|
||||
return freq * multiplier
|
||||
}
|
||||
|
||||
// collectPlatformInfo fetches and caches system platform information.
|
||||
func (s *systemInfo) collectPlatformInfo() error {
|
||||
var err error
|
||||
|
|
@ -49,6 +104,45 @@ func (s *systemInfo) collectPlatformInfo() error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
if s.cpuModel == "" {
|
||||
infos, err := cpu.Info()
|
||||
if err != nil || len(infos) == 0 {
|
||||
s.cpuModel = "unknown"
|
||||
// if err is nil, but we got no infos, we don't
|
||||
// have a meaningful error to return.
|
||||
return err
|
||||
}
|
||||
s.cpuModel = infos[0].ModelName
|
||||
s.cpuMHz = computeMHz(s.cpuModel)
|
||||
|
||||
// gopsutil reports core and clock speed info inconsistently
|
||||
// by OS
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
// Each reported "CPU" is a logical core. Some cores may
|
||||
// have the same Core ID, which is a strictly numeric
|
||||
// value which gopsutil returned as a string, which
|
||||
// indicates that they're hyperthreading or similar things
|
||||
// on the same physical core.
|
||||
uniqueCores := make(map[string]struct{}, len(infos))
|
||||
totalCores := 0
|
||||
for _, info := range infos {
|
||||
uniqueCores[info.CoreID] = struct{}{}
|
||||
totalCores += int(info.Cores)
|
||||
}
|
||||
s.cpuPhysicalCores = len(uniqueCores)
|
||||
s.cpuLogicalCores = totalCores
|
||||
case "darwin":
|
||||
fallthrough
|
||||
default: // let's hope other systems give useful core info?
|
||||
s.cpuPhysicalCores = int(infos[0].Cores)
|
||||
// we have no way to know, let's try runtime
|
||||
s.cpuLogicalCores = runtime.NumCPU()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -116,6 +210,33 @@ func (s *systemInfo) CPUArch() string {
|
|||
return runtime.GOARCH
|
||||
}
|
||||
|
||||
// CPUModel returns the CPU model string
|
||||
func (s *systemInfo) CPUModel() string {
|
||||
err := s.collectPlatformInfo()
|
||||
if err != nil {
|
||||
return "unknown"
|
||||
}
|
||||
return s.cpuModel
|
||||
}
|
||||
|
||||
// CPUMhz returns the CPU clock speed
|
||||
func (s *systemInfo) CPUMHz() (int, error) {
|
||||
err := s.collectPlatformInfo()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return s.cpuMHz, nil
|
||||
}
|
||||
|
||||
// CPUCores returns the number of (physical or logical) CPU cores
|
||||
func (s *systemInfo) CPUCores() (physical, logical int, err error) {
|
||||
err = s.collectPlatformInfo()
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return s.cpuPhysicalCores, s.cpuLogicalCores, nil
|
||||
}
|
||||
|
||||
// NewSystemInfo is a constructor for the gopsutil implementation of SystemInfo.
|
||||
func NewSystemInfo() *systemInfo {
|
||||
return &systemInfo{}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import (
|
|||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
gohttp "net/http"
|
||||
|
|
@ -56,8 +55,34 @@ func TestHandler_Endpoints(t *testing.T) {
|
|||
h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/info", nil))
|
||||
if w.Code != gohttp.StatusOK {
|
||||
t.Fatalf("unexpected status code: %d", w.Code)
|
||||
} else if body := w.Body.String(); body != fmt.Sprintf("{\"shardWidth\":%d}\n", pilosa.ShardWidth) {
|
||||
t.Fatalf("unexpected body: %s", body)
|
||||
}
|
||||
var details map[string]interface{}
|
||||
body := w.Body.Bytes()
|
||||
err := json.Unmarshal(body, &details)
|
||||
if err != nil {
|
||||
t.Fatalf("error unmarshalling json body [%s]: %v", body, err)
|
||||
}
|
||||
sw := details["shardWidth"]
|
||||
if sw == nil {
|
||||
t.Fatalf("no shardWidth in json body [%s]", body)
|
||||
}
|
||||
var n float64
|
||||
var ok bool
|
||||
if n, ok = sw.(float64); !ok {
|
||||
t.Fatalf("shardWidth not float64 (%T) in json body [%s]", sw, body)
|
||||
}
|
||||
if uint64(n) != pilosa.ShardWidth {
|
||||
t.Fatalf("incorrect shard width: got %d, expected %d", uint64(n), pilosa.ShardWidth)
|
||||
}
|
||||
count := details["cpuPhysicalCores"]
|
||||
if count == nil {
|
||||
t.Fatalf("no cpuPhysicalCores in json body [%s]", body)
|
||||
}
|
||||
if n, ok = count.(float64); !ok {
|
||||
t.Fatalf("cpuPhysicalCores not float64 (%T) in json body [%s]", count, body)
|
||||
}
|
||||
if int(n) == 0 {
|
||||
t.Fatal("cpu count should not be 0")
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue