benchmain: format output of benchmark to a table (#1493)
This commit is contained in:
@ -22,14 +22,25 @@ Package main provides benchmark with setting flags.
|
|||||||
An example to run some benchmarks with profiling enabled:
|
An example to run some benchmarks with profiling enabled:
|
||||||
|
|
||||||
go run benchmark/benchmain/main.go -benchtime=10s -workloads=all \
|
go run benchmark/benchmain/main.go -benchtime=10s -workloads=all \
|
||||||
-compression=on -maxConcurrentCalls=1 -traceMode=false \
|
-compression=on -maxConcurrentCalls=1 -trace=off \
|
||||||
-reqSizeBytes=1,1048576 -respSizeBytes=1,1048576 \
|
-reqSizeBytes=1,1048576 -respSizeBytes=1,1048576 -networkMode=Local \
|
||||||
-latency=0s -kbps=0 -mtu=0 \
|
-cpuProfile=cpuProf -memProfile=memProf -memProfileRate=10000 -resultFile=result
|
||||||
-cpuProfile=cpuProf -memProfile=memProf -memProfileRate=10000
|
|
||||||
|
As a suggestion, when creating a branch, you can run this benchmark and save the result
|
||||||
|
file "-resultFile=basePerf", and later when you at the middle of the work or finish the
|
||||||
|
work, you can get the benchmark result and compare it with the base anytime.
|
||||||
|
|
||||||
|
Assume there are two result files names as "basePerf" and "curPerf" created by adding
|
||||||
|
-resultFile=basePerf and -resultFile=curPerf.
|
||||||
|
To format the curPerf, run:
|
||||||
|
go run benchmark/benchresult/main.go curPerf
|
||||||
|
To observe how the performance changes based on a base result, run:
|
||||||
|
go run benchmark/benchresult/main.go basePerf curPerf
|
||||||
*/
|
*/
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/gob"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -58,12 +69,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
compressionOn = "on"
|
modeOn = "on"
|
||||||
compressionOff = "off"
|
modeOff = "off"
|
||||||
compressionBoth = "both"
|
modeBoth = "both"
|
||||||
)
|
)
|
||||||
|
|
||||||
var allCompressionModes = []string{compressionOn, compressionOff, compressionBoth}
|
var allCompressionModes = []string{modeOn, modeOff, modeBoth}
|
||||||
|
var allTraceModes = []string{modeOn, modeOff, modeBoth}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
workloadsUnary = "unary"
|
workloadsUnary = "unary"
|
||||||
@ -83,26 +95,34 @@ var (
|
|||||||
maxConcurrentCalls = []int{1, 8, 64, 512}
|
maxConcurrentCalls = []int{1, 8, 64, 512}
|
||||||
reqSizeBytes = []int{1, 1024, 1024 * 1024}
|
reqSizeBytes = []int{1, 1024, 1024 * 1024}
|
||||||
respSizeBytes = []int{1, 1024, 1024 * 1024}
|
respSizeBytes = []int{1, 1024, 1024 * 1024}
|
||||||
enableTrace = []bool{false}
|
enableTrace []bool
|
||||||
benchtime time.Duration
|
benchtime time.Duration
|
||||||
memProfile, cpuProfile string
|
memProfile, cpuProfile string
|
||||||
memProfileRate int
|
memProfileRate int
|
||||||
enableCompressor []bool
|
enableCompressor []bool
|
||||||
|
networkMode string
|
||||||
|
benchmarkResultFile string
|
||||||
|
networks = map[string]latency.Network{
|
||||||
|
"Local": latency.Local,
|
||||||
|
"LAN": latency.LAN,
|
||||||
|
"WAN": latency.WAN,
|
||||||
|
"Longhaul": latency.Longhaul,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func unaryBenchmark(startTimer func(), stopTimer func(int32), benchFeatures bm.Features, benchtime time.Duration, s *stats.Stats) {
|
func unaryBenchmark(startTimer func(), stopTimer func(int32), benchFeatures stats.Features, benchtime time.Duration, s *stats.Stats) {
|
||||||
caller, close := makeFuncUnary(benchFeatures)
|
caller, close := makeFuncUnary(benchFeatures)
|
||||||
defer close()
|
defer close()
|
||||||
runBenchmark(caller, startTimer, stopTimer, benchFeatures, benchtime, s)
|
runBenchmark(caller, startTimer, stopTimer, benchFeatures, benchtime, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func streamBenchmark(startTimer func(), stopTimer func(int32), benchFeatures bm.Features, benchtime time.Duration, s *stats.Stats) {
|
func streamBenchmark(startTimer func(), stopTimer func(int32), benchFeatures stats.Features, benchtime time.Duration, s *stats.Stats) {
|
||||||
caller, close := makeFuncStream(benchFeatures)
|
caller, close := makeFuncStream(benchFeatures)
|
||||||
defer close()
|
defer close()
|
||||||
runBenchmark(caller, startTimer, stopTimer, benchFeatures, benchtime, s)
|
runBenchmark(caller, startTimer, stopTimer, benchFeatures, benchtime, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeFuncUnary(benchFeatures bm.Features) (func(int), func()) {
|
func makeFuncUnary(benchFeatures stats.Features) (func(int), func()) {
|
||||||
nw := &latency.Network{Kbps: benchFeatures.Kbps, Latency: benchFeatures.Latency, MTU: benchFeatures.Mtu}
|
nw := &latency.Network{Kbps: benchFeatures.Kbps, Latency: benchFeatures.Latency, MTU: benchFeatures.Mtu}
|
||||||
opts := []grpc.DialOption{}
|
opts := []grpc.DialOption{}
|
||||||
sopts := []grpc.ServerOption{}
|
sopts := []grpc.ServerOption{}
|
||||||
@ -133,8 +153,7 @@ func makeFuncUnary(benchFeatures bm.Features) (func(int), func()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeFuncStream(benchFeatures bm.Features) (func(int), func()) {
|
func makeFuncStream(benchFeatures stats.Features) (func(int), func()) {
|
||||||
fmt.Println(benchFeatures)
|
|
||||||
nw := &latency.Network{Kbps: benchFeatures.Kbps, Latency: benchFeatures.Latency, MTU: benchFeatures.Mtu}
|
nw := &latency.Network{Kbps: benchFeatures.Kbps, Latency: benchFeatures.Latency, MTU: benchFeatures.Mtu}
|
||||||
opts := []grpc.DialOption{}
|
opts := []grpc.DialOption{}
|
||||||
sopts := []grpc.ServerOption{}
|
sopts := []grpc.ServerOption{}
|
||||||
@ -185,7 +204,7 @@ func streamCaller(stream testpb.BenchmarkService_StreamingCallClient, reqSize, r
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runBenchmark(caller func(int), startTimer func(), stopTimer func(int32), benchFeatures bm.Features, benchtime time.Duration, s *stats.Stats) {
|
func runBenchmark(caller func(int), startTimer func(), stopTimer func(int32), benchFeatures stats.Features, benchtime time.Duration, s *stats.Stats) {
|
||||||
// Warm up connection.
|
// Warm up connection.
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
caller(0)
|
caller(0)
|
||||||
@ -224,14 +243,14 @@ func runBenchmark(caller func(int), startTimer func(), stopTimer func(int32), be
|
|||||||
// Initiate main function to get settings of features.
|
// Initiate main function to get settings of features.
|
||||||
func init() {
|
func init() {
|
||||||
var (
|
var (
|
||||||
workloads, compressorMode, readLatency string
|
workloads, traceMode, compressorMode, readLatency string
|
||||||
readKbps, readMtu, readMaxConcurrentCalls intSliceType
|
readKbps, readMtu, readMaxConcurrentCalls intSliceType
|
||||||
readReqSizeBytes, readRespSizeBytes intSliceType
|
readReqSizeBytes, readRespSizeBytes intSliceType
|
||||||
traceMode bool
|
|
||||||
)
|
)
|
||||||
flag.StringVar(&workloads, "workloads", workloadsAll,
|
flag.StringVar(&workloads, "workloads", workloadsAll,
|
||||||
fmt.Sprintf("Workloads to execute - One of: %v", strings.Join(allWorkloads, ", ")))
|
fmt.Sprintf("Workloads to execute - One of: %v", strings.Join(allWorkloads, ", ")))
|
||||||
flag.BoolVar(&traceMode, "traceMode", false, "Enable gRPC tracing")
|
flag.StringVar(&traceMode, "trace", modeOff,
|
||||||
|
fmt.Sprintf("Trace mode - One of: %v", strings.Join(allTraceModes, ", ")))
|
||||||
flag.StringVar(&readLatency, "latency", "", "Simulated one-way network latency - may be a comma-separated list")
|
flag.StringVar(&readLatency, "latency", "", "Simulated one-way network latency - may be a comma-separated list")
|
||||||
flag.DurationVar(&benchtime, "benchtime", time.Second, "Configures the amount of time to run each benchmark")
|
flag.DurationVar(&benchtime, "benchtime", time.Second, "Configures the amount of time to run each benchmark")
|
||||||
flag.Var(&readKbps, "kbps", "Simulated network throughput (in kbps) - may be a comma-separated list")
|
flag.Var(&readKbps, "kbps", "Simulated network throughput (in kbps) - may be a comma-separated list")
|
||||||
@ -239,11 +258,15 @@ func init() {
|
|||||||
flag.Var(&readMaxConcurrentCalls, "maxConcurrentCalls", "Number of concurrent RPCs during benchmarks")
|
flag.Var(&readMaxConcurrentCalls, "maxConcurrentCalls", "Number of concurrent RPCs during benchmarks")
|
||||||
flag.Var(&readReqSizeBytes, "reqSizeBytes", "Request size in bytes - may be a comma-separated list")
|
flag.Var(&readReqSizeBytes, "reqSizeBytes", "Request size in bytes - may be a comma-separated list")
|
||||||
flag.Var(&readRespSizeBytes, "respSizeBytes", "Response size in bytes - may be a comma-separated list")
|
flag.Var(&readRespSizeBytes, "respSizeBytes", "Response size in bytes - may be a comma-separated list")
|
||||||
flag.StringVar(&memProfile, "memProfile", "", "Enables memory profiling output to the filename provided")
|
flag.StringVar(&memProfile, "memProfile", "", "Enables memory profiling output to the filename provided.")
|
||||||
flag.IntVar(&memProfileRate, "memProfileRate", 0, "Configures the memory profiling rate")
|
flag.IntVar(&memProfileRate, "memProfileRate", 512*1024, "Configures the memory profiling rate. \n"+
|
||||||
|
"memProfile should be set before setting profile rate. To include every allocated block in the profile, "+
|
||||||
|
"set MemProfileRate to 1. To turn off profiling entirely, set MemProfileRate to 0. 512 * 1024 by default.")
|
||||||
flag.StringVar(&cpuProfile, "cpuProfile", "", "Enables CPU profiling output to the filename provided")
|
flag.StringVar(&cpuProfile, "cpuProfile", "", "Enables CPU profiling output to the filename provided")
|
||||||
flag.StringVar(&compressorMode, "compression", compressionOff,
|
flag.StringVar(&compressorMode, "compression", modeOff,
|
||||||
fmt.Sprintf("Compression mode - One of: %v", strings.Join(allCompressionModes, ", ")))
|
fmt.Sprintf("Compression mode - One of: %v", strings.Join(allCompressionModes, ", ")))
|
||||||
|
flag.StringVar(&benchmarkResultFile, "resultFile", "", "Save the benchmark result into a binary file")
|
||||||
|
flag.StringVar(&networkMode, "networkMode", "", "Network mode includes LAN, WAN, Local and Longhaul")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if flag.NArg() != 0 {
|
if flag.NArg() != 0 {
|
||||||
log.Fatal("Error: unparsed arguments: ", flag.Args())
|
log.Fatal("Error: unparsed arguments: ", flag.Args())
|
||||||
@ -262,20 +285,8 @@ func init() {
|
|||||||
log.Fatalf("Unknown workloads setting: %v (want one of: %v)",
|
log.Fatalf("Unknown workloads setting: %v (want one of: %v)",
|
||||||
workloads, strings.Join(allWorkloads, ", "))
|
workloads, strings.Join(allWorkloads, ", "))
|
||||||
}
|
}
|
||||||
switch compressorMode {
|
enableCompressor = setMode(compressorMode)
|
||||||
case compressionOn:
|
enableTrace = setMode(traceMode)
|
||||||
enableCompressor = []bool{true}
|
|
||||||
case compressionOff:
|
|
||||||
enableCompressor = []bool{false}
|
|
||||||
case compressionBoth:
|
|
||||||
enableCompressor = []bool{false, true}
|
|
||||||
default:
|
|
||||||
log.Fatalf("Unknown compression mode setting: %v (want one of: %v)",
|
|
||||||
compressorMode, strings.Join(allCompressionModes, ", "))
|
|
||||||
}
|
|
||||||
if traceMode {
|
|
||||||
enableTrace = []bool{true}
|
|
||||||
}
|
|
||||||
// Time input formats as (time + unit).
|
// Time input formats as (time + unit).
|
||||||
readTimeFromInput(<c, readLatency)
|
readTimeFromInput(<c, readLatency)
|
||||||
readIntFromIntSlice(&kbps, readKbps)
|
readIntFromIntSlice(&kbps, readKbps)
|
||||||
@ -283,6 +294,27 @@ func init() {
|
|||||||
readIntFromIntSlice(&maxConcurrentCalls, readMaxConcurrentCalls)
|
readIntFromIntSlice(&maxConcurrentCalls, readMaxConcurrentCalls)
|
||||||
readIntFromIntSlice(&reqSizeBytes, readReqSizeBytes)
|
readIntFromIntSlice(&reqSizeBytes, readReqSizeBytes)
|
||||||
readIntFromIntSlice(&respSizeBytes, readRespSizeBytes)
|
readIntFromIntSlice(&respSizeBytes, readRespSizeBytes)
|
||||||
|
// Re-write latency, kpbs and mtu if network mode is set.
|
||||||
|
if network, ok := networks[networkMode]; ok {
|
||||||
|
ltc = []time.Duration{network.Latency}
|
||||||
|
kbps = []int{network.Kbps}
|
||||||
|
mtu = []int{network.MTU}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setMode(name string) []bool {
|
||||||
|
switch name {
|
||||||
|
case modeOn:
|
||||||
|
return []bool{true}
|
||||||
|
case modeOff:
|
||||||
|
return []bool{false}
|
||||||
|
case modeBoth:
|
||||||
|
return []bool{false, true}
|
||||||
|
default:
|
||||||
|
log.Fatalf("Unknown %s setting: %v (want one of: %v)",
|
||||||
|
name, name, strings.Join(allCompressionModes, ", "))
|
||||||
|
return []bool{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type intSliceType []int
|
type intSliceType []int
|
||||||
@ -334,6 +366,7 @@ func main() {
|
|||||||
len(maxConcurrentCalls), len(reqSizeBytes), len(respSizeBytes), len(enableCompressor)}
|
len(maxConcurrentCalls), len(reqSizeBytes), len(respSizeBytes), len(enableCompressor)}
|
||||||
initalPos := make([]int, len(featuresPos))
|
initalPos := make([]int, len(featuresPos))
|
||||||
s := stats.NewStats(10)
|
s := stats.NewStats(10)
|
||||||
|
s.SortLatency()
|
||||||
var memStats runtime.MemStats
|
var memStats runtime.MemStats
|
||||||
var results testing.BenchmarkResult
|
var results testing.BenchmarkResult
|
||||||
var startAllocs, startBytes uint64
|
var startAllocs, startBytes uint64
|
||||||
@ -350,14 +383,19 @@ func main() {
|
|||||||
results = testing.BenchmarkResult{N: int(count), T: time.Now().Sub(startTime),
|
results = testing.BenchmarkResult{N: int(count), T: time.Now().Sub(startTime),
|
||||||
Bytes: 0, MemAllocs: memStats.Mallocs - startAllocs, MemBytes: memStats.TotalAlloc - startBytes}
|
Bytes: 0, MemAllocs: memStats.Mallocs - startAllocs, MemBytes: memStats.TotalAlloc - startBytes}
|
||||||
}
|
}
|
||||||
|
sharedPos := make([]bool, len(featuresPos))
|
||||||
|
for i := 0; i < len(featuresPos); i++ {
|
||||||
|
if featuresNum[i] <= 1 {
|
||||||
|
sharedPos[i] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Run benchmarks
|
// Run benchmarks
|
||||||
|
resultSlice := []stats.BenchResults{}
|
||||||
for !reflect.DeepEqual(featuresPos, initalPos) || start {
|
for !reflect.DeepEqual(featuresPos, initalPos) || start {
|
||||||
start = false
|
start = false
|
||||||
tracing := "Trace"
|
benchFeature := stats.Features{
|
||||||
if !enableTrace[featuresPos[0]] {
|
NetworkMode: networkMode,
|
||||||
tracing = "noTrace"
|
|
||||||
}
|
|
||||||
benchFeature := bm.Features{
|
|
||||||
EnableTrace: enableTrace[featuresPos[0]],
|
EnableTrace: enableTrace[featuresPos[0]],
|
||||||
Latency: ltc[featuresPos[1]],
|
Latency: ltc[featuresPos[1]],
|
||||||
Kbps: kbps[featuresPos[2]],
|
Kbps: kbps[featuresPos[2]],
|
||||||
@ -370,28 +408,30 @@ func main() {
|
|||||||
|
|
||||||
grpc.EnableTracing = enableTrace[featuresPos[0]]
|
grpc.EnableTracing = enableTrace[featuresPos[0]]
|
||||||
if runMode[0] {
|
if runMode[0] {
|
||||||
fmt.Printf("Unary-%s-%s:\n", tracing, benchFeature.String())
|
|
||||||
unaryBenchmark(startTimer, stopTimer, benchFeature, benchtime, s)
|
unaryBenchmark(startTimer, stopTimer, benchFeature, benchtime, s)
|
||||||
fmt.Println(results.String(), results.MemString())
|
s.SetBenchmarkResult("Unary", benchFeature, results.N,
|
||||||
|
results.AllocedBytesPerOp(), results.AllocsPerOp(), sharedPos)
|
||||||
|
fmt.Println(s.BenchString())
|
||||||
fmt.Println(s.String())
|
fmt.Println(s.String())
|
||||||
|
resultSlice = append(resultSlice, s.GetBenchmarkResults())
|
||||||
s.Clear()
|
s.Clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
if runMode[1] {
|
if runMode[1] {
|
||||||
fmt.Printf("Stream-%s-%s\n", tracing, benchFeature.String())
|
|
||||||
streamBenchmark(startTimer, stopTimer, benchFeature, benchtime, s)
|
streamBenchmark(startTimer, stopTimer, benchFeature, benchtime, s)
|
||||||
fmt.Println(results.String(), results.MemString())
|
s.SetBenchmarkResult("Stream", benchFeature, results.N,
|
||||||
|
results.AllocedBytesPerOp(), results.AllocsPerOp(), sharedPos)
|
||||||
|
fmt.Println(s.BenchString())
|
||||||
fmt.Println(s.String())
|
fmt.Println(s.String())
|
||||||
|
resultSlice = append(resultSlice, s.GetBenchmarkResults())
|
||||||
s.Clear()
|
s.Clear()
|
||||||
}
|
}
|
||||||
bm.AddOne(featuresPos, featuresNum)
|
bm.AddOne(featuresPos, featuresNum)
|
||||||
}
|
}
|
||||||
after()
|
after(resultSlice)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func before() {
|
func before() {
|
||||||
if memProfileRate > 0 {
|
if memProfile != "" {
|
||||||
runtime.MemProfileRate = memProfileRate
|
runtime.MemProfileRate = memProfileRate
|
||||||
}
|
}
|
||||||
if cpuProfile != "" {
|
if cpuProfile != "" {
|
||||||
@ -408,7 +448,7 @@ func before() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func after() {
|
func after(data []stats.BenchResults) {
|
||||||
if cpuProfile != "" {
|
if cpuProfile != "" {
|
||||||
pprof.StopCPUProfile() // flushes profile to disk
|
pprof.StopCPUProfile() // flushes profile to disk
|
||||||
}
|
}
|
||||||
@ -420,11 +460,20 @@ func after() {
|
|||||||
}
|
}
|
||||||
runtime.GC() // materialize all statistics
|
runtime.GC() // materialize all statistics
|
||||||
if err = pprof.WriteHeapProfile(f); err != nil {
|
if err = pprof.WriteHeapProfile(f); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "testing: can't write %s: %s\n", memProfile, err)
|
fmt.Fprintf(os.Stderr, "testing: can't write heap profile %s: %s\n", memProfile, err)
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
f.Close()
|
f.Close()
|
||||||
}
|
}
|
||||||
|
if benchmarkResultFile != "" {
|
||||||
|
f, err := os.Create(benchmarkResultFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("testing: can't write benchmark result %s: %s\n", benchmarkResultFile, err)
|
||||||
|
}
|
||||||
|
dataEncoder := gob.NewEncoder(f)
|
||||||
|
dataEncoder.Encode(data)
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// nopCompressor is a compressor that just copies data.
|
// nopCompressor is a compressor that just copies data.
|
||||||
|
|||||||
@ -39,24 +39,6 @@ import (
|
|||||||
"google.golang.org/grpc/grpclog"
|
"google.golang.org/grpc/grpclog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Features contains most fields for a benchmark
|
|
||||||
type Features struct {
|
|
||||||
EnableTrace bool
|
|
||||||
Latency time.Duration
|
|
||||||
Kbps int
|
|
||||||
Mtu int
|
|
||||||
MaxConcurrentCalls int
|
|
||||||
ReqSizeBytes int
|
|
||||||
RespSizeBytes int
|
|
||||||
EnableCompressor bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Features) String() string {
|
|
||||||
return fmt.Sprintf("latency_%s-kbps_%#v-MTU_%#v-maxConcurrentCalls_"+
|
|
||||||
"%#v-reqSize_%#vB-respSize_%#vB-Compressor_%t",
|
|
||||||
f.Latency.String(), f.Kbps, f.Mtu, f.MaxConcurrentCalls, f.ReqSizeBytes, f.RespSizeBytes, f.EnableCompressor)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddOne add 1 to the features slice
|
// AddOne add 1 to the features slice
|
||||||
func AddOne(features []int, featuresMaxPosition []int) {
|
func AddOne(features []int, featuresMaxPosition []int) {
|
||||||
for i := len(features) - 1; i >= 0; i-- {
|
for i := len(features) - 1; i >= 0; i-- {
|
||||||
@ -261,7 +243,7 @@ func NewClientConn(addr string, opts ...grpc.DialOption) *grpc.ClientConn {
|
|||||||
return conn
|
return conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func runUnary(b *testing.B, benchFeatures Features) {
|
func runUnary(b *testing.B, benchFeatures stats.Features) {
|
||||||
s := stats.AddStats(b, 38)
|
s := stats.AddStats(b, 38)
|
||||||
nw := &latency.Network{Kbps: benchFeatures.Kbps, Latency: benchFeatures.Latency, MTU: benchFeatures.Mtu}
|
nw := &latency.Network{Kbps: benchFeatures.Kbps, Latency: benchFeatures.Latency, MTU: benchFeatures.Mtu}
|
||||||
target, stopper := StartServer(ServerInfo{Addr: "localhost:0", Type: "protobuf", Network: nw}, grpc.MaxConcurrentStreams(uint32(benchFeatures.MaxConcurrentCalls+1)))
|
target, stopper := StartServer(ServerInfo{Addr: "localhost:0", Type: "protobuf", Network: nw}, grpc.MaxConcurrentStreams(uint32(benchFeatures.MaxConcurrentCalls+1)))
|
||||||
@ -309,7 +291,7 @@ func runUnary(b *testing.B, benchFeatures Features) {
|
|||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func runStream(b *testing.B, benchFeatures Features) {
|
func runStream(b *testing.B, benchFeatures stats.Features) {
|
||||||
s := stats.AddStats(b, 38)
|
s := stats.AddStats(b, 38)
|
||||||
nw := &latency.Network{Kbps: benchFeatures.Kbps, Latency: benchFeatures.Latency, MTU: benchFeatures.Mtu}
|
nw := &latency.Network{Kbps: benchFeatures.Kbps, Latency: benchFeatures.Latency, MTU: benchFeatures.Mtu}
|
||||||
target, stopper := StartServer(ServerInfo{Addr: "localhost:0", Type: "protobuf", Network: nw}, grpc.MaxConcurrentStreams(uint32(benchFeatures.MaxConcurrentCalls+1)))
|
target, stopper := StartServer(ServerInfo{Addr: "localhost:0", Type: "protobuf", Network: nw}, grpc.MaxConcurrentStreams(uint32(benchFeatures.MaxConcurrentCalls+1)))
|
||||||
|
|||||||
@ -30,80 +30,81 @@ import (
|
|||||||
|
|
||||||
func BenchmarkClientStreamc1(b *testing.B) {
|
func BenchmarkClientStreamc1(b *testing.B) {
|
||||||
grpc.EnableTracing = true
|
grpc.EnableTracing = true
|
||||||
runStream(b, Features{true, 0, 0, 0, 1, 1, 1, false})
|
runStream(b, stats.Features{"", true, 0, 0, 0, 1, 1, 1, false})
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkClientStreamc8(b *testing.B) {
|
func BenchmarkClientStreamc8(b *testing.B) {
|
||||||
grpc.EnableTracing = true
|
grpc.EnableTracing = true
|
||||||
runStream(b, Features{true, 0, 0, 0, 8, 1, 1, false})
|
runStream(b, stats.Features{"", true, 0, 0, 0, 8, 1, 1, false})
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkClientStreamc64(b *testing.B) {
|
func BenchmarkClientStreamc64(b *testing.B) {
|
||||||
grpc.EnableTracing = true
|
grpc.EnableTracing = true
|
||||||
runStream(b, Features{true, 0, 0, 0, 64, 1, 1, false})
|
runStream(b, stats.Features{"", true, 0, 0, 0, 64, 1, 1, false})
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkClientStreamc512(b *testing.B) {
|
func BenchmarkClientStreamc512(b *testing.B) {
|
||||||
grpc.EnableTracing = true
|
grpc.EnableTracing = true
|
||||||
runStream(b, Features{true, 0, 0, 0, 512, 1, 1, false})
|
runStream(b, stats.Features{"", true, 0, 0, 0, 512, 1, 1, false})
|
||||||
}
|
}
|
||||||
func BenchmarkClientUnaryc1(b *testing.B) {
|
func BenchmarkClientUnaryc1(b *testing.B) {
|
||||||
grpc.EnableTracing = true
|
grpc.EnableTracing = true
|
||||||
runStream(b, Features{true, 0, 0, 0, 1, 1, 1, false})
|
runStream(b, stats.Features{"", true, 0, 0, 0, 1, 1, 1, false})
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkClientUnaryc8(b *testing.B) {
|
func BenchmarkClientUnaryc8(b *testing.B) {
|
||||||
grpc.EnableTracing = true
|
grpc.EnableTracing = true
|
||||||
runStream(b, Features{true, 0, 0, 0, 8, 1, 1, false})
|
runStream(b, stats.Features{"", true, 0, 0, 0, 8, 1, 1, false})
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkClientUnaryc64(b *testing.B) {
|
func BenchmarkClientUnaryc64(b *testing.B) {
|
||||||
grpc.EnableTracing = true
|
grpc.EnableTracing = true
|
||||||
runStream(b, Features{true, 0, 0, 0, 64, 1, 1, false})
|
runStream(b, stats.Features{"", true, 0, 0, 0, 64, 1, 1, false})
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkClientUnaryc512(b *testing.B) {
|
func BenchmarkClientUnaryc512(b *testing.B) {
|
||||||
grpc.EnableTracing = true
|
grpc.EnableTracing = true
|
||||||
runStream(b, Features{true, 0, 0, 0, 512, 1, 1, false})
|
runStream(b, stats.Features{"", true, 0, 0, 0, 512, 1, 1, false})
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkClientStreamNoTracec1(b *testing.B) {
|
func BenchmarkClientStreamNoTracec1(b *testing.B) {
|
||||||
grpc.EnableTracing = false
|
grpc.EnableTracing = false
|
||||||
runStream(b, Features{false, 0, 0, 0, 1, 1, 1, false})
|
runStream(b, stats.Features{"", false, 0, 0, 0, 1, 1, 1, false})
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkClientStreamNoTracec8(b *testing.B) {
|
func BenchmarkClientStreamNoTracec8(b *testing.B) {
|
||||||
grpc.EnableTracing = false
|
grpc.EnableTracing = false
|
||||||
runStream(b, Features{false, 0, 0, 0, 8, 1, 1, false})
|
runStream(b, stats.Features{"", false, 0, 0, 0, 8, 1, 1, false})
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkClientStreamNoTracec64(b *testing.B) {
|
func BenchmarkClientStreamNoTracec64(b *testing.B) {
|
||||||
grpc.EnableTracing = false
|
grpc.EnableTracing = false
|
||||||
runStream(b, Features{false, 0, 0, 0, 64, 1, 1, false})
|
runStream(b, stats.Features{"", false, 0, 0, 0, 64, 1, 1, false})
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkClientStreamNoTracec512(b *testing.B) {
|
func BenchmarkClientStreamNoTracec512(b *testing.B) {
|
||||||
grpc.EnableTracing = false
|
grpc.EnableTracing = false
|
||||||
runStream(b, Features{false, 0, 0, 0, 512, 1, 1, false})
|
runStream(b, stats.Features{"", false, 0, 0, 0, 512, 1, 1, false})
|
||||||
}
|
}
|
||||||
func BenchmarkClientUnaryNoTracec1(b *testing.B) {
|
func BenchmarkClientUnaryNoTracec1(b *testing.B) {
|
||||||
grpc.EnableTracing = false
|
grpc.EnableTracing = false
|
||||||
runStream(b, Features{false, 0, 0, 0, 1, 1, 1, false})
|
runStream(b, stats.Features{"", false, 0, 0, 0, 1, 1, 1, false})
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkClientUnaryNoTracec8(b *testing.B) {
|
func BenchmarkClientUnaryNoTracec8(b *testing.B) {
|
||||||
grpc.EnableTracing = false
|
grpc.EnableTracing = false
|
||||||
runStream(b, Features{false, 0, 0, 0, 8, 1, 1, false})
|
runStream(b, stats.Features{"", false, 0, 0, 0, 8, 1, 1, false})
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkClientUnaryNoTracec64(b *testing.B) {
|
func BenchmarkClientUnaryNoTracec64(b *testing.B) {
|
||||||
grpc.EnableTracing = false
|
grpc.EnableTracing = false
|
||||||
runStream(b, Features{false, 0, 0, 0, 64, 1, 1, false})
|
runStream(b, stats.Features{"", false, 0, 0, 0, 64, 1, 1, false})
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkClientUnaryNoTracec512(b *testing.B) {
|
func BenchmarkClientUnaryNoTracec512(b *testing.B) {
|
||||||
grpc.EnableTracing = false
|
grpc.EnableTracing = false
|
||||||
runStream(b, Features{false, 0, 0, 0, 512, 1, 1, false})
|
runStream(b, stats.Features{"", false, 0, 0, 0, 512, 1, 1, false})
|
||||||
|
runStream(b, stats.Features{"", false, 0, 0, 0, 512, 1, 1, false})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
|
|||||||
@ -56,7 +56,7 @@ func BenchmarkClient(b *testing.B) {
|
|||||||
tracing = "noTrace"
|
tracing = "noTrace"
|
||||||
}
|
}
|
||||||
|
|
||||||
benchFeature := Features{
|
benchFeature := stats.Features{
|
||||||
EnableTrace: enableTrace[featuresCurPos[0]],
|
EnableTrace: enableTrace[featuresCurPos[0]],
|
||||||
Latency: latency[featuresCurPos[1]],
|
Latency: latency[featuresCurPos[1]],
|
||||||
Kbps: kbps[featuresCurPos[2]],
|
Kbps: kbps[featuresCurPos[2]],
|
||||||
|
|||||||
133
benchmark/benchresult/main.go
Normal file
133
benchmark/benchresult/main.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2017 gRPC authors.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
To format the benchmark result:
|
||||||
|
go run benchmark/benchresult/main.go resultfile
|
||||||
|
|
||||||
|
To see the performance change based on a old result:
|
||||||
|
go run benchmark/benchresult/main.go resultfile_old resultfile
|
||||||
|
It will print the comparison result of intersection benchmarks between two files.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/gob"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/benchmark/stats"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createMap(fileName string, m map[string]stats.BenchResults) {
|
||||||
|
f, err := os.Open(fileName)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Read file %s error: %s\n", fileName, err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
var data []stats.BenchResults
|
||||||
|
decoder := gob.NewDecoder(f)
|
||||||
|
if err = decoder.Decode(&data); err != nil {
|
||||||
|
log.Fatalf("Decode file %s error: %s\n", fileName, err)
|
||||||
|
}
|
||||||
|
for _, d := range data {
|
||||||
|
m[d.RunMode+"-"+d.Features.String()] = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func intChange(title string, val1, val2 int64) string {
|
||||||
|
return fmt.Sprintf("%10s %12s %12s %8.2f%%\n", title, strconv.FormatInt(val1, 10),
|
||||||
|
strconv.FormatInt(val2, 10), float64(val2-val1)*100/float64(val1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeChange(title int, val1, val2 time.Duration) string {
|
||||||
|
return fmt.Sprintf("%10s %12s %12s %8.2f%%\n", strconv.Itoa(title)+" latency", val1.String(),
|
||||||
|
val2.String(), float64(val2-val1)*100/float64(val1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareTwoMap(m1, m2 map[string]stats.BenchResults) {
|
||||||
|
for k2, v2 := range m2 {
|
||||||
|
if v1, ok := m1[k2]; ok {
|
||||||
|
changes := k2 + "\n"
|
||||||
|
changes += fmt.Sprintf("%10s %12s %12s %8s\n", "Title", "Before", "After", "Percentage")
|
||||||
|
changes += intChange("Bytes/op", v1.AllocedBytesPerOp, v2.AllocedBytesPerOp)
|
||||||
|
changes += intChange("Allocs/op", v1.AllocsPerOp, v2.AllocsPerOp)
|
||||||
|
changes += timeChange(v1.Latency[1].Percent, v1.Latency[1].Value, v2.Latency[1].Value)
|
||||||
|
changes += timeChange(v1.Latency[2].Percent, v1.Latency[2].Value, v2.Latency[2].Value)
|
||||||
|
fmt.Printf("%s\n", changes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareBenchmark(file1, file2 string) {
|
||||||
|
var BenchValueFile1 map[string]stats.BenchResults
|
||||||
|
var BenchValueFile2 map[string]stats.BenchResults
|
||||||
|
BenchValueFile1 = make(map[string]stats.BenchResults)
|
||||||
|
BenchValueFile2 = make(map[string]stats.BenchResults)
|
||||||
|
|
||||||
|
createMap(file1, BenchValueFile1)
|
||||||
|
createMap(file2, BenchValueFile2)
|
||||||
|
|
||||||
|
compareTwoMap(BenchValueFile1, BenchValueFile2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printline(benchName, ltc50, ltc90, allocByte, allocsOp interface{}) {
|
||||||
|
fmt.Printf("%-80v%12v%12v%12v%12v\n", benchName, ltc50, ltc90, allocByte, allocsOp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatBenchmark(fileName string) {
|
||||||
|
f, err := os.Open(fileName)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Read file %s error: %s\n", fileName, err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
var data []stats.BenchResults
|
||||||
|
decoder := gob.NewDecoder(f)
|
||||||
|
if err = decoder.Decode(&data); err != nil {
|
||||||
|
log.Fatalf("Decode file %s error: %s\n", fileName, err)
|
||||||
|
}
|
||||||
|
if len(data) == 0 {
|
||||||
|
log.Fatalf("No data in file %s\n", fileName)
|
||||||
|
}
|
||||||
|
printPos := data[0].SharedPosion
|
||||||
|
fmt.Println("\nShared features:\n" + strings.Repeat("-", 20))
|
||||||
|
fmt.Print(stats.PartialPrintString(printPos, data[0].Features, true))
|
||||||
|
fmt.Println(strings.Repeat("-", 35))
|
||||||
|
for i := 0; i < len(data[0].SharedPosion); i++ {
|
||||||
|
printPos[i] = !printPos[i]
|
||||||
|
}
|
||||||
|
printline("Name", "latency-50", "latency-90", "Alloc (B)", "Alloc (#)")
|
||||||
|
for _, d := range data {
|
||||||
|
name := d.RunMode + stats.PartialPrintString(printPos, d.Features, false)
|
||||||
|
printline(name, d.Latency[1].Value.String(), d.Latency[2].Value.String(),
|
||||||
|
d.AllocedBytesPerOp, d.AllocsPerOp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) == 2 {
|
||||||
|
formatBenchmark(os.Args[1])
|
||||||
|
} else {
|
||||||
|
compareBenchmark(os.Args[1], os.Args[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -63,6 +63,17 @@ type Network struct {
|
|||||||
MTU int // Bytes per packet; if non-positive, infinite
|
MTU int // Bytes per packet; if non-positive, infinite
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
//Local simulates local network.
|
||||||
|
Local = Network{0, 0, 0}
|
||||||
|
//LAN simulates local area network network.
|
||||||
|
LAN = Network{100 * 1024, 2 * time.Millisecond, 1500}
|
||||||
|
//WAN simulates wide area network.
|
||||||
|
WAN = Network{20 * 1024, 30 * time.Millisecond, 1500}
|
||||||
|
//Longhaul simulates bad network.
|
||||||
|
Longhaul = Network{1000 * 1024, 200 * time.Millisecond, 9000}
|
||||||
|
)
|
||||||
|
|
||||||
// Conn returns a net.Conn that wraps c and injects n's latency into that
|
// Conn returns a net.Conn that wraps c and injects n's latency into that
|
||||||
// connection. This function also imposes latency for connection creation.
|
// connection. This function also imposes latency for connection creation.
|
||||||
// If n's Latency is lower than the measured latency in c, an error is
|
// If n's Latency is lower than the measured latency in c, an error is
|
||||||
|
|||||||
@ -23,9 +23,132 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Features contains most fields for a benchmark
|
||||||
|
type Features struct {
|
||||||
|
NetworkMode string
|
||||||
|
EnableTrace bool
|
||||||
|
Latency time.Duration
|
||||||
|
Kbps int
|
||||||
|
Mtu int
|
||||||
|
MaxConcurrentCalls int
|
||||||
|
ReqSizeBytes int
|
||||||
|
RespSizeBytes int
|
||||||
|
EnableCompressor bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the textual output of the Features as string.
|
||||||
|
func (f Features) String() string {
|
||||||
|
return fmt.Sprintf("traceMode_%t-latency_%s-kbps_%#v-MTU_%#v-maxConcurrentCalls_"+
|
||||||
|
"%#v-reqSize_%#vB-respSize_%#vB-Compressor_%t", f.EnableTrace,
|
||||||
|
f.Latency.String(), f.Kbps, f.Mtu, f.MaxConcurrentCalls, f.ReqSizeBytes, f.RespSizeBytes, f.EnableCompressor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PartialPrintString can print certain features with different format.
|
||||||
|
func PartialPrintString(noneEmptyPos []bool, f Features, shared bool) string {
|
||||||
|
s := ""
|
||||||
|
var (
|
||||||
|
prefix, suffix, linker string
|
||||||
|
isNetwork bool
|
||||||
|
)
|
||||||
|
if shared {
|
||||||
|
suffix = "\n"
|
||||||
|
linker = ": "
|
||||||
|
} else {
|
||||||
|
prefix = "-"
|
||||||
|
linker = "_"
|
||||||
|
}
|
||||||
|
if noneEmptyPos[0] {
|
||||||
|
s += fmt.Sprintf("%sTrace%s%t%s", prefix, linker, f.EnableCompressor, suffix)
|
||||||
|
}
|
||||||
|
if shared && f.NetworkMode != "" {
|
||||||
|
s += fmt.Sprintf("Network: %s \n", f.NetworkMode)
|
||||||
|
isNetwork = true
|
||||||
|
}
|
||||||
|
if !isNetwork {
|
||||||
|
if noneEmptyPos[1] {
|
||||||
|
s += fmt.Sprintf("%slatency%s%s%s", prefix, linker, f.Latency.String(), suffix)
|
||||||
|
}
|
||||||
|
if noneEmptyPos[2] {
|
||||||
|
s += fmt.Sprintf("%skbps%s%#v%s", prefix, linker, f.Kbps, suffix)
|
||||||
|
}
|
||||||
|
if noneEmptyPos[3] {
|
||||||
|
s += fmt.Sprintf("%sMTU%s%#v%s", prefix, linker, f.Mtu, suffix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if noneEmptyPos[4] {
|
||||||
|
s += fmt.Sprintf("%sCallers%s%#v%s", prefix, linker, f.MaxConcurrentCalls, suffix)
|
||||||
|
}
|
||||||
|
if noneEmptyPos[5] {
|
||||||
|
s += fmt.Sprintf("%sreqSize%s%#vB%s", prefix, linker, f.ReqSizeBytes, suffix)
|
||||||
|
}
|
||||||
|
if noneEmptyPos[6] {
|
||||||
|
s += fmt.Sprintf("%srespSize%s%#vB%s", prefix, linker, f.RespSizeBytes, suffix)
|
||||||
|
}
|
||||||
|
if noneEmptyPos[7] {
|
||||||
|
s += fmt.Sprintf("%sCompressor%s%t%s", prefix, linker, f.EnableCompressor, suffix)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type percentLatency struct {
|
||||||
|
Percent int
|
||||||
|
Value time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchResults records features and result of a benchmark.
|
||||||
|
type BenchResults struct {
|
||||||
|
RunMode string
|
||||||
|
Features Features
|
||||||
|
Latency []percentLatency
|
||||||
|
Operations int
|
||||||
|
NsPerOp int64
|
||||||
|
AllocedBytesPerOp int64
|
||||||
|
AllocsPerOp int64
|
||||||
|
SharedPosion []bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBenchmarkResult sets features of benchmark and basic results.
|
||||||
|
func (stats *Stats) SetBenchmarkResult(mode string, features Features, o int, allocdBytes, allocs int64, sharedPos []bool) {
|
||||||
|
stats.result.RunMode = mode
|
||||||
|
stats.result.Features = features
|
||||||
|
stats.result.Operations = o
|
||||||
|
stats.result.AllocedBytesPerOp = allocdBytes
|
||||||
|
stats.result.AllocsPerOp = allocs
|
||||||
|
stats.result.SharedPosion = sharedPos
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBenchmarkResults returns the result of the benchmark including features and result.
|
||||||
|
func (stats *Stats) GetBenchmarkResults() BenchResults {
|
||||||
|
return stats.result
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchString output latency stats as the format as time + unit.
|
||||||
|
func (stats *Stats) BenchString() string {
|
||||||
|
stats.maybeUpdate()
|
||||||
|
s := stats.result
|
||||||
|
res := s.RunMode + "-" + s.Features.String() + ": \n"
|
||||||
|
if len(s.Latency) != 0 {
|
||||||
|
var statsUnit = s.Latency[0].Value
|
||||||
|
var timeUnit = fmt.Sprintf("%v", statsUnit)[1:]
|
||||||
|
for i := 1; i < len(s.Latency)-1; i++ {
|
||||||
|
res += fmt.Sprintf("%d_Latency: %s %s \t", s.Latency[i].Percent,
|
||||||
|
strconv.FormatFloat(float64(s.Latency[i].Value)/float64(statsUnit), 'f', 4, 64), timeUnit)
|
||||||
|
}
|
||||||
|
res += fmt.Sprintf("Avg latency: %s %s \t",
|
||||||
|
strconv.FormatFloat(float64(s.Latency[len(s.Latency)-1].Value)/float64(statsUnit), 'f', 4, 64), timeUnit)
|
||||||
|
}
|
||||||
|
res += fmt.Sprintf("Count: %v \t", s.Operations)
|
||||||
|
res += fmt.Sprintf("%v Bytes/op\t", s.AllocedBytesPerOp)
|
||||||
|
res += fmt.Sprintf("%v Allocs/op\t", s.AllocsPerOp)
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
// Stats is a simple helper for gathering additional statistics like histogram
|
// Stats is a simple helper for gathering additional statistics like histogram
|
||||||
// during benchmarks. This is not thread safe.
|
// during benchmarks. This is not thread safe.
|
||||||
type Stats struct {
|
type Stats struct {
|
||||||
@ -36,6 +159,9 @@ type Stats struct {
|
|||||||
|
|
||||||
durations durationSlice
|
durations durationSlice
|
||||||
dirty bool
|
dirty bool
|
||||||
|
|
||||||
|
sortLatency bool
|
||||||
|
result BenchResults
|
||||||
}
|
}
|
||||||
|
|
||||||
type durationSlice []time.Duration
|
type durationSlice []time.Duration
|
||||||
@ -64,6 +190,18 @@ func (stats *Stats) Clear() {
|
|||||||
stats.durations = stats.durations[:0]
|
stats.durations = stats.durations[:0]
|
||||||
stats.histogram = nil
|
stats.histogram = nil
|
||||||
stats.dirty = false
|
stats.dirty = false
|
||||||
|
stats.result = BenchResults{}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Sort method for durations
|
||||||
|
func (a durationSlice) Len() int { return len(a) }
|
||||||
|
func (a durationSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a durationSlice) Less(i, j int) bool { return a[i] < a[j] }
|
||||||
|
func max(a, b int64) int64 {
|
||||||
|
if a > b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// maybeUpdate updates internal stat data if there was any newly added
|
// maybeUpdate updates internal stat data if there was any newly added
|
||||||
@ -73,6 +211,12 @@ func (stats *Stats) maybeUpdate() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if stats.sortLatency {
|
||||||
|
sort.Sort(stats.durations)
|
||||||
|
stats.min = int64(stats.durations[0])
|
||||||
|
stats.max = int64(stats.durations[len(stats.durations)-1])
|
||||||
|
}
|
||||||
|
|
||||||
stats.min = math.MaxInt64
|
stats.min = math.MaxInt64
|
||||||
stats.max = 0
|
stats.max = 0
|
||||||
for _, d := range stats.durations {
|
for _, d := range stats.durations {
|
||||||
@ -109,12 +253,28 @@ func (stats *Stats) maybeUpdate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stats.dirty = false
|
stats.dirty = false
|
||||||
|
|
||||||
|
if stats.durations.Len() != 0 {
|
||||||
|
var percentToObserve = []int{50, 90}
|
||||||
|
// First data record min unit from the latency result.
|
||||||
|
stats.result.Latency = append(stats.result.Latency, percentLatency{Percent: -1, Value: stats.unit})
|
||||||
|
for _, position := range percentToObserve {
|
||||||
|
stats.result.Latency = append(stats.result.Latency, percentLatency{Percent: position, Value: stats.durations[max(stats.histogram.Count*int64(position)/100-1, 0)]})
|
||||||
|
}
|
||||||
|
// Last data record the average latency.
|
||||||
|
avg := float64(stats.histogram.Sum) / float64(stats.histogram.Count)
|
||||||
|
stats.result.Latency = append(stats.result.Latency, percentLatency{Percent: -1, Value: time.Duration(avg)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortLatency blocks the output
|
||||||
|
func (stats *Stats) SortLatency() {
|
||||||
|
stats.sortLatency = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print writes textual output of the Stats.
|
// Print writes textual output of the Stats.
|
||||||
func (stats *Stats) Print(w io.Writer) {
|
func (stats *Stats) Print(w io.Writer) {
|
||||||
stats.maybeUpdate()
|
stats.maybeUpdate()
|
||||||
|
|
||||||
if stats.histogram == nil {
|
if stats.histogram == nil {
|
||||||
fmt.Fprint(w, "Histogram (empty)\n")
|
fmt.Fprint(w, "Histogram (empty)\n")
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user