128 lines
4.5 KiB
Go
128 lines
4.5 KiB
Go
package observability
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
// ExportPrometheus exports metrics in Prometheus text format.
|
||
|
|
func (m *Metrics) ExportPrometheus() string {
|
||
|
|
m.mu.RLock()
|
||
|
|
defer m.mu.RUnlock()
|
||
|
|
|
||
|
|
var sb strings.Builder
|
||
|
|
|
||
|
|
// Help and type for request_total counter
|
||
|
|
sb.WriteString("# HELP gateway_requests_total Total number of HTTP requests\n")
|
||
|
|
sb.WriteString("# TYPE gateway_requests_total counter\n")
|
||
|
|
for key, count := range m.requestTotal {
|
||
|
|
parts := strings.Split(key, ":")
|
||
|
|
if len(parts) == 3 {
|
||
|
|
route, upstream, status := parts[0], parts[1], parts[2]
|
||
|
|
sb.WriteString(fmt.Sprintf("gateway_requests_total{route=\"%s\",upstream=\"%s\",status=\"%s\"} %d\n",
|
||
|
|
route, upstream, status, count))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
sb.WriteString("\n")
|
||
|
|
|
||
|
|
// Help and type for request_duration_seconds histogram
|
||
|
|
sb.WriteString("# HELP gateway_request_duration_seconds Request latency in seconds\n")
|
||
|
|
sb.WriteString("# TYPE gateway_request_duration_seconds histogram\n")
|
||
|
|
buckets := []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10}
|
||
|
|
for key := range m.requestDurationBuckets {
|
||
|
|
parts := strings.Split(key, ":")
|
||
|
|
if len(parts) == 2 {
|
||
|
|
route, upstream := parts[0], parts[1]
|
||
|
|
|
||
|
|
// Write buckets
|
||
|
|
cumulativeCount := int64(0)
|
||
|
|
for _, bucket := range buckets {
|
||
|
|
if count, ok := m.requestDurationBuckets[key][bucket]; ok {
|
||
|
|
cumulativeCount += count
|
||
|
|
}
|
||
|
|
sb.WriteString(fmt.Sprintf("gateway_request_duration_seconds_bucket{route=\"%s\",upstream=\"%s\",le=\"%g\"} %d\n",
|
||
|
|
route, upstream, bucket, cumulativeCount))
|
||
|
|
}
|
||
|
|
|
||
|
|
// Write +Inf bucket
|
||
|
|
totalCount := int64(0)
|
||
|
|
for _, count := range m.requestDurationBuckets[key] {
|
||
|
|
totalCount += count
|
||
|
|
}
|
||
|
|
sb.WriteString(fmt.Sprintf("gateway_request_duration_seconds_bucket{route=\"%s\",upstream=\"%s\",le=\"+Inf\"} %d\n",
|
||
|
|
route, upstream, totalCount))
|
||
|
|
|
||
|
|
// Write sum
|
||
|
|
totalDuration := m.requestDuration[key]
|
||
|
|
sb.WriteString(fmt.Sprintf("gateway_request_duration_seconds_sum{route=\"%s\",upstream=\"%s\"} %g\n",
|
||
|
|
route, upstream, float64(totalDuration)/1000.0)) // convert ms to seconds
|
||
|
|
|
||
|
|
// Write count
|
||
|
|
sb.WriteString(fmt.Sprintf("gateway_request_duration_seconds_count{route=\"%s\",upstream=\"%s\"} %d\n",
|
||
|
|
route, upstream, totalCount))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
sb.WriteString("\n")
|
||
|
|
|
||
|
|
// Help and type for gateway_bytes_in counter
|
||
|
|
sb.WriteString("# HELP gateway_bytes_in_total Total bytes received from clients\n")
|
||
|
|
sb.WriteString("# TYPE gateway_bytes_in_total counter\n")
|
||
|
|
for key, count := range m.bytesIn {
|
||
|
|
parts := strings.Split(key, ":")
|
||
|
|
if len(parts) == 2 {
|
||
|
|
route, upstream := parts[0], parts[1]
|
||
|
|
sb.WriteString(fmt.Sprintf("gateway_bytes_in_total{route=\"%s\",upstream=\"%s\"} %d\n",
|
||
|
|
route, upstream, count))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
sb.WriteString("\n")
|
||
|
|
|
||
|
|
// Help and type for gateway_bytes_out counter
|
||
|
|
sb.WriteString("# HELP gateway_bytes_out_total Total bytes sent to clients\n")
|
||
|
|
sb.WriteString("# TYPE gateway_bytes_out_total counter\n")
|
||
|
|
for key, count := range m.bytesOut {
|
||
|
|
parts := strings.Split(key, ":")
|
||
|
|
if len(parts) == 2 {
|
||
|
|
route, upstream := parts[0], parts[1]
|
||
|
|
sb.WriteString(fmt.Sprintf("gateway_bytes_out_total{route=\"%s\",upstream=\"%s\"} %d\n",
|
||
|
|
route, upstream, count))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
sb.WriteString("\n")
|
||
|
|
|
||
|
|
// Help and type for upstream_health gauge
|
||
|
|
sb.WriteString("# HELP gateway_upstream_health Upstream health status (1=healthy, 0=unhealthy)\n")
|
||
|
|
sb.WriteString("# TYPE gateway_upstream_health gauge\n")
|
||
|
|
for upstream, health := range m.upstreamHealth {
|
||
|
|
sb.WriteString(fmt.Sprintf("gateway_upstream_health{upstream=\"%s\"} %d\n", upstream, health))
|
||
|
|
}
|
||
|
|
sb.WriteString("\n")
|
||
|
|
|
||
|
|
// Help and type for streaming responses
|
||
|
|
sb.WriteString("# HELP gateway_streaming_responses_total Total streaming responses\n")
|
||
|
|
sb.WriteString("# TYPE gateway_streaming_responses_total counter\n")
|
||
|
|
for key, count := range m.streamingResponsesTotal {
|
||
|
|
parts := strings.Split(key, ":")
|
||
|
|
if len(parts) == 2 {
|
||
|
|
route, upstream := parts[0], parts[1]
|
||
|
|
sb.WriteString(fmt.Sprintf("gateway_streaming_responses_total{route=\"%s\",upstream=\"%s\"} %d\n",
|
||
|
|
route, upstream, count))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
sb.WriteString("\n")
|
||
|
|
|
||
|
|
// Help and type for streaming byte count
|
||
|
|
sb.WriteString("# HELP gateway_streaming_bytes_total Total bytes in streaming responses\n")
|
||
|
|
sb.WriteString("# TYPE gateway_streaming_bytes_total counter\n")
|
||
|
|
for key, count := range m.streamingByteCount {
|
||
|
|
parts := strings.Split(key, ":")
|
||
|
|
if len(parts) == 2 {
|
||
|
|
route, upstream := parts[0], parts[1]
|
||
|
|
sb.WriteString(fmt.Sprintf("gateway_streaming_bytes_total{route=\"%s\",upstream=\"%s\"} %d\n",
|
||
|
|
route, upstream, count))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return sb.String()
|
||
|
|
}
|