diff --git a/bench/bench.go b/bench/bench.go index f366cf7c0..83d078c43 100644 --- a/bench/bench.go +++ b/bench/bench.go @@ -118,7 +118,7 @@ func (sb *serialBenchmark) Init(hosts []string, agentNum int) error { // and the values are the results of each benchmark's Run method. func (sb *serialBenchmark) Run(ctx context.Context, agentNum int) map[string]interface{} { results := make(map[string]interface{}, len(sb.benchmarkers)) - runtimes := make(map[string]time.Duration) + runtimes := make(map[string]interface{}) total_start := time.Now() for i, b := range sb.benchmarkers { start := time.Now() diff --git a/bench/prettify.go b/bench/prettify.go new file mode 100644 index 000000000..e0636bec3 --- /dev/null +++ b/bench/prettify.go @@ -0,0 +1,34 @@ +package bench + +import "time" + +// wrapper type to force human-readable JSON output +type PrettyDuration time.Duration + +func (d PrettyDuration) MarshalJSON() ([]byte, error) { + s := time.Duration(d).String() + return []byte("\"" + s + "\""), nil +} + +// Recursively replaces elements of ugly types with their pretty wrappers +func Prettify(m map[string]interface{}) map[string]interface{} { + newmap := make(map[string]interface{}) + for k, v := range m { + switch v.(type) { + case map[string]interface{}: + newmap[k] = Prettify(v.(map[string]interface{})) + case []time.Duration: + newslice := make([]PrettyDuration, len(v.([]time.Duration))) + slice := v.([]time.Duration) + for n, e := range slice { + newslice[n] = PrettyDuration(e) + } + newmap[k] = newslice + case time.Duration: + newmap[k] = PrettyDuration(v.(time.Duration)) + default: + newmap[k] = v + } + } + return newmap +} diff --git a/bench/prettify_test.go b/bench/prettify_test.go new file mode 100644 index 000000000..20269d73e --- /dev/null +++ b/bench/prettify_test.go @@ -0,0 +1,93 @@ +package bench_test + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "testing" + "time" + + "github.com/pilosa/pilosa/bench" +) + +func prettyEncode(data map[string]interface{}) string { + pretty := bench.Prettify(data) + jsonString := new(bytes.Buffer) + enc := json.NewEncoder(jsonString) + enc.SetIndent("", " ") + err := enc.Encode(pretty) + if err != nil { + fmt.Fprintln(os.Stderr, err) + } + + return jsonString.String() +} + +func TestPrettifyString(t *testing.T) { + res := make(map[string]interface{}, 1) + res["0"] = "foobar" + pretty := prettyEncode(res) + + expected := ` +{ + "0": "foobar" +} +`[1:] + + if pretty != expected { + t.Fatalf("Pretty string doesn't match") + } +} + +func TestPrettifyInt(t *testing.T) { + res := make(map[string]interface{}, 1) + res["0"] = 234567 + pretty := prettyEncode(res) + + expected := ` +{ + "0": 234567 +} +`[1:] + + if pretty != expected { + t.Fatalf("Pretty int doesn't match") + } +} + +func TestPrettifyDuration(t *testing.T) { + res := make(map[string]interface{}, 1) + res["0"] = time.Duration(234567) + pretty := prettyEncode(res) + + expected := ` +{ + "0": "234.567µs" +} +`[1:] + + if pretty != expected { + t.Fatalf("Pretty duration doesn't match") + } +} + +func TestPrettifyDurationSlice(t *testing.T) { + res := make(map[string]interface{}, 1) + res["0"] = []time.Duration{123, 234567, 34567890} + pretty := prettyEncode(res) + + expected := ` +{ + "0": [ + "123ns", + "234.567µs", + "34.56789ms" + ] +} +`[1:] + + if pretty != expected { + t.Fatalf("Pretty duration slice doesn't match") + } +} diff --git a/cmd/pilosactl/main.go b/cmd/pilosactl/main.go index 08c20c285..aea1aee0a 100644 --- a/cmd/pilosactl/main.go +++ b/cmd/pilosactl/main.go @@ -1156,6 +1156,8 @@ type BagentCommand struct { AgentNum int // Slice of pilosa hosts to run the Benchmarks against. Hosts []string + // Enable pretty printing of results, for human consumption. + HumanReadable bool Stdin io.Reader Stdout io.Writer @@ -1165,9 +1167,10 @@ type BagentCommand struct { // NewBagentCommand returns a new instance of BagentCommand. func NewBagentCommand(stdin io.Reader, stdout, stderr io.Writer) *BagentCommand { return &BagentCommand{ - Benchmarks: []bench.Benchmark{}, - Hosts: []string{}, - AgentNum: 0, + Benchmarks: []bench.Benchmark{}, + Hosts: []string{}, + AgentNum: 0, + HumanReadable: false, Stdin: stdin, Stdout: stdout, @@ -1188,6 +1191,7 @@ func (cmd *BagentCommand) ParseFlags(args []string) error { var pilosaHosts string fs.StringVar(&pilosaHosts, "hosts", "localhost:15000", "Comma separated list of host:port") fs.IntVar(&cmd.AgentNum, "agentNum", 0, "An integer differentiating this agent from other in the fleet.") + fs.BoolVar(&cmd.HumanReadable, "human", false, "Boolean to enable human-readable format.") if err := fs.Parse(args); err != nil { return err @@ -1251,6 +1255,9 @@ The following arguments are available: -agentNum N An integer differentiating this agent from others in the fleet. + -human + Boolean to enable human-readable format. + subcommands: diagonal-set-bits random-set-bits @@ -1273,6 +1280,9 @@ func (cmd *BagentCommand) Run(ctx context.Context) error { res := sbm.Run(ctx, cmd.AgentNum) enc := json.NewEncoder(cmd.Stdout) enc.SetIndent("", " ") + if cmd.HumanReadable { + res = bench.Prettify(res) + } err = enc.Encode(res) if err != nil { fmt.Fprintln(cmd.Stderr, err)