Skip to content

Commit

Permalink
Prometheus output format (#308)
Browse files Browse the repository at this point in the history
* initiap wip on prometheus output format

* wip on prometheus format output

* fixing printer tests

* wip on prometheus tests

* prometheus output docs

* fix prometheus format tests

* update options CLI documents
  • Loading branch information
bojand authored Sep 4, 2021
1 parent 9e72b48 commit 496891f
Show file tree
Hide file tree
Showing 13 changed files with 831 additions and 218 deletions.
2 changes: 1 addition & 1 deletion cmd/ghz/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ var (

isFormatSet = false
format = kingpin.Flag("format", "Output format. One of: summary, csv, json, pretty, html, influx-summary, influx-details. Default is summary.").
Short('O').Default("summary").PlaceHolder(" ").IsSetByUser(&isFormatSet).Enum("summary", "csv", "json", "pretty", "html", "influx-summary", "influx-details")
Short('O').Default("summary").PlaceHolder(" ").IsSetByUser(&isFormatSet).Enum("summary", "csv", "json", "pretty", "html", "influx-summary", "influx-details", "prometheus")

isSkipFirstSet = false
skipFirst = kingpin.Flag("skipFirst", "Skip the first X requests when doing the results tally.").
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ require (
github.com/leodido/go-urn v1.2.0 // indirect
github.com/mfridman/tparse v0.8.3
github.com/pkg/errors v0.9.1
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4
github.com/prometheus/common v0.4.0
github.com/rakyll/statik v0.1.6
github.com/stretchr/testify v1.6.1
go.uber.org/multierr v1.3.0
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOq
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mfridman/tparse v0.8.3 h1:DnjEnBXdlUJPo8ShfNPasu7m52iI1ETiST5RvS6b0c4=
github.com/mfridman/tparse v0.8.3/go.mod h1:LzZWLkqcQrOfgvqZn7LOSBzgZwWnqI5NQsfgQVOT1o8=
Expand Down Expand Up @@ -378,9 +379,11 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
Expand Down
203 changes: 203 additions & 0 deletions printer/influx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package printer

import (
"encoding/json"
"fmt"
"strings"
)

func (rp *ReportPrinter) printInfluxLine() error {
measurement := "ghz_run"
tags := rp.getInfluxTags(true)
fields := rp.getInfluxFields()
timestamp := rp.Report.Date.UnixNano()
if timestamp < 0 {
timestamp = 0
}

if _, err := fmt.Fprintf(rp.Out, "%v,%v %v %v", measurement, tags, fields, timestamp); err != nil {
return err
}

return nil
}

func (rp *ReportPrinter) printInfluxDetails() error {
measurement := "ghz_detail"
commonTags := rp.getInfluxTags(false)

for _, v := range rp.Report.Details {
values := make([]string, 3)
values[0] = fmt.Sprintf("latency=%v", v.Latency.Nanoseconds())
values[1] = fmt.Sprintf(`error="%v"`, cleanInfluxString(v.Error))
values[2] = fmt.Sprintf(`status="%v"`, v.Status)

tags := commonTags

if v.Error != "" {
tags = tags + ",hasError=true"
} else {
tags = tags + ",hasError=false"
}

timestamp := v.Timestamp.UnixNano()

fields := strings.Join(values, ",")

if _, err := fmt.Fprintf(rp.Out, "%v,%v %v %v\n", measurement, tags, fields, timestamp); err != nil {
return err
}
}
return nil
}

func (rp *ReportPrinter) getInfluxTags(addErrors bool) string {
s := make([]string, 0, 10)

if rp.Report.Name != "" {
s = append(s, fmt.Sprintf(`name="%v"`, cleanInfluxString(strings.TrimSpace(rp.Report.Name))))
}

options := rp.Report.Options

if options.Proto != "" {
s = append(s, fmt.Sprintf(`proto="%v"`, options.Proto))
} else if options.Protoset != "" {
s = append(s, fmt.Sprintf(`Protoset="%v"`, options.Protoset))
}

s = append(s, fmt.Sprintf(`call="%v"`, options.Call))
s = append(s, fmt.Sprintf(`host="%v"`, options.Host))
s = append(s, fmt.Sprintf("n=%v", options.Total))

if options.CSchedule == "const" {
s = append(s, fmt.Sprintf("c=%v", options.Concurrency))
} else {
s = append(s, fmt.Sprintf("concurrency-schedule=%v", options.CSchedule))
s = append(s, fmt.Sprintf("concurrency-start=%v", options.CStart))
s = append(s, fmt.Sprintf("concurrency-end=%v", options.CEnd))
s = append(s, fmt.Sprintf("concurrency-step=%v", options.CStep))
s = append(s, fmt.Sprintf("concurrency-step-duration=%v", options.CStepDuration))
s = append(s, fmt.Sprintf("concurrency-max-duration=%v", options.CMaxDuration))
}

if options.LoadSchedule == "const" {
s = append(s, fmt.Sprintf("rps=%v", options.RPS))
} else {
s = append(s, fmt.Sprintf("load-schedule=%v", options.LoadSchedule))
s = append(s, fmt.Sprintf("load-start=%v", options.LoadStart))
s = append(s, fmt.Sprintf("load-end=%v", options.LoadEnd))
s = append(s, fmt.Sprintf("load-step=%v", options.LoadStep))
s = append(s, fmt.Sprintf("load-step-duration=%v", options.LoadStepDuration))
s = append(s, fmt.Sprintf("load-max-duration=%v", options.LoadMaxDuration))
}

s = append(s, fmt.Sprintf("z=%v", options.Duration.Nanoseconds()))
s = append(s, fmt.Sprintf("timeout=%v", options.Timeout.Seconds()))
s = append(s, fmt.Sprintf("dial_timeout=%v", options.DialTimeout.Seconds()))
s = append(s, fmt.Sprintf("keepalive=%v", options.KeepaliveTime.Seconds()))

dataStr := `""`
dataBytes, err := json.Marshal(options.Data)
if err == nil && len(dataBytes) > 0 {
dataBytes, err = json.Marshal(string(dataBytes))
if err == nil {
dataStr = string(dataBytes)
}
}

dataStr = cleanInfluxString(dataStr)

s = append(s, fmt.Sprintf("data=%s", dataStr))

mdStr := `""`
if options.Metadata != nil {
mdBytes, err := json.Marshal(options.Metadata)
if err == nil {
mdBytes, err = json.Marshal(string(mdBytes))
if err == nil {
mdStr = string(mdBytes)
}
}

mdStr = cleanInfluxString(mdStr)
}

s = append(s, fmt.Sprintf("metadata=%s", mdStr))

callTagsStr := `""`
if len(rp.Report.Tags) > 0 {
callTagsBytes, err := json.Marshal(rp.Report.Tags)
if err == nil {
callTagsBytes, err = json.Marshal(string(callTagsBytes))
if err == nil {
callTagsStr = string(callTagsBytes)
}
}

callTagsStr = cleanInfluxString(callTagsStr)
}

s = append(s, fmt.Sprintf("tags=%s", callTagsStr))

if addErrors {
errCount := 0
if len(rp.Report.ErrorDist) > 0 {
for _, v := range rp.Report.ErrorDist {
errCount += v
}
}

s = append(s, fmt.Sprintf("errors=%v", errCount))

hasErrors := false
if errCount > 0 {
hasErrors = true
}

s = append(s, fmt.Sprintf("has_errors=%v", hasErrors))
}

return strings.Join(s, ",")
}

func (rp *ReportPrinter) getInfluxFields() string {
s := make([]string, 0, 5)

s = append(s, fmt.Sprintf("count=%v", rp.Report.Count))
s = append(s, fmt.Sprintf("total=%v", rp.Report.Total.Nanoseconds()))
s = append(s, fmt.Sprintf("average=%v", rp.Report.Average.Nanoseconds()))
s = append(s, fmt.Sprintf("fastest=%v", rp.Report.Fastest.Nanoseconds()))
s = append(s, fmt.Sprintf("slowest=%v", rp.Report.Slowest.Nanoseconds()))
s = append(s, fmt.Sprintf("rps=%4.2f", rp.Report.Rps))

if len(rp.Report.LatencyDistribution) > 0 {
for _, v := range rp.Report.LatencyDistribution {
if v.Percentage == 50 {
s = append(s, fmt.Sprintf("median=%v", v.Latency.Nanoseconds()))
}

if v.Percentage == 95 {
s = append(s, fmt.Sprintf("p95=%v", v.Latency.Nanoseconds()))
}
}
}

errCount := 0
if len(rp.Report.ErrorDist) > 0 {
for _, v := range rp.Report.ErrorDist {
errCount += v
}
}

s = append(s, fmt.Sprintf("errors=%v", errCount))

return strings.Join(s, ",")
}

func cleanInfluxString(input string) string {
input = strings.Replace(input, " ", "\\ ", -1)
input = strings.Replace(input, ",", "\\,", -1)
input = strings.Replace(input, "=", "\\=", -1)
return input
}
9 changes: 6 additions & 3 deletions printer/printer_test.go → printer/influx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/stretchr/testify/assert"
)

func TestPrinter_getInfluxLine(t *testing.T) {
func TestPrinter_printInfluxLine(t *testing.T) {
date := time.Now()
unixTimeNow := date.UnixNano()

Expand Down Expand Up @@ -119,8 +119,11 @@ func TestPrinter_getInfluxLine(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := ReportPrinter{Report: &tt.report}
actual := p.getInfluxLine()
buf := bytes.NewBufferString("")
p := ReportPrinter{Report: &tt.report, Out: buf}
err := p.printInfluxLine()
assert.NoError(t, err)
actual := buf.String()
assert.Equal(t, tt.expected, actual)
})
}
Expand Down
Loading

0 comments on commit 496891f

Please sign in to comment.