diff --git a/cli/client/client_test.go b/cli/client/client_test.go index 4bd94ca3..663a2ae7 100644 --- a/cli/client/client_test.go +++ b/cli/client/client_test.go @@ -586,16 +586,17 @@ func TestBackends(t *testing.T) { } } +// The report is served as the backend writes it, not as the Go struct +// encodes it, so a phase key the CLI names wrongly fails here. func TestReport(t *testing.T) { - c, _ := testServer(t, jsonHandler(t, "GET", "/api/tests/", Report{ - Summary: &ReportSummary{ - TotalRecords: 100000, AvgLatencyMs: 5.2, P99LatencyMs: 12.3, - AvgThroughputRecPerSec: 50000, ErrorRate: 0.001, - }, - OverallSlaVerdict: &SlaVerdict{ - Passed: true, - }, - })) + c, _ := testServer(t, func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"summary":{"totalRecords":100000,"avgThroughputRecPerSec":50000,` + + `"avgLatencyMs":5.2,"p99LatencyMs":12.3,"errorRate":0.001},` + + `"overallSlaVerdict":{"passed":true},"phases":[` + + `{"phaseName":"produce","metrics":{"totalRecords":60000,"avgThroughputRecPerSec":30000,"p99LatencyMs":9.5},"slaVerdict":{"passed":true}},` + + `{"phaseName":"consume","metrics":{"totalRecords":40000,"avgThroughputRecPerSec":20000},"slaVerdict":{"passed":false}}]}`)) + }) resp, err := c.Report(context.Background(), "run-1") if err != nil { t.Fatal(err) @@ -606,6 +607,15 @@ func TestReport(t *testing.T) { if !resp.OverallSlaVerdict.Passed { t.Error("SLA should have passed") } + if len(resp.Phases) != 2 { + t.Fatalf("Phases = %+v", resp.Phases) + } + if p := resp.Phases[0]; p.Metrics == nil || p.Metrics.AvgThroughputRecPerSec != 30000 || p.Metrics.P99LatencyMs != 9.5 { + t.Errorf("produce = %+v", p.Metrics) + } + if p := resp.Phases[1]; p.Name != "consume" || p.SlaVerdict == nil || p.SlaVerdict.Passed { + t.Errorf("consume = %+v", p) + } } func TestReportSummary(t *testing.T) { diff --git a/cli/client/types.go b/cli/client/types.go index 9c243187..081ed28f 100644 --- a/cli/client/types.go +++ b/cli/client/types.go @@ -286,13 +286,12 @@ type Report struct { Phases []ReportPhase `json:"phases,omitempty"` } +// ReportPhase is the backend's PhaseReport: a phase's name, its own summary +// and its own SLA verdict. type ReportPhase struct { - Name string `json:"name"` - Status string `json:"status"` - RecordsSent float64 `json:"recordsSent"` - ThroughputRecPerSec float64 `json:"throughputRecordsPerSec"` - P99LatencyMs float64 `json:"p99LatencyMs"` - ErrorRate float64 `json:"errorRate"` + Name string `json:"phaseName"` + Metrics *ReportSummary `json:"metrics,omitempty"` + SlaVerdict *SlaVerdict `json:"slaVerdict,omitempty"` } type SlaVerdict struct { diff --git a/cli/cmd/report.go b/cli/cmd/report.go index 59e41e8d..d2e494f5 100644 --- a/cli/cmd/report.go +++ b/cli/cmd/report.go @@ -35,61 +35,39 @@ var reportShowCmd = &cobra.Command{ summary := result.Summary if summary == nil || summary.AvgThroughputRecPerSec == 0 { - if len(result.Phases) > 0 { + run, fetchErr := apiClient.GetTest(context.Background(), args[0]) + if fetchErr == nil && len(run.Results) > 0 && !isStaleResult(run.Results) { s := &client.ReportSummary{} count := 0 - for _, p := range result.Phases { - s.TotalRecords += p.RecordsSent - if p.ThroughputRecPerSec > s.PeakThroughputRecPerSec { - s.PeakThroughputRecPerSec = p.ThroughputRecPerSec + for _, r := range run.Results { + s.TotalRecords += r.RecordsSent + if r.ThroughputRecordsPerSec > s.PeakThroughputRecPerSec { + s.PeakThroughputRecPerSec = r.ThroughputRecordsPerSec } - if p.ThroughputRecPerSec > 0 { - s.AvgThroughputRecPerSec += p.ThroughputRecPerSec + if r.ThroughputRecordsPerSec > 0 { + s.AvgThroughputRecPerSec += r.ThroughputRecordsPerSec count++ } - if p.P99LatencyMs > s.P99LatencyMs { - s.P99LatencyMs = p.P99LatencyMs + if r.P99LatencyMs > s.P99LatencyMs { + s.P99LatencyMs = r.P99LatencyMs + } + if r.AvgLatencyMs > s.AvgLatencyMs { + s.AvgLatencyMs = r.AvgLatencyMs + } + if r.P50LatencyMs > s.P50LatencyMs { + s.P50LatencyMs = r.P50LatencyMs + } + if r.P95LatencyMs > s.P95LatencyMs { + s.P95LatencyMs = r.P95LatencyMs + } + if r.MaxLatencyMs > s.MaxLatencyMs { + s.MaxLatencyMs = r.MaxLatencyMs } } if count > 0 { s.AvgThroughputRecPerSec /= float64(count) } summary = s - } else { - run, fetchErr := apiClient.GetTest(context.Background(), args[0]) - if fetchErr == nil && len(run.Results) > 0 && !isStaleResult(run.Results) { - s := &client.ReportSummary{} - count := 0 - for _, r := range run.Results { - s.TotalRecords += r.RecordsSent - if r.ThroughputRecordsPerSec > s.PeakThroughputRecPerSec { - s.PeakThroughputRecPerSec = r.ThroughputRecordsPerSec - } - if r.ThroughputRecordsPerSec > 0 { - s.AvgThroughputRecPerSec += r.ThroughputRecordsPerSec - count++ - } - if r.P99LatencyMs > s.P99LatencyMs { - s.P99LatencyMs = r.P99LatencyMs - } - if r.AvgLatencyMs > s.AvgLatencyMs { - s.AvgLatencyMs = r.AvgLatencyMs - } - if r.P50LatencyMs > s.P50LatencyMs { - s.P50LatencyMs = r.P50LatencyMs - } - if r.P95LatencyMs > s.P95LatencyMs { - s.P95LatencyMs = r.P95LatencyMs - } - if r.MaxLatencyMs > s.MaxLatencyMs { - s.MaxLatencyMs = r.MaxLatencyMs - } - } - if count > 0 { - s.AvgThroughputRecPerSec /= float64(count) - } - summary = s - } } } diff --git a/cli/cmd/report_html.go b/cli/cmd/report_html.go index 98f3ef0f..a28dcd69 100644 --- a/cli/cmd/report_html.go +++ b/cli/cmd/report_html.go @@ -213,17 +213,20 @@ func renderHTMLReport(id string, r *client.Report) string { b.WriteString(`
| Phase | Status | Records | Throughput | P99 (ms) | Errors |
|---|---|---|---|---|---|
| Phase | SLA | Records | Throughput | P99 (ms) | Errors |
| %s | @@ -233,7 +236,7 @@ func renderHTMLReport(id string, r *client.Report) string {%s | %.4f%% |