mirror of
https://github.com/hamburghammer/gstat.git
synced 2024-05-17 04:34:37 +02:00
Compare commits
42 commits
Author | SHA1 | Date | |
---|---|---|---|
Augusto Dwenger J. | 381dd4819d | ||
1c9b053ec0 | |||
874960b897 | |||
7698718458 | |||
e6ab66ad2e | |||
Augusto Dwenger J. | 676c0ae573 | ||
Augusto Dwenger J. | ecd40f940d | ||
fefa87ad52 | |||
966735a184 | |||
7b1a54d697 | |||
Augusto Dwenger J. | d27183d362 | ||
Augusto Dwenger J. | 8e32c292a5 | ||
66abdebf3b | |||
e62c4853c9 | |||
0b81a55351 | |||
68eaf1d195 | |||
421faacb21 | |||
79db0bb6e5 | |||
fae0bae315 | |||
ceb197d60f | |||
e1a70507ec | |||
8ccd1e13ce | |||
a72b4e2405 | |||
d536dce03c | |||
57c1bca182 | |||
016a109bb0 | |||
2b57fd0b7a | |||
176de4c0d3 | |||
b3cbf3d6f3 | |||
f197208947 | |||
d1e1614737 | |||
d34bb06879 | |||
78a37d9a86 | |||
a12b6d3c64 | |||
b09fbbcfcd | |||
6162291d84 | |||
f41fca5f55 | |||
172b69cc45 | |||
c136a80cd4 | |||
64afe95c39 | |||
fcad1ad504 | |||
0f37747e1d |
32
.drone.yml
Normal file
32
.drone.yml
Normal file
|
@ -0,0 +1,32 @@
|
|||
kind: pipeline
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: unit-test
|
||||
image: golang
|
||||
volumes:
|
||||
- name: cache
|
||||
path: /go
|
||||
commands:
|
||||
- go test -coverprofile=coverage.out -covermode=count ./...
|
||||
- go tool cover -func=coverage.out | grep total
|
||||
|
||||
- name: race-test
|
||||
image: golang
|
||||
volumes:
|
||||
- name: cache
|
||||
path: /go
|
||||
commands:
|
||||
- go test -race ./...
|
||||
|
||||
- name: build
|
||||
image: golang
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /go
|
||||
commands:
|
||||
- CGO_ENABLED=0 go build
|
||||
|
||||
volumes:
|
||||
- name: cache
|
||||
temp: {}
|
19
LICENSE
Normal file
19
LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
|||
MIT License Copyright (c) 2020 Augusto Dwenger J.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next
|
||||
paragraph) shall be included in all copies or substantial portions of the
|
||||
Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
||||
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
83
README.md
83
README.md
|
@ -1,64 +1,51 @@
|
|||
# gstat
|
||||
|
||||
Is a tool to get the system stats in a parsable format.
|
||||
The tool is part of the competition with [Niklas](https://github.com/nhh).
|
||||
[![Build Status](https://cloud.drone.io/api/badges/hamburghammer/gstat/status.svg?ref=refs/heads/master)](https://cloud.drone.io/hamburghammer/gstat)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/hamburghammer/gstat)](https://goreportcard.com/report/github.com/hamburghammer/gstat)
|
||||
|
||||
Checkout his solution: [cstat](https://github.com/nhh/cstat)
|
||||
Is a cli tool to get some system stats in a machine parsable format (JSON). It supports only Linux but might also run on some UNIX based operation systems.
|
||||
|
||||
## Terms of the competition
|
||||
|
||||
### Allgemein:
|
||||
This tool is part of a competition with [nhh](https://github.com/nhh) -> [Details](docs/competition.md)
|
||||
|
||||
Name des Programms:
|
||||
- cstat
|
||||
- gstat
|
||||
**WIP: expect some changes for the stats gathering**
|
||||
|
||||
Das Tool kann mehrere Metriken auf einmal zurückgeben.
|
||||
## Features
|
||||
- Easy to use
|
||||
- Single executable
|
||||
- Runs on Linux
|
||||
|
||||
### Anforderung:
|
||||
## Installation
|
||||
For the time there are no binaries provided this means you need to install it through go.
|
||||
|
||||
- Aktuelle CPU Auslastung
|
||||
- Gesamtverbrauch aller CPU Kerne in %
|
||||
- Die nach cpu sortierten Prozesse als Liste
|
||||
- Aktueller Speicherplatzverbrauch
|
||||
-used / free in megabyte
|
||||
- Aktueller RAM Verbrauch
|
||||
- used / free in megabyte
|
||||
- Healthchecks mit Latency
|
||||
- http / https / *ICMP*
|
||||
- GET /
|
||||
- *Aktueller Network IO (optional)*
|
||||
- *Disk IO (optional)*
|
||||
- JSON Output
|
||||
- Datum und Uhrzeit im ISO Format
|
||||
Requirements:
|
||||
- Go is installed.
|
||||
- You have the `$GOPATH` defined.
|
||||
- The `$GOPATH/bin` directory is in your `$PATH`.
|
||||
|
||||
### Kriterien:
|
||||
Install and update it with `go get -u github.com/hamburghammer/gstat`.
|
||||
|
||||
- Single Executable
|
||||
- Linux
|
||||
## Usage
|
||||
```
|
||||
Usage:
|
||||
gstat [OPTIONS]
|
||||
|
||||
### Bewertungs:
|
||||
Application Options:
|
||||
-c, --cpu Include the total CPU consumption.
|
||||
-m, --mem Include the RAM usage.
|
||||
-d, --disk Include the Disk usage.
|
||||
-p, --proc Include the top 10 running processes with the highest CPU consumption.
|
||||
--health= Make a healthcheck call against the URI.
|
||||
|
||||
1. Wie groß ist das Binary
|
||||
2. Performance von Befehlen
|
||||
3. Cpu Auslastung
|
||||
4. Ram Auslastung
|
||||
Help Options:
|
||||
-h, --help Show this help message
|
||||
```
|
||||
*not all flags a jet supported or fully implemented!
|
||||
|
||||
### Kommandozeilenaufrufe:
|
||||
example output:
|
||||
|
||||
Alphabetische Reihenfolge
|
||||
|
||||
-c -h -d // --healtheck=http://example.com --disk --format=json
|
||||
|
||||
[https://de.wikipedia.org/wiki/Uniform_Resource_Identifier](https://de.wikipedia.org/wiki/Uniform_Resource_Identifier)
|
||||
|
||||
`$ cstat/gstat --cpu --format=json`
|
||||
`$ cstat/gstat --metric=disk`
|
||||
`$ cstat/gstat --check`
|
||||
|
||||
#### Goals:
|
||||
|
||||
cstat/gstat -c -d -i -h https://my-server.com > log.json
|
||||
|
||||
curl -X POST -d $(cstat/gstat -c -d -i -h https://my-server.com) https://my-logging.com/logs
|
||||
|
||||
`gstat -cmd`
|
||||
```json
|
||||
{"Date":"2020-11-21T16:32:18+01:00","CPU":3.49999999997029,"mem":{"used":5777,"total":16022},"disk":{"used":90319,"total":224323}}
|
||||
```
|
||||
|
|
19
args/args.go
19
args/args.go
|
@ -2,6 +2,8 @@ package args
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/jessevdk/go-flags"
|
||||
|
@ -9,11 +11,11 @@ import (
|
|||
|
||||
// Arguments represent the flags given at program start.
|
||||
type Arguments struct {
|
||||
CPU bool `short:"c" long:"cpu" description:"Include the total CPU consumption"`
|
||||
Mem bool `short:"m" long:"mem" description:"Include the total RAM consumption"`
|
||||
Disk bool `short:"d" long:"disk" description:"Include the total CPU consumption"`
|
||||
Processes bool `short:"p" long:"proc" description:"Include the top 10 processes"`
|
||||
Health string `long:"health" description:"Make a healthcheck call against the URI"`
|
||||
CPU bool `short:"c" long:"cpu" description:"Include the total CPU consumption."`
|
||||
Mem bool `short:"m" long:"mem" description:"Include the RAM usage."`
|
||||
Disk bool `short:"d" long:"disk" description:"Include the Disk usage."`
|
||||
Processes bool `short:"p" long:"proc" description:"Include the top 10 running processes with the highest CPU consumption."`
|
||||
Health string `long:"health" description:"Make a healthcheck call against the URI."`
|
||||
rest []string
|
||||
}
|
||||
|
||||
|
@ -61,7 +63,12 @@ func Parse() Arguments {
|
|||
_, err := flags.Parse(&args)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
if _, ok := err.(*flags.Error); ok {
|
||||
os.Exit(1)
|
||||
} else {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
return args
|
||||
|
|
|
@ -2,11 +2,10 @@ package commands
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hamburghammer/gstat/args"
|
||||
e "github.com/hamburghammer/gstat/errors"
|
||||
"github.com/hamburghammer/gstat/errors"
|
||||
"github.com/shirou/gopsutil/cpu"
|
||||
)
|
||||
|
||||
|
@ -16,11 +15,12 @@ const OperationKeyCPUReading = "CPUReading"
|
|||
// CPU holds the config to get the cpu load in percentage
|
||||
type CPU struct {
|
||||
TimeInMilSec int
|
||||
ReadCPUStat func(interval time.Duration, percpu bool) ([]float64, error)
|
||||
}
|
||||
|
||||
// NewCPU creates a new cpu percentage struct
|
||||
func NewCPU() CPU {
|
||||
return CPU{500}
|
||||
return CPU{TimeInMilSec: 500, ReadCPUStat: cpu.Percent}
|
||||
}
|
||||
|
||||
// Exec gets the cpu value and maps it to the executiondata struct
|
||||
|
@ -28,27 +28,10 @@ func (c CPU) Exec(args args.Arguments) ([]byte, error) {
|
|||
if !args.CPU {
|
||||
return []byte{}, nil
|
||||
}
|
||||
total, err := c.TotalCPU()
|
||||
total, err := c.ReadCPUStat(time.Millisecond*time.Duration(c.TimeInMilSec), false)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
return []byte{}, errors.BaseError{Operation: OperationKeyCPUReading, Message: err.Error()}
|
||||
}
|
||||
data := struct{ CPU float64 }{CPU: total}
|
||||
data := struct{ CPU float64 }{CPU: total[0]}
|
||||
return json.Marshal(data)
|
||||
}
|
||||
|
||||
// TotalCPU returns the first entry of the return array form the given function
|
||||
func (c CPU) TotalCPU() (float64, error) {
|
||||
total, err := cpu.Percent(time.Millisecond*time.Duration(c.TimeInMilSec), false)
|
||||
|
||||
if err != nil {
|
||||
wrappedError := fmt.Errorf("Something went wrong reading the CPU: %w", err)
|
||||
return float64(0), e.BaseError{Operation: OperationKeyCPUReading, Message: wrappedError.Error()}
|
||||
}
|
||||
|
||||
if len(total) != 1 {
|
||||
return float64(0), e.BaseError{
|
||||
Operation: OperationKeyCPUReading,
|
||||
Message: "No CPU data was found. Please check the HOST_PROC env to point to the right directory."}
|
||||
}
|
||||
return total[0], nil
|
||||
}
|
||||
|
|
|
@ -1,64 +1,54 @@
|
|||
package commands_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hamburghammer/gstat/args"
|
||||
"github.com/hamburghammer/gstat/commands"
|
||||
)
|
||||
|
||||
func TestCPUTotal(t *testing.T) {
|
||||
func TestExec(t *testing.T) {
|
||||
t.Run("should not run if no args are given", func(t *testing.T) {
|
||||
args := args.Arguments{CPU: false}
|
||||
|
||||
t.Run("should return one float", func(t *testing.T) {
|
||||
orig := os.Getenv("HOST_PROC")
|
||||
os.Setenv("HOST_PROC", "./testdata/proc")
|
||||
got, err := commands.CPU{}.Exec(args)
|
||||
|
||||
got, err := commands.CPU{}.TotalCPU()
|
||||
want := 0.000000
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Non error was expected but following occurred: %w", err)
|
||||
assertNoError(err, t)
|
||||
if len(got) != 0 {
|
||||
t.Error("Got something even though it was not expected")
|
||||
}
|
||||
|
||||
if got != want {
|
||||
t.Errorf("Want '%f' but got '%f'", want, got)
|
||||
}
|
||||
os.Setenv("HOST_PROC", orig)
|
||||
})
|
||||
|
||||
// Deactivated due to parallel running test clashing with the env setup
|
||||
// t.Run("should catch custom error", func(t *testing.T) {
|
||||
// orig := os.Getenv("HOST_PROC")
|
||||
// os.Setenv("HOST_PROC", "./testdata/empty")
|
||||
t.Run("should return one float", func(t *testing.T) {
|
||||
args := args.Arguments{CPU: true}
|
||||
|
||||
// _, got := commands.CPU{}.TotalCPU()
|
||||
// want := "CPUReading failed because of: No CPU data was found. Please check the HOST_PROC env to point to the right directory."
|
||||
readCPUStat := func(interval time.Duration, percpu bool) ([]float64, error) {
|
||||
return []float64{float64(0)}, nil
|
||||
}
|
||||
|
||||
// if got == nil {
|
||||
// t.Errorf("An error was expected but not nil")
|
||||
// }
|
||||
// if got.Error() != want {
|
||||
// t.Errorf("Want '%s' but got '%s'", want, got.Error())
|
||||
// }
|
||||
// os.Setenv("HOST_PROC", orig)
|
||||
// })
|
||||
}
|
||||
|
||||
func TestExec(t *testing.T) {
|
||||
orig := os.Getenv("HOST_PROC")
|
||||
os.Setenv("HOST_PROC", "./testdata/proc")
|
||||
|
||||
got, err := commands.CPU{}.Exec(args.Arguments{CPU: true})
|
||||
want := []byte("{\"CPU\":0}")
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("There was an unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(got[:], want[:]) {
|
||||
t.Errorf("want: '%s' but got: '%s'", string(want), string(got))
|
||||
}
|
||||
os.Setenv("HOST_PROC", orig)
|
||||
got, err := commands.CPU{ReadCPUStat: readCPUStat}.Exec(args)
|
||||
want := "{\"CPU\":0}"
|
||||
|
||||
assertNoError(err, t)
|
||||
|
||||
if string(got) != want {
|
||||
t.Errorf("Want '%s' but got '%s'", want, string(got))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should return wrapped error", func(t *testing.T) {
|
||||
args := args.Arguments{CPU: true}
|
||||
|
||||
readCPUStat := func(interval time.Duration, percpu bool) ([]float64, error) {
|
||||
return []float64{}, errors.New("Testing error")
|
||||
}
|
||||
|
||||
_, got := commands.CPU{ReadCPUStat: readCPUStat}.Exec(args)
|
||||
want := "CPUReading failed because of: Testing error"
|
||||
|
||||
assertEqualString(got.Error(), want, t)
|
||||
})
|
||||
}
|
||||
|
|
31
commands/date.go
Normal file
31
commands/date.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/hamburghammer/gstat/args"
|
||||
)
|
||||
|
||||
// Date is a the configuration struct to execute the date command
|
||||
type Date struct {
|
||||
// GetTime is the function to get the actual Time in string format.
|
||||
// It should be use to replace/cusomise the time output.
|
||||
GetTime func() string
|
||||
}
|
||||
|
||||
// NewDate is a convinice constructor for the Date struct.
|
||||
// It sets the GetTime function to standard formatting.
|
||||
func NewDate() Date {
|
||||
return Date{GetTime: getFormattedTime}
|
||||
}
|
||||
|
||||
// Exec is the implementation of the execution interface for the Date struct.
|
||||
func (d Date) Exec(args args.Arguments) ([]byte, error) {
|
||||
data := struct{ Date string }{Date: d.GetTime()}
|
||||
return json.Marshal(data)
|
||||
}
|
||||
|
||||
func getFormattedTime() string {
|
||||
return time.Now().Format(time.RFC3339)
|
||||
}
|
22
commands/date_test.go
Normal file
22
commands/date_test.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
package commands_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hamburghammer/gstat/args"
|
||||
"github.com/hamburghammer/gstat/commands"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDate(t *testing.T) {
|
||||
t.Run("", func(t *testing.T) {
|
||||
customTime := func() string { return "2020-08-09T17:43:31+02:00" }
|
||||
date := commands.Date{GetTime: customTime}
|
||||
|
||||
got, err := date.Exec(args.Arguments{})
|
||||
want := "{\"Date\":\"2020-08-09T17:43:31+02:00\"}"
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, want, string(got))
|
||||
})
|
||||
}
|
35
commands/disk.go
Normal file
35
commands/disk.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/hamburghammer/gstat/args"
|
||||
goDisk "github.com/shirou/gopsutil/disk"
|
||||
)
|
||||
|
||||
// Disk the struct for the DI to compose the disk space reader
|
||||
type Disk struct {
|
||||
ReadDiskStats func(string) (*goDisk.UsageStat, error)
|
||||
}
|
||||
|
||||
// NewDisk is a ctor for the Disk struct
|
||||
func NewDisk() Disk {
|
||||
return Disk{ReadDiskStats: goDisk.Usage}
|
||||
}
|
||||
|
||||
// Exec gets the disk space value for the root partition and maps it to the executiondata struct
|
||||
func (d Disk) Exec(args args.Arguments) ([]byte, error) {
|
||||
if !args.Disk {
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
usage, err := d.ReadDiskStats("/")
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
data := struct {
|
||||
Disk Memory `json:"disk"`
|
||||
}{Disk: Memory{Used: bytesToMegaByte(usage.Used), Total: bytesToMegaByte(usage.Total)}}
|
||||
return json.Marshal(data)
|
||||
}
|
62
commands/disk_test.go
Normal file
62
commands/disk_test.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package commands_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/hamburghammer/gstat/args"
|
||||
"github.com/hamburghammer/gstat/commands"
|
||||
goDisk "github.com/shirou/gopsutil/disk"
|
||||
)
|
||||
|
||||
func TestDiskExec(t *testing.T) {
|
||||
t.Run("should test for flag in Arguments", func(t *testing.T) {
|
||||
|
||||
got, err := commands.Disk{}.Exec(args.Arguments{Disk: false})
|
||||
|
||||
assertNoError(err, t)
|
||||
|
||||
if len(got) != 0 {
|
||||
t.Errorf("No result was expected but got: '%s'", string(got))
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
t.Run("should return error if reading disk stats creates one", func(t *testing.T) {
|
||||
errorStr := "Test error"
|
||||
disk := commands.Disk{ReadDiskStats: func(s string) (*goDisk.UsageStat, error) {
|
||||
return nil, errors.New(errorStr)
|
||||
}}
|
||||
|
||||
_, err := disk.Exec(args.Arguments{Disk: true})
|
||||
want := errorStr
|
||||
|
||||
assertEqualString(err.Error(), want, t)
|
||||
|
||||
})
|
||||
|
||||
t.Run("should convert to MB in JSON formatt", func(t *testing.T) {
|
||||
disk := commands.Disk{ReadDiskStats: func(s string) (*goDisk.UsageStat, error) {
|
||||
return &goDisk.UsageStat{Total: 100000000, Used: 50000000}, nil
|
||||
}}
|
||||
|
||||
got, err := disk.Exec(args.Arguments{Disk: true})
|
||||
want := "{\"disk\":{\"used\":47,\"total\":95}}"
|
||||
|
||||
assertNoError(err, t)
|
||||
|
||||
assertEqualString(string(got), want, t)
|
||||
})
|
||||
}
|
||||
|
||||
func assertNoError(err error, t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("No error was expected but got: '%s'", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func assertEqualString(got, want string, t *testing.T) {
|
||||
if got != want {
|
||||
t.Errorf("Want: '%s' but got: '%s'", want, got)
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ package commands
|
|||
|
||||
import "github.com/hamburghammer/gstat/args"
|
||||
|
||||
// Executor is a functional interface to execute an command and return the result as a json string.
|
||||
// Executor is a functional interface to execute a command and return the result as a json string ([]byte).
|
||||
type Executor interface {
|
||||
// Exec executes something and returns the result as a byte array of json and an error if something unexpected happened.
|
||||
Exec(args.Arguments) ([]byte, error)
|
||||
|
|
48
commands/mem.go
Normal file
48
commands/mem.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/hamburghammer/gstat/args"
|
||||
"github.com/shirou/gopsutil/mem"
|
||||
)
|
||||
|
||||
// Memory usage representation.
|
||||
type Memory struct {
|
||||
Used uint64 `json:"used"`
|
||||
Total uint64 `json:"total"`
|
||||
}
|
||||
|
||||
// Mem holds the memory usage for the json transformation
|
||||
type Mem struct {
|
||||
ReadVirtualMemoryStat func() (*mem.VirtualMemoryStat, error)
|
||||
}
|
||||
|
||||
// NewMem is a constructor for the Mem struct
|
||||
func NewMem() Mem {
|
||||
return Mem{mem.VirtualMemory}
|
||||
}
|
||||
|
||||
// Exec gets the mem value and maps it to the executiondata struct
|
||||
func (m Mem) Exec(args args.Arguments) ([]byte, error) {
|
||||
if !args.Mem {
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
mem, err := m.ReadVirtualMemoryStat()
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
usage := Memory{Used: bytesToMegaByte(mem.Used), Total: bytesToMegaByte(mem.Total)}
|
||||
data := struct {
|
||||
Mem Memory `json:"mem"`
|
||||
}{usage}
|
||||
return json.Marshal(data)
|
||||
}
|
||||
|
||||
func bytesToMegaByte(bytes uint64) uint64 {
|
||||
kb := bytes / 1024
|
||||
mb := kb / 1024
|
||||
return mb
|
||||
}
|
50
commands/mem_test.go
Normal file
50
commands/mem_test.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package commands_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/hamburghammer/gstat/args"
|
||||
"github.com/hamburghammer/gstat/commands"
|
||||
"github.com/shirou/gopsutil/mem"
|
||||
)
|
||||
|
||||
func TestMemExec(t *testing.T) {
|
||||
t.Run("should test for flag in Arguments", func(t *testing.T) {
|
||||
|
||||
got, err := commands.Mem{}.Exec(args.Arguments{Mem: false})
|
||||
|
||||
assertNoError(err, t)
|
||||
|
||||
if len(got) != 0 {
|
||||
t.Errorf("No result was expected but got: '%s'", string(got))
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
t.Run("should return error if reading disk stats creates one", func(t *testing.T) {
|
||||
errorStr := "Test error"
|
||||
disk := commands.Mem{ReadVirtualMemoryStat: func() (*mem.VirtualMemoryStat, error) {
|
||||
return nil, errors.New(errorStr)
|
||||
}}
|
||||
|
||||
_, err := disk.Exec(args.Arguments{Mem: true})
|
||||
want := errorStr
|
||||
|
||||
assertEqualString(err.Error(), want, t)
|
||||
|
||||
})
|
||||
|
||||
t.Run("should convert to MB in JSON formatt", func(t *testing.T) {
|
||||
disk := commands.Mem{ReadVirtualMemoryStat: func() (*mem.VirtualMemoryStat, error) {
|
||||
return &mem.VirtualMemoryStat{Total: 100000000, Used: 50000000}, nil
|
||||
}}
|
||||
|
||||
got, err := disk.Exec(args.Arguments{Mem: true})
|
||||
want := "{\"mem\":{\"used\":47,\"total\":95}}"
|
||||
|
||||
assertNoError(err, t)
|
||||
|
||||
assertEqualString(string(got), want, t)
|
||||
})
|
||||
}
|
105
commands/processes.go
Normal file
105
commands/processes.go
Normal file
|
@ -0,0 +1,105 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sort"
|
||||
|
||||
"github.com/hamburghammer/gstat/args"
|
||||
"github.com/shirou/gopsutil/process"
|
||||
)
|
||||
|
||||
// Processes holds the function to get the process list
|
||||
type Processes struct {
|
||||
ReadProcesses func() ([]*Process, error)
|
||||
}
|
||||
|
||||
// Exec is the implementation of the execution interface to be able to be used as a command
|
||||
func (p Processes) Exec(args args.Arguments) ([]byte, error) {
|
||||
if !args.Processes {
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
processes, err := p.ReadProcesses()
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
processesWithCPU, err := getProcessesCPUInfos(processes)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
sort.Sort(byCPU(processesWithCPU))
|
||||
|
||||
data := struct{ Processes []cpuProcess }{Processes: getFirstTenOrLess(processesWithCPU)}
|
||||
return json.Marshal(data)
|
||||
|
||||
}
|
||||
|
||||
// NewProcesses is a factory ctor to build a Processes struct
|
||||
func NewProcesses() Processes {
|
||||
return Processes{ReadProcesses: getProcesses}
|
||||
}
|
||||
|
||||
// getProcesses maps the process.Process array to a local Process struct
|
||||
func getProcesses() ([]*Process, error) {
|
||||
processes, err := process.Processes()
|
||||
|
||||
p := make([]*Process, 0, len(processes))
|
||||
|
||||
for _, process := range processes {
|
||||
p = append(p, &Process{Pid: process.Pid, CPUPercent: process.CPUPercent, Name: process.Name})
|
||||
}
|
||||
|
||||
return p, err
|
||||
}
|
||||
|
||||
func getFirstTenOrLess(array []cpuProcess) []cpuProcess {
|
||||
if len(array) >= 9 {
|
||||
return array[0:10]
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
func getProcessesCPUInfos(processes []*Process) ([]cpuProcess, error) {
|
||||
processesWithCPU := make([]cpuProcess, 0, len(processes))
|
||||
for _, process := range processes {
|
||||
processCPUInfo, err := getProcessCPUInfos(process)
|
||||
if err != nil {
|
||||
return processesWithCPU, err
|
||||
}
|
||||
processesWithCPU = append(processesWithCPU, *processCPUInfo)
|
||||
}
|
||||
return processesWithCPU, nil
|
||||
}
|
||||
|
||||
func getProcessCPUInfos(process *Process) (*cpuProcess, error) {
|
||||
cpuPercent, err := process.CPUPercent()
|
||||
if err != nil {
|
||||
return &cpuProcess{}, err
|
||||
}
|
||||
name, err := process.Name()
|
||||
if err != nil {
|
||||
return &cpuProcess{}, err
|
||||
}
|
||||
return &cpuProcess{Pid: process.Pid, CPU: cpuPercent, Name: name}, nil
|
||||
}
|
||||
|
||||
type cpuProcess struct {
|
||||
Name string
|
||||
Pid int32
|
||||
CPU float64
|
||||
}
|
||||
|
||||
type byCPU []cpuProcess
|
||||
|
||||
func (c byCPU) Len() int { return len(c) }
|
||||
func (c byCPU) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c byCPU) Less(i, j int) bool { return c[i].CPU > c[j].CPU }
|
||||
|
||||
// Process is an adapter struct for the external process struct from github.com/shirou/gopsutil/process
|
||||
type Process struct {
|
||||
Pid int32
|
||||
Name func() (string, error)
|
||||
CPUPercent func() (float64, error)
|
||||
}
|
137
commands/processes_internals_test.go
Normal file
137
commands/processes_internals_test.go
Normal file
|
@ -0,0 +1,137 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestByCPULen(t *testing.T) {
|
||||
t.Run("three item inside the array", func(t *testing.T) {
|
||||
array := []cpuProcess{{}, {}, {}}
|
||||
got := byCPU(array).Len()
|
||||
want := 3
|
||||
|
||||
assert.Equal(t, want, got, "they should be equal")
|
||||
})
|
||||
|
||||
t.Run("one item inside the array", func(t *testing.T) {
|
||||
array := []cpuProcess{{}}
|
||||
got := byCPU(array).Len()
|
||||
want := 1
|
||||
|
||||
assert.Equal(t, want, got, "they should be equal")
|
||||
})
|
||||
|
||||
t.Run("empty array", func(t *testing.T) {
|
||||
array := []cpuProcess{}
|
||||
got := byCPU(array).Len()
|
||||
want := 0
|
||||
|
||||
assert.Equal(t, want, got, "they should be equal")
|
||||
})
|
||||
}
|
||||
|
||||
func TestByCPUSwap(t *testing.T) {
|
||||
t.Run("swap array items", func(t *testing.T) {
|
||||
unswaped := []cpuProcess{{Name: "foo"}, {Name: "bar"}}
|
||||
swaped := []cpuProcess{{Name: "bar"}, {Name: "foo"}}
|
||||
|
||||
got := byCPU(unswaped)
|
||||
got.Swap(0, 1)
|
||||
want := byCPU(swaped)
|
||||
|
||||
assert.Equal(t, want, got)
|
||||
})
|
||||
}
|
||||
|
||||
func TestByCPULess(t *testing.T) {
|
||||
cpuProcessArray := []cpuProcess{{CPU: 1}, {CPU: 2}}
|
||||
|
||||
t.Run("less on cpu field smaller", func(t *testing.T) {
|
||||
|
||||
got := byCPU(cpuProcessArray).Less(0, 1)
|
||||
want := false
|
||||
|
||||
assert.Equal(t, want, got)
|
||||
})
|
||||
|
||||
t.Run("less on cpu field bigger", func(t *testing.T) {
|
||||
|
||||
got := byCPU(cpuProcessArray).Less(1, 0)
|
||||
want := true
|
||||
|
||||
assert.Equal(t, want, got)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetProcessCPUInfos(t *testing.T) {
|
||||
t.Run("transform Process to cpuProcess", func(t *testing.T) {
|
||||
nameFunc := func() (string, error) { return "foo", nil }
|
||||
cpuProcessFunc := func() (float64, error) { return 0, nil }
|
||||
process := Process{Pid: 1, Name: nameFunc, CPUPercent: cpuProcessFunc}
|
||||
|
||||
got, err := getProcessCPUInfos(&process)
|
||||
want := &cpuProcess{Name: "foo", Pid: 1, CPU: 0}
|
||||
|
||||
assert.Nil(t, err, "No error expected")
|
||||
assert.Equal(t, want, got)
|
||||
})
|
||||
|
||||
t.Run("calling name function returns error", func(t *testing.T) {
|
||||
wantErr := errors.New("error")
|
||||
|
||||
nameFunc := func() (string, error) { return "", wantErr }
|
||||
cpuProcessFunc := func() (float64, error) { return 0, nil }
|
||||
process := Process{Pid: 1, Name: nameFunc, CPUPercent: cpuProcessFunc}
|
||||
|
||||
_, gotErr := getProcessCPUInfos(&process)
|
||||
|
||||
assert.NotNil(t, gotErr, "An error was expected")
|
||||
assert.Equal(t, wantErr, gotErr)
|
||||
})
|
||||
|
||||
t.Run("calling cpuProcess function returns error", func(t *testing.T) {
|
||||
wantErr := errors.New("error")
|
||||
|
||||
nameFunc := func() (string, error) { return "", nil }
|
||||
cpuProcessFunc := func() (float64, error) { return 0, wantErr }
|
||||
process := Process{Pid: 1, Name: nameFunc, CPUPercent: cpuProcessFunc}
|
||||
|
||||
_, gotErr := getProcessCPUInfos(&process)
|
||||
|
||||
assert.NotNil(t, gotErr, "An error was expected")
|
||||
assert.Equal(t, wantErr, gotErr)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetProcessesCPUInfos(t *testing.T) {
|
||||
err := errors.New("error")
|
||||
|
||||
nameFunc := func() (string, error) { return "foo", nil }
|
||||
nameErrFunc := func() (string, error) { return "foo", err }
|
||||
|
||||
cpuProcessFunc := func() (float64, error) { return 0, nil }
|
||||
|
||||
t.Run("transform array of process into an array of cpuProcess", func(t *testing.T) {
|
||||
processes := []*Process{{Pid: 2, Name: nameFunc, CPUPercent: cpuProcessFunc}, {Pid: 1, Name: nameFunc, CPUPercent: cpuProcessFunc}}
|
||||
got, gotErr := getProcessesCPUInfos(processes)
|
||||
want := []cpuProcess{{Name: "foo", CPU: 0, Pid: 2}, {Name: "foo", CPU: 0, Pid: 1}}
|
||||
|
||||
assert.Nil(t, gotErr, "No error expected")
|
||||
assert.Equal(t, want, got)
|
||||
})
|
||||
|
||||
t.Run("return error directly if one happens", func(t *testing.T) {
|
||||
processes := []*Process{{Pid: 2, Name: nameFunc, CPUPercent: cpuProcessFunc}, {Pid: 1, Name: nameErrFunc, CPUPercent: cpuProcessFunc}}
|
||||
got, gotErr := getProcessesCPUInfos(processes)
|
||||
want := []cpuProcess{{Name: "foo", CPU: 0, Pid: 2}}
|
||||
wantErr := err
|
||||
|
||||
assert.NotNil(t, gotErr, "an error was expected")
|
||||
|
||||
assert.Equal(t, wantErr, gotErr)
|
||||
assert.Equal(t, want, got)
|
||||
})
|
||||
}
|
80
commands/processes_test.go
Normal file
80
commands/processes_test.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
package commands_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/hamburghammer/gstat/args"
|
||||
"github.com/hamburghammer/gstat/commands"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestProcessExec(t *testing.T) {
|
||||
t.Run("should test for the Argument for Process", func(t *testing.T) {
|
||||
arguments := args.Arguments{Processes: false}
|
||||
got, err := commands.Processes{}.Exec(arguments)
|
||||
|
||||
assert.Nil(t, err, "no error expected")
|
||||
assert.Equal(t, 0, len(got))
|
||||
})
|
||||
|
||||
t.Run("should exit if the getting the process data returns an error", func(t *testing.T) {
|
||||
wantErr := errors.New("getting data error")
|
||||
arguments := args.Arguments{Processes: true}
|
||||
mockProcessData := func() ([]*commands.Process, error) { return nil, wantErr }
|
||||
_, err := commands.Processes{ReadProcesses: mockProcessData}.Exec(arguments)
|
||||
|
||||
assert.NotNil(t, err, "an error was expected")
|
||||
assert.Equal(t, wantErr, err)
|
||||
})
|
||||
|
||||
t.Run("should pass errors from getting cpu infos to output", func(t *testing.T) {
|
||||
wantErr := errors.New("getting data error")
|
||||
arguments := args.Arguments{Processes: true}
|
||||
nameFunc := func() (string, error) { return "", nil }
|
||||
cpuErrorProcessFunc := func() (float64, error) { return 0, wantErr }
|
||||
process := commands.Process{Pid: 1, Name: nameFunc, CPUPercent: cpuErrorProcessFunc}
|
||||
mockProcessData := func() ([]*commands.Process, error) { return []*commands.Process{&process}, nil }
|
||||
|
||||
_, gotErr := commands.Processes{ReadProcesses: mockProcessData}.Exec(arguments)
|
||||
|
||||
assert.Equal(t, wantErr, gotErr)
|
||||
})
|
||||
|
||||
t.Run("should sort per cpu value", func(t *testing.T) {
|
||||
arguments := args.Arguments{Processes: true}
|
||||
nameFunc := func() (string, error) { return "", nil }
|
||||
cpuProcessFunc1 := func() (float64, error) { return 0, nil }
|
||||
cpuProcessFunc2 := func() (float64, error) { return 1, nil }
|
||||
process1 := commands.Process{Pid: 1, Name: nameFunc, CPUPercent: cpuProcessFunc1}
|
||||
process2 := commands.Process{Pid: 2, Name: nameFunc, CPUPercent: cpuProcessFunc2}
|
||||
mockProcessData := func() ([]*commands.Process, error) { return []*commands.Process{&process1, &process2}, nil }
|
||||
|
||||
got, err := commands.Processes{ReadProcesses: mockProcessData}.Exec(arguments)
|
||||
want := "{\"Processes\":[{\"Name\":\"\",\"Pid\":2,\"CPU\":1},{\"Name\":\"\",\"Pid\":1,\"CPU\":0}]}"
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, want, string(got))
|
||||
})
|
||||
|
||||
t.Run("should return max 10 entries", func(t *testing.T) {
|
||||
arguments := args.Arguments{Processes: true}
|
||||
nameFunc := func() (string, error) { return "", nil }
|
||||
cpuProcessFunc := func() (float64, error) { return 0, nil }
|
||||
|
||||
processes := make([]*commands.Process, 12)
|
||||
for i := 0; i <= 11; i++ {
|
||||
processes[i] = &commands.Process{Pid: int32(i), Name: nameFunc, CPUPercent: cpuProcessFunc}
|
||||
}
|
||||
|
||||
mockProcessData := func() ([]*commands.Process, error) { return processes, nil }
|
||||
|
||||
got, err := commands.Processes{ReadProcesses: mockProcessData}.Exec(arguments)
|
||||
want := "{\"Processes\":[{\"Name\":\"\",\"Pid\":0,\"CPU\":0},{\"Name\":\"\",\"Pid\":1,\"CPU\":0},{\"Name\":\"\",\"Pid\":2,\"CPU\":0},{\"Name\":\"\",\"Pid\":3,\"CPU\":0},{\"Name\":\"\",\"Pid\":4,\"CPU\":0},{\"Name\":\"\",\"Pid\":5,\"CPU\":0},{\"Name\":\"\",\"Pid\":6,\"CPU\":0},{\"Name\":\"\",\"Pid\":7,\"CPU\":0},{\"Name\":\"\",\"Pid\":8,\"CPU\":0},{\"Name\":\"\",\"Pid\":9,\"CPU\":0}]}"
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, want, string(got))
|
||||
})
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hamburghammer/gstat/args"
|
||||
|
@ -10,12 +9,34 @@ import (
|
|||
// Result gethers all the results for all commands
|
||||
type Result struct {
|
||||
args.Arguments
|
||||
collection
|
||||
Collection collection
|
||||
}
|
||||
|
||||
type collection struct {
|
||||
results []string
|
||||
errs []error
|
||||
Results []string
|
||||
Errs []error
|
||||
}
|
||||
|
||||
func (c collection) collectionEquals(otherCollection collection) bool {
|
||||
if len(c.Results) != len(otherCollection.Results) {
|
||||
return false
|
||||
}
|
||||
for i, s := range c.Results {
|
||||
if s != otherCollection.Results[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.Errs) != len(otherCollection.Errs) {
|
||||
return false
|
||||
}
|
||||
for i, s := range c.Errs {
|
||||
if s != otherCollection.Errs[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// NewResult creates new result struct
|
||||
|
@ -24,17 +45,45 @@ func NewResult(a args.Arguments) Result {
|
|||
}
|
||||
|
||||
// ExecCommands runs all commands
|
||||
func (r *Result) ExecCommands() {
|
||||
cpu, err := NewCPU().Exec(r.Arguments)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
func (r Result) ExecCommands(executors []Executor) Result {
|
||||
results := make([]string, 0, len(executors))
|
||||
errors := make([]error, 0, len(executors))
|
||||
|
||||
for _, executor := range executors {
|
||||
output, err := executor.Exec(r.Arguments)
|
||||
s := string(output)
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
} else if s != "" {
|
||||
results = append(results, rmFirstAndLastBracket(s))
|
||||
}
|
||||
}
|
||||
cpustr := rmFirstLastBracket(string(cpu))
|
||||
|
||||
fmt.Printf("{%s}\n", cpustr)
|
||||
r.Collection.Errs = errors
|
||||
r.Collection.Results = results
|
||||
return r
|
||||
}
|
||||
|
||||
func rmFirstLastBracket(s string) string {
|
||||
// ResultEquals checks for field equality
|
||||
func (r Result) ResultEquals(otherResult Result) bool {
|
||||
if !r.Arguments.Equals(otherResult.Arguments) {
|
||||
return false
|
||||
}
|
||||
if !r.Collection.collectionEquals(otherResult.Collection) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func rmFirstAndLastBracket(s string) string {
|
||||
s = strings.Replace(s, "{", "", 1)
|
||||
return strings.Replace(s, "}", "", 1)
|
||||
s = reverse(strings.Replace(reverse(s), "}", "", 1))
|
||||
return s
|
||||
}
|
||||
|
||||
func reverse(s string) (result string) {
|
||||
for _, v := range s {
|
||||
result = string(v) + result
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
119
commands/results_internals_test.go
Normal file
119
commands/results_internals_test.go
Normal file
|
@ -0,0 +1,119 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRmFirstLastBracket(t *testing.T) {
|
||||
t.Run("should remove first and last bracket", func(t *testing.T) {
|
||||
got := rmFirstAndLastBracket("{test}")
|
||||
want := "test"
|
||||
|
||||
assertEqualString(got, want, t)
|
||||
})
|
||||
|
||||
t.Run("should remove first and last bracket", func(t *testing.T) {
|
||||
got := rmFirstAndLastBracket("{test}{}")
|
||||
want := "test}{"
|
||||
|
||||
assertEqualString(got, want, t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCollectionEquals(t *testing.T) {
|
||||
|
||||
t.Run("should be true if Results are equals", func(t *testing.T) {
|
||||
results := []string{"foo"}
|
||||
c1 := collection{Results: results}
|
||||
c2 := collection{Results: results}
|
||||
|
||||
got := c1.collectionEquals(c2)
|
||||
want := true
|
||||
|
||||
assertEqualBool(got, want, t)
|
||||
})
|
||||
|
||||
t.Run("should be false if Results have different lengths", func(t *testing.T) {
|
||||
results1 := []string{"foo"}
|
||||
results2 := []string{"foo", "bar"}
|
||||
c1 := collection{Results: results1}
|
||||
c2 := collection{Results: results2}
|
||||
|
||||
got := c1.collectionEquals(c2)
|
||||
want := false
|
||||
|
||||
assertEqualBool(got, want, t)
|
||||
})
|
||||
|
||||
t.Run("should be false if Results have different strings", func(t *testing.T) {
|
||||
results1 := []string{"foo"}
|
||||
results2 := []string{"bar"}
|
||||
c1 := collection{Results: results1}
|
||||
c2 := collection{Results: results2}
|
||||
|
||||
got := c1.collectionEquals(c2)
|
||||
want := false
|
||||
|
||||
assertEqualBool(got, want, t)
|
||||
})
|
||||
|
||||
t.Run("should be true if Errs are equals", func(t *testing.T) {
|
||||
errs := []error{errors.New("foo")}
|
||||
c1 := collection{Errs: errs}
|
||||
c2 := collection{Errs: errs}
|
||||
|
||||
got := c1.collectionEquals(c2)
|
||||
want := true
|
||||
|
||||
assertEqualBool(got, want, t)
|
||||
})
|
||||
|
||||
t.Run("should be false if Errs have different lengths", func(t *testing.T) {
|
||||
errs1 := []error{errors.New("foo")}
|
||||
errs2 := []error{errors.New("foo"), errors.New("bar")}
|
||||
c1 := collection{Errs: errs1}
|
||||
c2 := collection{Errs: errs2}
|
||||
|
||||
got := c1.collectionEquals(c2)
|
||||
want := false
|
||||
|
||||
assertEqualBool(got, want, t)
|
||||
})
|
||||
|
||||
t.Run("should be false if Errs have different errors", func(t *testing.T) {
|
||||
errs1 := []error{errors.New("foo")}
|
||||
errs2 := []error{errors.New("bar")}
|
||||
c1 := collection{Errs: errs1}
|
||||
c2 := collection{Errs: errs2}
|
||||
|
||||
got := c1.collectionEquals(c2)
|
||||
want := false
|
||||
|
||||
assertEqualBool(got, want, t)
|
||||
})
|
||||
|
||||
t.Run("should be false if Errs have different errors one being nil", func(t *testing.T) {
|
||||
errs1 := []error{errors.New("foo")}
|
||||
errs2 := []error{nil}
|
||||
c1 := collection{Errs: errs1}
|
||||
c2 := collection{Errs: errs2}
|
||||
|
||||
got := c1.collectionEquals(c2)
|
||||
want := false
|
||||
|
||||
assertEqualBool(got, want, t)
|
||||
})
|
||||
}
|
||||
|
||||
func assertEqualString(got, want string, t *testing.T) {
|
||||
if got != want {
|
||||
t.Errorf("Want: '%s' but got: '%s'", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func assertEqualBool(got, want bool, t *testing.T) {
|
||||
if got != want {
|
||||
t.Errorf("Want: '%t' but got: '%t'", want, got)
|
||||
}
|
||||
}
|
106
commands/results_test.go
Normal file
106
commands/results_test.go
Normal file
|
@ -0,0 +1,106 @@
|
|||
package commands_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/hamburghammer/gstat/args"
|
||||
"github.com/hamburghammer/gstat/commands"
|
||||
)
|
||||
|
||||
func TestNewResults(t *testing.T) {
|
||||
args := args.Arguments{}
|
||||
got := commands.NewResult(args)
|
||||
want := commands.Result{Arguments: args}
|
||||
|
||||
if !got.ResultEquals(want) {
|
||||
t.Errorf("Want: '%v' but got: '%v'", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResultEquals(t *testing.T) {
|
||||
t.Run("should be equals", func(t *testing.T) {
|
||||
r1 := commands.Result{}
|
||||
r2 := commands.Result{}
|
||||
|
||||
got := r1.ResultEquals(r2)
|
||||
want := true
|
||||
|
||||
if got != want {
|
||||
t.Error("True was expected but got false")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should not be equals if args differ", func(t *testing.T) {
|
||||
r1 := commands.Result{Arguments: args.Arguments{CPU: true}}
|
||||
r2 := commands.Result{}
|
||||
|
||||
got := r1.ResultEquals(r2)
|
||||
want := false
|
||||
|
||||
if got != want {
|
||||
t.Error("True was expected but got false")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestExecCommands(t *testing.T) {
|
||||
t.Run("should return string array without opening and closing brackets", func(t *testing.T) {
|
||||
arguments := args.Arguments{}
|
||||
result := commands.NewResult(arguments)
|
||||
|
||||
mE := mockExecutor{}
|
||||
mE.mockExec = func(args.Arguments) ([]byte, error) { return []byte("{test}"), nil }
|
||||
|
||||
executors := []commands.Executor{mE}
|
||||
|
||||
got := result.ExecCommands(executors)
|
||||
want := "test"
|
||||
|
||||
if got.Collection.Results[0] != want {
|
||||
t.Errorf("Want: '%s' but got: '%s'", want, got.Collection.Results[0])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should return error array", func(t *testing.T) {
|
||||
arguments := args.Arguments{}
|
||||
result := commands.NewResult(arguments)
|
||||
|
||||
mE := mockExecutor{}
|
||||
mE.mockExec = func(args.Arguments) ([]byte, error) { return make([]byte, 0), errors.New("test error") }
|
||||
|
||||
executors := []commands.Executor{mE}
|
||||
|
||||
got := result.ExecCommands(executors)
|
||||
want := "test error"
|
||||
|
||||
if got.Collection.Errs[0].Error() != want {
|
||||
t.Errorf("Want: '%s' but got: '%s'", want, got.Collection.Results[0])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should return empty result if it gets an empty byte array", func(t *testing.T) {
|
||||
arguments := args.Arguments{}
|
||||
result := commands.NewResult(arguments)
|
||||
|
||||
mE := mockExecutor{}
|
||||
mE.mockExec = func(args.Arguments) ([]byte, error) { return make([]byte, 0), nil }
|
||||
|
||||
executors := []commands.Executor{mE}
|
||||
|
||||
got := len(result.ExecCommands(executors).Collection.Results)
|
||||
want := 0
|
||||
|
||||
if got != want {
|
||||
t.Errorf("Want: '%d' but got: '%d' entries", want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type mockExecutor struct {
|
||||
mockExec func(args.Arguments) ([]byte, error)
|
||||
}
|
||||
|
||||
func (mE mockExecutor) Exec(args args.Arguments) ([]byte, error) {
|
||||
return mE.mockExec(args)
|
||||
}
|
56
docs/competition.md
Normal file
56
docs/competition.md
Normal file
|
@ -0,0 +1,56 @@
|
|||
# Competition
|
||||
The tool is part of the competition with [Niklas](https://github.com/nhh).
|
||||
|
||||
Checkout his solution: [cstat](https://github.com/nhh/cstat)
|
||||
|
||||
## Terms of the competition
|
||||
|
||||
### Allgemein:
|
||||
Name des Programms:
|
||||
- cstat
|
||||
- gstat
|
||||
|
||||
Das Tool kann mehrere Metriken auf einmal zurückgeben.
|
||||
|
||||
### Anforderung:
|
||||
- Aktuelle CPU Auslastung
|
||||
- Gesamtverbrauch aller CPU Kerne in %
|
||||
- Die nach cpu sortierten Prozesse als Liste
|
||||
- Aktueller Speicherplatzverbrauch
|
||||
-used / free in megabyte
|
||||
- Aktueller RAM Verbrauch
|
||||
- used / free in megabyte
|
||||
- Healthchecks mit Latency
|
||||
- http / https / *ICMP*
|
||||
- GET /
|
||||
- *Aktueller Network IO (optional)*
|
||||
- *Disk IO (optional)*
|
||||
- JSON Output
|
||||
- Datum und Uhrzeit im ISO Format
|
||||
|
||||
### Kriterien:
|
||||
- Single Executable
|
||||
- Linux
|
||||
|
||||
### Bewertungs:
|
||||
1. Wie groß ist das Binary
|
||||
2. Performance von Befehlen
|
||||
3. Cpu Auslastung
|
||||
4. Ram Auslastung
|
||||
|
||||
### Kommandozeilenaufrufe:
|
||||
Alphabetische Reihenfolge
|
||||
|
||||
-c -h -d // --healtheck=http://example.com --disk --format=json
|
||||
|
||||
[https://de.wikipedia.org/wiki/Uniform_Resource_Identifier](https://de.wikipedia.org/wiki/Uniform_Resource_Identifier)
|
||||
|
||||
`$ cstat/gstat --cpu --format=json`
|
||||
`$ cstat/gstat --metric=disk`
|
||||
`$ cstat/gstat --check`
|
||||
|
||||
#### Goals:
|
||||
cstat/gstat -c -d -i -h https://my-server.com > log.json
|
||||
|
||||
curl -X POST -d $(cstat/gstat -c -d -i -h https://my-server.com) https://my-logging.com/logs
|
||||
|
6
go.mod
6
go.mod
|
@ -3,6 +3,10 @@ module github.com/hamburghammer/gstat
|
|||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/shirou/gopsutil v2.20.4+incompatible
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
|
||||
github.com/go-ole/go-ole v1.2.4 // indirect
|
||||
github.com/jessevdk/go-flags v1.4.0
|
||||
github.com/shirou/gopsutil v2.20.6+incompatible
|
||||
github.com/stretchr/testify v1.6.1
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 // indirect
|
||||
)
|
||||
|
|
18
go.sum
18
go.sum
|
@ -1,4 +1,22 @@
|
|||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
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/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/shirou/gopsutil v2.20.4+incompatible h1:cMT4rxS55zx9NVUnCkrmXCsEB/RNfG9SwHY9evtX8Ng=
|
||||
github.com/shirou/gopsutil v2.20.4+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil v2.20.6+incompatible h1:P37G9YH8M4vqkKcwBosp+URN5O8Tay67D2MbR361ioY=
|
||||
github.com/shirou/gopsutil v2.20.6+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/sys v0.0.0-20200610111108-226ff32320da h1:bGb80FudwxpeucJUjPYJXuJ8Hk91vNtfvrymzwiei38=
|
||||
golang.org/x/sys v0.0.0-20200610111108-226ff32320da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 h1:B6caxRw+hozq68X2MY7jEpZh/cr4/aHLv9xU8Kkadrw=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
13
gstat.go
13
gstat.go
|
@ -1,13 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/hamburghammer/gstat/args"
|
||||
"github.com/hamburghammer/gstat/commands"
|
||||
)
|
||||
|
||||
func main() {
|
||||
args := args.Parse()
|
||||
|
||||
result := commands.NewResult(args)
|
||||
result.ExecCommands()
|
||||
}
|
39
main.go
Normal file
39
main.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hamburghammer/gstat/args"
|
||||
"github.com/hamburghammer/gstat/commands"
|
||||
)
|
||||
|
||||
func main() {
|
||||
args := args.Parse()
|
||||
|
||||
result := commands.NewResult(args)
|
||||
executs := []commands.Executor{
|
||||
commands.NewDate(),
|
||||
commands.NewCPU(),
|
||||
commands.NewMem(),
|
||||
commands.NewDisk(),
|
||||
commands.NewProcesses(),
|
||||
}
|
||||
output := result.ExecCommands(executs)
|
||||
|
||||
fmt.Println(formatToJSON(output.Collection.Results))
|
||||
}
|
||||
|
||||
func formatToJSON(strings []string) string {
|
||||
stringBuilder := "{"
|
||||
elements := len(strings)
|
||||
|
||||
for i, s := range strings {
|
||||
stringBuilder = stringBuilder + s
|
||||
if i != (elements - 1) {
|
||||
stringBuilder = stringBuilder + ","
|
||||
}
|
||||
}
|
||||
stringBuilder = stringBuilder + "}"
|
||||
|
||||
return stringBuilder
|
||||
}
|
14
main_internals_test.go
Normal file
14
main_internals_test.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFormatToJSON(t *testing.T) {
|
||||
strings := []string{"foo", "bar"}
|
||||
|
||||
got := formatToJSON(strings)
|
||||
want := "{foo,bar}"
|
||||
|
||||
if got != want {
|
||||
t.Errorf("Want: '%s' but got: '%s'", want, got)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue