2018-09-13 14:30:28 +01:00
|
|
|
// Copyright (C) 2018 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
2018-09-11 16:35:46 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"text/tabwriter"
|
|
|
|
"time"
|
2018-10-06 13:26:35 +01:00
|
|
|
"strconv"
|
2018-09-11 16:35:46 +01:00
|
|
|
|
|
|
|
"github.com/loov/hrtime"
|
2018-09-21 20:44:45 +01:00
|
|
|
|
2018-09-28 13:40:08 +01:00
|
|
|
"storj.io/storj/internal/memory"
|
2018-09-11 16:35:46 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
func main() {
|
2018-09-28 13:40:08 +01:00
|
|
|
var conf Config
|
|
|
|
|
|
|
|
flag.StringVar(&conf.Endpoint, "endpoint", "127.0.0.1:7777", "endpoint address")
|
|
|
|
flag.StringVar(&conf.AccessKey, "accesskey", "insecure-dev-access-key", "access key")
|
|
|
|
flag.StringVar(&conf.SecretKey, "secretkey", "insecure-dev-secret-key", "secret key")
|
|
|
|
flag.BoolVar(&conf.NoSSL, "no-ssl", false, "disable ssl")
|
|
|
|
|
|
|
|
clientName := flag.String("client", "minio", "client to use for requests (supported: minio, aws-cli)")
|
|
|
|
|
2018-09-11 16:35:46 +01:00
|
|
|
location := flag.String("location", "", "bucket location")
|
2018-09-21 20:44:45 +01:00
|
|
|
count := flag.Int("count", 50, "benchmark count")
|
2018-10-06 13:26:35 +01:00
|
|
|
duration := flag.Duration("time", 2*time.Minute, "maximum benchmark time per filesize")
|
2018-09-21 20:44:45 +01:00
|
|
|
|
|
|
|
suffix := time.Now().Format("-2006-01-02-150405")
|
|
|
|
|
|
|
|
plotname := flag.String("plot", "plot"+suffix+".svg", "plot results")
|
2018-09-11 16:35:46 +01:00
|
|
|
|
2018-10-06 13:26:35 +01:00
|
|
|
filesizes := &memory.Sizes{
|
2018-09-28 13:40:08 +01:00
|
|
|
Default: []memory.Size{
|
|
|
|
1 * memory.KB,
|
|
|
|
256 * memory.KB,
|
|
|
|
1 * memory.MB,
|
|
|
|
32 * memory.MB,
|
|
|
|
64 * memory.MB,
|
|
|
|
256 * memory.MB,
|
|
|
|
},
|
2018-09-11 16:35:46 +01:00
|
|
|
}
|
2018-10-06 13:26:35 +01:00
|
|
|
flag.Var(filesizes, "filesize", "filesizes to test with")
|
|
|
|
listsize := flag.Int("listsize", 1000, "listsize to test with")
|
2018-09-11 16:35:46 +01:00
|
|
|
|
|
|
|
flag.Parse()
|
|
|
|
|
2018-09-28 13:40:08 +01:00
|
|
|
var client Client
|
|
|
|
var err error
|
|
|
|
|
|
|
|
switch *clientName {
|
|
|
|
default:
|
|
|
|
log.Println("unknown client name ", *clientName, " defaulting to minio")
|
|
|
|
fallthrough
|
|
|
|
case "minio":
|
|
|
|
client, err = NewMinio(conf)
|
|
|
|
case "aws-cli":
|
|
|
|
client, err = NewAWSCLI(conf)
|
|
|
|
}
|
2018-09-11 16:35:46 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-10-06 13:26:35 +01:00
|
|
|
bucket := "benchmark" + suffix
|
2018-09-11 16:35:46 +01:00
|
|
|
log.Println("Creating bucket", bucket)
|
2018-10-06 13:26:35 +01:00
|
|
|
|
|
|
|
// 1 bucket for file up and downloads
|
2018-09-11 16:35:46 +01:00
|
|
|
err = client.MakeBucket(bucket, *location)
|
|
|
|
if err != nil {
|
2018-09-28 13:40:08 +01:00
|
|
|
log.Fatalf("failed to create bucket %q: %+v\n", bucket, err)
|
2018-09-11 16:35:46 +01:00
|
|
|
}
|
|
|
|
|
2018-10-06 13:26:35 +01:00
|
|
|
data := make([]byte, 1)
|
|
|
|
log.Println("Creating files", bucket)
|
|
|
|
// n files in one folder
|
|
|
|
for k := 0; k < *listsize; k++ {
|
|
|
|
err := client.Upload(bucket, "folder/data" + strconv.Itoa(k), data)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("failed to create file %q: %+v\n", "folder/data" + strconv.Itoa(k), err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Println("Creating folders", bucket)
|
|
|
|
// n - 1 (one folder already exists) folders with one file in each folder
|
|
|
|
for k := 0; k < *listsize - 1; k++ {
|
|
|
|
err := client.Upload(bucket, "folder" + strconv.Itoa(k) + "/data", data)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("failed to create folder %q: %+v\n", "folder" + strconv.Itoa(k) + "/data", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-11 16:35:46 +01:00
|
|
|
defer func() {
|
2018-10-06 13:26:35 +01:00
|
|
|
log.Println("Removing files")
|
|
|
|
for k := 0; k < *listsize; k++ {
|
|
|
|
err := client.Delete(bucket, "folder/data" + strconv.Itoa(k))
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("failed to delete file %q: %+v\n", "folder/data" + strconv.Itoa(k), err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Println("Removing folders")
|
|
|
|
for k := 0; k < *listsize - 1; k++ {
|
|
|
|
err := client.Delete(bucket, "folder" + strconv.Itoa(k) + "/data")
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("failed to delete folder %q: %+v\n", "folder" + strconv.Itoa(k) + "/data", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-11 16:35:46 +01:00
|
|
|
log.Println("Removing bucket")
|
|
|
|
err := client.RemoveBucket(bucket)
|
|
|
|
if err != nil {
|
2018-09-28 13:40:08 +01:00
|
|
|
log.Fatalf("failed to remove bucket %q", bucket)
|
2018-09-11 16:35:46 +01:00
|
|
|
}
|
2018-10-06 13:26:35 +01:00
|
|
|
|
2018-09-11 16:35:46 +01:00
|
|
|
}()
|
|
|
|
|
|
|
|
measurements := []Measurement{}
|
2018-10-06 13:26:35 +01:00
|
|
|
measurement, err := ListBenchmark(client, bucket, *listsize, *count, *duration)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
measurements = append(measurements, measurement)
|
|
|
|
for _, filesize := range filesizes.Sizes() {
|
|
|
|
measurement, err := FileBenchmark(client, bucket, filesize, *count, *duration)
|
2018-09-11 16:35:46 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
measurements = append(measurements, measurement)
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Print("\n\n")
|
|
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
|
|
|
|
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n",
|
|
|
|
"Size", "",
|
|
|
|
"Avg", "",
|
|
|
|
"Max", "",
|
|
|
|
"P50", "", "P90", "", "P99", "",
|
|
|
|
)
|
|
|
|
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n",
|
|
|
|
"", "",
|
|
|
|
"s", "MB/s",
|
|
|
|
"s", "MB/s",
|
|
|
|
"s", "MB/s", "s", "MB/s", "s", "MB/s",
|
|
|
|
)
|
|
|
|
for _, m := range measurements {
|
|
|
|
m.PrintStats(w)
|
|
|
|
}
|
|
|
|
_ = w.Flush()
|
|
|
|
|
|
|
|
if *plotname != "" {
|
2018-09-28 13:40:08 +01:00
|
|
|
err := Plot(*plotname, measurements)
|
2018-09-11 16:35:46 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-28 13:40:08 +01:00
|
|
|
// Measurement contains measurements for different requests
|
|
|
|
type Measurement struct {
|
|
|
|
Size memory.Size
|
|
|
|
Results []*Result
|
2018-09-11 16:35:46 +01:00
|
|
|
}
|
|
|
|
|
2018-09-28 13:40:08 +01:00
|
|
|
// Result contains durations for specific tests
|
|
|
|
type Result struct {
|
|
|
|
Name string
|
|
|
|
WithSpeed bool
|
|
|
|
Durations []time.Duration
|
|
|
|
}
|
|
|
|
|
|
|
|
// Result finds or creates a result with the specified name
|
|
|
|
func (m *Measurement) Result(name string) *Result {
|
|
|
|
for _, x := range m.Results {
|
|
|
|
if x.Name == name {
|
|
|
|
return x
|
|
|
|
}
|
2018-09-11 16:35:46 +01:00
|
|
|
}
|
2018-09-28 13:40:08 +01:00
|
|
|
|
|
|
|
r := &Result{}
|
|
|
|
r.Name = name
|
|
|
|
m.Results = append(m.Results, r)
|
|
|
|
return r
|
2018-09-11 16:35:46 +01:00
|
|
|
}
|
|
|
|
|
2018-09-28 13:40:08 +01:00
|
|
|
// Record records a time measurement
|
|
|
|
func (m *Measurement) Record(name string, duration time.Duration) {
|
|
|
|
r := m.Result(name)
|
|
|
|
r.WithSpeed = false
|
|
|
|
r.Durations = append(r.Durations, duration)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RecordSpeed records a time measurement that can be expressed in speed
|
|
|
|
func (m *Measurement) RecordSpeed(name string, duration time.Duration) {
|
|
|
|
r := m.Result(name)
|
|
|
|
r.WithSpeed = true
|
|
|
|
r.Durations = append(r.Durations, duration)
|
2018-09-11 16:35:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// PrintStats prints important valueas about the measurement
|
|
|
|
func (m *Measurement) PrintStats(w io.Writer) {
|
|
|
|
const binCount = 10
|
|
|
|
|
2018-09-28 13:40:08 +01:00
|
|
|
type Hist struct {
|
|
|
|
*Result
|
|
|
|
*hrtime.Histogram
|
|
|
|
}
|
|
|
|
|
|
|
|
hists := []Hist{}
|
|
|
|
for _, result := range m.Results {
|
|
|
|
hists = append(hists, Hist{
|
|
|
|
Result: result,
|
|
|
|
Histogram: hrtime.NewDurationHistogram(result.Durations, binCount),
|
|
|
|
})
|
2018-09-11 16:35:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
sec := func(ns float64) string {
|
|
|
|
return fmt.Sprintf("%.2f", ns/1e9)
|
|
|
|
}
|
|
|
|
speed := func(ns float64) string {
|
2018-09-28 13:40:08 +01:00
|
|
|
return fmt.Sprintf("%.2f", m.Size.MB()/(ns/1e9))
|
2018-09-11 16:35:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, hist := range hists {
|
2018-09-28 13:40:08 +01:00
|
|
|
if !hist.WithSpeed {
|
2018-09-11 16:35:46 +01:00
|
|
|
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n",
|
2018-09-28 13:40:08 +01:00
|
|
|
m.Size, hist.Name,
|
|
|
|
sec(hist.Average), "",
|
|
|
|
sec(hist.Maximum), "",
|
|
|
|
sec(hist.P50), "",
|
|
|
|
sec(hist.P90), "",
|
|
|
|
sec(hist.P99), "",
|
2018-09-11 16:35:46 +01:00
|
|
|
)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n",
|
2018-09-28 13:40:08 +01:00
|
|
|
m.Size, hist.Name,
|
|
|
|
sec(hist.Average), speed(hist.Average),
|
|
|
|
sec(hist.Maximum), speed(hist.Maximum),
|
|
|
|
sec(hist.P50), speed(hist.P50),
|
|
|
|
sec(hist.P90), speed(hist.P90),
|
|
|
|
sec(hist.P99), speed(hist.P99),
|
2018-09-11 16:35:46 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-06 13:26:35 +01:00
|
|
|
// FileBenchmark runs file upload, download and delete benchmarks on bucket with given filesize
|
|
|
|
func FileBenchmark(client Client, bucket string, filesize memory.Size, count int, duration time.Duration) (Measurement, error) {
|
|
|
|
log.Print("Benchmarking file size ", filesize.String(), " ")
|
2018-09-11 16:35:46 +01:00
|
|
|
|
2018-10-06 13:26:35 +01:00
|
|
|
data := make([]byte, filesize.Int())
|
|
|
|
result := make([]byte, filesize.Int())
|
2018-09-11 16:35:46 +01:00
|
|
|
|
|
|
|
defer fmt.Println()
|
|
|
|
|
|
|
|
measurement := Measurement{}
|
2018-10-06 13:26:35 +01:00
|
|
|
measurement.Size = filesize
|
2018-09-21 20:44:45 +01:00
|
|
|
start := time.Now()
|
2018-09-11 16:35:46 +01:00
|
|
|
for k := 0; k < count; k++ {
|
2018-09-21 20:44:45 +01:00
|
|
|
if time.Since(start) > duration {
|
|
|
|
break
|
|
|
|
}
|
2018-09-11 16:35:46 +01:00
|
|
|
fmt.Print(".")
|
|
|
|
|
2018-09-28 13:40:08 +01:00
|
|
|
// rand.Read(data[:])
|
|
|
|
for i := range data {
|
|
|
|
data[i] = 'a' + byte(i%26)
|
|
|
|
}
|
|
|
|
|
2018-09-11 16:35:46 +01:00
|
|
|
{ // uploading
|
|
|
|
start := hrtime.Now()
|
2018-09-28 13:40:08 +01:00
|
|
|
err := client.Upload(bucket, "data", data)
|
2018-09-11 16:35:46 +01:00
|
|
|
finish := hrtime.Now()
|
|
|
|
if err != nil {
|
2018-09-28 13:40:08 +01:00
|
|
|
return measurement, fmt.Errorf("upload failed: %+v", err)
|
2018-09-11 16:35:46 +01:00
|
|
|
}
|
2018-09-28 13:40:08 +01:00
|
|
|
|
|
|
|
measurement.RecordSpeed("Upload", finish-start)
|
2018-09-11 16:35:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
{ // downloading
|
|
|
|
start := hrtime.Now()
|
2018-09-28 13:40:08 +01:00
|
|
|
var err error
|
|
|
|
result, err = client.Download(bucket, "data", result)
|
2018-09-11 16:35:46 +01:00
|
|
|
if err != nil {
|
2018-09-28 13:40:08 +01:00
|
|
|
return measurement, fmt.Errorf("get object failed: %+v", err)
|
2018-09-11 16:35:46 +01:00
|
|
|
}
|
|
|
|
finish := hrtime.Now()
|
|
|
|
|
2018-09-28 13:40:08 +01:00
|
|
|
if !bytes.Equal(data, result) {
|
|
|
|
return measurement, fmt.Errorf("upload/download do not match: lengths %d and %d", len(data), len(result))
|
2018-09-11 16:35:46 +01:00
|
|
|
}
|
|
|
|
|
2018-09-28 13:40:08 +01:00
|
|
|
measurement.RecordSpeed("Download", finish-start)
|
2018-09-11 16:35:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
{ // deleting
|
|
|
|
start := hrtime.Now()
|
2018-09-28 13:40:08 +01:00
|
|
|
err := client.Delete(bucket, "data")
|
2018-09-11 16:35:46 +01:00
|
|
|
if err != nil {
|
2018-09-28 13:40:08 +01:00
|
|
|
return measurement, fmt.Errorf("delete failed: %+v", err)
|
2018-09-11 16:35:46 +01:00
|
|
|
}
|
|
|
|
finish := hrtime.Now()
|
2018-09-28 13:40:08 +01:00
|
|
|
|
|
|
|
measurement.Record("Delete", finish-start)
|
2018-09-11 16:35:46 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return measurement, nil
|
|
|
|
}
|
2018-09-28 13:40:08 +01:00
|
|
|
|
2018-10-06 13:26:35 +01:00
|
|
|
// ListBenchmark runs list buckets, folders and files benchmarks on bucket
|
|
|
|
func ListBenchmark(client Client, bucket string, listsize int, count int, duration time.Duration) (Measurement, error) {
|
|
|
|
log.Print("Benchmarking list")
|
|
|
|
defer fmt.Println()
|
|
|
|
measurement := Measurement{}
|
|
|
|
//measurement.Size = listsize
|
|
|
|
for k := 0; k < count; k++ {
|
|
|
|
{ // list folders
|
|
|
|
start := hrtime.Now()
|
|
|
|
result, err := client.ListObjects(bucket, "")
|
|
|
|
if err != nil {
|
|
|
|
return measurement, fmt.Errorf("list folders failed: %+v", err)
|
|
|
|
}
|
|
|
|
finish := hrtime.Now()
|
|
|
|
if len(result) != listsize {
|
|
|
|
return measurement, fmt.Errorf("list folders result wrong: %+v", len(result))
|
|
|
|
}
|
|
|
|
measurement.Record("List Folders", finish-start)
|
|
|
|
}
|
|
|
|
{ // list files
|
|
|
|
start := hrtime.Now()
|
|
|
|
result, err := client.ListObjects(bucket, "folder")
|
|
|
|
if err != nil {
|
|
|
|
return measurement, fmt.Errorf("list files failed: %+v", err)
|
|
|
|
}
|
|
|
|
finish := hrtime.Now()
|
|
|
|
if len(result) != listsize {
|
|
|
|
return measurement, fmt.Errorf("list files result to low: %+v", len(result))
|
|
|
|
}
|
|
|
|
measurement.Record("List Files", finish-start)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return measurement, nil
|
|
|
|
}
|
|
|
|
|
2018-09-28 13:40:08 +01:00
|
|
|
// Config is the setup for a particular client
|
|
|
|
type Config struct {
|
|
|
|
Endpoint string
|
|
|
|
AccessKey string
|
|
|
|
SecretKey string
|
|
|
|
NoSSL bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// Client is the common interface for different implementations
|
|
|
|
type Client interface {
|
|
|
|
MakeBucket(bucket, location string) error
|
|
|
|
RemoveBucket(bucket string) error
|
|
|
|
ListBuckets() ([]string, error)
|
|
|
|
|
|
|
|
Upload(bucket, objectName string, data []byte) error
|
|
|
|
UploadMultipart(bucket, objectName string, data []byte, multipartThreshold int) error
|
|
|
|
Download(bucket, objectName string, buffer []byte) ([]byte, error)
|
|
|
|
Delete(bucket, objectName string) error
|
|
|
|
ListObjects(bucket, prefix string) ([]string, error)
|
|
|
|
}
|