Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
354 lines
11 KiB
Go
354 lines
11 KiB
Go
package metrics
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"sort"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
|
)
|
|
|
|
// Collector handles metrics collection for the load test
|
|
type Collector struct {
|
|
// Atomic counters for thread-safe operations
|
|
messagesProduced int64
|
|
messagesConsumed int64
|
|
bytesProduced int64
|
|
bytesConsumed int64
|
|
producerErrors int64
|
|
consumerErrors int64
|
|
|
|
// Latency tracking
|
|
latencies []time.Duration
|
|
latencyMutex sync.RWMutex
|
|
|
|
// Consumer lag tracking
|
|
consumerLag map[string]int64
|
|
consumerLagMutex sync.RWMutex
|
|
|
|
// Test timing
|
|
startTime time.Time
|
|
|
|
// Prometheus metrics
|
|
prometheusMetrics *PrometheusMetrics
|
|
}
|
|
|
|
// PrometheusMetrics holds all Prometheus metric definitions
|
|
type PrometheusMetrics struct {
|
|
MessagesProducedTotal prometheus.Counter
|
|
MessagesConsumedTotal prometheus.Counter
|
|
BytesProducedTotal prometheus.Counter
|
|
BytesConsumedTotal prometheus.Counter
|
|
ProducerErrorsTotal prometheus.Counter
|
|
ConsumerErrorsTotal prometheus.Counter
|
|
|
|
MessageLatencyHistogram prometheus.Histogram
|
|
ProducerThroughput prometheus.Gauge
|
|
ConsumerThroughput prometheus.Gauge
|
|
ConsumerLagGauge *prometheus.GaugeVec
|
|
|
|
ActiveProducers prometheus.Gauge
|
|
ActiveConsumers prometheus.Gauge
|
|
}
|
|
|
|
// NewCollector creates a new metrics collector
|
|
func NewCollector() *Collector {
|
|
return &Collector{
|
|
startTime: time.Now(),
|
|
consumerLag: make(map[string]int64),
|
|
prometheusMetrics: &PrometheusMetrics{
|
|
MessagesProducedTotal: promauto.NewCounter(prometheus.CounterOpts{
|
|
Name: "kafka_loadtest_messages_produced_total",
|
|
Help: "Total number of messages produced",
|
|
}),
|
|
MessagesConsumedTotal: promauto.NewCounter(prometheus.CounterOpts{
|
|
Name: "kafka_loadtest_messages_consumed_total",
|
|
Help: "Total number of messages consumed",
|
|
}),
|
|
BytesProducedTotal: promauto.NewCounter(prometheus.CounterOpts{
|
|
Name: "kafka_loadtest_bytes_produced_total",
|
|
Help: "Total bytes produced",
|
|
}),
|
|
BytesConsumedTotal: promauto.NewCounter(prometheus.CounterOpts{
|
|
Name: "kafka_loadtest_bytes_consumed_total",
|
|
Help: "Total bytes consumed",
|
|
}),
|
|
ProducerErrorsTotal: promauto.NewCounter(prometheus.CounterOpts{
|
|
Name: "kafka_loadtest_producer_errors_total",
|
|
Help: "Total number of producer errors",
|
|
}),
|
|
ConsumerErrorsTotal: promauto.NewCounter(prometheus.CounterOpts{
|
|
Name: "kafka_loadtest_consumer_errors_total",
|
|
Help: "Total number of consumer errors",
|
|
}),
|
|
MessageLatencyHistogram: promauto.NewHistogram(prometheus.HistogramOpts{
|
|
Name: "kafka_loadtest_message_latency_seconds",
|
|
Help: "Message end-to-end latency in seconds",
|
|
Buckets: prometheus.ExponentialBuckets(0.001, 2, 15), // 1ms to ~32s
|
|
}),
|
|
ProducerThroughput: promauto.NewGauge(prometheus.GaugeOpts{
|
|
Name: "kafka_loadtest_producer_throughput_msgs_per_sec",
|
|
Help: "Current producer throughput in messages per second",
|
|
}),
|
|
ConsumerThroughput: promauto.NewGauge(prometheus.GaugeOpts{
|
|
Name: "kafka_loadtest_consumer_throughput_msgs_per_sec",
|
|
Help: "Current consumer throughput in messages per second",
|
|
}),
|
|
ConsumerLagGauge: promauto.NewGaugeVec(prometheus.GaugeOpts{
|
|
Name: "kafka_loadtest_consumer_lag_messages",
|
|
Help: "Consumer lag in messages",
|
|
}, []string{"consumer_group", "topic", "partition"}),
|
|
ActiveProducers: promauto.NewGauge(prometheus.GaugeOpts{
|
|
Name: "kafka_loadtest_active_producers",
|
|
Help: "Number of active producers",
|
|
}),
|
|
ActiveConsumers: promauto.NewGauge(prometheus.GaugeOpts{
|
|
Name: "kafka_loadtest_active_consumers",
|
|
Help: "Number of active consumers",
|
|
}),
|
|
},
|
|
}
|
|
}
|
|
|
|
// RecordProducedMessage records a successfully produced message
|
|
func (c *Collector) RecordProducedMessage(size int, latency time.Duration) {
|
|
atomic.AddInt64(&c.messagesProduced, 1)
|
|
atomic.AddInt64(&c.bytesProduced, int64(size))
|
|
|
|
c.prometheusMetrics.MessagesProducedTotal.Inc()
|
|
c.prometheusMetrics.BytesProducedTotal.Add(float64(size))
|
|
c.prometheusMetrics.MessageLatencyHistogram.Observe(latency.Seconds())
|
|
|
|
// Store latency for percentile calculations
|
|
c.latencyMutex.Lock()
|
|
c.latencies = append(c.latencies, latency)
|
|
// Keep only recent latencies to avoid memory bloat
|
|
if len(c.latencies) > 100000 {
|
|
c.latencies = c.latencies[50000:]
|
|
}
|
|
c.latencyMutex.Unlock()
|
|
}
|
|
|
|
// RecordConsumedMessage records a successfully consumed message
|
|
func (c *Collector) RecordConsumedMessage(size int) {
|
|
atomic.AddInt64(&c.messagesConsumed, 1)
|
|
atomic.AddInt64(&c.bytesConsumed, int64(size))
|
|
|
|
c.prometheusMetrics.MessagesConsumedTotal.Inc()
|
|
c.prometheusMetrics.BytesConsumedTotal.Add(float64(size))
|
|
}
|
|
|
|
// RecordProducerError records a producer error
|
|
func (c *Collector) RecordProducerError() {
|
|
atomic.AddInt64(&c.producerErrors, 1)
|
|
c.prometheusMetrics.ProducerErrorsTotal.Inc()
|
|
}
|
|
|
|
// RecordConsumerError records a consumer error
|
|
func (c *Collector) RecordConsumerError() {
|
|
atomic.AddInt64(&c.consumerErrors, 1)
|
|
c.prometheusMetrics.ConsumerErrorsTotal.Inc()
|
|
}
|
|
|
|
// UpdateConsumerLag updates consumer lag metrics
|
|
func (c *Collector) UpdateConsumerLag(consumerGroup, topic string, partition int32, lag int64) {
|
|
key := fmt.Sprintf("%s-%s-%d", consumerGroup, topic, partition)
|
|
|
|
c.consumerLagMutex.Lock()
|
|
c.consumerLag[key] = lag
|
|
c.consumerLagMutex.Unlock()
|
|
|
|
c.prometheusMetrics.ConsumerLagGauge.WithLabelValues(
|
|
consumerGroup, topic, fmt.Sprintf("%d", partition),
|
|
).Set(float64(lag))
|
|
}
|
|
|
|
// UpdateThroughput updates throughput gauges
|
|
func (c *Collector) UpdateThroughput(producerRate, consumerRate float64) {
|
|
c.prometheusMetrics.ProducerThroughput.Set(producerRate)
|
|
c.prometheusMetrics.ConsumerThroughput.Set(consumerRate)
|
|
}
|
|
|
|
// UpdateActiveClients updates active client counts
|
|
func (c *Collector) UpdateActiveClients(producers, consumers int) {
|
|
c.prometheusMetrics.ActiveProducers.Set(float64(producers))
|
|
c.prometheusMetrics.ActiveConsumers.Set(float64(consumers))
|
|
}
|
|
|
|
// GetStats returns current statistics
|
|
func (c *Collector) GetStats() Stats {
|
|
produced := atomic.LoadInt64(&c.messagesProduced)
|
|
consumed := atomic.LoadInt64(&c.messagesConsumed)
|
|
bytesProduced := atomic.LoadInt64(&c.bytesProduced)
|
|
bytesConsumed := atomic.LoadInt64(&c.bytesConsumed)
|
|
producerErrors := atomic.LoadInt64(&c.producerErrors)
|
|
consumerErrors := atomic.LoadInt64(&c.consumerErrors)
|
|
|
|
duration := time.Since(c.startTime)
|
|
|
|
// Calculate throughput
|
|
producerThroughput := float64(produced) / duration.Seconds()
|
|
consumerThroughput := float64(consumed) / duration.Seconds()
|
|
|
|
// Calculate latency percentiles
|
|
var latencyPercentiles map[float64]time.Duration
|
|
c.latencyMutex.RLock()
|
|
if len(c.latencies) > 0 {
|
|
latencyPercentiles = c.calculatePercentiles(c.latencies)
|
|
}
|
|
c.latencyMutex.RUnlock()
|
|
|
|
// Get consumer lag summary
|
|
c.consumerLagMutex.RLock()
|
|
totalLag := int64(0)
|
|
maxLag := int64(0)
|
|
for _, lag := range c.consumerLag {
|
|
totalLag += lag
|
|
if lag > maxLag {
|
|
maxLag = lag
|
|
}
|
|
}
|
|
avgLag := float64(0)
|
|
if len(c.consumerLag) > 0 {
|
|
avgLag = float64(totalLag) / float64(len(c.consumerLag))
|
|
}
|
|
c.consumerLagMutex.RUnlock()
|
|
|
|
return Stats{
|
|
Duration: duration,
|
|
MessagesProduced: produced,
|
|
MessagesConsumed: consumed,
|
|
BytesProduced: bytesProduced,
|
|
BytesConsumed: bytesConsumed,
|
|
ProducerErrors: producerErrors,
|
|
ConsumerErrors: consumerErrors,
|
|
ProducerThroughput: producerThroughput,
|
|
ConsumerThroughput: consumerThroughput,
|
|
LatencyPercentiles: latencyPercentiles,
|
|
TotalConsumerLag: totalLag,
|
|
MaxConsumerLag: maxLag,
|
|
AvgConsumerLag: avgLag,
|
|
}
|
|
}
|
|
|
|
// PrintSummary prints a summary of the test statistics
|
|
func (c *Collector) PrintSummary() {
|
|
stats := c.GetStats()
|
|
|
|
fmt.Printf("\n=== Load Test Summary ===\n")
|
|
fmt.Printf("Test Duration: %v\n", stats.Duration)
|
|
fmt.Printf("\nMessages:\n")
|
|
fmt.Printf(" Produced: %d (%.2f MB)\n", stats.MessagesProduced, float64(stats.BytesProduced)/1024/1024)
|
|
fmt.Printf(" Consumed: %d (%.2f MB)\n", stats.MessagesConsumed, float64(stats.BytesConsumed)/1024/1024)
|
|
fmt.Printf(" Producer Errors: %d\n", stats.ProducerErrors)
|
|
fmt.Printf(" Consumer Errors: %d\n", stats.ConsumerErrors)
|
|
|
|
fmt.Printf("\nThroughput:\n")
|
|
fmt.Printf(" Producer: %.2f msgs/sec\n", stats.ProducerThroughput)
|
|
fmt.Printf(" Consumer: %.2f msgs/sec\n", stats.ConsumerThroughput)
|
|
|
|
if stats.LatencyPercentiles != nil {
|
|
fmt.Printf("\nLatency Percentiles:\n")
|
|
percentiles := []float64{50, 90, 95, 99, 99.9}
|
|
for _, p := range percentiles {
|
|
if latency, exists := stats.LatencyPercentiles[p]; exists {
|
|
fmt.Printf(" p%.1f: %v\n", p, latency)
|
|
}
|
|
}
|
|
}
|
|
|
|
fmt.Printf("\nConsumer Lag:\n")
|
|
fmt.Printf(" Total: %d messages\n", stats.TotalConsumerLag)
|
|
fmt.Printf(" Max: %d messages\n", stats.MaxConsumerLag)
|
|
fmt.Printf(" Average: %.2f messages\n", stats.AvgConsumerLag)
|
|
fmt.Printf("=========================\n")
|
|
}
|
|
|
|
// WriteStats writes statistics to a writer (for HTTP endpoint)
|
|
func (c *Collector) WriteStats(w io.Writer) {
|
|
stats := c.GetStats()
|
|
|
|
fmt.Fprintf(w, "# Load Test Statistics\n")
|
|
fmt.Fprintf(w, "duration_seconds %v\n", stats.Duration.Seconds())
|
|
fmt.Fprintf(w, "messages_produced %d\n", stats.MessagesProduced)
|
|
fmt.Fprintf(w, "messages_consumed %d\n", stats.MessagesConsumed)
|
|
fmt.Fprintf(w, "bytes_produced %d\n", stats.BytesProduced)
|
|
fmt.Fprintf(w, "bytes_consumed %d\n", stats.BytesConsumed)
|
|
fmt.Fprintf(w, "producer_errors %d\n", stats.ProducerErrors)
|
|
fmt.Fprintf(w, "consumer_errors %d\n", stats.ConsumerErrors)
|
|
fmt.Fprintf(w, "producer_throughput_msgs_per_sec %f\n", stats.ProducerThroughput)
|
|
fmt.Fprintf(w, "consumer_throughput_msgs_per_sec %f\n", stats.ConsumerThroughput)
|
|
fmt.Fprintf(w, "total_consumer_lag %d\n", stats.TotalConsumerLag)
|
|
fmt.Fprintf(w, "max_consumer_lag %d\n", stats.MaxConsumerLag)
|
|
fmt.Fprintf(w, "avg_consumer_lag %f\n", stats.AvgConsumerLag)
|
|
|
|
if stats.LatencyPercentiles != nil {
|
|
for percentile, latency := range stats.LatencyPercentiles {
|
|
fmt.Fprintf(w, "latency_p%g_seconds %f\n", percentile, latency.Seconds())
|
|
}
|
|
}
|
|
}
|
|
|
|
// calculatePercentiles calculates latency percentiles
|
|
func (c *Collector) calculatePercentiles(latencies []time.Duration) map[float64]time.Duration {
|
|
if len(latencies) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Make a copy and sort
|
|
sorted := make([]time.Duration, len(latencies))
|
|
copy(sorted, latencies)
|
|
sort.Slice(sorted, func(i, j int) bool {
|
|
return sorted[i] < sorted[j]
|
|
})
|
|
|
|
percentiles := map[float64]time.Duration{
|
|
50: calculatePercentile(sorted, 50),
|
|
90: calculatePercentile(sorted, 90),
|
|
95: calculatePercentile(sorted, 95),
|
|
99: calculatePercentile(sorted, 99),
|
|
99.9: calculatePercentile(sorted, 99.9),
|
|
}
|
|
|
|
return percentiles
|
|
}
|
|
|
|
// calculatePercentile calculates a specific percentile from sorted data
|
|
func calculatePercentile(sorted []time.Duration, percentile float64) time.Duration {
|
|
if len(sorted) == 0 {
|
|
return 0
|
|
}
|
|
|
|
index := percentile / 100.0 * float64(len(sorted)-1)
|
|
if index == float64(int(index)) {
|
|
return sorted[int(index)]
|
|
}
|
|
|
|
lower := sorted[int(index)]
|
|
upper := sorted[int(index)+1]
|
|
weight := index - float64(int(index))
|
|
|
|
return time.Duration(float64(lower) + weight*float64(upper-lower))
|
|
}
|
|
|
|
// Stats represents the current test statistics
|
|
type Stats struct {
|
|
Duration time.Duration
|
|
MessagesProduced int64
|
|
MessagesConsumed int64
|
|
BytesProduced int64
|
|
BytesConsumed int64
|
|
ProducerErrors int64
|
|
ConsumerErrors int64
|
|
ProducerThroughput float64
|
|
ConsumerThroughput float64
|
|
LatencyPercentiles map[float64]time.Duration
|
|
TotalConsumerLag int64
|
|
MaxConsumerLag int64
|
|
AvgConsumerLag float64
|
|
}
|