diff --git a/README.md b/README.md index c2c7746..3f7377a 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,53 @@ +# StatusMonitor +Simple command line app for monitoring CPU of the Status.im application running on the device (or simulator). +--- + +TODO: + + - iOS support + - Add more metrics (memory, custom expvars, etc) + - Optimize for higher frequencies (now 1s resolution) + +--- + +# Installation + +Just: + +``` +go get github.com/divan/statusmonitor +``` + +# Usage + +Just run `statusmonitor` binary. It will automatically connect to your device using `adb`, find the Status.im PID and start collecting data: +``` +./statusmonitor +``` +![](./demo/demo0.png) + +Press `q` to exit. + +If you want to analyze data after, use `-csv` switch — all data will be written in the CSV file. Name of the file is autogenerated in form `20160102_150405.csv` in the current folder. + +# Plot CSV with R +To analyze the data, you may use R lang. Here is a sample code that works well in R Studio: + +Load data and convert UNIX timestamps into R's POSIXct object: ``` df = read.csv("~/Downloads/data.csv") df$timestamp = as.POSIXct(df$timestamp, origin="1970-01-01") ``` -Draw the plot; +Draw the plot: + ``` plot(df, type="l", main="status CPU usage", col = 'green', lwd=2, cex.main=1.5, cex.axis=1, xlab="time", xaxt="n") axis.POSIXct(1, df$timestamp, format="%H:%M:%S") ``` +![](./demo/demo1.png) or interactive version with plotly: @@ -17,3 +55,4 @@ or interactive version with plotly: library(plotly) plot_ly(df, x = df$timestamp, y = df$cpu, type = 'scatter', mode = 'lines', fill = 'tozeroy') ``` +![](./demo/demo2.png) diff --git a/adb_shell.go b/adb_shell.go index 93612a3..184c767 100644 --- a/adb_shell.go +++ b/adb_shell.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "os/exec" - "strconv" "strings" ) @@ -22,22 +21,12 @@ func adbCPU(pid int64) (float64, error) { return 0, err } - line := parseTopOutput(out) - - fields := strings.Fields(line) - if len(fields) < 10 { - fmt.Println("[ERROR]: wrong top output", fields) - return 0, ErrParse - } - cpu, err := strconv.ParseFloat(fields[9], 64) + top, err := NewTopOutput(out) if err != nil { - // this usually means that app is in background and top - // omits - fmt.Println("[ERROR] Parse CPU value:", err) - fmt.Println("Output:", fields) - return 0, ErrParse + return 0, err } - return cpu, nil + + return top.CPU, nil } // adbShell calls custom command via 'adb shell` and returns it's stdout output. @@ -59,10 +48,3 @@ func adbShell(command string) (string, error) { return buf.String(), nil } - -// parseTopOutput parses line from the android shell top's output. -// It contains one line with data and many newlines. -func parseTopOutput(data string) string { - lines := strings.Split(data, "\r") - return strings.Replace(lines[0], "\r", "", -1) -} diff --git a/demo/demo0.png b/demo/demo0.png new file mode 100644 index 0000000..c9ab914 Binary files /dev/null and b/demo/demo0.png differ diff --git a/demo/demo1.png b/demo/demo1.png new file mode 100644 index 0000000..e631abf Binary files /dev/null and b/demo/demo1.png differ diff --git a/demo/demo2.png b/demo/demo2.png new file mode 100644 index 0000000..2f5f67f Binary files /dev/null and b/demo/demo2.png differ diff --git a/top.go b/top.go new file mode 100644 index 0000000..9a3c6eb --- /dev/null +++ b/top.go @@ -0,0 +1,44 @@ +package main + +import ( + "fmt" + "strconv" + "strings" +) + +// TopOutput represents data from 'top' command output +// for single process. +type TopOutput struct { + CPU float64 +} + +// NewTopOutput creates new TopOutput from raw stdout data. +func NewTopOutput(data string) (*TopOutput, error) { + fields := parseTopOutput(data) + if len(fields) < 11 { + fmt.Println("[ERROR]: wrong top output", fields) + return nil, ErrParse + } + + // eithh field is supposed to be CPU value + cpu, err := strconv.ParseFloat(fields[8], 64) + if err != nil { + // top output might be different from system to system, so log + // this verbosely + fmt.Println("[ERROR] Parse CPU value:", err) + fmt.Println("Output:", fields) + return nil, ErrParse + } + + return &TopOutput{ + CPU: cpu, + }, nil +} + +func parseTopOutput(data string) []string { + lines := strings.Split(data, "\r") + line := strings.Replace(lines[0], "\r", "", -1) + line = strings.TrimSpace(line) + fields := strings.Fields(line) + return fields +}