Skip to content

Commit dca4e07

Browse files
committed
Merge pull request kubernetes-retired#145 from brendandburns/fix
Add a simple tool for calculating a CSV of perf. data from Jenkins.
2 parents c15fcb6 + 79c0a30 commit dca4e07

2 files changed

Lines changed: 165 additions & 0 deletions

File tree

perfdash/perfdash.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
Copyright 2015 The Kubernetes Authors All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"bufio"
21+
"bytes"
22+
"encoding/json"
23+
"fmt"
24+
"strings"
25+
26+
// TODO: move this somewhere central
27+
"k8s.io/contrib/submit-queue/jenkins"
28+
"k8s.io/kubernetes/pkg/util/sets"
29+
)
30+
31+
// LatencyData represents the latency data for a set of RESTful API calls
32+
type LatencyData struct {
33+
APICalls []APICallLatency `json:"apicalls"`
34+
}
35+
36+
// APICallLatency represents the latency data for a (resource, verb) tuple
37+
type APICallLatency struct {
38+
Latency Histogram `json:"latency"`
39+
Resource string `json:"resource"`
40+
Verb string `json:"verb"`
41+
}
42+
43+
// Histogram is a map from bucket to latency (e.g. "Perc90" -> 23.5)
44+
type Histogram map[string]float64
45+
46+
type resourceToHistogram map[string][]APICallLatency
47+
48+
var buildLatency = map[int]resourceToHistogram{}
49+
50+
func main() {
51+
job := "kubernetes-e2e-gce-scalability"
52+
client := jenkins.JenkinsClient{
53+
Host: "http://kubekins.dls.corp.google.com",
54+
}
55+
56+
queue, err := client.GetJob(job)
57+
if err != nil {
58+
fmt.Printf("%v\n", err)
59+
return
60+
}
61+
resources := sets.NewString()
62+
methods := sets.NewString()
63+
for ix := range queue.Builds {
64+
build := queue.Builds[ix]
65+
reader, err := client.GetConsoleLog(job, build.Number)
66+
if err != nil {
67+
fmt.Printf("error getting logs: %v", err)
68+
continue
69+
}
70+
defer reader.Close()
71+
scanner := bufio.NewScanner(reader)
72+
buff := &bytes.Buffer{}
73+
inLatency := false
74+
75+
hist := resourceToHistogram{}
76+
found := false
77+
for scanner.Scan() {
78+
line := scanner.Text()
79+
// TODO: This is brittle, we should emit a tail delimiter too
80+
if strings.Contains(line, "INFO") || strings.Contains(line, "STEP") || strings.Contains(line, "Failure") {
81+
if inLatency {
82+
obj := LatencyData{}
83+
if err := json.Unmarshal(buff.Bytes(), &obj); err != nil {
84+
fmt.Printf("error parsing JSON in build %d: %v %s\n", build.Number, err, buff.String())
85+
// reset state and try again with more input
86+
inLatency = false
87+
continue
88+
}
89+
90+
for _, call := range obj.APICalls {
91+
list := hist[call.Resource]
92+
list = append(list, call)
93+
hist[call.Resource] = list
94+
resources.Insert(call.Resource)
95+
methods.Insert(call.Verb)
96+
}
97+
98+
buff.Reset()
99+
}
100+
inLatency = false
101+
}
102+
if strings.Contains(line, "API calls latencies") {
103+
found = true
104+
inLatency = true
105+
ix = strings.Index(line, "{")
106+
line = line[ix:]
107+
}
108+
if inLatency {
109+
buff.WriteString(line + " ")
110+
}
111+
}
112+
if !found {
113+
continue
114+
}
115+
116+
buildLatency[build.Number] = hist
117+
}
118+
119+
header := []string{"build"}
120+
for _, rsrc := range resources.List() {
121+
header = append(header, fmt.Sprintf("%s_50", rsrc))
122+
header = append(header, fmt.Sprintf("%s_90", rsrc))
123+
header = append(header, fmt.Sprintf("%s_99", rsrc))
124+
}
125+
fmt.Println(strings.Join(header, ","))
126+
127+
for _, method := range methods.List() {
128+
fmt.Println(method)
129+
for build, data := range buildLatency {
130+
line := []string{fmt.Sprintf("%d", build)}
131+
for _, rsrc := range resources.List() {
132+
podData := data[rsrc]
133+
line = append(line, fmt.Sprintf("%g", findMethod(method, "Perc50", podData)))
134+
line = append(line, fmt.Sprintf("%g", findMethod(method, "Perc90", podData)))
135+
line = append(line, fmt.Sprintf("%g", findMethod(method, "Perc99", podData)))
136+
}
137+
fmt.Println(strings.Join(line, ","))
138+
}
139+
}
140+
}
141+
142+
func findMethod(method, item string, data []APICallLatency) float64 {
143+
for _, datum := range data {
144+
if datum.Verb == method {
145+
return datum.Latency[item]
146+
}
147+
}
148+
return -1
149+
}

submit-queue/jenkins/jenkins.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package jenkins
1919
import (
2020
"encoding/json"
2121
"fmt"
22+
"io"
2223
"io/ioutil"
2324
"net/http"
2425

@@ -64,6 +65,21 @@ func (j *JenkinsClient) request(path string) ([]byte, error) {
6465
return ioutil.ReadAll(res.Body)
6566
}
6667

68+
// GetConsoleLog downloads the logs for a particular job and build number
69+
func (j *JenkinsClient) GetConsoleLog(name string, build int) (io.ReadCloser, error) {
70+
url := fmt.Sprintf("%s/job/%s/%d/consoleText", j.Host, name, build)
71+
glog.V(3).Infof("Hitting: %s", url)
72+
res, err := http.Get(url)
73+
if err != nil {
74+
return nil, err
75+
}
76+
if res.StatusCode != http.StatusOK {
77+
res.Body.Close()
78+
return nil, fmt.Errorf("unexpected status: %s %d", res.Status, res.StatusCode)
79+
}
80+
return res.Body, nil
81+
}
82+
6783
// GetJob will get information about a single job
6884
func (j *JenkinsClient) GetJob(name string) (*Queue, error) {
6985
data, err := j.request("/job/" + name + "/api/json")

0 commit comments

Comments
 (0)