From ac255fc58de52e9c002e94fc5cfd73e3fb5be8b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Miri=C4=87?= Date: Tue, 3 Sep 2019 09:33:15 +0200 Subject: [PATCH] feat(ui): add optional 'count' column to CLI summary output To enable it run e.g. `k6 run --summary-trend-stats 'avg,p(99.99),count' test.js`. Closes #1087 --- ui/summary.go | 24 ++++++++++++--- ui/summary_test.go | 76 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 93 insertions(+), 7 deletions(-) diff --git a/ui/summary.go b/ui/summary.go index 7c6b7b1a4b0..5b085d021b2 100644 --- a/ui/summary.go +++ b/ui/summary.go @@ -68,6 +68,10 @@ func VerifyTrendColumnStat(stat string) error { return ErrStatEmptyString } + if stat == "count" { + return nil + } + for _, col := range TrendColumns { if col.Key == stat { return nil @@ -79,12 +83,16 @@ func VerifyTrendColumnStat(stat string) error { } // UpdateTrendColumns updates the default trend columns with user defined ones -func UpdateTrendColumns(stats []string) { - newTrendColumns := make([]TrendColumn, 0, len(stats)) +func UpdateTrendColumns(metricStats []string) { + newTrendColumns := make([]TrendColumn, 0, len(metricStats)) - for _, stat := range stats { + for _, stat := range metricStats { + if stat == "count" { + trendCountColumn := TrendColumn{"count", func(s *stats.TrendSink) float64 { return float64(s.Count) }} + newTrendColumns = append(newTrendColumns, trendCountColumn) + continue + } percentileTrendColumn, err := generatePercentileTrendColumn(stat) - if err == nil { newTrendColumns = append(newTrendColumns, TrendColumn{stat, percentileTrendColumn}) continue @@ -277,7 +285,13 @@ func SummarizeMetrics(w io.Writer, indent string, t time.Duration, timeUnit stri if sink, ok := m.Sink.(*stats.TrendSink); ok { cols := make([]string, len(TrendColumns)) for i, col := range TrendColumns { - value := m.HumanizeValue(col.Get(sink), timeUnit) + var value string + if col.Key == "count" { + value = strconv.FormatFloat(col.Get(sink), 'f', 0, 64) + } else { + value = m.HumanizeValue(col.Get(sink), timeUnit) + } + if l := StrWidth(value); l > trendColMaxLens[i] { trendColMaxLens[i] = l } diff --git a/ui/summary_test.go b/ui/summary_test.go index 341c121169f..7dfaa2fc590 100644 --- a/ui/summary_test.go +++ b/ui/summary_test.go @@ -21,10 +21,14 @@ package ui import ( + "bytes" + "strconv" "testing" + "time" "github.com/loadimpact/k6/stats" "github.com/stretchr/testify/assert" + "gopkg.in/guregu/null.v3" ) var verifyTests = []struct { @@ -41,6 +45,7 @@ var verifyTests = []struct { {"p(99)", nil}, {"p(99.9)", nil}, {"p(99.9999)", nil}, + {"count", nil}, {"nil", ErrStatUnknownFormat}, {" avg", ErrStatUnknownFormat}, {"avg ", ErrStatUnknownFormat}, @@ -67,6 +72,10 @@ func TestVerifyTrendColumnStat(t *testing.T) { } func TestUpdateTrendColumns(t *testing.T) { + tcOld := TrendColumns + defer func() { + TrendColumns = tcOld + }() sink := createTestTrendSink(100) t.Run("No stats", func(t *testing.T) { @@ -91,11 +100,12 @@ func TestUpdateTrendColumns(t *testing.T) { t.Run("Multiple stats", func(t *testing.T) { TrendColumns = defaultTrendColumns - UpdateTrendColumns([]string{"med", "max"}) + UpdateTrendColumns([]string{"med", "max", "count"}) - assert.Exactly(t, 2, len(TrendColumns)) + assert.Exactly(t, 3, len(TrendColumns)) assert.Exactly(t, sink.Med, TrendColumns[0].Get(sink)) assert.Exactly(t, sink.Max, TrendColumns[1].Get(sink)) + assert.Exactly(t, float64(100), TrendColumns[2].Get(sink)) }) t.Run("Ignore invalid stats", func(t *testing.T) { @@ -158,3 +168,65 @@ func TestGeneratePercentileTrendColumn(t *testing.T) { assert.Exactly(t, err, ErrPercentileStatInvalidValue) }) } + +func createTestMetrics() map[string]*stats.Metric { + metrics := make(map[string]*stats.Metric) + gaugeMetric := stats.New("vus", stats.Gauge) + gaugeMetric.Sink.Add(stats.Sample{Value: 1}) + countMetric := stats.New("http_reqs", stats.Counter) + countMetric.Tainted = null.BoolFrom(true) + checksMetric := stats.New("checks", stats.Rate) + checksMetric.Tainted = null.BoolFrom(false) + sink := &stats.TrendSink{} + + samples := []float64{10.0, 15.0, 20.0} + for _, s := range samples { + sink.Add(stats.Sample{Value: s}) + checksMetric.Sink.Add(stats.Sample{Value: 1}) + countMetric.Sink.Add(stats.Sample{Value: 1}) + } + + metrics["vus"] = gaugeMetric + metrics["http_reqs"] = countMetric + metrics["checks"] = checksMetric + metrics["my_trend"] = &stats.Metric{Name: "my_trend", Type: stats.Trend, Contains: stats.Time, Sink: sink} + + return metrics +} + +func TestSummarizeMetrics(t *testing.T) { + tcOld := TrendColumns + defer func() { + TrendColumns = tcOld + }() + + trendCountColumn := TrendColumn{"count", func(s *stats.TrendSink) float64 { return float64(s.Count) }} + + var ( + checksOut = " ✓ checks......: 100.00% ✓ 3 ✗ 0 \n" + countOut = " ✗ http_reqs...: 3 3/s\n" + gaugeOut = " vus.........: 1 min=1 max=1\n" + trendOut = " my_trend....: avg=15ms min=10ms med=15ms max=20ms p(90)=19ms p(95)=19.5ms\n" + ) + + metrics := createTestMetrics() + testCases := []struct { + columns []TrendColumn + expected string + }{ + {tcOld, checksOut + countOut + trendOut + gaugeOut}, + {[]TrendColumn{trendCountColumn}, checksOut + countOut + " my_trend....: count=3\n" + gaugeOut}, + {[]TrendColumn{TrendColumns[0], trendCountColumn}, + checksOut + countOut + " my_trend....: avg=15ms count=3\n" + gaugeOut}, + } + + for i, tc := range testCases { + tc := tc + t.Run(strconv.Itoa(i), func(t *testing.T) { + TrendColumns = tc.columns + var w bytes.Buffer + SummarizeMetrics(&w, " ", time.Second, "", metrics) + assert.Equal(t, tc.expected, w.String()) + }) + } +}