fix: inaccurate to report stats for total, feat: add average response time in total.

This commit is contained in:
徐聪
2022-01-19 19:55:06 +08:00
parent ac9565c0e8
commit 0ec6fe0dc3
3 changed files with 57 additions and 36 deletions

View File

@@ -81,17 +81,13 @@ func getAvgContentLength(numRequests int64, totalContentLength int64) (avgConten
return avgContentLength return avgContentLength
} }
func getCurrentRps(numRequests int64) (currentRps float64) { func getCurrentRps(numRequests int64, duration float64) (currentRps float64) {
currentRps = float64(numRequests) / float64(reportStatsInterval/time.Second) currentRps = float64(numRequests) / duration
return currentRps return currentRps
} }
func getCurrentFailPerSec(numFailures int64, numFailPerSecond map[int64]int64) (currentFailPerSec int64) { func getCurrentFailPerSec(numFailures int64, duration float64) (currentFailPerSec float64) {
currentFailPerSec = int64(0) currentFailPerSec = float64(numFailures) / duration
numFailPerSecondLength := int64(len(numFailPerSecond))
if numFailPerSecondLength != 0 {
currentFailPerSec = numFailures / numFailPerSecondLength
}
return currentFailPerSec return currentFailPerSec
} }
@@ -135,8 +131,8 @@ func (o *ConsoleOutput) OnEvent(data map[string]interface{}) {
} }
currentTime := time.Now() currentTime := time.Now()
println(fmt.Sprintf("Current time: %s, Users: %d, State: %s, Total RPS: %.1f, Total Fail Ratio: %.1f%%", println(fmt.Sprintf("Current time: %s, Users: %d, State: %s, Total RPS: %.1f, Total Average Response Time: %.1f, Total Fail Ratio: %.1f%%",
currentTime.Format("2006/01/02 15:04:05"), output.UserCount, state, output.TotalRPS, output.TotalFailRatio*100)) currentTime.Format("2006/01/02 15:04:05"), output.UserCount, state, output.TotalRPS, output.TotalAvgResponseTime, output.TotalFailRatio*100))
println(fmt.Sprintf("Accumulated Transactions: %d Passed, %d Failed", println(fmt.Sprintf("Accumulated Transactions: %d Passed, %d Failed",
output.TransactionsPassed, output.TransactionsFailed)) output.TransactionsPassed, output.TransactionsFailed))
table := tablewriter.NewWriter(os.Stdout) table := tablewriter.NewWriter(os.Stdout)
@@ -154,7 +150,7 @@ func (o *ConsoleOutput) OnEvent(data map[string]interface{}) {
row[7] = strconv.FormatInt(stat.MaxResponseTime, 10) row[7] = strconv.FormatInt(stat.MaxResponseTime, 10)
row[8] = strconv.FormatInt(stat.avgContentLength, 10) row[8] = strconv.FormatInt(stat.avgContentLength, 10)
row[9] = strconv.FormatFloat(stat.currentRps, 'f', 2, 64) row[9] = strconv.FormatFloat(stat.currentRps, 'f', 2, 64)
row[10] = strconv.FormatInt(stat.currentFailPerSec, 10) row[10] = strconv.FormatFloat(stat.currentFailPerSec, 'f', 2, 64)
table.Append(row) table.Append(row)
} }
table.Render() table.Render()
@@ -168,19 +164,20 @@ type statsEntryOutput struct {
avgResponseTime float64 // average response time, round float to 2 decimal places avgResponseTime float64 // average response time, round float to 2 decimal places
avgContentLength int64 // average content size avgContentLength int64 // average content size
currentRps float64 // # reqs/sec currentRps float64 // # reqs/sec
currentFailPerSec int64 // # fails/sec currentFailPerSec float64 // # fails/sec
} }
type dataOutput struct { type dataOutput struct {
UserCount int32 `json:"user_count"` UserCount int32 `json:"user_count"`
State int32 `json:"state"` State int32 `json:"state"`
TotalStats *statsEntryOutput `json:"stats_total"` TotalStats *statsEntryOutput `json:"stats_total"`
TransactionsPassed int64 `json:"transactions_passed"` TransactionsPassed int64 `json:"transactions_passed"`
TransactionsFailed int64 `json:"transactions_failed"` TransactionsFailed int64 `json:"transactions_failed"`
TotalRPS float64 `json:"total_rps"` TotalAvgResponseTime float64 `json:"total_avg_response_time"`
TotalFailRatio float64 `json:"total_fail_ratio"` TotalRPS float64 `json:"total_rps"`
Stats []*statsEntryOutput `json:"stats"` TotalFailRatio float64 `json:"total_fail_ratio"`
Errors map[string]map[string]interface{} `json:"errors"` Stats []*statsEntryOutput `json:"stats"`
Errors map[string]map[string]interface{} `json:"errors"`
} }
func convertData(data map[string]interface{}) (output *dataOutput, err error) { func convertData(data map[string]interface{}) (output *dataOutput, err error) {
@@ -217,15 +214,16 @@ func convertData(data map[string]interface{}) (output *dataOutput, err error) {
} }
output = &dataOutput{ output = &dataOutput{
UserCount: userCount, UserCount: userCount,
State: state, State: state,
TotalStats: entryTotalOutput, TotalStats: entryTotalOutput,
TransactionsPassed: transactionsPassed, TransactionsPassed: transactionsPassed,
TransactionsFailed: transactionsFailed, TransactionsFailed: transactionsFailed,
TotalRPS: getCurrentRps(entryTotalOutput.NumRequests), TotalAvgResponseTime: entryTotalOutput.avgResponseTime,
TotalFailRatio: getTotalFailRatio(entryTotalOutput.NumRequests, entryTotalOutput.NumFailures), TotalRPS: entryTotalOutput.currentRps,
Stats: make([]*statsEntryOutput, 0, len(stats)), TotalFailRatio: getTotalFailRatio(entryTotalOutput.NumRequests, entryTotalOutput.NumFailures),
Errors: errors, Stats: make([]*statsEntryOutput, 0, len(stats)),
Errors: errors,
} }
// convert stats // convert stats
@@ -253,14 +251,21 @@ func deserializeStatsEntry(stat interface{}) (entryOutput *statsEntryOutput, err
return nil, err return nil, err
} }
var duration float64
if entry.Name == "Total" {
duration = float64(entry.LastRequestTimestamp - entry.StartTime)
} else {
duration = float64(reportStatsInterval / time.Second)
}
numRequests := entry.NumRequests numRequests := entry.NumRequests
entryOutput = &statsEntryOutput{ entryOutput = &statsEntryOutput{
statsEntry: entry, statsEntry: entry,
medianResponseTime: getMedianResponseTime(numRequests, entry.ResponseTimes), medianResponseTime: getMedianResponseTime(numRequests, entry.ResponseTimes),
avgResponseTime: getAvgResponseTime(numRequests, entry.TotalResponseTime), avgResponseTime: getAvgResponseTime(numRequests, entry.TotalResponseTime),
avgContentLength: getAvgContentLength(numRequests, entry.TotalContentLength), avgContentLength: getAvgContentLength(numRequests, entry.TotalContentLength),
currentRps: getCurrentRps(numRequests), currentRps: getCurrentRps(numRequests, duration),
currentFailPerSec: getCurrentFailPerSec(entry.NumFailures, entry.NumFailPerSec), currentFailPerSec: getCurrentFailPerSec(entry.NumFailures, duration),
} }
return return
} }
@@ -364,6 +369,12 @@ var (
Help: "The current runner state, 1=initializing, 2=spawning, 3=running, 4=quitting, 5=stopped", Help: "The current runner state, 1=initializing, 2=spawning, 3=running, 4=quitting, 5=stopped",
}, },
) )
gaugeTotalAverageResponseTime = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "total_average_response_time",
Help: "The average response time in total",
},
)
gaugeTotalRPS = prometheus.NewGauge( gaugeTotalRPS = prometheus.NewGauge(
prometheus.GaugeOpts{ prometheus.GaugeOpts{
Name: "total_rps", Name: "total_rps",
@@ -431,6 +442,7 @@ func (o *PrometheusPusherOutput) OnStart() {
// gauges for total // gauges for total
gaugeUsers, gaugeUsers,
gaugeState, gaugeState,
gaugeTotalAverageResponseTime,
gaugeTotalRPS, gaugeTotalRPS,
gaugeTotalFailRatio, gaugeTotalFailRatio,
gaugeTransactionsPassed, gaugeTransactionsPassed,
@@ -458,6 +470,9 @@ func (o *PrometheusPusherOutput) OnEvent(data map[string]interface{}) {
// runner state // runner state
gaugeState.Set(float64(output.State)) gaugeState.Set(float64(output.State))
// avg response time in total
gaugeTotalAverageResponseTime.Set(output.TotalAvgResponseTime)
// rps in total // rps in total
gaugeTotalRPS.Set(output.TotalRPS) gaugeTotalRPS.Set(output.TotalRPS)
@@ -479,7 +494,7 @@ func (o *PrometheusPusherOutput) OnEvent(data map[string]interface{}) {
gaugeMaxResponseTime.WithLabelValues(method, name).Set(float64(stat.MaxResponseTime)) gaugeMaxResponseTime.WithLabelValues(method, name).Set(float64(stat.MaxResponseTime))
gaugeAverageContentLength.WithLabelValues(method, name).Set(float64(stat.avgContentLength)) gaugeAverageContentLength.WithLabelValues(method, name).Set(float64(stat.avgContentLength))
gaugeCurrentRPS.WithLabelValues(method, name).Set(stat.currentRps) gaugeCurrentRPS.WithLabelValues(method, name).Set(stat.currentRps)
gaugeCurrentFailPerSec.WithLabelValues(method, name).Set(float64(stat.currentFailPerSec)) gaugeCurrentFailPerSec.WithLabelValues(method, name).Set(stat.currentFailPerSec)
for responseTime, count := range stat.ResponseTimes { for responseTime, count := range stat.ResponseTimes {
var i int64 var i int64
for i = 0; i < count; i++ { for i = 0; i < count; i++ {

View File

@@ -58,14 +58,15 @@ func TestGetAvgContentLength(t *testing.T) {
} }
func TestGetCurrentRps(t *testing.T) { func TestGetCurrentRps(t *testing.T) {
duration := float64(3)
numRequests := int64(6) numRequests := int64(6)
currentRps := getCurrentRps(numRequests) currentRps := getCurrentRps(numRequests, duration)
if currentRps != 2 { if currentRps != 2 {
t.Error("currentRps should be 2") t.Error("currentRps should be 2")
} }
numRequests = int64(8) numRequests = int64(8)
currentRps = getCurrentRps(numRequests) currentRps = getCurrentRps(numRequests, duration)
if fmt.Sprintf("%.2f", currentRps) != "2.67" { if fmt.Sprintf("%.2f", currentRps) != "2.67" {
t.Error("currentRps should be 2.67") t.Error("currentRps should be 2.67")
} }

View File

@@ -148,7 +148,7 @@ func (s *requestStats) collectReportData() map[string]interface{} {
"failed": s.transactionFailed, "failed": s.transactionFailed,
} }
data["stats"] = s.serializeStats() data["stats"] = s.serializeStats()
data["stats_total"] = s.total.getStrippedReport() data["stats_total"] = s.total.getReport()
data["errors"] = s.serializeErrors() data["errors"] = s.serializeErrors()
s.errors = make(map[string]*statsError) s.errors = make(map[string]*statsError)
return data return data
@@ -294,6 +294,11 @@ func (s *statsEntry) getStrippedReport() map[string]interface{} {
return report return report
} }
func (s *statsEntry) getReport() map[string]interface{} {
report := s.serialize()
return report
}
type statsError struct { type statsError struct {
name string name string
method string method string