From 27a1aac63b10ffc94a1bdf7ec9a1c261bd682d2b Mon Sep 17 00:00:00 2001 From: davinci Date: Mon, 10 Sep 2018 15:26:33 +0300 Subject: [PATCH] lightweight stats --- commands/auto.go | 9 +--- commands/connect.go | 110 ++------------------------------------------ commands/init.go | 7 +-- commands/logs.go | 2 +- commands/restart.go | 2 +- commands/run.go | 22 +++++++-- commands/start.go | 2 +- commands/stop.go | 2 +- commands/utils.go | 73 ++++------------------------- 9 files changed, 38 insertions(+), 191 deletions(-) diff --git a/commands/auto.go b/commands/auto.go index 54c920e..17585ec 100644 --- a/commands/auto.go +++ b/commands/auto.go @@ -46,10 +46,6 @@ $ iptb auto -count 5 -type Name: "start", Usage: "starts nodes immediately", }, - cli.BoolFlag{ - Name: "stats", - Usage: "Output statistics on the command execution", - }, }, Action: func(c *cli.Context) error { flagRoot := c.GlobalString("IPTB_ROOT") @@ -58,7 +54,6 @@ $ iptb auto -count 5 -type flagStart := c.Bool("start") flagCount := c.Int("count") flagForce := c.Bool("force") - flagStats := c.Bool("stats") tb := testbed.NewTestbed(path.Join(flagRoot, "testbeds", flagTestbed)) if err := testbed.AlreadyInitCheck(tb.Dir(), flagForce); err != nil { @@ -93,7 +88,7 @@ $ iptb auto -count 5 -type return err } - if err := buildReport(results, "Initialize Nodes", flagStats); err != nil { + if err := buildReport(results, "text"); err != nil { return err } @@ -107,7 +102,7 @@ $ iptb auto -count 5 -type return err } - if err := buildReport(results, "Start nodes", flagStats); err != nil { + if err := buildReport(results, "text"); err != nil { return err } } diff --git a/commands/connect.go b/commands/connect.go index 6b24eb7..fe24d96 100644 --- a/commands/connect.go +++ b/commands/connect.go @@ -1,13 +1,9 @@ package commands import ( - "bufio" "context" "fmt" - "os" "path" - "strconv" - "strings" "time" "github.com/pkg/errors" @@ -16,7 +12,6 @@ import ( "github.com/ipfs/iptb/testbed" ) -// TODO:Add explanation in the description for topology flag var ConnectCmd = cli.Command{ Category: "CORE", Name: "connect", @@ -28,17 +23,12 @@ The connect command allows for connecting sets of nodes together. Every node listed in the first set, will try to connect to every node listed in the second set. -There are four variants of the command. It can accept no arugments, -a single argument, two arguments or a file. The no argument and single argument -expands out to the two argument usage. The file argument, parses the file and exapnds it -to the two argument usage. The file should have the following format: -- Node Origin:Connection 1 Connection 2 .... Connection n e.g. 0:1,2,3,4 -- Lines starting with # are ignored (comment line), empty lines are also ignored - +There are three variants of the command. It can accept no arugments, +a single argument, two arguments. The no argument and single argument +expands out to the two argument usage $ iptb connect => iptb connect [0-C] [0-C] $ iptb connect [n-m] => iptb connect [n-m] [n-m] $ iptb connect [n-m] [i-k] -$ iptb connect -topology /directory/topology.txt Sets of nodes can be expressed in the following ways @@ -56,16 +46,11 @@ INPUT EXPANDED Usage: "timeout on the command", Value: "30s", }, - cli.StringFlag{ - Name: "topology", - Usage: "specify a network topology file", - }, }, Action: func(c *cli.Context) error { flagRoot := c.GlobalString("IPTB_ROOT") flagTestbed := c.GlobalString("testbed") flagTimeout := c.String("timeout") - flagTopology := c.String("topology") timeout, err := time.ParseDuration(flagTimeout) if err != nil { @@ -73,27 +58,6 @@ INPUT EXPANDED } tb := testbed.NewTestbed(path.Join(flagRoot, "testbeds", flagTestbed)) - // Case Topoloogy is specified - if len(flagTopology) != 0 { - nodes, err := tb.Nodes() - if err != nil { - return err - } - topologyGraph, err := parseTopology(flagTopology, len(nodes)) - if err != nil { - return err - } - for _, connectionRow := range topologyGraph { - from := connectionRow[0] - to := connectionRow[1:] - err = connectNodes(tb, []int{from}, to, timeout) - if err != nil { - return err - } - } - return nil - } - // Case range is specified args := c.Args() switch c.NArg() { @@ -156,71 +120,5 @@ func connectNodes(tb testbed.BasicTestbed, from, to []int, timeout time.Duration } } - return buildReport(results, "", false) -} - -func parseTopology(fileDir string, numberOfNodes int) ([][]int, error) { - - // Scan Input file Line by Line // - inFile, err := os.Open(fileDir) - defer inFile.Close() - if err != nil { - return nil, err - } - scanner := bufio.NewScanner(inFile) - scanner.Split(bufio.ScanLines) - - // Store number of line to produce meaningful errors - lineNumber := 1 - // Topology graph implemented as an Adjacency Matrix DS - // This intermediate variable it could be terminated to increase peformance - // This would decrease code readability. - var topology [][]int - - for scanner.Scan() { - var destinations []string - var lineTokenized []string - line := scanner.Text() - // Check if the line is a comment or empty and skip it// - if len(line) == 0 || line[0] == '#' { - lineNumber++ - continue - } else { - lineTokenized = strings.Split(line, ":") - // Check if the format is correct - if len(lineTokenized) == 1 { - return nil, errors.New("Line " + strconv.Itoa(lineNumber) + " does not follow the correct format") - } - destinations = strings.Split(lineTokenized[1], ",") - } - // Declare the topology in that line, the first element is the origin - var topologyLine []int - // Parse origin in the line - origin, err := strconv.Atoi(lineTokenized[0]) - // Check if it can be casted to integer - if err != nil { - return nil, errors.New("Line " + strconv.Itoa(lineNumber) + " of connection graph, could not be parsed") - } - // Check if the node is out of range - if origin >= numberOfNodes { - return nil, errors.New("Origin node in line " + strconv.Itoa(lineNumber) + " out of range") - } - topologyLine = append(topologyLine, origin) - for _, destination := range destinations { - // Check if it can be casted to integer - target, err := strconv.Atoi(destination) - if err != nil { - return nil, errors.New("Check line " + strconv.Itoa(lineNumber) + " of connection graph, could not be parsed") - } - // Check if the node is out of range - if target >= numberOfNodes { - return nil, errors.New("Target node in line " + strconv.Itoa(lineNumber) + " out of range") - } - // Append destination to graph - topologyLine = append(topologyLine, target) - } - lineNumber++ - topology = append(topology, topologyLine) - } - return topology, nil + return buildReport(results, "text") } diff --git a/commands/init.go b/commands/init.go index 942b420..81684c4 100644 --- a/commands/init.go +++ b/commands/init.go @@ -21,10 +21,6 @@ var InitCmd = cli.Command{ Name: "terminator", Hidden: true, }, - cli.BoolFlag{ - Name: "stats", - Usage: "Output statistics on the command execution", - }, }, Before: func(c *cli.Context) error { if present := isTerminatorPresent(c); present { @@ -36,7 +32,6 @@ var InitCmd = cli.Command{ Action: func(c *cli.Context) error { flagRoot := c.GlobalString("IPTB_ROOT") flagTestbed := c.GlobalString("testbed") - flagStats := c.Bool("stats") tb := testbed.NewTestbed(path.Join(flagRoot, "testbeds", flagTestbed)) nodes, err := tb.Nodes() @@ -64,6 +59,6 @@ var InitCmd = cli.Command{ return err } - return buildReport(results, "Initialize Nodes", flagStats) + return buildReport(results, "text") }, } diff --git a/commands/logs.go b/commands/logs.go index c20df1e..d7435d7 100644 --- a/commands/logs.go +++ b/commands/logs.go @@ -84,7 +84,7 @@ var LogsCmd = cli.Command{ return err } - return buildReport(results, "", false) + return buildReport(results, "text") }, } diff --git a/commands/restart.go b/commands/restart.go index a46796e..2ec6d71 100644 --- a/commands/restart.go +++ b/commands/restart.go @@ -68,6 +68,6 @@ var RestartCmd = cli.Command{ return err } - return buildReport(results, "", false) + return buildReport(results, "text") }, } diff --git a/commands/run.go b/commands/run.go index f751655..6ef12c1 100644 --- a/commands/run.go +++ b/commands/run.go @@ -22,9 +22,10 @@ var RunCmd = cli.Command{ Name: "terminator", Hidden: true, }, - cli.BoolFlag{ - Name: "stats", - Usage: "Output statistics on the command execution", + cli.StringFlag{ + Name: "encoding", + Usage: "Specify the output format, current options JSON and text", + Value: "text", }, }, Before: func(c *cli.Context) error { @@ -37,8 +38,19 @@ var RunCmd = cli.Command{ Action: func(c *cli.Context) error { flagRoot := c.GlobalString("IPTB_ROOT") flagTestbed := c.GlobalString("testbed") - flagStats := c.Bool("stats") + flagFormat := c.String("encoding") + // Compare everything to lower to make it case insentive + flagFormatLwr := strings.ToLower(flagFormat) + // Parse output format + switch flagFormatLwr { + case "text": + // input is correct + case "json": + // input is correct + default: + NewUsageError("the output encoding provided is not parsable") + } tb := testbed.NewTestbed(path.Join(flagRoot, "testbeds", flagTestbed)) nodes, err := tb.Nodes() if err != nil { @@ -65,6 +77,6 @@ var RunCmd = cli.Command{ return err } - return buildReport(results, strings.Join(args[:], " "), flagStats) + return buildReport(results, flagFormatLwr) }, } diff --git a/commands/start.go b/commands/start.go index 7fbd601..f7f28fb 100644 --- a/commands/start.go +++ b/commands/start.go @@ -64,6 +64,6 @@ var StartCmd = cli.Command{ return err } - return buildReport(results, "", false) + return buildReport(results, "text") }, } diff --git a/commands/stop.go b/commands/stop.go index 6d520b4..f13e792 100644 --- a/commands/stop.go +++ b/commands/stop.go @@ -46,6 +46,6 @@ var StopCmd = cli.Command{ return err } - return buildReport(results, "", false) + return buildReport(results, "text") }, } diff --git a/commands/utils.go b/commands/utils.go index fc2ae2e..a25ecd4 100644 --- a/commands/utils.go +++ b/commands/utils.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "io" - "math" "os" "strconv" "strings" @@ -181,7 +180,7 @@ func validRange(list []int, total int) error { return nil } -func buildReport(results []Result, command string, statsFlag bool) error { +func buildReport(results []Result, encoding string) error { var errs []error for _, rs := range results { @@ -190,77 +189,25 @@ func buildReport(results []Result, command string, statsFlag bool) error { } if rs.Output != nil { - fmt.Printf("node[%d] exit %d\n", rs.Node, rs.Output.ExitCode()) - if rs.Output.Error() != nil { - fmt.Printf("%s", rs.Output.Error()) + if encoding == "text" { + fmt.Printf("node[%d] exit %d Elapsed %.4fs \n", rs.Node, rs.Output.ExitCode(), rs.TimeElapsed) + if rs.Output.Error() != nil { + fmt.Printf("%s", rs.Output.Error()) + } + + } else { + rsJSON, _ := json.Marshal(rs) + fmt.Printf("%s\n", rsJSON) } - fmt.Println() - io.Copy(os.Stdout, rs.Output.Stdout()) io.Copy(os.Stdout, rs.Output.Stderr()) - - fmt.Println() } } - if statsFlag { - stats, err := buildStats(results) - if err != nil { - errs = append(errs, err) - } else { - statsJSON, _ := json.Marshal(stats) - fmt.Printf("Executed command < %s > on %d node(s) \nTime Statistics %s \n", command, len(results), string(statsJSON)) - } - } - if len(errs) != 0 { return cli.NewMultiError(errs...) } return nil } - -type Stats struct { - Mean float64 - Std float64 - Max float64 - Quartile3 float64 - Median float64 - Quartile1 float64 - Min float64 -} - -func buildStats(results []Result) (Stats, error) { - if len(results) == 0 { - return Stats{}, errors.New("Results are empty") - } - sum := 0. - sumSquarred := 0. - min := results[0].TimeElapsed - max := results[0].TimeElapsed - for _, rs := range results { - sum += rs.TimeElapsed - sumSquarred += rs.TimeElapsed * rs.TimeElapsed - if rs.TimeElapsed < min { - min = rs.TimeElapsed - } - if rs.TimeElapsed > max { - max = rs.TimeElapsed - } - } - - mean := sum / float64(len(results)) - variance := sumSquarred/float64(len(results)) - mean*mean - - return Stats{ - Mean: mean, - Std: math.Sqrt(variance), - Max: max, - Quartile3: 0., - Median: 0., - Quartile1: 0., - Min: min, - }, nil - -}