diff --git a/api/types/metric.go b/api/types/metric.go index 97b21f3..8049d74 100644 --- a/api/types/metric.go +++ b/api/types/metric.go @@ -1,7 +1,5 @@ package types -import "time" - // ResponseStats is the report about benchmark result. type ResponseStats struct { // List of failures @@ -14,15 +12,15 @@ type ResponseStats struct { type RunnerMetricReport struct { // Total represents total number of requests. - Total int + Total int `json:"total"` // List of failures - FailureList []error + FailureList []error `json:"failureList,omitempty"` // Duration means the time of benchmark. - Duration time.Duration + Duration string `json:"duration"` // All the observed latencies - Latencies []float64 + Latencies []float64 `json:"latencies,omitempty"` // total bytes read from apiserver - TotalReceivedBytes int64 + TotalReceivedBytes int64 `json:"totalReceivedBytes"` // PercentileLatencies represents the latency distribution in seconds. - PercentileLatencies [][2]float64 // [2]float64{percentile, value} + PercentileLatencies [][2]float64 `json:"percentileLatencies,omitempty"` } diff --git a/cmd/kperf/commands/runner/runner.go b/cmd/kperf/commands/runner/runner.go index 7e72d47..d341294 100644 --- a/cmd/kperf/commands/runner/runner.go +++ b/cmd/kperf/commands/runner/runner.go @@ -11,6 +11,7 @@ import ( "strconv" "github.com/Azure/kperf/api/types" + "github.com/Azure/kperf/metrics" "github.com/Azure/kperf/request" "github.com/urfave/cli" @@ -183,13 +184,15 @@ func loadConfig(cliCtx *cli.Context) (*types.LoadProfile, error) { return &profileCfg, nil } +// printResponseStats prints types.RunnerMetricReport into underlying file. func printResponseStats(f *os.File, rawDataFlagIncluded bool, stats *request.Result) error { output := types.RunnerMetricReport{ - Total: stats.Total, - FailureList: stats.FailureList, - Duration: stats.Duration, - Latencies: stats.Latencies, - TotalReceivedBytes: stats.TotalReceivedBytes, + Total: stats.Total, + FailureList: stats.FailureList, + Duration: stats.Duration.String(), + Latencies: stats.Latencies, + TotalReceivedBytes: stats.TotalReceivedBytes, + PercentileLatencies: metrics.BuildPercentileLatencies(stats.Latencies), } encoder := json.NewEncoder(f) @@ -203,7 +206,5 @@ func printResponseStats(f *os.File, rawDataFlagIncluded bool, stats *request.Res if err != nil { return fmt.Errorf("failed to encode json: %w", err) } - return nil - } diff --git a/metrics/utils.go b/metrics/utils.go new file mode 100644 index 0000000..7d273fb --- /dev/null +++ b/metrics/utils.go @@ -0,0 +1,28 @@ +package metrics + +import ( + "math" + "sort" +) + +// BuildPercentileLatencies builds percentile latencies. +func BuildPercentileLatencies(latencies []float64) [][2]float64 { + if len(latencies) == 0 { + return nil + } + + var percentiles = []float64{0, 0.5, 0.90, 0.95, 0.99, 1} + + res := make([][2]float64, len(percentiles)) + + n := len(latencies) + sort.Float64s(latencies) + for pi, pv := range percentiles { + idx := int(math.Ceil(float64(n) * pv)) + if idx > 0 { + idx-- + } + res[pi] = [2]float64{pv, latencies[idx]} + } + return res +} diff --git a/metrics/utils_test.go b/metrics/utils_test.go new file mode 100644 index 0000000..62c6b73 --- /dev/null +++ b/metrics/utils_test.go @@ -0,0 +1,33 @@ +package metrics + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBuildPercentileLatencies(t *testing.T) { + ls := make([]float64, 100) + ls[0] = 50 + ls[1] = 49 + ls[2] = 1 + res := BuildPercentileLatencies(ls) + assert.Equal(t, [2]float64{0, 0}, res[0]) + assert.Equal(t, [2]float64{0.5, 0}, res[1]) + assert.Equal(t, [2]float64{0.9, 0}, res[2]) + assert.Equal(t, [2]float64{0.95, 0}, res[3]) + assert.Equal(t, [2]float64{0.99, 49}, res[4]) + assert.Equal(t, [2]float64{1, 50}, res[5]) + + ls = make([]float64, 1000) + ls[0] = 50 + ls[1] = 49 + ls[2] = -1 + res = BuildPercentileLatencies(ls) + assert.Equal(t, [2]float64{0, -1}, res[0]) + assert.Equal(t, [2]float64{0.5, 0}, res[1]) + assert.Equal(t, [2]float64{0.9, 0}, res[2]) + assert.Equal(t, [2]float64{0.95, 0}, res[3]) + assert.Equal(t, [2]float64{0.99, 0}, res[4]) + assert.Equal(t, [2]float64{1, 50}, res[5]) +}