Skip to content

Commit

Permalink
feat(ui): add optional 'count' column to CLI summary output
Browse files Browse the repository at this point in the history
To enable it run e.g. `k6 run --summary-trend-stats 'avg,p(99.99),count' test.js`.

Closes #1087
  • Loading branch information
Ivan Mirić committed Sep 4, 2019
1 parent d385166 commit 802da9e
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 10 deletions.
31 changes: 23 additions & 8 deletions ui/summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -79,15 +83,20 @@ 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 {
percentileTrendColumn, err := generatePercentileTrendColumn(stat)

if err == nil {
newTrendColumns = append(newTrendColumns, TrendColumn{stat, percentileTrendColumn})
for _, stat := range metricStats {
if stat == "count" {
trendCountColumn := TrendColumn{"count", func(s *stats.TrendSink) float64 { return float64(s.Count) }}
newTrendColumns = append(newTrendColumns, trendCountColumn)
continue
} else {
percentileTrendColumn, err := generatePercentileTrendColumn(stat)
if err == nil {
newTrendColumns = append(newTrendColumns, TrendColumn{stat, percentileTrendColumn})
continue
}
}

for _, col := range TrendColumns {
Expand Down Expand Up @@ -277,7 +286,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
}
Expand Down
76 changes: 74 additions & 2 deletions ui/summary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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},
Expand All @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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())
})
}
}

0 comments on commit 802da9e

Please sign in to comment.