package load import ( "fmt" "math" "sort" "strings" ) // Percentiles 是毫秒延迟分位。 type Percentiles struct { P50 float64 P95 float64 P99 float64 N int } func calcPercentiles(ms []float64) Percentiles { out := Percentiles{N: len(ms)} if len(ms) == 0 { return out } cp := append([]float64(nil), ms...) sort.Float64s(cp) out.P50 = percentileAt(cp, 0.50) out.P95 = percentileAt(cp, 0.95) out.P99 = percentileAt(cp, 0.99) return out } func percentileAt(sorted []float64, p float64) float64 { n := len(sorted) if n == 1 { return sorted[0] } idx := int(math.Ceil(p*float64(n))) - 1 if idx < 0 { idx = 0 } if idx >= n { idx = n - 1 } return sorted[idx] } // Report 是一次压测输出:提交/送达分位、错误码分布、掉线数。 type Report struct { Connections int Alive int Disconnects int Sends int ActualRate float64 SubmitMs Percentiles DeliverMs Percentiles Errors map[string]int GroupMembers int GroupQueueMs float64 } func (r Report) Format() string { var b strings.Builder fmt.Fprintf(&b, "connections: %d alive: %d disconnects: %d\n", r.Connections, r.Alive, r.Disconnects) fmt.Fprintf(&b, "sends: %d", r.Sends) if r.ActualRate > 0 { fmt.Fprintf(&b, " actual_rate: %.2f/s", r.ActualRate) } b.WriteByte('\n') fmt.Fprintf(&b, "submit_ms: n=%d p50=%.1f p95=%.1f p99=%.1f\n", r.SubmitMs.N, r.SubmitMs.P50, r.SubmitMs.P95, r.SubmitMs.P99) fmt.Fprintf(&b, "deliver_ms: n=%d p50=%.1f p95=%.1f p99=%.1f\n", r.DeliverMs.N, r.DeliverMs.P50, r.DeliverMs.P95, r.DeliverMs.P99) if r.GroupMembers > 0 { fmt.Fprintf(&b, "group: members=%d queue_ms=%.1f\n", r.GroupMembers, r.GroupQueueMs) } if len(r.Errors) == 0 { b.WriteString("errors: (none)\n") } else { b.WriteString("errors:") keys := make([]string, 0, len(r.Errors)) for k := range r.Errors { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { fmt.Fprintf(&b, " %s=%d", k, r.Errors[k]) } b.WriteByte('\n') } return b.String() } func addError(m map[string]int, code string) { if code == "" { code = "error" } m[code]++ }