#46 support JSON snapshot
This commit is contained in:
parent
72b10d10a8
commit
cf5e6eef85
|
@ -99,7 +99,7 @@ A high-performance HTTP benchmarking tool with real-time web UI and terminal dis
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
plow http://127.0.0.1:8080/ -c 20 -n 100000
|
plow http://127.0.0.1:8080/ -c 20 -n 100000
|
||||||
plow https://httpbin.org/post -c 20 -d 5m --body @file.json -T 'application/json' -m POST
|
plow https://httpbin.org/post -c 20 -d 5m --body @file.jsonFormat -T 'application/jsonFormat' -m POST
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
--help Show context-sensitive help.
|
--help Show context-sensitive help.
|
||||||
|
@ -109,6 +109,7 @@ Flags:
|
||||||
-d, --duration=DURATION Duration of test, examples: -d 10s -d 3m
|
-d, --duration=DURATION Duration of test, examples: -d 10s -d 3m
|
||||||
-i, --interval=200ms Print snapshot result every interval, use 0 to print once at the end
|
-i, --interval=200ms Print snapshot result every interval, use 0 to print once at the end
|
||||||
--seconds Use seconds as time unit to print
|
--seconds Use seconds as time unit to print
|
||||||
|
--json Print snapshot result as JSON
|
||||||
-b, --body=BODY HTTP request body, if start the body with @, the rest should be a filename to read
|
-b, --body=BODY HTTP request body, if start the body with @, the rest should be a filename to read
|
||||||
--stream Specify whether to stream file specified by '--body @file' using chunked encoding or to read into memory
|
--stream Specify whether to stream file specified by '--body @file' using chunked encoding or to read into memory
|
||||||
-m, --method="GET" HTTP method
|
-m, --method="GET" HTTP method
|
||||||
|
@ -126,7 +127,7 @@ Flags:
|
||||||
--socks5=ip:port Socks5 proxy
|
--socks5=ip:port Socks5 proxy
|
||||||
--auto-open-browser Specify whether auto open browser to show Web charts
|
--auto-open-browser Specify whether auto open browser to show Web charts
|
||||||
--[no-]clean Clean the histogram bar once its finished. Default is true
|
--[no-]clean Clean the histogram bar once its finished. Default is true
|
||||||
--[no-]summary Only print the summary without realtime reports
|
--summary Only print the summary without realtime reports
|
||||||
--version Show application version.
|
--version Show application version.
|
||||||
|
|
||||||
Flags default values also read from env PLOW_SOME_FLAG, such as PLOW_TIMEOUT=5s equals to --timeout=5s
|
Flags default values also read from env PLOW_SOME_FLAG, such as PLOW_TIMEOUT=5s equals to --timeout=5s
|
||||||
|
|
|
@ -42,7 +42,7 @@ function {{ .ViewID }}_sync() {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "GET",
|
type: "GET",
|
||||||
url: "{{ .APIPath }}{{ .Route }}",
|
url: "{{ .APIPath }}{{ .Route }}",
|
||||||
dataType: "json",
|
dataType: "jsonFormat",
|
||||||
success: function (result) {
|
success: function (result) {
|
||||||
let opt = goecharts_{{ .ViewID }}.getOption();
|
let opt = goecharts_{{ .ViewID }}.getOption();
|
||||||
let x = opt.xAxis[0].data;
|
let x = opt.xAxis[0].data;
|
||||||
|
@ -141,8 +141,8 @@ func (c *Charts) newRPSView() components.Charter {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Metrics struct {
|
type Metrics struct {
|
||||||
Values []interface{} `json:"values"`
|
Values []interface{} `jsonFormat:"values"`
|
||||||
Time string `json:"time"`
|
Time string `jsonFormat:"time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Charts struct {
|
type Charts struct {
|
||||||
|
|
26
main.go
26
main.go
|
@ -5,6 +5,8 @@ import (
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
|
_ "net/http/pprof"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -20,6 +22,7 @@ var (
|
||||||
duration = kingpin.Flag("duration", "Duration of test, examples: -d 10s -d 3m").Short('d').PlaceHolder("DURATION").Duration()
|
duration = kingpin.Flag("duration", "Duration of test, examples: -d 10s -d 3m").Short('d').PlaceHolder("DURATION").Duration()
|
||||||
interval = kingpin.Flag("interval", "Print snapshot result every interval, use 0 to print once at the end").Short('i').Default("200ms").Duration()
|
interval = kingpin.Flag("interval", "Print snapshot result every interval, use 0 to print once at the end").Short('i').Default("200ms").Duration()
|
||||||
seconds = kingpin.Flag("seconds", "Use seconds as time unit to print").Bool()
|
seconds = kingpin.Flag("seconds", "Use seconds as time unit to print").Bool()
|
||||||
|
jsonFormat = kingpin.Flag("json", "Print snapshot result as JSON").Bool()
|
||||||
|
|
||||||
body = kingpin.Flag("body", "HTTP request body, if start the body with @, the rest should be a filename to read").Short('b').String()
|
body = kingpin.Flag("body", "HTTP request body, if start the body with @, the rest should be a filename to read").Short('b').String()
|
||||||
stream = kingpin.Flag("stream", "Specify whether to stream file specified by '--body @file' using chunked encoding or to read into memory").Default("false").Bool()
|
stream = kingpin.Flag("stream", "Specify whether to stream file specified by '--body @file' using chunked encoding or to read into memory").Default("false").Bool()
|
||||||
|
@ -40,7 +43,8 @@ var (
|
||||||
|
|
||||||
autoOpenBrowser = kingpin.Flag("auto-open-browser", "Specify whether auto open browser to show Web charts").Bool()
|
autoOpenBrowser = kingpin.Flag("auto-open-browser", "Specify whether auto open browser to show Web charts").Bool()
|
||||||
clean = kingpin.Flag("clean", "Clean the histogram bar once its finished. Default is true").Default("true").NegatableBool()
|
clean = kingpin.Flag("clean", "Clean the histogram bar once its finished. Default is true").Default("true").NegatableBool()
|
||||||
summary = kingpin.Flag("summary", "Only print the summary without realtime reports").Default("false").NegatableBool()
|
summary = kingpin.Flag("summary", "Only print the summary without realtime reports").Default("false").Bool()
|
||||||
|
pprofAddr = kingpin.Flag("pprof", "Enable pprof at special address").Hidden().String()
|
||||||
url = kingpin.Arg("url", "request url").Required().String()
|
url = kingpin.Arg("url", "request url").Required().String()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -79,7 +83,7 @@ var CompactUsageTemplate = `{{define "FormatCommand" -}}
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
plow http://127.0.0.1:8080/ -c 20 -n 100000
|
plow http://127.0.0.1:8080/ -c 20 -n 100000
|
||||||
plow https://httpbin.org/post -c 20 -d 5m --body @file.json -T 'application/json' -m POST
|
plow https://httpbin.org/post -c 20 -d 5m --body @file.jsonFormat -T 'application/jsonFormat' -m POST
|
||||||
|
|
||||||
{{if .Context.Flags -}}
|
{{if .Context.Flags -}}
|
||||||
{{T "Flags:"}}
|
{{T "Flags:"}}
|
||||||
|
@ -182,6 +186,10 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *pprofAddr != "" {
|
||||||
|
go http.ListenAndServe(*pprofAddr, nil)
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
var bodyBytes []byte
|
var bodyBytes []byte
|
||||||
var bodyFile string
|
var bodyFile string
|
||||||
|
@ -232,11 +240,6 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
outStream := os.Stdout
|
|
||||||
if *summary {
|
|
||||||
outStream = os.Stderr
|
|
||||||
isTerminal = false
|
|
||||||
}
|
|
||||||
// description
|
// description
|
||||||
var desc string
|
var desc string
|
||||||
desc = fmt.Sprintf("Benchmarking %s", *url)
|
desc = fmt.Sprintf("Benchmarking %s", *url)
|
||||||
|
@ -247,7 +250,7 @@ func main() {
|
||||||
desc += fmt.Sprintf(" for %s", duration.String())
|
desc += fmt.Sprintf(" for %s", duration.String())
|
||||||
}
|
}
|
||||||
desc += fmt.Sprintf(" using %d connection(s).", *concurrency)
|
desc += fmt.Sprintf(" using %d connection(s).", *concurrency)
|
||||||
fmt.Fprintln(outStream, desc)
|
fmt.Fprintln(os.Stderr, desc)
|
||||||
|
|
||||||
// charts listener
|
// charts listener
|
||||||
var ln net.Listener
|
var ln net.Listener
|
||||||
|
@ -257,9 +260,9 @@ func main() {
|
||||||
errAndExit(err.Error())
|
errAndExit(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Fprintf(outStream, "@ Real-time charts is listening on http://%s\n", ln.Addr().String())
|
fmt.Fprintf(os.Stderr, "@ Real-time charts is listening on http://%s\n", ln.Addr().String())
|
||||||
}
|
}
|
||||||
fmt.Fprintln(outStream, "")
|
fmt.Fprintln(os.Stderr, "")
|
||||||
|
|
||||||
// do request
|
// do request
|
||||||
go requester.Run()
|
go requester.Run()
|
||||||
|
@ -280,6 +283,5 @@ func main() {
|
||||||
|
|
||||||
// terminal printer
|
// terminal printer
|
||||||
printer := NewPrinter(*requests, *duration, !*clean, *summary)
|
printer := NewPrinter(*requests, *duration, !*clean, *summary)
|
||||||
printer.PrintLoop(report.Snapshot, *interval, *seconds, report.Done())
|
printer.PrintLoop(report.Snapshot, *interval, *seconds, *jsonFormat, report.Done())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
167
print.go
167
print.go
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mattn/go-isatty"
|
"github.com/mattn/go-isatty"
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
|
@ -63,12 +64,12 @@ func (p *Printer) updateProgressValue(rs *SnapshotReport) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Printer) PrintLoop(snapshot func() *SnapshotReport, interval time.Duration, useSeconds bool, doneChan <-chan struct{}) {
|
func (p *Printer) PrintLoop(snapshot func() *SnapshotReport, interval time.Duration, useSeconds bool, json bool, doneChan <-chan struct{}) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
var backCursor string
|
var backCursor string
|
||||||
cl := clearLine
|
cl := clearLine
|
||||||
if p.summary || interval == 0 {
|
if p.summary || interval == 0 || !isTerminal {
|
||||||
cl = nil
|
cl = nil
|
||||||
}
|
}
|
||||||
echo := func(isFinal bool) {
|
echo := func(isFinal bool) {
|
||||||
|
@ -76,7 +77,11 @@ func (p *Printer) PrintLoop(snapshot func() *SnapshotReport, interval time.Durat
|
||||||
p.updateProgressValue(report)
|
p.updateProgressValue(report)
|
||||||
os.Stdout.WriteString(backCursor)
|
os.Stdout.WriteString(backCursor)
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
p.formatTableReports(&buf, report, isFinal, useSeconds)
|
if json {
|
||||||
|
p.formatJSONReports(&buf, report, isFinal, useSeconds)
|
||||||
|
} else {
|
||||||
|
p.formatTableReports(&buf, report, isFinal, useSeconds)
|
||||||
|
}
|
||||||
result := buf.Bytes()
|
result := buf.Bytes()
|
||||||
n := 0
|
n := 0
|
||||||
for {
|
for {
|
||||||
|
@ -93,7 +98,9 @@ func (p *Printer) PrintLoop(snapshot func() *SnapshotReport, interval time.Durat
|
||||||
result = result[i+1:]
|
result = result[i+1:]
|
||||||
}
|
}
|
||||||
os.Stdout.Sync()
|
os.Stdout.Sync()
|
||||||
backCursor = fmt.Sprintf("\033[%dA", n)
|
if isTerminal {
|
||||||
|
backCursor = fmt.Sprintf("\033[%dA", n)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if interval > 0 {
|
if interval > 0 {
|
||||||
|
@ -185,6 +192,24 @@ func formatFloat64(f float64) string {
|
||||||
return strconv.FormatFloat(f, 'f', -1, 64)
|
return strconv.FormatFloat(f, 'f', -1, 64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Printer) formatJSONReports(writer *bytes.Buffer, snapshot *SnapshotReport, isFinal bool, useSeconds bool) {
|
||||||
|
indent := 0
|
||||||
|
writer.WriteString("{\n")
|
||||||
|
indent++
|
||||||
|
p.buildJSONSummary(writer, snapshot, indent)
|
||||||
|
if len(snapshot.Errors) != 0 {
|
||||||
|
writer.WriteString(",\n")
|
||||||
|
p.buildJSONErrors(writer, snapshot, indent)
|
||||||
|
}
|
||||||
|
writer.WriteString(",\n")
|
||||||
|
p.buildJSONStats(writer, snapshot, useSeconds, indent)
|
||||||
|
writer.WriteString(",\n")
|
||||||
|
p.buildJSONPercentile(writer, snapshot, useSeconds, indent)
|
||||||
|
writer.WriteString(",\n")
|
||||||
|
p.buildJSONHistogram(writer, snapshot, useSeconds, indent)
|
||||||
|
writer.WriteString("\n}\n")
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Printer) formatTableReports(writer *bytes.Buffer, snapshot *SnapshotReport, isFinal bool, useSeconds bool) {
|
func (p *Printer) formatTableReports(writer *bytes.Buffer, snapshot *SnapshotReport, isFinal bool, useSeconds bool) {
|
||||||
summaryBulk := p.buildSummary(snapshot, isFinal)
|
summaryBulk := p.buildSummary(snapshot, isFinal)
|
||||||
errorsBulks := p.buildErrors(snapshot)
|
errorsBulks := p.buildErrors(snapshot)
|
||||||
|
@ -213,6 +238,30 @@ func (p *Printer) formatTableReports(writer *bytes.Buffer, snapshot *SnapshotRep
|
||||||
writeBulk(writer, hisBulk)
|
writeBulk(writer, hisBulk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Printer) buildJSONHistogram(writer *bytes.Buffer, snapshot *SnapshotReport, useSeconds bool, indent int) {
|
||||||
|
tab0 := strings.Repeat(" ", indent)
|
||||||
|
writer.WriteString(tab0 + "\"Histograms\": [\n")
|
||||||
|
tab1 := strings.Repeat(" ", indent+1)
|
||||||
|
|
||||||
|
maxCount := 0
|
||||||
|
hisSum := 0
|
||||||
|
for _, bin := range snapshot.Histograms {
|
||||||
|
if maxCount < bin.Count {
|
||||||
|
maxCount = bin.Count
|
||||||
|
}
|
||||||
|
hisSum += bin.Count
|
||||||
|
}
|
||||||
|
for i, bin := range snapshot.Histograms {
|
||||||
|
writer.WriteString(fmt.Sprintf(`%s[ "%s", %d ]`, tab1,
|
||||||
|
durationToString(bin.Mean, useSeconds), bin.Count))
|
||||||
|
if i != len(snapshot.Histograms)-1 {
|
||||||
|
writer.WriteString(",")
|
||||||
|
}
|
||||||
|
writer.WriteString("\n")
|
||||||
|
}
|
||||||
|
writer.WriteString(tab0 + "]")
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Printer) buildHistogram(snapshot *SnapshotReport, useSeconds bool, isFinal bool) [][]string {
|
func (p *Printer) buildHistogram(snapshot *SnapshotReport, useSeconds bool, isFinal bool) [][]string {
|
||||||
hisBulk := make([][]string, 0, 8)
|
hisBulk := make([][]string, 0, 8)
|
||||||
maxCount := 0
|
maxCount := 0
|
||||||
|
@ -245,6 +294,22 @@ func (p *Printer) buildHistogram(snapshot *SnapshotReport, useSeconds bool, isFi
|
||||||
return hisBulk
|
return hisBulk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Printer) buildJSONPercentile(writer *bytes.Buffer, snapshot *SnapshotReport, useSeconds bool, indent int) {
|
||||||
|
tab0 := strings.Repeat(" ", indent)
|
||||||
|
writer.WriteString(tab0 + "\"Percentiles\": {\n")
|
||||||
|
tab1 := strings.Repeat(" ", indent+1)
|
||||||
|
for i, percentile := range snapshot.Percentiles {
|
||||||
|
perc := formatFloat64(percentile.Percentile * 100)
|
||||||
|
writer.WriteString(fmt.Sprintf(`%s"%s": "%s"`, tab1, "P"+perc,
|
||||||
|
durationToString(percentile.Latency, useSeconds)))
|
||||||
|
if i != len(snapshot.Percentiles)-1 {
|
||||||
|
writer.WriteString(",")
|
||||||
|
}
|
||||||
|
writer.WriteString("\n")
|
||||||
|
}
|
||||||
|
writer.WriteString(tab0 + "}")
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Printer) buildPercentile(snapshot *SnapshotReport, useSeconds bool) [][]string {
|
func (p *Printer) buildPercentile(snapshot *SnapshotReport, useSeconds bool) [][]string {
|
||||||
percBulk := make([][]string, 2)
|
percBulk := make([][]string, 2)
|
||||||
percAligns := make([]int, 0, len(snapshot.Percentiles))
|
percAligns := make([]int, 0, len(snapshot.Percentiles))
|
||||||
|
@ -259,6 +324,30 @@ func (p *Printer) buildPercentile(snapshot *SnapshotReport, useSeconds bool) [][
|
||||||
return percBulk
|
return percBulk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Printer) buildJSONStats(writer *bytes.Buffer, snapshot *SnapshotReport, useSeconds bool, indent int) {
|
||||||
|
tab0 := strings.Repeat(" ", indent)
|
||||||
|
writer.WriteString(tab0 + "\"Statistics\": {\n")
|
||||||
|
tab1 := strings.Repeat(" ", indent+1)
|
||||||
|
writer.WriteString(fmt.Sprintf(`%s"Latency": { "Min": "%s", "Mean": "%s", "StdDev": "%s", "Max": "%s" }`,
|
||||||
|
tab1,
|
||||||
|
durationToString(snapshot.Stats.Min, useSeconds),
|
||||||
|
durationToString(snapshot.Stats.Mean, useSeconds),
|
||||||
|
durationToString(snapshot.Stats.StdDev, useSeconds),
|
||||||
|
durationToString(snapshot.Stats.Max, useSeconds),
|
||||||
|
))
|
||||||
|
if snapshot.RpsStats != nil {
|
||||||
|
writer.WriteString(",\n")
|
||||||
|
writer.WriteString(fmt.Sprintf(`%s"RPS": { "Min": %s, "Mean": %s, "StdDev": %s, "Max": %s }`,
|
||||||
|
tab1,
|
||||||
|
formatFloat64(math.Trunc(snapshot.RpsStats.Min*100)/100.0),
|
||||||
|
formatFloat64(math.Trunc(snapshot.RpsStats.Mean*100)/100.0),
|
||||||
|
formatFloat64(math.Trunc(snapshot.RpsStats.StdDev*100)/100.0),
|
||||||
|
formatFloat64(math.Trunc(snapshot.RpsStats.Max*100)/100.0),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
writer.WriteString("\n" + tab0 + "}")
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Printer) buildStats(snapshot *SnapshotReport, useSeconds bool) [][]string {
|
func (p *Printer) buildStats(snapshot *SnapshotReport, useSeconds bool) [][]string {
|
||||||
var statsBulk [][]string
|
var statsBulk [][]string
|
||||||
statsBulk = append(statsBulk,
|
statsBulk = append(statsBulk,
|
||||||
|
@ -286,6 +375,23 @@ func (p *Printer) buildStats(snapshot *SnapshotReport, useSeconds bool) [][]stri
|
||||||
return statsBulk
|
return statsBulk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Printer) buildJSONErrors(writer *bytes.Buffer, snapshot *SnapshotReport, indent int) {
|
||||||
|
tab0 := strings.Repeat(" ", indent)
|
||||||
|
writer.WriteString(tab0 + "\"Error\": {\n")
|
||||||
|
tab1 := strings.Repeat(" ", indent+1)
|
||||||
|
errors := sortMapStrInt(snapshot.Errors)
|
||||||
|
for i, v := range errors {
|
||||||
|
v[1] = colorize(v[1], FgRedColor)
|
||||||
|
vb, _ := json.Marshal(v[0])
|
||||||
|
writer.WriteString(fmt.Sprintf(`%s%s: %s`, tab1, vb, v[1]))
|
||||||
|
if i != len(errors)-1 {
|
||||||
|
writer.WriteString(",")
|
||||||
|
}
|
||||||
|
writer.WriteString("\n")
|
||||||
|
}
|
||||||
|
writer.WriteString(tab0 + "}")
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Printer) buildErrors(snapshot *SnapshotReport) [][]string {
|
func (p *Printer) buildErrors(snapshot *SnapshotReport) [][]string {
|
||||||
var errorsBulks [][]string
|
var errorsBulks [][]string
|
||||||
for k, v := range snapshot.Errors {
|
for k, v := range snapshot.Errors {
|
||||||
|
@ -299,9 +405,47 @@ func (p *Printer) buildErrors(snapshot *SnapshotReport) [][]string {
|
||||||
return errorsBulks
|
return errorsBulks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sortMapStrInt(m map[string]int64) (ret [][]string) {
|
||||||
|
for k, v := range m {
|
||||||
|
ret = append(ret, []string{k, strconv.FormatInt(v, 10)})
|
||||||
|
}
|
||||||
|
sort.Slice(ret, func(i, j int) bool { return ret[i][0] < ret[j][0] })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Printer) buildJSONSummary(writer *bytes.Buffer, snapshot *SnapshotReport, indent int) {
|
||||||
|
tab0 := strings.Repeat(" ", indent)
|
||||||
|
writer.WriteString(tab0 + "\"Summary\": {\n")
|
||||||
|
{
|
||||||
|
tab1 := strings.Repeat(" ", indent+1)
|
||||||
|
writer.WriteString(fmt.Sprintf("%s\"Elapsed\": \"%s\",\n", tab1, snapshot.Elapsed.Truncate(100*time.Millisecond).String()))
|
||||||
|
writer.WriteString(fmt.Sprintf("%s\"Count\": %d,\n", tab1, snapshot.Count))
|
||||||
|
writer.WriteString(fmt.Sprintf("%s\"Counts\": {\n", tab1))
|
||||||
|
i := 0
|
||||||
|
tab2 := strings.Repeat(" ", indent+2)
|
||||||
|
codes := sortMapStrInt(snapshot.Codes)
|
||||||
|
for _, v := range codes {
|
||||||
|
i++
|
||||||
|
if v[0] != "2xx" {
|
||||||
|
v[1] = colorize(v[1], FgMagentaColor)
|
||||||
|
}
|
||||||
|
writer.WriteString(fmt.Sprintf(`%s"%s": %s`, tab2, v[0], v[1]))
|
||||||
|
if i != len(snapshot.Codes) {
|
||||||
|
writer.WriteString(",")
|
||||||
|
}
|
||||||
|
writer.WriteString("\n")
|
||||||
|
}
|
||||||
|
writer.WriteString(tab1 + "},\n")
|
||||||
|
writer.WriteString(fmt.Sprintf("%s\"RPS\": %.3f,\n", tab1, snapshot.RPS))
|
||||||
|
writer.WriteString(fmt.Sprintf("%s\"Reads\": \"%.3fMB/s\",\n", tab1, snapshot.ReadThroughput))
|
||||||
|
writer.WriteString(fmt.Sprintf("%s\"Writes\": \"%.3fMB/s\"\n", tab1, snapshot.WriteThroughput))
|
||||||
|
}
|
||||||
|
writer.WriteString(tab0 + "}")
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Printer) buildSummary(snapshot *SnapshotReport, isFinal bool) [][]string {
|
func (p *Printer) buildSummary(snapshot *SnapshotReport, isFinal bool) [][]string {
|
||||||
summarybulk := make([][]string, 0, 8)
|
summarybulk := make([][]string, 0, 8)
|
||||||
elapsedLine := []string{"Elapsed", snapshot.Elapsed.Truncate(time.Millisecond).String()}
|
elapsedLine := []string{"Elapsed", snapshot.Elapsed.Truncate(100 * time.Millisecond).String()}
|
||||||
if p.maxDuration > 0 && !isFinal {
|
if p.maxDuration > 0 && !isFinal {
|
||||||
elapsedLine = append(elapsedLine, p.pbDurStr)
|
elapsedLine = append(elapsedLine, p.pbDurStr)
|
||||||
}
|
}
|
||||||
|
@ -315,16 +459,13 @@ func (p *Printer) buildSummary(snapshot *SnapshotReport, isFinal bool) [][]strin
|
||||||
countLine,
|
countLine,
|
||||||
)
|
)
|
||||||
|
|
||||||
codesBulks := make([][]string, 0, len(snapshot.Codes))
|
codes := sortMapStrInt(snapshot.Codes)
|
||||||
for k, v := range snapshot.Codes {
|
for _, v := range codes {
|
||||||
vs := strconv.FormatInt(v, 10)
|
if v[0] != "2xx" {
|
||||||
if k != "2xx" {
|
v[1] = colorize(v[1], FgMagentaColor)
|
||||||
vs = colorize(vs, FgMagentaColor)
|
|
||||||
}
|
}
|
||||||
codesBulks = append(codesBulks, []string{" " + k, vs})
|
summarybulk = append(summarybulk, []string{" " + v[0], v[1]})
|
||||||
}
|
}
|
||||||
sort.Slice(codesBulks, func(i, j int) bool { return codesBulks[i][0] < codesBulks[j][0] })
|
|
||||||
summarybulk = append(summarybulk, codesBulks...)
|
|
||||||
summarybulk = append(summarybulk,
|
summarybulk = append(summarybulk,
|
||||||
[]string{"RPS", fmt.Sprintf("%.3f", snapshot.RPS)},
|
[]string{"RPS", fmt.Sprintf("%.3f", snapshot.RPS)},
|
||||||
[]string{"Reads", fmt.Sprintf("%.3fMB/s", snapshot.ReadThroughput)},
|
[]string{"Reads", fmt.Sprintf("%.3fMB/s", snapshot.ReadThroughput)},
|
||||||
|
|
Loading…
Reference in New Issue