1818package cli
1919
2020import (
21+ "encoding/json"
2122 "errors"
2223 "fmt"
2324 "os"
@@ -27,6 +28,7 @@ import (
2728 "github.com/minio/cli"
2829 "github.com/minio/mc/pkg/probe"
2930 "github.com/minio/pkg/v3/console"
31+ "github.com/minio/warp/pkg/aggregate"
3032 "github.com/minio/warp/pkg/bench"
3133)
3234
@@ -48,21 +50,83 @@ var mergeCmd = cli.Command{
4850 {{.HelpName}} - {{.Usage}}
4951
5052USAGE:
51- {{.HelpName}} [FLAGS] benchmark-data-file1 benchmark-data-file2 ...
53+ {{.HelpName}} [FLAGS] benchmark-data-file1 benchmark-data-file2 ...
5254 -> see https://github.com/minio/warp#merging-benchmarks
5355
5456FLAGS:
5557 {{range .VisibleFlags}}{{.}}
5658 {{end}}` ,
5759}
5860
59- // mainAnalyze is the entry point for analyze command.
6061func mainMerge (ctx * cli.Context ) error {
6162 checkMerge (ctx )
6263 args := ctx .Args ()
6364 if len (args ) <= 1 {
6465 console .Fatal ("Two or more benchmark data files must be supplied" )
6566 }
67+
68+ rc , isJSON := openInput (args [0 ])
69+ rc .Close ()
70+
71+ if isJSON {
72+ return mergeJSON (ctx , args )
73+ }
74+ return mergeCSV (ctx , args )
75+ }
76+
77+ func timeOverlaps (a , b aggregate.LiveAggregate ) bool {
78+ return a .StartTime .Before (b .EndTime ) && b .StartTime .Before (a .EndTime )
79+ }
80+
81+ func mergeJSON (ctx * cli.Context , args []string ) error {
82+ var merged aggregate.Realtime
83+ for i , arg := range args {
84+ rc , isJSON := openInput (arg )
85+ if ! isJSON {
86+ rc .Close ()
87+ fatalIf (probe .NewError (errors .New ("mixed input types" )), "mixed input types (JSON and CSV)" )
88+ }
89+ var rt aggregate.Realtime
90+ if err := json .NewDecoder (rc ).Decode (& rt ); err != nil {
91+ rc .Close ()
92+ fatalIf (probe .NewError (err ), "Unable to parse input" )
93+ }
94+ rc .Close ()
95+ if i > 0 && ! timeOverlaps (merged .Total , rt .Total ) {
96+ fatalIf (probe .NewError (fmt .Errorf (
97+ "file %q (%s - %s) does not overlap with merged range (%s - %s)" ,
98+ arg , rt .Total .StartTime .Format (time .RFC3339 ), rt .Total .EndTime .Format (time .RFC3339 ),
99+ merged .Total .StartTime .Format (time .RFC3339 ), merged .Total .EndTime .Format (time .RFC3339 ),
100+ )), "time ranges do not overlap" )
101+ }
102+ merged .Merge (& rt )
103+ }
104+ merged .Final = true
105+
106+ fileName := ctx .String ("benchdata" )
107+ if fileName == "" {
108+ fileName = fmt .Sprintf ("%s-%s-%s" , appName , ctx .Command .Name , time .Now ().Format ("2006-01-02[150405]" ))
109+ }
110+ f , err := os .Create (fileName + ".json.zst" )
111+ if err != nil {
112+ console .Error ("Unable to write benchmark data:" , err )
113+ return nil
114+ }
115+ defer f .Close ()
116+ enc , err := zstd .NewWriter (f , zstd .WithEncoderLevel (zstd .SpeedBetterCompression ))
117+ fatalIf (probe .NewError (err ), "Unable to compress benchmark output" )
118+ defer enc .Close ()
119+
120+ js := json .NewEncoder (enc )
121+ js .SetIndent ("" , " " )
122+ err = js .Encode (merged )
123+ fatalIf (probe .NewError (err ), "Unable to write benchmark output" )
124+
125+ console .Infof ("Benchmark data written to %q\n " , fileName + ".json.zst" )
126+ return nil
127+ }
128+
129+ func mergeCSV (ctx * cli.Context , args []string ) error {
66130 zstdDec , _ := zstd .NewReader (nil )
67131 defer zstdDec .Close ()
68132 var allOps bench.Operations
@@ -72,6 +136,13 @@ func mainMerge(ctx *cli.Context) error {
72136 log = nil
73137 }
74138 for _ , arg := range args {
139+ rc , isJSON := openInput (arg )
140+ if isJSON {
141+ rc .Close ()
142+ fatalIf (probe .NewError (errors .New ("mixed input types" )), "mixed input types (JSON and CSV)" )
143+ }
144+ rc .Close ()
145+
75146 f , err := os .Open (arg )
76147 fatalIf (probe .NewError (err ), "Unable to open input file" )
77148 defer f .Close ()
0 commit comments