From 891316004266b292e737eb209808ef754643d5c7 Mon Sep 17 00:00:00 2001 From: Bjarne Koll Date: Tue, 30 Mar 2021 01:55:59 +0200 Subject: [PATCH] Improve documentation in readme and cli Switches to the urfave/cli/v2 framework for improved cli paramenter documentation. This commit also updates the README.md to make the first usage of this small cli even easier. --- .github/workflows/release.yml | 2 +- README.md | 66 +++++++++++++++++++++++++++++-- {lib => cmd}/lib.go | 22 ++++++----- cmd/root.go | 38 ++++++++++++++++++ go.mod | 2 +- go.sum | 17 ++++++++ main.go | 73 +++++++++++++++++------------------ scripts/build.sh | 2 +- 8 files changed, 169 insertions(+), 53 deletions(-) rename {lib => cmd}/lib.go (65%) create mode 100644 cmd/root.go mode change 100644 => 100755 scripts/build.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8c207a6..9a6fc2e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,7 @@ jobs: # Build - name: Build project - run: 'go build' + run: './scripts/build.sh' # Release binaries - name: Release diff --git a/README.md b/README.md index 0e55edb..0719574 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -carre -======== +# carre ------------------------------------------------------------------------------------------------------------------------ @@ -9,9 +8,68 @@ information but on container basis. ------------------------------------------------------------------------------------------------------------------------ -Install -------- +## Install +### Through golang + +You may simply install the go project using the following go command: + +```shell +go get -u github.com/lynxplay/carre +``` + +### Release binaries + +**Carre** also provides pre-build binaries for finished releases to avoid the requirement for a complete go installation +on every system. The binaries can be found in the respective releases [here](https://github.com/lynxplay/carre/releases) +. + +------------------------------------------------------------------------------------------------------------------------ + +## Usage + +### -container (required) + +The container parameter specifies the name of the container for which the metrics are to be gathered. It is required as +this is the core point of caree, + +### -interval + +The interval parameter can specify the duration between each data point collected by caree. + +#### Default: 500ms + +#### Units: "ns", "us" (or "µs"), "ms", "s", "m", "h" + +### -format + +The format parameter specifies in which format the output is printed. As of right now the follow formats exist. + +- 'CSV' provides a comma separated list of values **including** the csv header defining the collum values. +- 'JSON' provides a set of json data points mapping the respective metric to its value. Each line is an individually + complete json string. + +#### Default: JSON + +------------------------------------------------------------------------------------------------------------------------ + +## Examples + +```shell +carre --container my_great_container +{"%CPU":"0.1","%MEM":"0.2","COMMAND":"java -jar application.jar","PID":"9809","RSS":"130072","START":"22:20","STAT":"Ssl","TIME":"0:04","TTY":"?","USER":"root","VSZ":"19894616"} +{"%CPU":"0.1","%MEM":"0.2","COMMAND":"java -jar application.jar","PID":"9809","RSS":"130072","START":"22:20","STAT":"Ssl","TIME":"0:04","TTY":"?","USER":"root","VSZ":"19894616"} +{"%CPU":"0.1","%MEM":"0.2","COMMAND":"java -jar application.jar","PID":"9809","RSS":"130072","START":"22:20","STAT":"Ssl","TIME":"0:04","TTY":"?","USER":"root","VSZ":"19894616"} +{"%CPU":"0.1","%MEM":"0.2","COMMAND":"java -jar application.jar","PID":"9809","RSS":"130072","START":"22:20","STAT":"Ssl","TIME":"0:04","TTY":"?","USER":"root","VSZ":"19894616"} +{"%CPU":"0.1","%MEM":"0.2","COMMAND":"java -jar application.jar","PID":"9809","RSS":"130072","START":"22:20","STAT":"Ssl","TIME":"0:04","TTY":"?","USER":"root","VSZ":"19894616"} +{"%CPU":"0.1","%MEM":"0.2","COMMAND":"java -jar application.jar","PID":"9809","RSS":"130072","START":"22:20","STAT":"Ssl","TIME":"0:04","TTY":"?","USER":"root","VSZ":"19894616"} +{"%CPU":"0.1","%MEM":"0.2","COMMAND":"java -jar application.jar","PID":"9809","RSS":"130072","START":"22:20","STAT":"Ssl","TIME":"0:04","TTY":"?","USER":"root","VSZ":"19894616"} +{"%CPU":"0.1","%MEM":"0.2","COMMAND":"java -jar application.jar","PID":"9809","RSS":"130072","START":"22:20","STAT":"Ssl","TIME":"0:04","TTY":"?","USER":"root","VSZ":"19894616"} +{"%CPU":"0.1","%MEM":"0.2","COMMAND":"java -jar application.jar","PID":"9809","RSS":"130072","START":"22:20","STAT":"Ssl","TIME":"0:04","TTY":"?","USER":"root","VSZ":"19894616"} +{"%CPU":"0.1","%MEM":"0.2","COMMAND":"java -jar application.jar","PID":"9809","RSS":"130072","START":"22:20","STAT":"Ssl","TIME":"0:04","TTY":"?","USER":"root","VSZ":"19894616"} +{"%CPU":"0.1","%MEM":"0.2","COMMAND":"java -jar application.jar","PID":"9809","RSS":"130072","START":"22:20","STAT":"Ssl","TIME":"0:04","TTY":"?","USER":"root","VSZ":"19894616"} +{"%CPU":"0.1","%MEM":"0.2","COMMAND":"java -jar application.jar","PID":"9809","RSS":"130072","START":"22:20","STAT":"Ssl","TIME":"0:04","TTY":"?","USER":"root","VSZ":"19894616"} +``` diff --git a/lib/lib.go b/cmd/lib.go similarity index 65% rename from lib/lib.go rename to cmd/lib.go index 955054c..e0d486a 100644 --- a/lib/lib.go +++ b/cmd/lib.go @@ -1,4 +1,4 @@ -package lib +package cmd import ( "context" @@ -8,6 +8,7 @@ import ( "github.com/docker/docker/client" "log" "strings" + "time" ) var ( @@ -24,8 +25,8 @@ const ( ) type DisplayContext struct { - Name string - OutputMode OutputMode + Name string + OutputFormat OutputMode } func DisplayCurrentStats(dockerClient *client.Client, ctx DisplayContext) { @@ -35,11 +36,12 @@ func DisplayCurrentStats(dockerClient *client.Client, ctx DisplayContext) { return } - switch ctx.OutputMode { + switch ctx.OutputFormat { case JSON: for i := range result.Processes { process := result.Processes[i] processValues := make(map[string]string) + processValues["TIMESTAMP"] = time.Now().String() for j := range process { processValues[result.Titles[j]] = process[j] } @@ -51,21 +53,23 @@ func DisplayCurrentStats(dockerClient *client.Client, ctx DisplayContext) { } case CSV: if !PrintedCSVHeader { - fmt.Println(strings.Join(result.Titles, ",")) + fmt.Println(strings.Join(result.Titles, ",") + ",TIMESTAMP") // Avoid slice reallocation for csv print PrintedCSVHeader = true } for i := range result.Processes { - fmt.Println(strings.Join(result.Processes[i], ",")) + fmt.Println(strings.Join(result.Processes[i], ",") + "," + time.Now().String()) } } } -func ParseOutputMode(str string) (OutputMode, error) { +func ParseOutputFormat(str string) (OutputMode, error) { switch strings.ToUpper(str) { + case "CSV": + return CSV, nil case "JSON": - return JSON, nil + return JSON, nil // Different case than the default case, JSON was parsed correctly! default: - return CSV, errors.New("failed to parse output mode; defaulting to CSV") + return JSON, errors.New("failed to parse output format; defaulting to JSON") } } diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..81f3dba --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "github.com/docker/docker/client" + "github.com/urfave/cli/v2" + "log" + "os" + "os/signal" + "time" +) + +func Execute(ctx *cli.Context) error { + outputFormat, err := ParseOutputFormat(ctx.String("format")) + + context := DisplayContext{ + Name: ctx.String("container"), + OutputFormat: outputFormat, + } + + dockerClient, err := client.NewEnvClient() + if err != nil { + log.Fatalf("Failed to create environment dockerClient: %s", err) + return err + } + + signalBus := make(chan os.Signal, 1) + signal.Notify(signalBus, os.Interrupt, os.Kill) + + timer := time.NewTicker(ctx.Duration("interval")) + for { + select { + case _ = <-signalBus: + return nil + case _ = <-timer.C: + DisplayCurrentStats(dockerClient, context) + } + } +} diff --git a/go.mod b/go.mod index a07771d..506dc09 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,8 @@ require ( github.com/docker/docker v1.13.1 github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect - github.com/gonvenience/bunt v1.3.2 github.com/opencontainers/go-digest v1.0.0 // indirect github.com/stretchr/testify v1.7.0 // indirect + github.com/urfave/cli/v2 v2.3.0 golang.org/x/net v0.0.0-20210329181859-df645c7b52b1 // indirect ) diff --git a/go.sum b/go.sum index 2e247a5..697096c 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -30,6 +34,7 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= @@ -58,12 +63,20 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -102,6 +115,7 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -115,10 +129,13 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/main.go b/main.go index 94d7cd8..1eab3e5 100644 --- a/main.go +++ b/main.go @@ -1,54 +1,53 @@ package main import ( - "flag" - "github.com/docker/docker/client" - "github.com/gonvenience/bunt" - "github.com/lynxplay/carre/lib" + "github.com/lynxplay/carre/cmd" + "github.com/urfave/cli/v2" "log" "os" - "os/signal" "time" ) -var ( - container = flag.String("container", "", "-container ") - interval = flag.Duration("interval", time.Millisecond*500, "-duration 10s") - output = flag.String("output", "CSV", "-output CSV") -) +var version string func main() { - flag.Parse() - if "" == *container { - flag.Usage() - _, _ = bunt.Println("Red{-container is required!}") - return - } - mode, err := lib.ParseOutputMode(*output) - if err != nil { - _, _ = bunt.Printf("Orange{WARN: %s}\n", err) + app := &cli.App{ + Name: "carre", + Usage: "Docker process data point collection", + Version: getVersion(), + Action: cmd.Execute, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "container", + Aliases: []string{"C"}, + Usage: "unique container name", + Required: true, + }, + &cli.StringFlag{ + Name: "format", + Aliases: []string{"F"}, + Value: "JSON", + Usage: "output format of caree", + }, + &cli.DurationFlag{ + Name: "interval", + Aliases: []string{"I"}, + Value: 500 * time.Millisecond, + Usage: "interval between data point collection", + }, + }, } + app.EnableBashCompletion = true - context := lib.DisplayContext{ - Name: *container, - OutputMode: mode, - } - - dockerClient, err := client.NewEnvClient() + err := app.Run(os.Args) if err != nil { - log.Fatalf("Failed to create environment dockerClient: %s", err) + log.Fatal(err) } +} - signalBus := make(chan os.Signal, 1) - signal.Notify(signalBus, os.Interrupt, os.Kill) - - timer := time.NewTicker(*interval) - for { - select { - case _ = <-signalBus: - return - case _ = <-timer.C: - lib.DisplayCurrentStats(dockerClient, context) - } +func getVersion() string { + if version == "" { + return "develop" } + return version } diff --git a/scripts/build.sh b/scripts/build.sh old mode 100644 new mode 100755 index 0d2a4d4..3bf9a81 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -52,7 +52,7 @@ while read -r OS ARCH; do echo -e "Running go build of version \\033[1;3m${TOOL_VERSION}\\033[0m for \\033[1;91m${OS}\\033[0m/\\033[1;31m${ARCH}\\033[0m: \\033[93m$(basename "${TARGET_FILE}")\\033[0m" CGO_ENABLED=0 GOOS="${OS}" GOARCH="${ARCH}" go build \ -tags netgo \ - -ldflags "-s -w -extldflags '-static'" \ + -ldflags "-s -w -extldflags '-static' -X main.version=${TOOL_VERSION}" \ -o "${TARGET_FILE}" \ main.go &