Skip to content

Commit e89d345

Browse files
committed
Add an utility to compare APIResponsiveness metrics.
Those are metrics generated by clusterloader2 for each test.
1 parent 580c241 commit e89d345

File tree

2 files changed

+136
-0
lines changed

2 files changed

+136
-0
lines changed

api_responsiveness/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.json

api_responsiveness/main.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"flag"
7+
"fmt"
8+
"io/ioutil"
9+
"log"
10+
"time"
11+
)
12+
13+
const (
14+
epsilon = 0.00001
15+
minDuration = 50 * time.Millisecond
16+
)
17+
18+
var (
19+
threshold = flag.Float64("threshold", 0.1, "Failure threshold.")
20+
mode = flag.String("mode", "compare", "Mode")
21+
)
22+
23+
type APIResponsiveness struct {
24+
DataItems []Item `json:"DataItems"`
25+
}
26+
27+
type Item struct {
28+
Data Data `json:"Data"`
29+
Labels Labels `json:"labels"`
30+
}
31+
32+
type Data struct {
33+
Perc99 float64 `json:"Perc99"`
34+
}
35+
36+
type Labels struct {
37+
Resource string `json:"Resource"`
38+
Scope string `json:"Scope"`
39+
Subresource string `json:"Subresource"`
40+
Verb string `json:"Verb"`
41+
}
42+
43+
func (l *Labels) asKey() string {
44+
key := fmt.Sprintf("%s %s/%s", l.Verb, l.Scope, l.Resource)
45+
if l.Subresource != "" {
46+
key = fmt.Sprintf("%s/%s", key, l.Subresource)
47+
}
48+
return key
49+
50+
}
51+
52+
func (d *APIResponsiveness) asMap() map[string]time.Duration {
53+
m := make(map[string]time.Duration)
54+
for _, item := range d.DataItems {
55+
if item.Labels.Scope != "" && item.Data.Perc99 > epsilon {
56+
// APIResponsiveness kee
57+
m[item.Labels.asKey()] = time.Millisecond * time.Duration(item.Data.Perc99)
58+
}
59+
}
60+
return m
61+
}
62+
63+
func parseResults(path string) (*APIResponsiveness, error) {
64+
raw, err := ioutil.ReadFile(path)
65+
if err != nil {
66+
return nil, err
67+
}
68+
69+
var result APIResponsiveness
70+
if err = json.Unmarshal(raw, &result); err != nil {
71+
return nil, err
72+
}
73+
return &result, nil
74+
}
75+
76+
func compareResults(base, result *APIResponsiveness) {
77+
b := base.asMap()
78+
r := result.asMap()
79+
80+
good := 0
81+
bad := 0
82+
83+
for k, v := range r {
84+
baseValue, ok := b[k]
85+
if !ok {
86+
fmt.Printf("%q missing in the baseline\n", k)
87+
continue
88+
}
89+
90+
ratio := v.Seconds() / baseValue.Seconds()
91+
if ratio > (*threshold+1) && v > minDuration {
92+
fmt.Printf("WARNING: %q took %.2f more time than baseline (baseline: %v, result: %v)\n", k, ratio, baseValue, v)
93+
bad++
94+
} else {
95+
fmt.Printf("OK: %q\n", k)
96+
good++
97+
}
98+
}
99+
fmt.Printf("good: %d, bad %d\n", good, bad)
100+
}
101+
102+
func compare() error {
103+
if flag.NArg() != 2 {
104+
return errors.New("expected 2 positional arguments: path to baseline and result")
105+
}
106+
107+
path := flag.Args()[0]
108+
baseline, err := parseResults(path)
109+
if err != nil {
110+
return err
111+
}
112+
path = flag.Args()[1]
113+
result, err := parseResults(path)
114+
if err != nil {
115+
return err
116+
117+
}
118+
compareResults(baseline, result)
119+
return nil
120+
}
121+
122+
func main() {
123+
flag.Parse()
124+
var err error
125+
switch *mode {
126+
case "compare":
127+
err = compare()
128+
default:
129+
err = errors.New("unknown mode")
130+
}
131+
if err != nil {
132+
log.Fatalf("failed: %v", err)
133+
}
134+
135+
}

0 commit comments

Comments
 (0)