From 53c6133ef1fce11d35779ce310a76c9e9f36b9b4 Mon Sep 17 00:00:00 2001 From: Bhargav SNV <44526455+Gituser143@users.noreply.github.com> Date: Sun, 13 Sep 2020 13:49:20 +0530 Subject: [PATCH] Update to version v1.1.0! (#51) * Add feature for more CPU info (#46) * added code for additional cpu info * added cpu rates to CPULoad type * Made UI for CPU info * Cleaned use fo sync.once variables for faster UI rendering Co-authored-by: Madhav Jivrajani * added cpuinfo in README * removed stray comment * Update README * Add extra comments * Refactored printStats, becoming serveStats! (#50) Co-authored-by: lrb * Readme update Co-authored-by: Madhav Jivrajani Co-authored-by: mattharwood Co-authored-by: lrb --- README.md | 26 ++++- cmd/root.go | 32 +++-- go.mod | 2 + go.sum | 10 ++ images/README/cpuload.png | Bin 0 -> 29832 bytes src/display/general/init.go | 116 +++++++++++++++++++ src/display/general/overallGraphs.go | 102 ++++++++++++++++ src/display/process/allProcs.go | 7 +- src/display/process/procGraphs.go | 3 +- src/general/cpuInfo.go | 112 ++++++++++++++++++ src/general/generalStats.go | 8 +- src/general/{printStats.go => serveStats.go} | 23 +++- 12 files changed, 418 insertions(+), 23 deletions(-) create mode 100644 images/README/cpuload.png create mode 100644 src/general/cpuInfo.go rename src/general/{printStats.go => serveStats.go} (80%) diff --git a/README.md b/README.md index e6c5e4ec..b2eda56b 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ Grofer A clean system monitor and profiler written purely in golang using [termui](https://github.com/gizak/termui) and [gopsutil](https://github.com/shirou/gopsutil)! +Currently compatible with Linux only. + Installation ------------ @@ -35,6 +37,8 @@ cd grofer go build grofer.go ``` +--- + Usage ----- @@ -52,6 +56,7 @@ Available Commands: Flags: --config string config file (default is $HOME/.grofer.yaml) + -c, --cpuinfo Info about the CPU Load over all CPUs -h, --help help for grofer -r, --refresh int32 Overall stats UI refreshes rate in milliseconds greater than 1000 (default 1000) -t, --toggle Help message for toggle @@ -60,11 +65,13 @@ Use "grofer [command] --help" for more information about a command. ``` +--- + Examples -------- -`grofer [-r refreshRate]` -------------------------- +`grofer [-r refreshRate] [-c]` +------------------------------ This gives overall utilization stats refreshed every `refreshRate` milliseconds. Default and minimum value of the refresh rate is `1000 ms`. @@ -76,6 +83,21 @@ Information provided: - Network usage - Disk storage +The `-c, --cpuinfo` flag displays finer details about the CPU load such as percentage of the time spent servicing software interrupts, hardware interrupts, etc. + +![grofer-cpu](images/README/cpuload.png) + +Information provided: +- Usr : % of time spent executing user level applications. +- Sys : % of time spent executing kernel level processes. +- Irq : % of time spent servicing hardware interrupts. +- Idle : % of time CPU was idle. +- Nice : % of time spent by CPU executing user level processes with a nice priority. +- Iowait: % of time spent by CPU waiting for an outstanding disk I/O. +- Soft : % of time spent by the CPU servicing software interrupts. + +- Steal : % of time spent in involuntary waiting by logical CPUs. + --- `grofer proc [-p PID] [-r refreshRate]` diff --git a/cmd/root.go b/cmd/root.go index 89e043b2..9e1c272c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -26,6 +26,7 @@ import ( overallGraph "github.com/pesos/grofer/src/display/general" "github.com/pesos/grofer/src/general" + info "github.com/pesos/grofer/src/general" "github.com/pesos/grofer/src/utils" ) @@ -40,22 +41,38 @@ var rootCmd = &cobra.Command{ Use: "grofer", Short: "grofer is a system profiler written in golang", RunE: func(cmd *cobra.Command, args []string) error { - overallRefreshRate, _ := cmd.Flags().GetInt32("refresh") if overallRefreshRate < 1000 { return fmt.Errorf("invalid refresh rate: minimum refresh rate is 1000(ms)") } var wg sync.WaitGroup - endChannel := make(chan os.Signal, 1) - dataChannel := make(chan utils.DataStats, 1) - wg.Add(2) + cpuLoadFlag, _ := cmd.Flags().GetBool("cpuinfo") + if cpuLoadFlag { + cpuLoad := info.NewCPULoad() + dataChannel := make(chan *info.CPULoad, 1) + endChannel := make(chan os.Signal, 1) + + wg.Add(2) + + go info.GetCPULoad(cpuLoad, dataChannel, endChannel, int32(4*overallRefreshRate/5), &wg) + + go overallGraph.RenderCPUinfo(endChannel, dataChannel, overallRefreshRate, &wg) - go general.GlobalStats(endChannel, dataChannel, int32(4*overallRefreshRate/5), &wg) - go overallGraph.RenderCharts(endChannel, dataChannel, overallRefreshRate, &wg) + wg.Wait() - wg.Wait() + } else { + endChannel := make(chan os.Signal, 1) + dataChannel := make(chan utils.DataStats, 1) + + wg.Add(2) + + go general.GlobalStats(endChannel, dataChannel, int32(4*overallRefreshRate/5), &wg) + go overallGraph.RenderCharts(endChannel, dataChannel, overallRefreshRate, &wg) + + wg.Wait() + } return nil }, @@ -73,6 +90,7 @@ func init() { rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.grofer.yaml)") rootCmd.Flags().Int32P("refresh", "r", DefaultOverallRefreshRate, "Overall stats UI refreshes rate in milliseconds greater than 1000") + rootCmd.Flags().BoolP("cpuinfo", "c", false, "Info about the CPU Load over all CPUs") rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } diff --git a/go.mod b/go.mod index 0cd11075..3130e8ba 100644 --- a/go.mod +++ b/go.mod @@ -11,5 +11,7 @@ require ( github.com/shirou/gopsutil v2.20.6+incompatible github.com/spf13/cobra v1.0.0 github.com/spf13/viper v1.7.0 + github.com/thedevsaddam/gojsonq/v2 v2.5.2 + github.com/tidwall/gjson v1.6.0 golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6 // indirect ) diff --git a/go.sum b/go.sum index 127ff4b6..00f8b7f9 100644 --- a/go.sum +++ b/go.sum @@ -204,6 +204,16 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/thedevsaddam/gojsonq v1.9.1 h1:zQulEP43nwmq5EKrNWyIgJVbqDeMdC1qzXM/f5O15a0= +github.com/thedevsaddam/gojsonq v2.3.0+incompatible h1:i2lFTvGY4LvoZ2VUzedsFlRiyaWcJm3Uh6cQ9+HyQA8= +github.com/thedevsaddam/gojsonq/v2 v2.5.2 h1:CoMVaYyKFsVj6TjU6APqAhAvC07hTI6IQen8PHzHYY0= +github.com/thedevsaddam/gojsonq/v2 v2.5.2/go.mod h1:bv6Xa7kWy82uT0LnXPE2SzGqTj33TAEeR560MdJkiXs= +github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc= +github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= +github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= +github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= diff --git a/images/README/cpuload.png b/images/README/cpuload.png new file mode 100644 index 0000000000000000000000000000000000000000..01e578c730dafb2a81ecf7a8817cb9b8feab57d1 GIT binary patch literal 29832 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 literal 0 HcmV?d00001 diff --git a/src/display/general/init.go b/src/display/general/init.go index 2677f74b..2b384ad6 100644 --- a/src/display/general/init.go +++ b/src/display/general/init.go @@ -32,6 +32,19 @@ type MainPage struct { NetPara *widgets.Paragraph } +type CPUPage struct { + Grid *ui.Grid + UsrChart *widgets.Gauge + NiceChart *widgets.Gauge + SysChart *widgets.Gauge + IowaitChart *widgets.Gauge + IrqChart *widgets.Gauge + SoftChart *widgets.Gauge + IdleChart *widgets.Gauge + StealChart *widgets.Gauge + CPUChart *widgets.Table +} + // NewPage returns a new page initialized from the MainPage struct func NewPage(numCores int) *MainPage { page := &MainPage{ @@ -46,6 +59,23 @@ func NewPage(numCores int) *MainPage { return page } +func NewCPUPage(numCores int) *CPUPage { + page := &CPUPage{ + Grid: ui.NewGrid(), + UsrChart: widgets.NewGauge(), + NiceChart: widgets.NewGauge(), + SysChart: widgets.NewGauge(), + IowaitChart: widgets.NewGauge(), + IrqChart: widgets.NewGauge(), + SoftChart: widgets.NewGauge(), + IdleChart: widgets.NewGauge(), + StealChart: widgets.NewGauge(), + CPUChart: widgets.NewTable(), + } + page.InitCPU(numCores) + return page +} + // InitGeneral initializes all ui elements for the ui rendered by the grofer command func (page *MainPage) InitGeneral(numCores int) { @@ -108,3 +138,89 @@ func (page *MainPage) InitGeneral(numCores int) { w, h := ui.TerminalDimensions() page.Grid.SetRect(w/2, 0, w, h) } + +func (page *CPUPage) InitCPU(numCores int) { + page.UsrChart.Title = " Usr " + page.UsrChart.Percent = 0 + page.UsrChart.BarColor = ui.ColorBlue + page.UsrChart.BorderStyle.Fg = ui.ColorCyan + page.UsrChart.TitleStyle.Fg = ui.ColorWhite + + page.NiceChart.Title = " Nice " + page.NiceChart.Percent = 0 + page.NiceChart.BarColor = ui.ColorBlue + page.NiceChart.BorderStyle.Fg = ui.ColorCyan + page.NiceChart.TitleStyle.Fg = ui.ColorWhite + + page.SysChart.Title = " Sys " + page.SysChart.Percent = 0 + page.SysChart.BarColor = ui.ColorBlue + page.SysChart.BorderStyle.Fg = ui.ColorCyan + page.SysChart.TitleStyle.Fg = ui.ColorWhite + + page.IowaitChart.Title = " Iowait " + page.IowaitChart.Percent = 0 + page.IowaitChart.BarColor = ui.ColorBlue + page.IowaitChart.BorderStyle.Fg = ui.ColorCyan + page.IowaitChart.TitleStyle.Fg = ui.ColorWhite + + page.IrqChart.Title = " Irq " + page.IrqChart.Percent = 0 + page.IrqChart.BarColor = ui.ColorBlue + page.IrqChart.BorderStyle.Fg = ui.ColorCyan + page.IrqChart.TitleStyle.Fg = ui.ColorWhite + + page.SoftChart.Title = " Soft " + page.SoftChart.Percent = 0 + page.SoftChart.BarColor = ui.ColorBlue + page.SoftChart.BorderStyle.Fg = ui.ColorCyan + page.SoftChart.TitleStyle.Fg = ui.ColorWhite + + page.IdleChart.Title = " Idle " + page.IdleChart.Percent = 0 + page.IdleChart.BarColor = ui.ColorBlue + page.IdleChart.BorderStyle.Fg = ui.ColorCyan + page.IdleChart.TitleStyle.Fg = ui.ColorWhite + + page.StealChart.Title = " Steal " + page.StealChart.Percent = 0 + page.StealChart.BarColor = ui.ColorBlue + page.StealChart.BorderStyle.Fg = ui.ColorCyan + page.StealChart.TitleStyle.Fg = ui.ColorWhite + + page.CPUChart.Title = " CPU " + page.CPUChart.TextStyle = ui.NewStyle(ui.ColorWhite) + page.CPUChart.TextAlignment = ui.AlignCenter + page.CPUChart.RowSeparator = true + + columnWidths := []int{} + for i := 0; i < numCores; i++ { + columnWidths = append(columnWidths, 9) + } + + page.CPUChart.ColumnWidths = columnWidths + + page.Grid.Set( + ui.NewRow(0.17, + ui.NewCol(0.5, page.UsrChart), + ui.NewCol(0.5, page.NiceChart), + ), + ui.NewRow(0.17, + ui.NewCol(0.5, page.SysChart), + ui.NewCol(0.5, page.IowaitChart), + ), + ui.NewRow(0.17, + ui.NewCol(0.5, page.IrqChart), + ui.NewCol(0.5, page.SoftChart), + ), + ui.NewRow(0.17, + ui.NewCol(0.5, page.IdleChart), + ui.NewCol(0.5, page.StealChart), + ), + ui.NewRow(0.30, page.CPUChart), + ) + + w, h := ui.TerminalDimensions() + page.Grid.SetRect(0, 0, w, h) + +} diff --git a/src/display/general/overallGraphs.go b/src/display/general/overallGraphs.go index 680884ac..538344aa 100644 --- a/src/display/general/overallGraphs.go +++ b/src/display/general/overallGraphs.go @@ -26,6 +26,7 @@ import ( "time" ui "github.com/gizak/termui/v3" + info "github.com/pesos/grofer/src/general" "github.com/pesos/grofer/src/utils" ) @@ -44,6 +45,7 @@ func RenderCharts(endChannel chan os.Signal, } defer ui.Close() + var on sync.Once var totalBytesRecv float64 var totalBytesSent float64 @@ -176,6 +178,32 @@ func RenderCharts(endChannel chan os.Signal, myPage.NetworkChart.Data = temp } + + on.Do(func() { + // Get Terminal Dimensions adn clear the UI + w, h := ui.TerminalDimensions() + ui.Clear() + + // Calculate Heigth offset + height := int(h / numCores) + heightOffset := h - (height * numCores) + + // Adjust Memory Bar graph values + myPage.MemoryChart.BarGap = ((w / 2) - (4 * myPage.MemoryChart.BarWidth)) / 4 + + // Adjust CPU Gauge dimensions + if isCPUSet { + for i := 0; i < numCores; i++ { + myPage.CPUCharts[i].SetRect(0, i*height, w/2, (i+1)*height) + ui.Render(myPage.CPUCharts[i]) + } + } + + // Adjust Grid dimensions + myPage.Grid.SetRect(w/2, 0, w, h-heightOffset) + + ui.Render(myPage.Grid) + }) } case <-tick: // Update page with new values @@ -183,3 +211,77 @@ func RenderCharts(endChannel chan os.Signal, } } } + +func RenderCPUinfo(endChannel chan os.Signal, + dataChannel chan *info.CPULoad, + refreshRate int32, + wg *sync.WaitGroup) { + + var on sync.Once + + if err := ui.Init(); err != nil { + log.Fatalf("failed to initialize termui: %v", err) + } + defer ui.Close() + + numCores := runtime.NumCPU() + myPage := NewCPUPage(numCores) + + pause := func() { + run = !run + } + + // Re render UI + updateUI := func() { + w, h := ui.TerminalDimensions() + ui.Clear() + myPage.Grid.SetRect(0, 0, w, h) + ui.Render(myPage.Grid) + } + + updateUI() + + uiEvents := ui.PollEvents() + tick := time.Tick(time.Duration(refreshRate) * time.Millisecond) + for { + select { + case e := <-uiEvents: // For keyboard events + switch e.ID { + case "q", "": // q or Ctrl-C to quit + endChannel <- os.Kill + wg.Done() + return + + case "": + updateUI() + + case "s": // s to stop + pause() + } + + case data := <-dataChannel: // Update chart values + if run { + myPage.UsrChart.Percent = data.Usr + myPage.NiceChart.Percent = data.Nice + myPage.SysChart.Percent = data.Sys + myPage.IowaitChart.Percent = data.Iowait + myPage.IrqChart.Percent = data.Irq + myPage.SoftChart.Percent = data.Soft + myPage.StealChart.Percent = data.Steal + myPage.IdleChart.Percent = data.Idle + + myPage.CPUChart.Rows = data.CPURates + + on.Do(func() { + w, h := ui.TerminalDimensions() + ui.Clear() + myPage.Grid.SetRect(0, 0, w, h) + ui.Render(myPage.Grid) + }) + } + + case <-tick: + updateUI() + } + } +} diff --git a/src/display/process/allProcs.go b/src/display/process/allProcs.go index f6082e72..0cc55b3e 100644 --- a/src/display/process/allProcs.go +++ b/src/display/process/allProcs.go @@ -31,7 +31,6 @@ import ( ) var runAllProc = true -var on1 sync.Once func getData(procs []*proc.Process) []string { var data []string @@ -107,7 +106,7 @@ func getData(procs []*proc.Process) []string { createTime := utils.GetDateFromUnix(ctime) temp = temp + createTime } - for i := 0; i < 24-len(createTime); i++ { + for i := 0; i < 9-len(createTime); i++ { temp = temp + " " } @@ -132,6 +131,8 @@ func AllProcVisuals(dataChannel chan []*proc.Process, log.Fatalf("failed to initialize termui: %v", err) } + var on sync.Once + myPage := NewAllProcsPage() updateUI := func() { @@ -200,7 +201,7 @@ func AllProcVisuals(dataChannel chan []*proc.Process, if runAllProc { myPage.BodyList.Rows = getData(data) - on1.Do(func() { + on.Do(func() { w, h := ui.TerminalDimensions() ui.Clear() myPage.Grid.SetRect(0, 0, w, h) diff --git a/src/display/process/procGraphs.go b/src/display/process/procGraphs.go index 7005adbb..c84a21f6 100644 --- a/src/display/process/procGraphs.go +++ b/src/display/process/procGraphs.go @@ -28,7 +28,6 @@ import ( ) var runProc = true -var on sync.Once func getChildProcs(proc *process.Process) []string { childProcs := []string{"PID Command"} @@ -59,6 +58,8 @@ func ProcVisuals(endChannel chan os.Signal, log.Fatalf("failed to initialize termui: %v", err) } + var on sync.Once + // Create new page myPage := NewPerProcPage() diff --git a/src/general/cpuInfo.go b/src/general/cpuInfo.go new file mode 100644 index 00000000..9c2156a2 --- /dev/null +++ b/src/general/cpuInfo.go @@ -0,0 +1,112 @@ +/* +Copyright © 2020 The PES Open Source Team pesos@pes.edu + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package general + +import ( + "fmt" + "os" + "os/exec" + "strconv" + "sync" + "time" + + gjson "github.com/tidwall/gjson" +) + +// CPULoad type contains info about load on CPU from various sources +// as well as general stats about the CPU. +type CPULoad struct { + Usr int `json:"usr"` + Nice int `json:"nice"` + Sys int `json:"sys"` + Iowait int `json:"iowait"` + Irq int `json:"irq"` + Soft int `json:"soft"` + Steal int `json:"steal"` + Guest int `json:"guest"` + Gnice int `json:"gnice"` + Idle int `json:"idle"` + CPURates [][]string `json:"-"` // Has first row with CPU names and second row with CPU usage rates, might not be ideal format for export +} + +func NewCPULoad() *CPULoad { + return &CPULoad{} +} + +func (c *CPULoad) updateCPULoad() error { + mpstat := "mpstat" + arg0 := "-o" + arg1 := "JSON" + cmd := exec.Command(mpstat, arg0, arg1) + stdout, err := cmd.Output() + if err != nil { + return err + } + + statsExtract := gjson.Get(string(stdout), "sysstat.hosts.0.statistics.0.cpu-load.0") + stats := statsExtract.Map() + c.Usr = int(stats["usr"].Int()) + c.Nice = int(stats["nice"].Int()) + c.Sys = int(stats["sys"].Int()) + c.Iowait = int(stats["iowait"].Int()) + c.Irq = int(stats["irq"].Int()) + c.Soft = int(stats["soft"].Int()) + c.Steal = int(stats["steal"].Int()) + c.Guest = int(stats["guest"].Int()) + c.Gnice = int(stats["gnice"].Int()) + c.Idle = int(stats["idle"].Int()) + + cpuRates, err := GetCPURates() + if err != nil { + return err + } + + rate := []string{} + cpus := []string{} + for i, cpuRate := range cpuRates { + cpus = append(cpus, "CPU "+strconv.Itoa(i)) + rate = append(rate, fmt.Sprintf("%.2f", cpuRate)) + } + rates := [][]string{cpus, rate} + + c.CPURates = rates + + return nil +} + +// GetCPULoad updated the CPULoad struct and serves the data to the data channel. +func GetCPULoad(cpuLoad *CPULoad, + dataChannel chan *CPULoad, + endChannel chan os.Signal, + refreshRate int32, + wg *sync.WaitGroup) error { + for { + select { + case <-endChannel: // Stop execution if end signal received + wg.Done() + return nil + + default: // Get Memory and CPU rates per core periodically + err := cpuLoad.updateCPULoad() + if err != nil { + return err + } + dataChannel <- cpuLoad + time.Sleep(time.Duration(refreshRate) * time.Millisecond) + } + } +} diff --git a/src/general/generalStats.go b/src/general/generalStats.go index bd04b70d..d5fe9437 100644 --- a/src/general/generalStats.go +++ b/src/general/generalStats.go @@ -36,10 +36,10 @@ func GlobalStats(endChannel chan os.Signal, default: // Get Memory and CPU rates per core periodically - go PrintCPURates(dataChannel) - go PrintMemRates(dataChannel) - go PrintDiskRates(dataChannel) - PrintNetRates(dataChannel) + go ServeCPURates(dataChannel) + go ServeMemRates(dataChannel) + go ServeDiskRates(dataChannel) + ServeNetRates(dataChannel) time.Sleep(time.Duration(refreshRate) * time.Millisecond) } } diff --git a/src/general/printStats.go b/src/general/serveStats.go similarity index 80% rename from src/general/printStats.go rename to src/general/serveStats.go index bcefeea3..6d4bf645 100644 --- a/src/general/printStats.go +++ b/src/general/serveStats.go @@ -34,8 +34,17 @@ func roundOff(num uint64) float64 { return math.Round(x*10) / 10 } -// PrintCPURates print the cpu rates -func PrintCPURates(cpuChannel chan utils.DataStats) { +// GetCPURates fetches and returns the current cpu rate +func GetCPURates() ([]float64, error) { + cpuRates, err := cpu.Percent(time.Second, true) + if err != nil { + return nil, err + } + return cpuRates, nil +} + +// ServeCPURates serves the cpu rates to the cpu channel +func ServeCPURates(cpuChannel chan utils.DataStats) { cpuRates, err := cpu.Percent(time.Second, true) if err != nil { log.Fatal(err) @@ -47,8 +56,8 @@ func PrintCPURates(cpuChannel chan utils.DataStats) { cpuChannel <- data } -// PrintMemRates prints stats about the memory -func PrintMemRates(dataChannel chan utils.DataStats) { +// ServeMemRates serves stats about the memory to the data channel +func ServeMemRates(dataChannel chan utils.DataStats) { memory, err := mem.VirtualMemory() if err != nil { log.Fatal(err) @@ -64,7 +73,8 @@ func PrintMemRates(dataChannel chan utils.DataStats) { dataChannel <- data } -func PrintDiskRates(dataChannel chan utils.DataStats) { +// ServeDiskRates serves the disk rate data to the data channel +func ServeDiskRates(dataChannel chan utils.DataStats) { var partitions []disk.PartitionStat var err error @@ -103,7 +113,8 @@ func PrintDiskRates(dataChannel chan utils.DataStats) { dataChannel <- data } -func PrintNetRates(dataChannel chan utils.DataStats) { +// ServeNetRates serves info about the network to the data channel +func ServeNetRates(dataChannel chan utils.DataStats) { netStats, err := net.IOCounters(false) if err != nil { log.Fatal(err)