2019-08-16 00:34:51 +03:00
|
|
|
// hsbench.go
|
2017-06-09 18:39:42 +03:00
|
|
|
// Copyright (c) 2017 Wasabi Technology, Inc.
|
2019-08-16 00:34:51 +03:00
|
|
|
// Copyright (c) 2019 Red Hat Inc.
|
2017-06-09 18:39:42 +03:00
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"crypto/hmac"
|
|
|
|
"crypto/md5"
|
|
|
|
"crypto/sha1"
|
|
|
|
"crypto/tls"
|
|
|
|
"encoding/base64"
|
2019-08-19 15:24:26 +03:00
|
|
|
"encoding/csv"
|
2017-06-09 18:39:42 +03:00
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
2019-08-13 20:22:07 +03:00
|
|
|
"math"
|
2017-06-09 18:39:42 +03:00
|
|
|
"math/rand"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"sort"
|
2019-08-13 20:22:07 +03:00
|
|
|
"strconv"
|
2017-06-09 18:39:42 +03:00
|
|
|
"strings"
|
2019-08-13 20:22:07 +03:00
|
|
|
"sync"
|
2017-06-09 18:39:42 +03:00
|
|
|
"sync/atomic"
|
|
|
|
"time"
|
2019-03-15 13:06:45 +03:00
|
|
|
"code.cloudfoundry.org/bytefmt"
|
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
|
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
|
|
"github.com/aws/aws-sdk-go/aws/session"
|
|
|
|
"github.com/aws/aws-sdk-go/service/s3"
|
2017-06-09 18:39:42 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
// Global variables
|
2019-08-19 15:24:26 +03:00
|
|
|
var access_key, secret_key, url_host, bucket_prefix, object_prefix, region, modes, output, sizeArg string
|
2019-08-12 02:26:21 +03:00
|
|
|
var buckets []string
|
2019-08-15 20:17:10 +03:00
|
|
|
var duration_secs, threads, loops int
|
2017-06-09 18:39:42 +03:00
|
|
|
var object_data []byte
|
|
|
|
var object_data_md5 string
|
2019-08-15 20:17:10 +03:00
|
|
|
var running_threads, bucket_count, object_count, object_size, op_counter int64
|
2019-08-16 00:23:38 +03:00
|
|
|
var object_count_flag bool
|
2019-08-15 23:06:28 +03:00
|
|
|
var endtime time.Time
|
2019-08-13 20:22:07 +03:00
|
|
|
var interval float64
|
2019-08-19 15:24:26 +03:00
|
|
|
var csvWriter *csv.Writer
|
2017-06-09 18:39:42 +03:00
|
|
|
|
|
|
|
// Our HTTP transport used for the roundtripper below
|
|
|
|
var HTTPTransport http.RoundTripper = &http.Transport{
|
|
|
|
Proxy: http.ProxyFromEnvironment,
|
|
|
|
Dial: (&net.Dialer{
|
|
|
|
Timeout: 30 * time.Second,
|
|
|
|
KeepAlive: 30 * time.Second,
|
|
|
|
}).Dial,
|
|
|
|
TLSHandshakeTimeout: 10 * time.Second,
|
|
|
|
ExpectContinueTimeout: 0,
|
|
|
|
// Allow an unlimited number of idle connections
|
|
|
|
MaxIdleConnsPerHost: 4096,
|
|
|
|
MaxIdleConns: 0,
|
|
|
|
// But limit their idle time
|
|
|
|
IdleConnTimeout: time.Minute,
|
|
|
|
// Ignore TLS errors
|
|
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
|
|
}
|
|
|
|
|
|
|
|
var httpClient = &http.Client{Transport: HTTPTransport}
|
|
|
|
|
|
|
|
func getS3Client() *s3.S3 {
|
|
|
|
// Build our config
|
|
|
|
creds := credentials.NewStaticCredentials(access_key, secret_key, "")
|
|
|
|
loglevel := aws.LogOff
|
|
|
|
// Build the rest of the configuration
|
|
|
|
awsConfig := &aws.Config{
|
2018-08-07 14:57:04 +03:00
|
|
|
Region: aws.String(region),
|
2017-06-09 18:39:42 +03:00
|
|
|
Endpoint: aws.String(url_host),
|
|
|
|
Credentials: creds,
|
|
|
|
LogLevel: &loglevel,
|
|
|
|
S3ForcePathStyle: aws.Bool(true),
|
|
|
|
S3Disable100Continue: aws.Bool(true),
|
|
|
|
// Comment following to use default transport
|
|
|
|
HTTPClient: &http.Client{Transport: HTTPTransport},
|
|
|
|
}
|
|
|
|
session := session.New(awsConfig)
|
|
|
|
client := s3.New(session)
|
|
|
|
if client == nil {
|
|
|
|
log.Fatalf("FATAL: Unable to create new client.")
|
|
|
|
}
|
|
|
|
// Return success
|
|
|
|
return client
|
|
|
|
}
|
|
|
|
|
|
|
|
// canonicalAmzHeaders -- return the x-amz headers canonicalized
|
|
|
|
func canonicalAmzHeaders(req *http.Request) string {
|
|
|
|
// Parse out all x-amz headers
|
|
|
|
var headers []string
|
|
|
|
for header := range req.Header {
|
|
|
|
norm := strings.ToLower(strings.TrimSpace(header))
|
|
|
|
if strings.HasPrefix(norm, "x-amz") {
|
|
|
|
headers = append(headers, norm)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Put them in sorted order
|
|
|
|
sort.Strings(headers)
|
|
|
|
// Now add back the values
|
|
|
|
for n, header := range headers {
|
|
|
|
headers[n] = header + ":" + strings.Replace(req.Header.Get(header), "\n", " ", -1)
|
|
|
|
}
|
|
|
|
// Finally, put them back together
|
|
|
|
if len(headers) > 0 {
|
|
|
|
return strings.Join(headers, "\n") + "\n"
|
|
|
|
} else {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func hmacSHA1(key []byte, content string) []byte {
|
|
|
|
mac := hmac.New(sha1.New, key)
|
|
|
|
mac.Write([]byte(content))
|
|
|
|
return mac.Sum(nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
func setSignature(req *http.Request) {
|
|
|
|
// Setup default parameters
|
|
|
|
dateHdr := time.Now().UTC().Format("20060102T150405Z")
|
|
|
|
req.Header.Set("X-Amz-Date", dateHdr)
|
|
|
|
// Get the canonical resource and header
|
|
|
|
canonicalResource := req.URL.EscapedPath()
|
|
|
|
canonicalHeaders := canonicalAmzHeaders(req)
|
|
|
|
stringToSign := req.Method + "\n" + req.Header.Get("Content-MD5") + "\n" + req.Header.Get("Content-Type") + "\n\n" +
|
|
|
|
canonicalHeaders + canonicalResource
|
|
|
|
hash := hmacSHA1([]byte(secret_key), stringToSign)
|
|
|
|
signature := base64.StdEncoding.EncodeToString(hash)
|
|
|
|
req.Header.Set("Authorization", fmt.Sprintf("AWS %s:%s", access_key, signature))
|
|
|
|
}
|
|
|
|
|
2019-08-13 20:22:07 +03:00
|
|
|
type IntervalStats struct {
|
2019-08-19 15:24:26 +03:00
|
|
|
loop int
|
|
|
|
name string
|
|
|
|
mode string
|
2019-08-13 20:22:07 +03:00
|
|
|
bytes int64
|
|
|
|
slowdowns int64
|
2019-08-19 15:24:26 +03:00
|
|
|
intervalNano int64
|
2019-08-13 20:22:07 +03:00
|
|
|
latNano []int64
|
|
|
|
}
|
|
|
|
|
2019-08-19 15:24:26 +03:00
|
|
|
func (is *IntervalStats) makeOutputStats() OutputStats {
|
|
|
|
// Compute and log the stats
|
|
|
|
ops := len(is.latNano)
|
|
|
|
totalLat := int64(0);
|
|
|
|
minLat := float64(0);
|
|
|
|
maxLat := float64(0);
|
|
|
|
NinetyNineLat := float64(0);
|
|
|
|
avgLat := float64(0);
|
|
|
|
if ops > 0 {
|
|
|
|
minLat = float64(is.latNano[0]) / 1000000
|
|
|
|
maxLat = float64(is.latNano[ops - 1]) / 1000000
|
|
|
|
for i := range is.latNano {
|
|
|
|
totalLat += is.latNano[i]
|
|
|
|
}
|
|
|
|
avgLat = float64(totalLat) / float64(ops) / 1000000
|
|
|
|
NintyNineLatNano := is.latNano[int64(math.Round(0.99*float64(ops))) - 1]
|
|
|
|
NinetyNineLat = float64(NintyNineLatNano) / 1000000
|
|
|
|
}
|
|
|
|
seconds := float64(is.intervalNano) / 1000000000
|
|
|
|
mbps := float64(is.bytes) / seconds / bytefmt.MEGABYTE
|
|
|
|
iops := float64(ops) / seconds
|
|
|
|
|
|
|
|
return OutputStats{
|
|
|
|
is.loop,
|
|
|
|
is.name,
|
|
|
|
seconds,
|
|
|
|
is.mode,
|
|
|
|
ops,
|
|
|
|
mbps,
|
|
|
|
iops,
|
|
|
|
minLat,
|
|
|
|
avgLat,
|
|
|
|
NinetyNineLat,
|
|
|
|
maxLat,
|
|
|
|
is.slowdowns}
|
|
|
|
}
|
|
|
|
|
|
|
|
type OutputStats struct {
|
|
|
|
loop int
|
|
|
|
intervalName string
|
|
|
|
seconds float64
|
|
|
|
mode string
|
|
|
|
ops int
|
|
|
|
mbps float64
|
|
|
|
iops float64
|
|
|
|
minLat float64
|
|
|
|
avgLat float64
|
|
|
|
NinetyNineLat float64
|
|
|
|
maxLat float64
|
|
|
|
slowdowns int64
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *OutputStats) log() {
|
|
|
|
log.Printf(
|
|
|
|
"Loop: %d, Int: %s, Dur(s): %.1f, Mode: %s, Ops: %d, MB/s: %.2f, IO/s: %.0f, Lat(ms): [ min: %.1f, avg: %.1f, 99%%: %.1f, max: %.1f ], Slowdowns: %d",
|
|
|
|
o.loop,
|
|
|
|
o.intervalName,
|
|
|
|
o.seconds,
|
|
|
|
o.mode,
|
|
|
|
o.ops,
|
|
|
|
o.mbps,
|
|
|
|
o.iops,
|
|
|
|
o.minLat,
|
|
|
|
o.avgLat,
|
|
|
|
o.NinetyNineLat,
|
|
|
|
o.maxLat,
|
|
|
|
o.slowdowns)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *OutputStats) csv_header(w *csv.Writer) {
|
|
|
|
if w == nil {
|
|
|
|
log.Fatal("OutputStats Passed nil csv writer")
|
|
|
|
}
|
|
|
|
|
|
|
|
s := []string{
|
|
|
|
"Loop",
|
|
|
|
"Inteval",
|
|
|
|
"Duration(s)",
|
|
|
|
"Mode", "Ops",
|
|
|
|
"MB/s",
|
|
|
|
"IO/s",
|
|
|
|
"Min Latency (ms)",
|
|
|
|
"Avg Latency(ms)",
|
|
|
|
"99% Latency(ms)",
|
|
|
|
"Max Latency(ms)",
|
|
|
|
"Slowdowns"}
|
|
|
|
|
|
|
|
if err := w.Write(s); err != nil {
|
|
|
|
log.Fatal("Error writing to csv: ", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *OutputStats) csv(w *csv.Writer) {
|
|
|
|
if w == nil {
|
|
|
|
log.Fatal("OutputStats Passed nil csv writer")
|
|
|
|
}
|
|
|
|
|
|
|
|
s := []string {
|
|
|
|
strconv.Itoa(o.loop),
|
|
|
|
o.intervalName,
|
|
|
|
strconv.FormatFloat(o.seconds, 'f', 2, 64),
|
|
|
|
o.mode,
|
|
|
|
strconv.Itoa(o.ops),
|
|
|
|
strconv.FormatFloat(o.mbps, 'f', 2, 64),
|
|
|
|
strconv.FormatFloat(o.iops, 'f', 2, 64),
|
|
|
|
strconv.FormatFloat(o.minLat, 'f', 2, 64),
|
|
|
|
strconv.FormatFloat(o.avgLat, 'f', 2, 64),
|
|
|
|
strconv.FormatFloat(o.NinetyNineLat, 'f', 2, 64),
|
|
|
|
strconv.FormatFloat(o.maxLat, 'f', 2, 64),
|
|
|
|
strconv.FormatInt(o.slowdowns, 10)}
|
|
|
|
|
|
|
|
if err := w.Write(s); err != nil {
|
|
|
|
log.Fatal("Error writing to csv: ", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-13 20:22:07 +03:00
|
|
|
type ThreadStats struct {
|
|
|
|
start int64
|
|
|
|
curInterval int64
|
|
|
|
intervals []IntervalStats
|
|
|
|
}
|
|
|
|
|
2019-08-19 15:24:26 +03:00
|
|
|
func makeThreadStats(s int64, loop int, mode string, intervalNano int64) ThreadStats {
|
|
|
|
ts := ThreadStats{s, 0, []IntervalStats{}}
|
|
|
|
ts.intervals = append(ts.intervals, IntervalStats{loop, "0", mode, 0, 0, intervalNano, []int64{}})
|
2019-08-13 20:22:07 +03:00
|
|
|
return ts
|
|
|
|
}
|
|
|
|
|
2019-08-19 15:24:26 +03:00
|
|
|
func (ts *ThreadStats) updateIntervals(loop int, mode string, intervalNano int64) int64 {
|
|
|
|
// Interval statistics disabled, so just return the current interval
|
2019-08-13 20:22:07 +03:00
|
|
|
if intervalNano < 0 {
|
|
|
|
return ts.curInterval
|
|
|
|
}
|
2019-08-19 15:24:26 +03:00
|
|
|
for ts.start + intervalNano*(ts.curInterval+1) < time.Now().UnixNano() {
|
|
|
|
ts.curInterval++
|
|
|
|
ts.intervals = append(
|
|
|
|
ts.intervals,
|
|
|
|
IntervalStats{
|
|
|
|
loop,
|
|
|
|
strconv.FormatInt(ts.curInterval, 10),
|
|
|
|
mode,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
intervalNano,
|
|
|
|
[]int64{}})
|
2019-08-13 20:22:07 +03:00
|
|
|
}
|
|
|
|
return ts.curInterval
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ts *ThreadStats) finish() {
|
|
|
|
ts.curInterval = -1
|
|
|
|
}
|
|
|
|
|
|
|
|
type Stats struct {
|
|
|
|
// threads
|
|
|
|
threads int
|
|
|
|
// The loop we are in
|
|
|
|
loop int
|
|
|
|
// Test mode being run
|
|
|
|
mode string
|
|
|
|
// start time in nanoseconds
|
|
|
|
startNano int64
|
|
|
|
// end time in nanoseconds
|
|
|
|
endNano int64
|
|
|
|
// Duration in nanoseconds for each interval
|
|
|
|
intervalNano int64
|
|
|
|
// Per-thread statistics
|
|
|
|
threadStats []ThreadStats
|
2019-08-19 15:24:26 +03:00
|
|
|
// a map of per-interval thread completion counters
|
2019-08-13 20:22:07 +03:00
|
|
|
intervalCompletions sync.Map
|
|
|
|
// a counter of how many threads have finished updating stats entirely
|
|
|
|
completions int32
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeStats(loop int, mode string, threads int, intervalNano int64) Stats {
|
|
|
|
start := time.Now().UnixNano()
|
|
|
|
s := Stats{threads, loop, mode, start, 0, intervalNano, []ThreadStats{}, sync.Map{}, 0}
|
|
|
|
for i := 0; i < threads; i++ {
|
2019-08-19 15:24:26 +03:00
|
|
|
s.threadStats = append(s.threadStats, makeThreadStats(start, s.loop, s.mode, s.intervalNano))
|
|
|
|
s.updateIntervals(i)
|
2019-08-13 20:22:07 +03:00
|
|
|
}
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2019-08-19 15:24:26 +03:00
|
|
|
func (stats *Stats) makeOutputStats(i int64) (OutputStats, bool) {
|
|
|
|
// Check bounds first
|
|
|
|
if stats.intervalNano < 0 || i < 0 {
|
|
|
|
return OutputStats{}, false
|
|
|
|
}
|
|
|
|
// Not safe to log if not all writers have completed.
|
|
|
|
value, ok := stats.intervalCompletions.Load(i)
|
|
|
|
if !ok {
|
|
|
|
return OutputStats{}, false
|
|
|
|
}
|
|
|
|
cp, ok := value.(*int32)
|
|
|
|
if !ok {
|
|
|
|
return OutputStats{}, false
|
|
|
|
}
|
|
|
|
count := atomic.LoadInt32(cp)
|
|
|
|
if count < int32(stats.threads) {
|
|
|
|
return OutputStats{}, false
|
|
|
|
}
|
|
|
|
|
2019-08-13 20:22:07 +03:00
|
|
|
bytes := int64(0)
|
|
|
|
ops := int64(0)
|
2019-08-19 15:24:26 +03:00
|
|
|
slowdowns := int64(0)
|
2019-08-13 20:22:07 +03:00
|
|
|
|
|
|
|
for t := 0; t < stats.threads; t++ {
|
|
|
|
bytes += stats.threadStats[t].intervals[i].bytes
|
|
|
|
ops += int64(len(stats.threadStats[t].intervals[i].latNano))
|
|
|
|
slowdowns += stats.threadStats[t].intervals[i].slowdowns
|
|
|
|
}
|
|
|
|
// Aggregate the per-thread Latency slice
|
|
|
|
tmpLat := make([]int64, ops)
|
|
|
|
var c int
|
|
|
|
for t := 0; t < stats.threads; t++ {
|
|
|
|
c += copy(tmpLat[c:], stats.threadStats[t].intervals[i].latNano)
|
|
|
|
}
|
|
|
|
sort.Slice(tmpLat, func(i, j int) bool { return tmpLat[i] < tmpLat[j] })
|
2019-08-19 15:24:26 +03:00
|
|
|
is := IntervalStats{stats.loop, strconv.FormatInt(i, 10), stats.mode, bytes, slowdowns, stats.intervalNano, tmpLat}
|
|
|
|
return is.makeOutputStats(), true
|
2019-08-13 20:22:07 +03:00
|
|
|
}
|
|
|
|
|
2019-08-19 15:24:26 +03:00
|
|
|
func (stats *Stats) makeTotalStats() (OutputStats, bool) {
|
|
|
|
// Not safe to log if not all writers have completed.
|
|
|
|
completions := atomic.LoadInt32(&stats.completions)
|
|
|
|
if (completions < int32(threads)) {
|
|
|
|
log.Printf("log, completions: %d", completions)
|
|
|
|
return OutputStats{}, false
|
|
|
|
}
|
|
|
|
|
2019-08-13 20:22:07 +03:00
|
|
|
bytes := int64(0)
|
|
|
|
ops := int64(0)
|
|
|
|
slowdowns := int64(0);
|
|
|
|
|
|
|
|
for t := 0; t < stats.threads; t++ {
|
|
|
|
for i := 0; i < len(stats.threadStats[t].intervals); i++ {
|
|
|
|
bytes += stats.threadStats[t].intervals[i].bytes
|
|
|
|
ops += int64(len(stats.threadStats[t].intervals[i].latNano))
|
|
|
|
slowdowns += stats.threadStats[t].intervals[i].slowdowns
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Aggregate the per-thread Latency slice
|
|
|
|
tmpLat := make([]int64, ops)
|
|
|
|
var c int
|
|
|
|
for t := 0; t < stats.threads; t++ {
|
|
|
|
for i := 0; i < len(stats.threadStats[t].intervals); i++ {
|
|
|
|
c += copy(tmpLat[c:], stats.threadStats[t].intervals[i].latNano)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sort.Slice(tmpLat, func(i, j int) bool { return tmpLat[i] < tmpLat[j] })
|
2019-08-19 15:24:26 +03:00
|
|
|
is := IntervalStats{stats.loop, "TOTAL", stats.mode, bytes, slowdowns, stats.endNano - stats.startNano, tmpLat}
|
|
|
|
return is.makeOutputStats(), true
|
2019-08-13 20:22:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Only safe to call from the calling thread
|
|
|
|
func (stats *Stats) updateIntervals(thread_num int) int64 {
|
|
|
|
curInterval := stats.threadStats[thread_num].curInterval
|
2019-08-19 15:24:26 +03:00
|
|
|
newInterval := stats.threadStats[thread_num].updateIntervals(stats.loop, stats.mode, stats.intervalNano)
|
2019-08-13 20:22:07 +03:00
|
|
|
|
|
|
|
// Finish has already been called
|
|
|
|
if curInterval < 0 {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := curInterval; i < newInterval; i++ {
|
|
|
|
// load or store the current value
|
|
|
|
value, _ := stats.intervalCompletions.LoadOrStore(i, new(int32))
|
|
|
|
cp, ok := value.(*int32)
|
|
|
|
if !ok {
|
|
|
|
log.Printf("updateIntervals: got data of type %T but wanted *int32", value)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
count := atomic.AddInt32(cp, 1)
|
|
|
|
if count == int32(stats.threads) {
|
2019-08-19 15:24:26 +03:00
|
|
|
if is, ok := stats.makeOutputStats(i); ok {
|
|
|
|
is.log()
|
|
|
|
}
|
2019-08-13 20:22:07 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return newInterval
|
|
|
|
}
|
|
|
|
|
|
|
|
func (stats *Stats) addOp(thread_num int, bytes int64, latNano int64) {
|
|
|
|
|
|
|
|
// Interval statistics
|
|
|
|
cur := stats.threadStats[thread_num].curInterval
|
|
|
|
if cur < 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
stats.threadStats[thread_num].intervals[cur].bytes += bytes
|
|
|
|
stats.threadStats[thread_num].intervals[cur].latNano =
|
|
|
|
append(stats.threadStats[thread_num].intervals[cur].latNano, latNano)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (stats *Stats) addSlowDown(thread_num int) {
|
|
|
|
cur := stats.threadStats[thread_num].curInterval
|
|
|
|
stats.threadStats[thread_num].intervals[cur].slowdowns++
|
|
|
|
}
|
|
|
|
|
|
|
|
func (stats *Stats) finish(thread_num int) {
|
2019-08-19 15:24:26 +03:00
|
|
|
stats.updateIntervals(thread_num)
|
2019-08-13 20:22:07 +03:00
|
|
|
stats.threadStats[thread_num].finish()
|
|
|
|
count := atomic.AddInt32(&stats.completions, 1)
|
2019-08-19 15:24:26 +03:00
|
|
|
if count == int32(stats.threads) {
|
2019-08-13 20:22:07 +03:00
|
|
|
stats.endNano = time.Now().UnixNano()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func runUpload(thread_num int, fendtime time.Time, stats *Stats) {
|
2019-03-15 13:06:45 +03:00
|
|
|
errcnt := 0
|
|
|
|
svc := s3.New(session.New(), cfg)
|
2019-08-12 03:50:46 +03:00
|
|
|
for {
|
|
|
|
if duration_secs > -1 && time.Now().After(endtime) {
|
|
|
|
break
|
|
|
|
}
|
2019-08-15 20:17:10 +03:00
|
|
|
objnum := atomic.AddInt64(&op_counter, 1)
|
2019-08-12 15:54:02 +03:00
|
|
|
bucket_num := objnum % int64(bucket_count)
|
2019-08-15 20:17:10 +03:00
|
|
|
if object_count > -1 && objnum >= object_count {
|
|
|
|
objnum = atomic.AddInt64(&op_counter, -1)
|
2019-08-12 03:50:46 +03:00
|
|
|
break
|
|
|
|
}
|
2017-06-09 18:39:42 +03:00
|
|
|
fileobj := bytes.NewReader(object_data)
|
2019-03-15 13:06:45 +03:00
|
|
|
|
2019-08-12 17:36:58 +03:00
|
|
|
key := fmt.Sprintf("%s%012d", object_prefix, objnum)
|
2019-03-15 13:06:45 +03:00
|
|
|
r := &s3.PutObjectInput{
|
2019-08-12 02:26:21 +03:00
|
|
|
Bucket: &buckets[bucket_num],
|
2019-03-15 13:06:45 +03:00
|
|
|
Key: &key,
|
|
|
|
Body: fileobj,
|
|
|
|
}
|
2019-08-13 20:22:07 +03:00
|
|
|
start := time.Now().UnixNano()
|
2019-03-15 13:06:45 +03:00
|
|
|
req, _ := svc.PutObjectRequest(r)
|
|
|
|
// Disable payload checksum calculation (very expensive)
|
|
|
|
req.HTTPRequest.Header.Add("X-Amz-Content-Sha256", "UNSIGNED-PAYLOAD")
|
|
|
|
err := req.Send()
|
2019-08-13 20:22:07 +03:00
|
|
|
end := time.Now().UnixNano()
|
|
|
|
stats.updateIntervals(thread_num)
|
|
|
|
|
2019-03-15 13:06:45 +03:00
|
|
|
if err != nil {
|
|
|
|
errcnt++
|
2019-08-13 20:22:07 +03:00
|
|
|
stats.addSlowDown(thread_num);
|
2019-08-15 20:17:10 +03:00
|
|
|
atomic.AddInt64(&op_counter, -1)
|
2019-08-15 23:06:28 +03:00
|
|
|
log.Printf("upload err", err)
|
2019-08-13 20:22:07 +03:00
|
|
|
} else {
|
|
|
|
// Update the stats
|
|
|
|
stats.addOp(thread_num, object_size, end-start)
|
2017-06-09 18:39:42 +03:00
|
|
|
}
|
2019-03-15 13:06:45 +03:00
|
|
|
if errcnt > 2 {
|
|
|
|
break
|
|
|
|
}
|
2017-06-09 18:39:42 +03:00
|
|
|
}
|
2019-08-19 15:24:26 +03:00
|
|
|
stats.finish(thread_num)
|
2019-08-12 03:50:46 +03:00
|
|
|
atomic.AddInt64(&running_threads, -1)
|
2017-06-09 18:39:42 +03:00
|
|
|
}
|
|
|
|
|
2019-08-13 20:22:07 +03:00
|
|
|
func runDownload(thread_num int, fendtime time.Time, stats *Stats) {
|
2019-03-15 13:06:45 +03:00
|
|
|
errcnt := 0
|
|
|
|
svc := s3.New(session.New(), cfg)
|
2019-08-12 15:54:02 +03:00
|
|
|
for {
|
2019-08-12 03:50:46 +03:00
|
|
|
if duration_secs > -1 && time.Now().After(endtime) {
|
2019-08-12 15:54:02 +03:00
|
|
|
break
|
|
|
|
}
|
2019-03-15 13:06:45 +03:00
|
|
|
|
2019-08-15 20:17:10 +03:00
|
|
|
objnum := atomic.AddInt64(&op_counter, 1)
|
2019-08-16 00:23:38 +03:00
|
|
|
if object_count > -1 && objnum >= object_count {
|
2019-08-15 20:17:10 +03:00
|
|
|
atomic.AddInt64(&op_counter, -1)
|
2019-08-12 15:54:02 +03:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
bucket_num := objnum % int64(bucket_count)
|
2019-08-12 17:36:58 +03:00
|
|
|
key := fmt.Sprintf("%s%012d", object_prefix, objnum)
|
2019-08-12 15:54:02 +03:00
|
|
|
r := &s3.GetObjectInput{
|
|
|
|
Bucket: &buckets[bucket_num],
|
|
|
|
Key: &key,
|
|
|
|
}
|
2019-03-15 13:06:45 +03:00
|
|
|
|
2019-08-13 20:22:07 +03:00
|
|
|
start := time.Now().UnixNano()
|
2019-08-12 15:54:02 +03:00
|
|
|
req, resp := svc.GetObjectRequest(r)
|
|
|
|
err := req.Send()
|
2019-08-13 20:22:07 +03:00
|
|
|
end := time.Now().UnixNano()
|
|
|
|
stats.updateIntervals(thread_num)
|
|
|
|
|
2019-08-12 15:54:02 +03:00
|
|
|
if err != nil {
|
|
|
|
errcnt++
|
2019-08-13 20:22:07 +03:00
|
|
|
stats.addSlowDown(thread_num);
|
2019-08-15 23:06:28 +03:00
|
|
|
log.Printf("download err", err)
|
2019-08-13 20:22:07 +03:00
|
|
|
} else {
|
|
|
|
// Update the stats
|
|
|
|
stats.addOp(thread_num, object_size, end-start)
|
|
|
|
}
|
|
|
|
|
2019-08-12 15:54:02 +03:00
|
|
|
if err == nil {
|
|
|
|
_, err = io.Copy(ioutil.Discard, resp.Body)
|
|
|
|
}
|
|
|
|
if errcnt > 2 {
|
|
|
|
break
|
|
|
|
}
|
2019-08-13 20:22:07 +03:00
|
|
|
|
2019-08-12 15:54:02 +03:00
|
|
|
}
|
2019-08-19 15:24:26 +03:00
|
|
|
stats.finish(thread_num)
|
2019-08-12 03:50:46 +03:00
|
|
|
atomic.AddInt64(&running_threads, -1)
|
2017-06-09 18:39:42 +03:00
|
|
|
}
|
|
|
|
|
2019-08-13 20:22:07 +03:00
|
|
|
func runDelete(thread_num int, stats *Stats) {
|
2019-03-15 13:06:45 +03:00
|
|
|
errcnt := 0
|
|
|
|
svc := s3.New(session.New(), cfg)
|
2019-08-12 02:26:21 +03:00
|
|
|
|
2019-08-12 15:54:02 +03:00
|
|
|
for {
|
2019-08-16 00:23:38 +03:00
|
|
|
if duration_secs > -1 && time.Now().After(endtime) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2019-08-15 20:17:10 +03:00
|
|
|
objnum := atomic.AddInt64(&op_counter, 1)
|
2019-08-16 00:23:38 +03:00
|
|
|
if object_count > -1 && objnum >= object_count {
|
2019-08-15 20:17:10 +03:00
|
|
|
atomic.AddInt64(&op_counter, -1)
|
2019-08-12 15:54:02 +03:00
|
|
|
break
|
2019-03-15 13:06:45 +03:00
|
|
|
}
|
|
|
|
|
2019-08-12 15:54:02 +03:00
|
|
|
bucket_num := objnum % int64(bucket_count)
|
|
|
|
|
2019-08-12 17:36:58 +03:00
|
|
|
key := fmt.Sprintf("%s%012d", object_prefix, objnum)
|
2019-08-12 15:54:02 +03:00
|
|
|
r := &s3.DeleteObjectInput{
|
|
|
|
Bucket: &buckets[bucket_num],
|
|
|
|
Key: &key,
|
|
|
|
}
|
2019-08-12 02:26:21 +03:00
|
|
|
|
2019-08-13 20:22:07 +03:00
|
|
|
start := time.Now().UnixNano()
|
2019-08-12 15:54:02 +03:00
|
|
|
req, out := svc.DeleteObjectRequest(r)
|
|
|
|
err := req.Send()
|
2019-08-13 20:22:07 +03:00
|
|
|
end := time.Now().UnixNano()
|
|
|
|
stats.updateIntervals(thread_num)
|
|
|
|
|
2019-08-12 15:54:02 +03:00
|
|
|
if err != nil {
|
|
|
|
errcnt++
|
2019-08-13 20:22:07 +03:00
|
|
|
stats.addSlowDown(thread_num);
|
2019-08-15 23:06:28 +03:00
|
|
|
log.Printf("delete err", err, "out", out.String())
|
2019-08-13 20:22:07 +03:00
|
|
|
} else {
|
|
|
|
// Update the stats
|
|
|
|
stats.addOp(thread_num, object_size, end-start)
|
|
|
|
}
|
2019-08-12 15:54:02 +03:00
|
|
|
if errcnt > 2 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2019-08-13 20:22:07 +03:00
|
|
|
stats.finish(thread_num)
|
2019-08-19 15:24:26 +03:00
|
|
|
atomic.AddInt64(&running_threads, -1)
|
2017-06-09 18:39:42 +03:00
|
|
|
}
|
|
|
|
|
2019-08-15 23:06:28 +03:00
|
|
|
func runBucketDelete(thread_num int, stats *Stats) {
|
|
|
|
svc := s3.New(session.New(), cfg)
|
2017-06-09 18:39:42 +03:00
|
|
|
|
2019-08-15 20:17:10 +03:00
|
|
|
for {
|
|
|
|
bucket_num := atomic.AddInt64(&op_counter, 1)
|
2019-08-15 23:06:28 +03:00
|
|
|
if bucket_num >= bucket_count {
|
|
|
|
atomic.AddInt64(&op_counter, -1)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
r := &s3.DeleteBucketInput{
|
|
|
|
Bucket: &buckets[bucket_num],
|
|
|
|
}
|
|
|
|
|
|
|
|
start := time.Now().UnixNano()
|
|
|
|
_, err := svc.DeleteBucket(r)
|
|
|
|
end := time.Now().UnixNano()
|
|
|
|
stats.updateIntervals(thread_num)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
stats.addOp(thread_num, 0, end-start)
|
|
|
|
}
|
|
|
|
stats.finish(thread_num)
|
2019-08-19 15:24:26 +03:00
|
|
|
atomic.AddInt64(&running_threads, -1)
|
2019-08-15 23:06:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
var cfg *aws.Config
|
|
|
|
|
|
|
|
func runBucketsInit(thread_num int, stats *Stats) {
|
|
|
|
svc := s3.New(session.New(), cfg)
|
|
|
|
|
|
|
|
for {
|
|
|
|
bucket_num := atomic.AddInt64(&op_counter, 1)
|
2019-08-15 20:17:10 +03:00
|
|
|
if bucket_num >= bucket_count {
|
|
|
|
atomic.AddInt64(&op_counter, -1)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
start := time.Now().UnixNano()
|
2019-08-15 23:06:28 +03:00
|
|
|
in := &s3.CreateBucketInput{Bucket: aws.String(buckets[bucket_num])}
|
|
|
|
_, err := svc.CreateBucket(in)
|
2019-08-15 20:17:10 +03:00
|
|
|
end := time.Now().UnixNano()
|
|
|
|
stats.updateIntervals(thread_num)
|
2019-08-15 23:06:28 +03:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
if !strings.Contains(err.Error(), s3.ErrCodeBucketAlreadyOwnedByYou) &&
|
|
|
|
!strings.Contains(err.Error(), "BucketAlreadyExists") {
|
|
|
|
log.Fatalf("FATAL: Unable to create bucket %s (is your access and secret correct?): %v", buckets[bucket_num], err)
|
|
|
|
}
|
|
|
|
}
|
2019-08-15 20:17:10 +03:00
|
|
|
stats.addOp(thread_num, 0, end-start)
|
2019-08-12 22:06:57 +03:00
|
|
|
}
|
2019-08-19 15:24:26 +03:00
|
|
|
stats.finish(thread_num)
|
2019-08-15 20:17:10 +03:00
|
|
|
atomic.AddInt64(&running_threads, -1)
|
2019-08-12 22:06:57 +03:00
|
|
|
}
|
|
|
|
|
2019-08-15 23:06:28 +03:00
|
|
|
func runBucketsClear(thread_num int, stats *Stats) {
|
|
|
|
svc := s3.New(session.New(), cfg)
|
|
|
|
|
|
|
|
for {
|
|
|
|
bucket_num := atomic.AddInt64(&op_counter, 1)
|
|
|
|
if bucket_num >= bucket_count {
|
|
|
|
atomic.AddInt64(&op_counter, -1)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
out, err := svc.ListObjects(&s3.ListObjectsInput{Bucket: &buckets[bucket_num]})
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
n := len(out.Contents)
|
|
|
|
for n > 0 {
|
|
|
|
for _, v := range out.Contents {
|
|
|
|
start := time.Now().UnixNano()
|
|
|
|
svc.DeleteObject(&s3.DeleteObjectInput{
|
|
|
|
Bucket: &buckets[bucket_num],
|
|
|
|
Key: v.Key,
|
|
|
|
})
|
|
|
|
end := time.Now().UnixNano()
|
|
|
|
stats.updateIntervals(thread_num)
|
|
|
|
stats.addOp(thread_num, *v.Size, end-start)
|
|
|
|
|
|
|
|
}
|
|
|
|
out, err = svc.ListObjects(&s3.ListObjectsInput{Bucket: &buckets[bucket_num]})
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
n = len(out.Contents)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
stats.finish(thread_num)
|
2019-08-19 15:24:26 +03:00
|
|
|
atomic.AddInt64(&running_threads, -1)
|
2019-08-15 23:06:28 +03:00
|
|
|
}
|
|
|
|
|
2019-08-13 20:22:07 +03:00
|
|
|
func runWrapper(loop int, r rune) {
|
2019-08-15 20:17:10 +03:00
|
|
|
op_counter = -1
|
2019-08-13 20:22:07 +03:00
|
|
|
running_threads = int64(threads)
|
|
|
|
intervalNano := int64(interval*1000000000)
|
|
|
|
endtime = time.Now().Add(time.Second * time.Duration(duration_secs))
|
|
|
|
var stats Stats
|
|
|
|
|
2019-08-16 00:23:38 +03:00
|
|
|
// If we perviously set the object count after running a put
|
|
|
|
// test, set the object count back to -1 for the new put test.
|
|
|
|
if r == 'p' && object_count_flag {
|
|
|
|
object_count = -1
|
|
|
|
object_count_flag = false
|
|
|
|
}
|
|
|
|
|
2019-08-13 20:22:07 +03:00
|
|
|
switch r {
|
2019-08-15 23:06:28 +03:00
|
|
|
case 'c':
|
|
|
|
log.Printf("Running Loop %d BUCKET CLEAR TEST", loop)
|
|
|
|
stats = makeStats(loop, "BCLR", threads, intervalNano)
|
|
|
|
for n := 0; n < threads; n++ {
|
|
|
|
go runBucketsClear(n, &stats);
|
|
|
|
}
|
|
|
|
case 'x':
|
|
|
|
log.Printf("Running Loop %d BUCKET DELETE TEST", loop)
|
|
|
|
stats = makeStats(loop, "BDEL", threads, intervalNano)
|
|
|
|
for n := 0; n < threads; n++ {
|
|
|
|
go runBucketDelete(n, &stats);
|
|
|
|
}
|
2019-08-13 20:22:07 +03:00
|
|
|
case 'i':
|
2019-08-15 23:06:28 +03:00
|
|
|
log.Printf("Running Loop %d BUCKET INIT TEST", loop)
|
|
|
|
stats = makeStats(loop, "BINIT", threads, intervalNano)
|
2019-08-15 20:17:10 +03:00
|
|
|
for n := 0; n < threads; n++ {
|
2019-08-15 23:06:28 +03:00
|
|
|
go runBucketsInit(n, &stats);
|
2019-08-15 20:17:10 +03:00
|
|
|
}
|
2019-08-13 20:22:07 +03:00
|
|
|
case 'p':
|
2019-08-15 23:06:28 +03:00
|
|
|
log.Printf("Running Loop %d OBJECT PUT TEST", loop)
|
2019-08-13 20:22:07 +03:00
|
|
|
stats = makeStats(loop, "PUT", threads, intervalNano)
|
|
|
|
for n := 0; n < threads; n++ {
|
|
|
|
go runUpload(n, endtime, &stats);
|
|
|
|
}
|
|
|
|
case 'g':
|
2019-08-15 23:06:28 +03:00
|
|
|
log.Printf("Running Loop %d OBJECT GET TEST", loop)
|
2019-08-13 20:22:07 +03:00
|
|
|
stats = makeStats(loop, "GET", threads, intervalNano)
|
|
|
|
for n := 0; n < threads; n++ {
|
|
|
|
go runDownload(n, endtime, &stats);
|
|
|
|
}
|
|
|
|
case 'd':
|
2019-08-15 23:06:28 +03:00
|
|
|
log.Printf("Running Loop %d OBJECT DELETE TEST", loop)
|
2019-08-13 20:22:07 +03:00
|
|
|
stats = makeStats(loop, "DEL", threads, intervalNano)
|
|
|
|
for n := 0; n < threads; n++ {
|
|
|
|
go runDelete(n, &stats);
|
|
|
|
}
|
2019-08-15 23:06:28 +03:00
|
|
|
}
|
|
|
|
|
2019-08-13 20:22:07 +03:00
|
|
|
// Wait for it to finish
|
|
|
|
for atomic.LoadInt64(&running_threads) > 0 {
|
|
|
|
time.Sleep(time.Millisecond)
|
|
|
|
}
|
2019-08-16 00:23:38 +03:00
|
|
|
|
|
|
|
// If the user didn't set the object_count, we can set it here
|
|
|
|
// to limit subsequent get/del tests to valid objects only.
|
|
|
|
if r == 'p' && object_count < 0 {
|
|
|
|
object_count = op_counter + 1
|
|
|
|
object_count_flag = true
|
|
|
|
}
|
2019-08-19 15:24:26 +03:00
|
|
|
|
|
|
|
// Print Interval Output
|
|
|
|
for i := int64(0); i >= 0;i++ {
|
|
|
|
if o, ok := stats.makeOutputStats(i); ok {
|
|
|
|
if csvWriter != nil {
|
|
|
|
o.csv(csvWriter)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Print Totals Output
|
|
|
|
if o, ok := stats.makeTotalStats(); ok {
|
|
|
|
o.log()
|
|
|
|
if csvWriter != nil {
|
|
|
|
o.csv(csvWriter)
|
|
|
|
csvWriter.Flush()
|
|
|
|
}
|
|
|
|
}
|
2019-08-12 22:06:57 +03:00
|
|
|
}
|
|
|
|
|
2019-03-15 13:06:45 +03:00
|
|
|
func init() {
|
2017-06-09 18:39:42 +03:00
|
|
|
// Parse command line
|
|
|
|
myflag := flag.NewFlagSet("myflag", flag.ExitOnError)
|
2019-03-15 13:06:45 +03:00
|
|
|
myflag.StringVar(&access_key, "a", os.Getenv("AWS_ACCESS_KEY_ID"), "Access key")
|
|
|
|
myflag.StringVar(&secret_key, "s", os.Getenv("AWS_SECRET_ACCESS_KEY"), "Secret key")
|
|
|
|
myflag.StringVar(&url_host, "u", os.Getenv("AWS_HOST"), "URL for host with method prefix")
|
2019-08-12 17:58:48 +03:00
|
|
|
myflag.StringVar(&object_prefix, "op", "", "Prefix for objects")
|
|
|
|
myflag.StringVar(&bucket_prefix, "bp", "hotsauce_bench", "Prefix for buckets")
|
2018-08-07 14:57:04 +03:00
|
|
|
myflag.StringVar(®ion, "r", "us-east-1", "Region for testing")
|
2019-08-15 23:06:28 +03:00
|
|
|
myflag.StringVar(&modes, "m", "cxipgdx", "Run modes in order. See NOTES for more info")
|
2019-08-19 15:24:26 +03:00
|
|
|
myflag.StringVar(&output, "o", "", "Write CSV output to this file")
|
2019-08-12 03:50:46 +03:00
|
|
|
myflag.Int64Var(&object_count, "n", -1, "Maximum number of objects <-1 for unlimited>")
|
2019-08-15 20:17:10 +03:00
|
|
|
myflag.Int64Var(&bucket_count, "b", 1, "Number of buckets to distribute IOs across")
|
2019-08-12 03:50:46 +03:00
|
|
|
myflag.IntVar(&duration_secs, "d", 60, "Maximum test duration in seconds <-1 for unlimited>")
|
2017-06-09 18:39:42 +03:00
|
|
|
myflag.IntVar(&threads, "t", 1, "Number of threads to run")
|
|
|
|
myflag.IntVar(&loops, "l", 1, "Number of times to repeat test")
|
|
|
|
myflag.StringVar(&sizeArg, "z", "1M", "Size of objects in bytes with postfix K, M, and G")
|
2019-08-13 20:22:07 +03:00
|
|
|
myflag.Float64Var(&interval, "ri", 1.0, "Number of seconds between report intervals")
|
2019-08-12 22:06:57 +03:00
|
|
|
// define custom usage output with notes
|
|
|
|
notes :=
|
|
|
|
`
|
|
|
|
NOTES:
|
|
|
|
- Valid mode types for the -m mode string are:
|
2019-08-15 23:06:28 +03:00
|
|
|
c: clear all existing objects from buckets (requires lookups)
|
|
|
|
x: delete buckets
|
|
|
|
i: initialize buckets
|
2019-08-12 22:06:57 +03:00
|
|
|
p: put objects in buckets
|
|
|
|
g: get objects from buckets
|
2019-08-15 23:06:28 +03:00
|
|
|
d: delete objects from buckets
|
2019-08-12 22:06:57 +03:00
|
|
|
|
|
|
|
These modes are processed in-order and can be repeated, ie "ippgd" will
|
|
|
|
initialize the buckets, put the objects, reput the objects, get the
|
|
|
|
objects, and then delete the objects. The repeat flag will repeat this
|
|
|
|
whole process the specified number of times.
|
|
|
|
`
|
|
|
|
myflag.Usage = func() {
|
|
|
|
fmt.Fprintf(flag.CommandLine.Output(), "\nUSAGE: %s [OPTIONS]\n\n", os.Args[0])
|
|
|
|
fmt.Fprintf(flag.CommandLine.Output(), "OPTIONS:\n")
|
|
|
|
myflag.PrintDefaults()
|
|
|
|
fmt.Fprintf(flag.CommandLine.Output(), notes);
|
|
|
|
}
|
|
|
|
|
2017-06-09 18:39:42 +03:00
|
|
|
if err := myflag.Parse(os.Args[1:]); err != nil {
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the arguments
|
2019-08-12 03:50:46 +03:00
|
|
|
if object_count < 0 && duration_secs < 0 {
|
|
|
|
log.Fatal("The number of objects and duration can not both be unlimited")
|
|
|
|
}
|
2017-06-09 18:39:42 +03:00
|
|
|
if access_key == "" {
|
|
|
|
log.Fatal("Missing argument -a for access key.")
|
|
|
|
}
|
|
|
|
if secret_key == "" {
|
|
|
|
log.Fatal("Missing argument -s for secret key.")
|
|
|
|
}
|
2019-03-15 13:06:45 +03:00
|
|
|
if url_host == "" {
|
|
|
|
log.Fatal("Missing argument -s for host endpoint.")
|
|
|
|
}
|
2019-08-12 22:06:57 +03:00
|
|
|
invalid_mode := false
|
|
|
|
for _, r := range modes {
|
2019-08-15 23:06:28 +03:00
|
|
|
if (
|
|
|
|
r != 'i' &&
|
|
|
|
r != 'c' &&
|
|
|
|
r != 'p' &&
|
|
|
|
r != 'g' &&
|
|
|
|
r != 'd' &&
|
|
|
|
r != 'x') {
|
2019-08-12 22:06:57 +03:00
|
|
|
s := fmt.Sprintf("Invalid mode '%s' passed to -m", string(r))
|
|
|
|
log.Printf(s)
|
|
|
|
invalid_mode = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if invalid_mode {
|
|
|
|
log.Fatal("Invalid modes passed to -m, see help for details.")
|
|
|
|
}
|
2017-06-09 18:39:42 +03:00
|
|
|
var err error
|
2019-08-13 20:22:07 +03:00
|
|
|
var size uint64
|
|
|
|
if size, err = bytefmt.ToBytes(sizeArg); err != nil {
|
2017-06-09 18:39:42 +03:00
|
|
|
log.Fatalf("Invalid -z argument for object size: %v", err)
|
|
|
|
}
|
2019-08-13 20:22:07 +03:00
|
|
|
object_size = int64(size)
|
|
|
|
}
|
|
|
|
|
|
|
|
func initData() {
|
|
|
|
// Initialize data for the bucket
|
|
|
|
object_data = make([]byte, object_size)
|
|
|
|
rand.Read(object_data)
|
|
|
|
hasher := md5.New()
|
|
|
|
hasher.Write(object_data)
|
|
|
|
object_data_md5 = base64.StdEncoding.EncodeToString(hasher.Sum(nil))
|
2019-03-15 13:06:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
// Hello
|
2019-08-16 00:23:38 +03:00
|
|
|
log.Printf("Hotsauce S3 Benchmark Version 0.1")
|
2019-03-15 13:06:45 +03:00
|
|
|
|
|
|
|
cfg = &aws.Config{
|
|
|
|
Endpoint: aws.String(url_host),
|
|
|
|
Credentials: credentials.NewStaticCredentials(access_key, secret_key, ""),
|
|
|
|
Region: aws.String(region),
|
|
|
|
// DisableParamValidation: aws.Bool(true),
|
|
|
|
DisableComputeChecksums: aws.Bool(true),
|
|
|
|
S3ForcePathStyle: aws.Bool(true),
|
|
|
|
}
|
2017-06-09 18:39:42 +03:00
|
|
|
|
|
|
|
// Echo the parameters
|
2019-08-16 00:23:38 +03:00
|
|
|
log.Printf("Parameters:")
|
|
|
|
log.Printf("url=%s", url_host)
|
|
|
|
log.Printf("object_prefix=%s", object_prefix)
|
|
|
|
log.Printf("bucket_prefix=%s", bucket_prefix)
|
|
|
|
log.Printf("region=%s", region)
|
|
|
|
log.Printf("modes=%s", modes)
|
|
|
|
log.Printf("object_count=%d", object_count)
|
|
|
|
log.Printf("bucket_count=%d", bucket_count)
|
|
|
|
log.Printf("duration=%d", duration_secs)
|
|
|
|
log.Printf("threads=%d", threads)
|
|
|
|
log.Printf("loops=%d", loops)
|
|
|
|
log.Printf("size=%s", sizeArg)
|
|
|
|
log.Printf("interval=%f", interval)
|
2017-06-09 18:39:42 +03:00
|
|
|
|
2019-08-13 20:22:07 +03:00
|
|
|
// Init Data
|
|
|
|
initData()
|
|
|
|
|
2019-08-12 22:06:57 +03:00
|
|
|
// Setup the slice of buckets
|
2019-08-15 20:17:10 +03:00
|
|
|
for i := int64(0); i < bucket_count; i++ {
|
2019-08-12 22:06:57 +03:00
|
|
|
buckets = append(buckets, fmt.Sprintf("%s%012d", bucket_prefix, i))
|
|
|
|
}
|
2017-06-09 18:39:42 +03:00
|
|
|
|
2019-08-19 15:24:26 +03:00
|
|
|
// Init CSV file
|
|
|
|
if output != "" {
|
|
|
|
file, err := os.OpenFile(output, os.O_CREATE|os.O_WRONLY, 0777)
|
|
|
|
defer file.Close()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("Could not open CSV file for writing.")
|
|
|
|
} else {
|
|
|
|
csvWriter = csv.NewWriter(file)
|
|
|
|
o := OutputStats{}
|
|
|
|
o.csv_header(csvWriter)
|
|
|
|
}
|
|
|
|
}
|
2017-06-09 18:39:42 +03:00
|
|
|
// Loop running the tests
|
2019-08-13 20:22:07 +03:00
|
|
|
for loop := 0; loop < loops; loop++ {
|
|
|
|
for _, r := range modes {
|
|
|
|
runWrapper(loop, r)
|
2017-06-09 18:39:42 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|