Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*~
*.iml
*.swp
*.o
Expand All @@ -10,4 +11,7 @@ main.retry
testing/ssh_config
testing/ve

build/
build/

/sysinfo
/cmd/sysinfo/sysinfo
124 changes: 124 additions & 0 deletions cmd/sysinfo/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package main

import (
"fmt"
"io"
"log"
"os"
"strings"
"text/template"
"time"

"github.com/dustin/go-humanize"
"github.com/elastic/go-sysinfo"
"github.com/elastic/go-sysinfo/types"
)

const (
hostTmpl = `Host Info:

Architecture: {{ .Info.Architecture }}
Boot Time: {{ .Info.BootTime }} ({{ .Info.BootTime | ago }} ago)
{{ with .Info.Containerized -}}
Containerized: {{ .Info.Containerized }}
{{ end -}}
Hostname: {{ .Info.Hostname }}
Unique ID: {{ .Info.UniqueID }}
OS: {{ .Info.OS }}
Kernel Version: {{ .Info.KernelVersion }}
Timezone: {{ .Info.Timezone }} (Offset: {{ .Info.TimezoneOffsetSec }})
IP Addresses: {{ .Info.IPs | join " " }}
MAC Addresses: {{ .Info.MACs | join " " }}
`
cpuTmpl = `CPU Usage:

User {{ .User }}
System {{ .System }}
Idle {{ .Idle }}
IOWait {{ .IOWait }}
IRQ {{ .IRQ }}
Nice {{ .Nice }}
SoftIRQ {{ .SoftIRQ }}
Steal {{ .Steal }}
`
memTmpl = `Memory Usage:

Total {{ .Total | bytes }}
Used {{ .Used | bytes }}
Available {{ .Available | bytes }}
Free {{ .Free | bytes }}
VirtualTotal {{ .VirtualTotal | bytes }}
VirtualUsed {{ .VirtualUsed | bytes }}
VirtualFree {{ .VirtualFree | bytes }}
`
loadTmpl = `Load Average: {{ .One }} {{ .Five }} {{ .Fifteen }}
`
)

func main() {
host, err := sysinfo.Host()
if err != nil {
log.Fatalf("error getting host info: %s", err)
}
if err := printHostInfo(os.Stdout, host); err != nil {
log.Fatalf("error printing host info: %s", err)
}
}

func printHostInfo(w io.Writer, host types.Host) error {
if err := render(hostTmpl, host, w); err != nil {
return fmt.Errorf("error rendering host information: %w", err)
}

cpuTimes, err := host.CPUTime()
if err != nil {
return fmt.Errorf("error getting host cpu times: %w", err)
}
fmt.Fprintln(w)
if err := render(cpuTmpl, cpuTimes, w); err != nil {
return fmt.Errorf("error rendering host cpu time: %w", err)
}

memory, err := host.Memory()
if err != nil {
return fmt.Errorf("error getting host memory: %w", err)
}
fmt.Fprintln(w)
if err := render(memTmpl, memory, w); err != nil {
return fmt.Errorf("error rendering host memory: %w", err)
}

load, err := host.LoadAverage()
if err != nil {
return fmt.Errorf("error getting host load: %w", err)
}
fmt.Fprintln(w)
if err := render(loadTmpl, load, w); err != nil {
return fmt.Errorf("error rendering host load: %w", err)
}

return nil
}

func ago(t time.Time) string {
return time.Since(t).String()
}

func join(sep string, xs []string) string {
return strings.Join(xs, sep)
}

func render(tmpl string, ctx interface{}, w io.Writer) error {
funcs := template.FuncMap{
"ago": ago,
"join": join,
"bytes": humanize.Bytes,
}

t, err := template.New("tmpl").Funcs(funcs).Parse(tmpl)
if err != nil {
return err
}

return t.Execute(w, ctx)
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/elastic/go-sysinfo
go 1.17

require (
github.com/dustin/go-humanize v1.0.0
github.com/elastic/go-windows v1.0.0
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901
github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/elastic/go-windows v1.0.0 h1:qLURgZFkkrYyTTkvYpsZIgf83AUsdIHfvlJaqaZ7aSY=
github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
Expand Down
15 changes: 15 additions & 0 deletions providers/darwin/host_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,21 @@ func (h *host) Memory() (*types.HostMemoryInfo, error) {
return &mem, nil
}

func (h *host) LoadAverage() (*types.LoadAverageInfo, error) {
load, err := getLoadAverage()
if err != nil {
return nil, fmt.Errorf("failed to get loadavg: %w", err)
}

scale := float64(load.scale)

return &types.LoadAverageInfo{
One: float64(load.load[0]) / scale,
Five: float64(load.load[1]) / scale,
Fifteen: float64(load.load[2]) / scale,
}, nil
}

func newHost() (*host, error) {
h := &host{}
r := &reader{}
Expand Down
45 changes: 45 additions & 0 deletions providers/darwin/load_average_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 amd64 || arm64
// +build amd64 arm64

package darwin

import (
"unsafe"

"golang.org/x/sys/unix"
)

const loadAverage = "vm.loadavg"

type loadAvg struct {
load [3]uint32
scale int
}

func getLoadAverage() (*loadAvg, error) {
data, err := unix.SysctlRaw(loadAverage)
if err != nil {
return nil, err
}

load := *(*loadAvg)(unsafe.Pointer((&data[0])))

return &load, nil
}
30 changes: 30 additions & 0 deletions providers/darwin/load_average_darwin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 darwin

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestGetLoadAverage(t *testing.T) {
a, err := getLoadAverage()
assert.NoError(t, err)
assert.NotEmpty(t, a)
}
10 changes: 10 additions & 0 deletions providers/linux/host_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,16 @@ func (h *host) VMStat() (*types.VMStatInfo, error) {
return parseVMStat(content)
}

// LoadAverage reports data from /proc/loadavg on linux.
func (h *host) LoadAverage() (*types.LoadAverageInfo, error) {
content, err := ioutil.ReadFile(h.procFS.path("loadavg"))
if err != nil {
return nil, err
}

return parseLoadAvg(content)
}

// NetworkCounters reports data from /proc/net on linux
func (h *host) NetworkCounters() (*types.NetworkCountersInfo, error) {
snmpRaw, err := ioutil.ReadFile(h.procFS.path("net/snmp"))
Expand Down
17 changes: 17 additions & 0 deletions providers/linux/host_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,23 @@ func TestHostVMStat(t *testing.T) {
t.Log(string(data))
}

func TestHostLoadAverage(t *testing.T) {
host, err := newLinuxSystem("testdata/ubuntu1710").Host()
if err != nil {
t.Fatal(err)
}
s, err := host.(types.LoadAverage).LoadAverage()
if err != nil {
t.Fatal(err)
}

data, err := json.Marshal(s)
if err != nil {
t.Fatal(err)
}
t.Log(string(data))
}

func TestHostNetworkCounters(t *testing.T) {
host, err := newLinuxSystem("testdata/fedora30").Host()
if err != nil {
Expand Down
68 changes: 68 additions & 0 deletions providers/linux/load_average.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 linux

import (
"bufio"
"bytes"
"fmt"
"reflect"

"github.com/elastic/go-sysinfo/types"
)

var loadAverageInfoFieldIndexToTag = make(map[int]string)

func init() {
var loadavg types.LoadAverageInfo
val := reflect.ValueOf(loadavg)
typ := reflect.TypeOf(loadavg)

for i := 0; i < val.NumField(); i++ {
field := typ.Field(i)
if tag := field.Tag.Get("json"); tag != "" {
loadAverageInfoFieldIndexToTag[i] = tag
}
}
}

// parseLoadAvg parses the content of /proc/loadavg
func parseLoadAvg(content []byte) (*types.LoadAverageInfo, error) {
var loadAvg types.LoadAverageInfo
refValues := reflect.ValueOf(&loadAvg).Elem()

s := bufio.NewScanner(bytes.NewReader(content))
s.Split(bufio.ScanWords)

for index := 0; index < len(loadAverageInfoFieldIndexToTag); index++ {
s.Scan()
data := s.Bytes()
val, err := parseBytesOrFloat(data)
if err != nil {
return nil, fmt.Errorf("failed to parse %v: %w", data, err)
}

sval := refValues.Field(index)

if sval.CanSet() {
sval.SetFloat(val)
}
}

return &loadAvg, s.Err()
}
Loading