mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-05 07:49:37 +08:00
fix: inaccurate to report stats for total, feat: add average response time in total.
This commit is contained in:
@@ -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++ {
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user