Compare commits

...

42 commits

Author SHA1 Message Date
Augusto Dwenger J. 381dd4819d
Merge pull request #7 from hamburghammer/docs
Add Docs
2020-11-25 14:06:49 +01:00
Augusto Dwenger 1c9b053ec0 Fix link to the competition details 2020-11-21 16:58:05 +01:00
Augusto Dwenger 874960b897 Add note to the usage section regarding the flags 2020-11-21 16:58:05 +01:00
Augusto Dwenger 7698718458 Replace competition terms with installation, usage etc documentation 2020-11-21 16:58:05 +01:00
Augusto Dwenger e6ab66ad2e Add competition terms in side an own file 2020-11-21 16:57:56 +01:00
Augusto Dwenger J. 676c0ae573
Fix wrong argument descriptions 2020-11-15 20:25:31 +01:00
Augusto Dwenger J. ecd40f940d
Merge pull request #6 from hamburghammer/develop
Change memory representation
2020-11-15 20:08:18 +01:00
Augusto Dwenger fefa87ad52 Fix printing the help message twice 2020-11-10 14:50:31 +01:00
Augusto Dwenger 966735a184 Update CI scripts
Add test coverage output.
Add race detector step.
Disable cgo for the build
2020-11-09 21:36:01 +01:00
Augusto Dwenger 7b1a54d697 Change usage representation of memory field
Changes apply to Disk and Mem. The show the state as an object.
This should improve the readablility.
2020-11-09 21:27:18 +01:00
Augusto Dwenger J. d27183d362 Add go report card badge 2020-10-20 16:47:04 +02:00
Augusto Dwenger J. 8e32c292a5 Fix misspells 2020-10-20 16:44:59 +02:00
Augusto Dwenger 66abdebf3b Fix Copyright section 2020-08-10 20:23:36 +02:00
Augusto Dwenger e62c4853c9 Merge branch 'feature/date' into develop 2020-08-10 20:04:14 +02:00
Augusto Dwenger 0b81a55351 Move Date to the beginning of the JSON 2020-08-10 20:02:24 +02:00
Augusto Dwenger 68eaf1d195 Implement date as command for the json output 2020-08-09 17:52:20 +02:00
Augusto Dwenger 421faacb21 Add name and year to the license 2020-08-07 12:22:13 +02:00
Augusto Dwenger 79db0bb6e5 Merge branch 'develop'
Include:
- Top 10 Process output
- Add CI setup
- Add Testify dependency
2020-08-07 11:53:31 +02:00
Augusto Dwenger fae0bae315 Merge branch 'feature/processes' into develop 2020-08-07 11:46:58 +02:00
Augusto Dwenger ceb197d60f Fix bug if the process list is > 10 2020-08-07 11:45:33 +02:00
Augusto Dwenger e1a70507ec Add test for command.Process testing the internals and externals 2020-08-07 11:45:33 +02:00
Augusto Dwenger 8ccd1e13ce Start replacing custom assert func with stretchr/testify
Add github.com/stretchr/testify with its dependencies.
2020-08-07 11:45:33 +02:00
Augusto Dwenger a72b4e2405 Refactor transforming Process to cpuProcess 2020-08-07 11:44:59 +02:00
Augusto Dwenger d536dce03c Add test file for internal function testing 2020-08-07 11:44:59 +02:00
Augusto Dwenger 57c1bca182 Extract the conversion to cpuProcess to own func 2020-08-07 11:44:59 +02:00
Augusto Dwenger 016a109bb0 Add comments to exported funcs and structs 2020-08-07 11:44:59 +02:00
Augusto Dwenger 2b57fd0b7a Implement process list 2020-08-07 11:44:52 +02:00
Augusto Dwenger 176de4c0d3 Merge branch 'feature/drone-ci' into develop 2020-07-24 23:33:51 +02:00
Augusto Dwenger b3cbf3d6f3 Change badge to point to master branch 2020-07-24 23:26:23 +02:00
Augusto Dwenger f197208947 Modify test job to test for sub packeges and output a cover report 2020-07-24 23:23:13 +02:00
Augusto Dwenger d1e1614737 Add drone build badges 2020-07-24 23:20:11 +02:00
Augusto Dwenger d34bb06879 Add .drone.yml for drone ci pipeline 2020-07-24 23:17:34 +02:00
Augusto Dwenger 78a37d9a86 Add the MIT license file 2020-07-24 23:15:05 +02:00
Augusto Dwenger a12b6d3c64 Merge branch 'develop'
Adds Disk and Memory stats.
2020-06-30 16:38:12 +02:00
Augusto Dwenger b09fbbcfcd Cleanup naming and parameters 2020-06-30 16:36:51 +02:00
Augusto Dwenger 6162291d84 Refactor Mem to use DI to be more testable 2020-06-29 14:02:28 +02:00
Augusto Dwenger f41fca5f55 Refactor CPU to use DI to be more testable 2020-06-29 13:27:21 +02:00
Augusto Dwenger 172b69cc45 Implement disk space usage command 2020-06-29 12:12:11 +02:00
Augusto Dwenger c136a80cd4 Implement memory read 2020-06-14 16:01:45 +02:00
Augusto Dwenger 64afe95c39 Fix result bug if byte array is empty 2020-06-14 15:57:00 +02:00
Augusto Dwenger fcad1ad504 Fix output format 2020-06-14 14:26:08 +02:00
Augusto Dwenger 0f37747e1d Refactor to make Result more testable and stable 2020-06-13 18:23:36 +02:00
25 changed files with 1130 additions and 150 deletions

32
.drone.yml Normal file
View 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
View 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.

View file

@ -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}}
```

View file

@ -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

View file

@ -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
}

View file

@ -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
View 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
View 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
View 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
View 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)
}
}

View file

@ -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
View 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
View 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
View 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)
}

View 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)
})
}

View 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))
})
}

View file

@ -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
}

View 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
View 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
View 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
View file

@ -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
View file

@ -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=

View file

@ -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
View 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
View 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)
}
}