Skip to content

Instantly share code, notes, and snippets.

@redbo
Created August 31, 2017 15:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save redbo/e3c0888ba266a5aaee8286b7692ba167 to your computer and use it in GitHub Desktop.
Save redbo/e3c0888ba266a5aaee8286b7692ba167 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Hummingbird Benchmarks</title>
<link rel="icon" type="image/png" href="/favicon.png">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/1.0.2/Chart.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.min.js"></script>
<style>
body {
padding: 0px;
margin: 0px;
color: #555;
font-family: 'helvetica neue', helvetica, arial, sans-serif;
font-size: 12px;
}
#reportbg {
border: 1px solid #3B3131;
background-color: #3B3131;
}
#report {
width: 700px;
margin: 20px auto 20px auto;
padding: 10px;
background-color: white;
}
h1 {
font-size: 25px;
margin: 0px;
padding: 0px;
}
h2 {
border-bottom: 1px solid #555;
font-size: 15px;
margin: 0px;
padding : 0px;
}
#header {
padding: 20px;
background: linear-gradient(white, white, white, white, #BBB);
}
.metric {
padding-top: 20px;
clear: both;
}
#chartfoot {
padding-top: 30px;
clear: both;
}
#version {
float: right;
}
#cpu, #mem, #failures, #median, #rate, #calcrate {
float: left;
}
.chart {
height: 200px;
width: 340px;
float: right;
}
.cpuchartholder {
float: left;
text-align: center;
margin: 5px;
}
.cpuchart {
height: 220px;
width: 220px;
}
td {
padding: 3px 10px 3px 0px;
}
#footer {
background: linear-gradient(#BBB, white);
height: 20px;
}
.legend {
margin-right: 4px;
width: 15px;
height: 15px;
display: inline-block;
border: 1px solid #CCC;
}
</style>
<script>
$.getJSON("data.json", function (json) {
var colors = [
["rgba(220,220,151,0.5)", "rgba(220,220,151,0.8)", "rgba(220,220,151,0.75)", "rgba(220,220,151,1)"],
["rgba(151,205,187,0.5)", "rgba(151,205,187,0.8)", "rgba(151,205,187,0.75)", "rgba(151,205,187,1)"],
["rgba(151,187,205,0.5)", "rgba(151,187,205,0.8)", "rgba(151,187,205,0.75)", "rgba(151,187,205,1)"],
["rgba(205,151,187,0.5)", "rgba(205,151,187,0.8)", "rgba(205,151,187,0.75)", "rgba(205,151,187,1)"],
];
colors.map(function(colorset, i) {
$("<style type=\"text/css\">.legend"+i+"{background-color: "+colorset[0]+";}</style>").appendTo("head");
});
function colorize(data) {
data.datasets.map(function(entry, i) {
entry.fillColor = colors[i][0];
entry.strokeColor = colors[i][1];
entry.highlightFill = colors[i][2];
entry.highlightStroke = colors[i][3];
});
return data;
}
function getScale(max) {
if (max > 25) {
return Math.ceil(max / 5);
}
return (max / 5).toPrecision(2);
}
function multichart(field, ctx) {
var data = colorize({
labels: json.labels,
datasets: json.series.map(function (arg, i) {
return {
label: arg.label,
data: arg.results.map(function (result) {return Math.round(result[field] * 1000) / 1000;}),
};
})
});
var max = Math.max.apply(null, data.datasets.map(function (arg) {return Math.max.apply(null, arg.data);}));
var scale = getScale(max);
new Chart(ctx).Bar(data, {
scaleSteps: Math.ceil(max / scale),
scaleStepWidth: scale,
barValueSpacing: 5,
barDatasetSpacing: 2,
scaleOverride: true,
barStrokeWidth: 1,
});
}
function singlechart(field, ctx, scaledown, label) {
var data = colorize({
labels: [label],
datasets: json.series.map(function (arg, i) {
return {
label: arg.label,
data: [Math.round((arg[field] / scaledown) * 1000) / 1000],
};
})
});
var max = Math.max.apply(null, data.datasets.map(function (arg) {return Math.max.apply(null, arg.data);}));
var scale = getScale(max);
new Chart(ctx).Bar(data, {
scaleSteps: Math.ceil(max / scale),
scaleStepWidth: scale,
barValueSpacing: 20,
barDatasetSpacing: 5,
scaleOverride: true,
barStrokeWidth: 1,
});
}
multichart('calcrate', $('#calcratechart').get(0).getContext("2d"));
multichart('median', $('#medianchart').get(0).getContext("2d"));
singlechart('mem', $('#memchart').get(0).getContext("2d"), 1024*1024, 'Memory Used in MB');
singlechart('cpu', $('#cpuchart').get(0).getContext("2d"), 1, 'CPU Usage in Seconds');
Handlebars.registerHelper("MB", function(bytes) {return Math.round((bytes * 10) / (1024 * 1024)) / 10;});
Handlebars.registerHelper("round", function(n, prec) {return Math.round(n * prec) / prec;});
$('#cpu').html(Handlebars.compile('<table>{{#series}}<tr><td><span class="legend legend{{@index}}">' +
'</span>{{label}}</td><td>{{round cpu 10}} sec</td></tr>{{/series}}</table>')(json));
$('#mem').html(Handlebars.compile('<table>{{#series}}<tr><td><span class="legend legend{{@index}}">' +
'</span>{{label}}</td><td>{{MB mem}} MB</td></tr>{{/series}}</table>')(json));
$('#median').html(Handlebars.compile('<table><tr><td>{{#labels}}<td>{{this}}</td>{{/labels}}</tr>{{#series}}<tr><td>' +
'<span class="legend legend{{@index}}"></span>{{label}}</td>{{#results}}<td>{{round median 1000}}s</td>{{/results}}</tr>{{/series}}</table>')(json));
$('#calcrate').html(Handlebars.compile('<table><tr><td>{{#labels}}<td>{{this}}</td>{{/labels}}</tr>{{#series}}<tr><td>' +
'<span class="legend legend{{@index}}"></span>{{label}}</td>{{#results}}<td>{{round calcrate 10}}/s</td>{{/results}}</tr>{{/series}}</table>')(json));
$('#failures').html(Handlebars.compile('<table><tr><td>{{#labels}}<td>{{this}}</td>{{/labels}}</tr>{{#series}}<tr><td>' +
'<span class="legend legend{{@index}}"></span>{{label}}</td>{{#results}}<td>{{failures}}</td>{{/results}}</tr>{{/series}}</table>')(json));
$('#testtime-value').html(json.time);
$('#version-value').html(json.version);
$('#concurrency-value').html(json.concurrency);
$('#object-size-value').html(json.object_size);
$('#object-count-value').html(json.object_count);
$('#object-gets-value').html(json.object_gets);
for (var i = 0; i < 3; i++) {
var cputime = Math.round((100 * json.series[i].cpu) / (json.series[i].clocktime * 4) * 100) / 100;
var mysteryTime = Math.round(((json.series[i].clocktime - json.series[i].cpu/4) - (json.series[2].clocktime - json.series[2].cpu/4)) * 100) / 100;
if (mysteryTime < 0) {
mysteryTime = 0;
}
var otherTime = Math.round((100 - (cputime + mysteryTime)) * 100) / 100;
var data = [
{
value: cputime,
color:colors[i][0],
highlight: colors[i][1],
label: "CPU"
},
{
value: otherTime,
color: "rgba(220,220,220,0.5)",
highlight: "rgba(220,220,220,0.8)",
label: "Other"
},
{
value: mysteryTime,
color: "rgba(220,220,220,0.5)",
highlight: "rgba(220,220,220,0.8)",
label: "Mystery Time"
}
];
new Chart($('#cpupiechart' + i).get(0).getContext("2d")).Pie(data, {
tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>%",
});
}
});
</script>
</head>
<body>
<div id="header">
<h1>Hummingbird Benchmarks</h1>
</div>
<div id="reportbg">
<div id="report">
<div class="metric">
<h2>Requests Per Second</h2>
<div id="calcrate"></div>
<canvas id="calcratechart" class="chart"></canvas>
</div>
<div class="metric">
<h2>Median Request Times</h2>
<div id="median"></div>
<canvas id="medianchart" class="chart"></canvas>
</div>
<div class="metric">
<h2>Memory Used (Max Resident)</h2>
<div id="mem"></div>
<canvas id="memchart" class="chart"></canvas>
</div>
<div class="metric">
<h2>CPU Usage</h2>
<div id="cpu"></div>
<canvas id="cpuchart" class="chart"></canvas>
</div>
<div class="metric">
<h2>Amount of Request Time that is CPU</h2>
<div class="cpuchartholder">
<canvas id="cpupiechart0" class="cpuchart"></canvas>
<div>Swift</div>
</div>
<div class="cpuchartholder">
<canvas id="cpupiechart1" class="cpuchart"></canvas>
<div>Swift Proxy + HB Object</div>
</div>
<div class="cpuchartholder">
<canvas id="cpupiechart2" class="cpuchart"></canvas>
<div>HB Proxy + Object</div>
</div>
</div>
<div class="metric">
<h2>Failed Requests</h2>
<div id="failures"></div>
<div></div>
</div>
<div class="metric">
<h2>Test Parameters</h2>
<div>Concurrency: <span id="concurrency-value"></span></div>
<div>Object Size: <span id="object-size-value"></span></div>
<div>Number of PUTs/DELETEs: <span id="object-count-value"></span></div>
<div>Number of GETs: <span id="object-gets-value"></span></div>
</div>
<div id="chartfoot">
<div id="version">Git version: <span id="version-value"></span></div>
<div id="testtime">Tested: <span id="testtime-value">A long time ago...</span></div>
</div>
</div>
</div>
<div id="footer">
</div>
</body>
</html>
// Copyright (c) 2015 Rackspace
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"math/rand"
"os"
"os/exec"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/troubling/hummingbird/client"
)
// #include <unistd.h>
import "C"
const (
CONCURRENCY = 24
OBJECT_COUNT = 5000
OBJECT_SIZE = 1024
OBJECT_GETS = 10000
)
type Object struct {
c client.Client
state int
container string
name string
data []byte
}
func (obj *Object) Put() bool {
err := obj.c.PutObject(obj.container, obj.name, nil, bytes.NewReader(obj.data))
return err == nil
}
func (obj *Object) Get() bool {
if r, _, err := obj.c.GetObject(obj.container, obj.name, nil); err != nil {
return false
} else {
io.Copy(ioutil.Discard, r)
r.Close()
return true
}
}
func (obj *Object) Delete() bool {
return obj.c.DeleteObject(obj.container, obj.name, nil) == nil
}
type Results struct {
Name string `json:"name"`
Failures int `json:"failures"`
CalcRate float64 `json:"calcrate"`
Rate float64 `json:"rate"`
Median float64 `json:"median"`
Mean float64 `json:"mean"`
ClockTime float64 `json:"clocktime"`
}
func DoJobs(name string, work []func() bool, concurrency int) Results {
wg := sync.WaitGroup{}
cwg := sync.WaitGroup{}
errorCount := 0
jobTimes := make([]float64, 0, len(work))
times := make(chan float64)
errors := make(chan int)
jobqueue := make(chan func() bool)
cwg.Add(2)
go func() {
for n := range errors {
errorCount += n
}
cwg.Done()
}()
go func() {
for t := range times {
jobTimes = append(jobTimes, t)
}
sort.Float64s(jobTimes)
cwg.Done()
}()
for i := 0; i < concurrency; i++ {
wg.Add(1)
go func() {
for job := range jobqueue {
startJob := time.Now()
if !job() {
errors <- 1
}
times <- float64(time.Now().Sub(startJob)) / float64(time.Second)
}
wg.Done()
}()
}
start := time.Now()
for _, job := range work {
jobqueue <- job
}
close(jobqueue)
wg.Wait()
totalTime := time.Now().Sub(start)
close(errors)
close(times)
cwg.Wait()
sum := 0.0
for _, val := range jobTimes {
sum += val
}
avg := sum / float64(len(work))
median := 0.0
if len(jobTimes)%2 == 1 {
median = jobTimes[len(jobTimes)/2]
} else if len(jobTimes) > 0 {
median = (jobTimes[len(jobTimes)/2] + jobTimes[(len(jobTimes)/2)-1]) / 2.0
}
return Results{
Name: name,
Failures: errorCount,
CalcRate: float64(concurrency) / avg,
Rate: float64(len(work)) / (float64(totalTime) / float64(time.Second)),
Mean: avg,
Median: median,
ClockTime: totalTime.Seconds(),
}
}
func benchmark(name string) []Results {
authURL := "http://localhost:8080/auth/v1.0"
authUser := "test:tester"
authKey := "testing"
concurrency := CONCURRENCY
objectSize := OBJECT_SIZE
numObjects := OBJECT_COUNT
numGets := OBJECT_GETS
salt := fmt.Sprintf("%d", rand.Int63())
cli, err := client.NewClient("", authUser, "", authKey, "", authURL, false)
if err != nil {
fmt.Println("Error creating client:", err)
os.Exit(1)
}
for i := 0; i < concurrency; i++ {
if err := cli.PutContainer(fmt.Sprintf("%d-%s", i, salt), nil); err != nil {
fmt.Println("Error putting container:", err)
os.Exit(1)
}
}
data := make([]byte, objectSize)
objects := make([]*Object, numObjects)
for i := range objects {
objects[i] = &Object{
container: fmt.Sprintf("%d-%s", i%concurrency, salt),
name: fmt.Sprintf("%x", rand.Int63()),
data: data,
c: cli,
}
}
startWarmup := time.Now()
for time.Since(startWarmup) < (30 * time.Second) {
for i := 0; i < 4; i++ {
objects[i].Put()
objects[i].Get()
objects[i].Delete()
}
}
puts := make([]func() bool, len(objects))
gets := make([]func() bool, numGets)
deletes := make([]func() bool, len(objects))
for i := range objects {
puts[i] = objects[i].Put
deletes[i] = objects[i].Delete
}
for i := 0; i < numGets; i++ {
gets[i] = objects[i%len(objects)].Get
}
putResults := DoJobs(fmt.Sprintf("%s Put", name), puts, concurrency)
time.Sleep(time.Second * 10)
getResults := DoJobs(fmt.Sprintf("%s Get", name), gets, concurrency)
time.Sleep(time.Second * 10)
deleteResults := DoJobs(fmt.Sprintf("%s Delete", name), deletes, concurrency)
return []Results{putResults, getResults, deleteResults}
}
type series struct {
Label string `json:"label"`
Cpu float64 `json:"cpu"`
ClockTime float64 `json:"clocktime"`
Mem int64 `json:"mem"`
AllResults []Results `json:"results"`
}
type TestResult struct {
Labels []string `json:"labels"`
Series []series `json:"series"`
Time string `json:"time"`
Version string `json:"version"`
Concurrency int `json:"concurrency"`
ObjectCount int `json:"object_count"`
ObjectSize int `json:"object_size"`
ObjectGets int `json:"object_gets"`
}
func usage(searchFor []string) (float64, int64) {
sc_clk_tck := float64(C.sysconf(C._SC_CLK_TCK))
var cpu float64
var mem int64
for _, search := range searchFor {
out, err := exec.Command("pgrep", "-f", search).Output()
if err != nil {
continue
}
pids := strings.Split(string(out), "\n")
for _, pid := range pids {
if len(pid) == 0 {
continue
}
if data, err := ioutil.ReadFile(fmt.Sprintf("/proc/%s/stat", pid)); err == nil {
entries := strings.Split(string(data), " ")
if v1, err := strconv.Atoi(entries[13]); err == nil {
if v2, err := strconv.Atoi(entries[14]); err == nil {
jiffies := v1 + v2
cpu += float64(jiffies) / sc_clk_tck
}
}
}
if out, err := exec.Command("grep", "VmHWM", fmt.Sprintf("/proc/%s/status", pid)).Output(); err == nil {
var memusage int64
if c, err := fmt.Sscanf(string(out), "VmHWM: %d kB", &memusage); err == nil && c == 1 {
mem += memusage * 1024
}
}
}
}
return cpu, mem
}
func resetSwift(use string) {
fmt.Fprintf(os.Stderr, "Resetting for %s\n", use)
fmt.Fprintf(os.Stderr, "Stopping all...\n")
exec.Command("hummingbird", "stop", "all").Run()
exec.Command("swift-init", "stop", "all").Run()
time.Sleep(time.Second * 30)
fmt.Fprintf(os.Stderr, "Formatting devices...\n")
exec.Command("sudo", "umount", "/mnt/sdb1").Run()
time.Sleep(time.Second * 5)
exec.Command("sudo", "mkfs.xfs", "-f", "/srv/swift-disk").Run()
time.Sleep(time.Second * 5)
exec.Command("sudo", "mount", "/mnt/sdb1").Run()
time.Sleep(time.Second * 5)
exec.Command("sudo", "mkdir", "/mnt/sdb1/1", "/mnt/sdb1/2", "/mnt/sdb1/3", "/mnt/sdb1/4").Run()
exec.Command("sudo", "chown", "redbo:redbo", "/mnt/sdb1/1", "/mnt/sdb1/2", "/mnt/sdb1/3", "/mnt/sdb1/4").Run()
exec.Command("mkdir", "-p", "/srv/1/node/sdb1", "/srv/1/node/sdb5", "/srv/2/node/sdb2", "/srv/2/node/sdb6", "/srv/3/node/sdb3", "/srv/3/node/sdb7", "/srv/4/node/sdb4", "/srv/4/node/sdb8").Run()
fmt.Fprintf(os.Stderr, "Restarting memcache...\n")
exec.Command("sudo", "service", "memcached", "restart").Run()
fmt.Fprintf(os.Stderr, "Starting account and container...\n")
exec.Command("sudo", "swift-init", "start", "container").Run()
exec.Command("sudo", "swift-init", "start", "account").Run()
fmt.Fprintf(os.Stderr, "Starting proxy and object...\n")
if use == "swift" {
exec.Command("sudo", "swift-init", "start", "proxy").Run()
exec.Command("sudo", "swift-init", "start", "object").Run()
} else if use == "hb" {
exec.Command("sudo", "swift-init", "start", "proxy").Run()
exec.Command("sudo", "hummingbird", "start", "object").Run()
} else if use == "hbp" {
exec.Command("sudo", "hummingbird", "start", "proxy").Run()
exec.Command("sudo", "hummingbird", "start", "object").Run()
} else if use == "pypy" {
oldpath := os.Getenv("PATH")
os.Setenv("PATH", "/usr/local/pypy/bin:"+oldpath)
os.Setenv("PYPY_GC_MIN", "15M")
exec.Command("sudo", "swift-init", "start", "object").Run()
exec.Command("sudo", "swift-init", "start", "proxy").Run()
os.Setenv("PATH", oldpath)
}
fmt.Fprintf(os.Stderr, "Environment reset\n\n")
time.Sleep(time.Second * 120)
}
func sumClockTime(results []Results) float64 {
total := 0.0
for _, r := range results {
total += r.ClockTime
}
return total
}
func main() {
resetSwift("hbp")
startupCPU, _ := usage([]string{"hummingbird proxy", "hummingbird object"})
hbResults := benchmark("HB Proxy+Obj")
hbCpu, hbMem := usage([]string{"hummingbird proxy", "hummingbird object"})
hbCpu -= startupCPU
resetSwift("hb")
startupCPU, _ = usage([]string{"swift-proxy-server", "hummingbird object"})
combResults := benchmark("Swift proxy + HB Object")
combCpu, combMem := usage([]string{"swift-proxy-server", "hummingbird object"})
combCpu -= startupCPU
resetSwift("swift")
startupCPU, _ = usage([]string{"swift-proxy-server", "swift-object-server"})
swiftResults := benchmark("Swift")
swiftCpu, swiftMem := usage([]string{"swift-proxy-server", "swift-object-server"})
swiftCpu -= startupCPU
/*
resetSwift("pypy")
startupCPU, _ = usage([]string{"swift-proxy-server", "swift-object-server"})
pypyResults := benchmark("Pypy")
pypyCpu, pypyMem := usage([]string{"swift-proxy-server", "swift-object-server"})
pypyCpu -= startupCPU
*/
version := "unknown"
if versionb, err := exec.Command("hummingbird", "version").Output(); err == nil {
version = string(versionb)
}
data, _ := json.MarshalIndent(
TestResult{
Labels: []string{"PUT", "GET", "DELETE"},
Series: []series{
{
Label: "Swift",
Cpu: swiftCpu,
ClockTime: sumClockTime(swiftResults),
Mem: swiftMem,
AllResults: swiftResults,
},
{
Label: "Swift Proxy + HB Object",
Cpu: combCpu,
ClockTime: sumClockTime(combResults),
Mem: combMem,
AllResults: combResults,
},
{
Label: "HB Proxy+Obj",
Cpu: hbCpu,
ClockTime: sumClockTime(hbResults),
Mem: hbMem,
AllResults: hbResults,
},
/*
series{
Label: "Pypy",
Cpu: pypyCpu,
ClockTime: sumClockTime(pypyResults),
Mem: pypyMem,
AllResults: pypyResults,
},
*/
},
Time: time.Now().Format(time.UnixDate),
Version: version,
Concurrency: CONCURRENCY,
ObjectCount: OBJECT_COUNT,
ObjectSize: OBJECT_SIZE,
ObjectGets: OBJECT_GETS,
}, "", " ")
fmt.Println(string(data))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment