featurebase/gopsutil/systeminfo.go
Seebs 5ee87bbf7c add server stats to /info endpoint
report the approximate hardware specs (CPU speed, cores, memory)
of the server in the /info endpoint. This may be useful when
benchmarking.

We do some workarounds because gopsutil's core count output is
confusingly different between Linux and Darwin, and the MHz output
is usually wrong on Linux. Intel's app notes say to just parse
the model string. Whyyyyyyy.
2019-03-26 09:14:51 -05:00

243 lines
6.1 KiB
Go

// Copyright 2017 Pilosa Corp.
//
// 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 gopsutil
import (
"runtime"
"strings"
"github.com/pilosa/pilosa"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/host"
"github.com/shirou/gopsutil/mem"
)
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
cpuModel string
cpuPhysicalCores int
cpuLogicalCores int
cpuMHz int
}
// Uptime returns the system uptime in seconds.
func (s *systemInfo) Uptime() (uptime uint64, err error) {
hostInfo, err := host.Info()
if err != nil {
return 0, err
}
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
if s.platform == "" {
s.platform, s.family, s.osVersion, err = host.PlatformInformation()
if err != nil {
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
}
// Platform returns the system platform.
func (s *systemInfo) Platform() (string, error) {
err := s.collectPlatformInfo()
if err != nil {
return "", err
}
return s.platform, nil
}
// Family returns the system family.
func (s *systemInfo) Family() (string, error) {
err := s.collectPlatformInfo()
if err != nil {
return "", err
}
return s.family, err
}
// OSVersion returns the OS Version.
func (s *systemInfo) OSVersion() (string, error) {
err := s.collectPlatformInfo()
if err != nil {
return "", err
}
return s.osVersion, err
}
// MemFree returns the amount of free memory in bytes.
func (s *systemInfo) MemFree() (uint64, error) {
memInfo, err := mem.VirtualMemory()
if err != nil {
return 0, err
}
return memInfo.Free, err
}
// MemTotal returns the amount of total memory in bytes.
func (s *systemInfo) MemTotal() (uint64, error) {
memInfo, err := mem.VirtualMemory()
if err != nil {
return 0, err
}
return memInfo.Total, err
}
// MemUsed returns the amount of used memory in bytes.
func (s *systemInfo) MemUsed() (uint64, error) {
memInfo, err := mem.VirtualMemory()
if err != nil {
return 0, err
}
return memInfo.Used, err
}
// KernelVersion returns the kernel version as a string.
func (s *systemInfo) KernelVersion() (string, error) {
return host.KernelVersion()
}
// CPUArch returns the CPU architecture, such as amd64
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{}
}