131 lines
4.5 KiB
Go
131 lines
4.5 KiB
Go
package metrics
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/longpeng/configcenter/internal/store"
|
|
)
|
|
|
|
type Collector struct {
|
|
mu sync.Mutex
|
|
http map[httpKey]httpValue
|
|
|
|
publishAccepted atomic.Uint64
|
|
outboxApplied atomic.Uint64
|
|
outboxFailed atomic.Uint64
|
|
runtimeReads atomic.Uint64
|
|
grayMatches atomic.Uint64
|
|
watchers atomic.Int64
|
|
}
|
|
|
|
type httpKey struct {
|
|
method string
|
|
route string
|
|
status int
|
|
}
|
|
|
|
type httpValue struct {
|
|
count uint64
|
|
durationSum float64
|
|
}
|
|
|
|
func New() *Collector { return &Collector{http: make(map[httpKey]httpValue)} }
|
|
|
|
func (c *Collector) ObserveHTTP(method, route string, status int, duration time.Duration) {
|
|
if route == "" {
|
|
route = "unmatched"
|
|
}
|
|
key := httpKey{method: method, route: route, status: status}
|
|
c.mu.Lock()
|
|
value := c.http[key]
|
|
value.count++
|
|
value.durationSum += duration.Seconds()
|
|
c.http[key] = value
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
func (c *Collector) PublishAccepted() { c.publishAccepted.Add(1) }
|
|
func (c *Collector) OutboxApplied() { c.outboxApplied.Add(1) }
|
|
func (c *Collector) OutboxFailed() { c.outboxFailed.Add(1) }
|
|
func (c *Collector) RuntimeRead() { c.runtimeReads.Add(1) }
|
|
func (c *Collector) GrayMatched(count int) {
|
|
if count > 0 {
|
|
c.grayMatches.Add(uint64(count))
|
|
}
|
|
}
|
|
func (c *Collector) WatcherAdded() { c.watchers.Add(1) }
|
|
func (c *Collector) WatcherRemoved() { c.watchers.Add(-1) }
|
|
|
|
func (c *Collector) Handler(repository store.Store) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "text/plain; version=0.0.4; charset=utf-8")
|
|
stats, err := repository.OutboxStats(r.Context())
|
|
if err != nil {
|
|
http.Error(w, "collect outbox metrics", http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
var output strings.Builder
|
|
writeHelp(&output, "configcenter_http_requests_total", "HTTP requests by method, route and status", "counter")
|
|
writeHelp(&output, "configcenter_http_request_duration_seconds_sum", "Cumulative HTTP request duration", "counter")
|
|
c.mu.Lock()
|
|
keys := make([]httpKey, 0, len(c.http))
|
|
for key := range c.http {
|
|
keys = append(keys, key)
|
|
}
|
|
sort.Slice(keys, func(i, j int) bool {
|
|
if keys[i].route != keys[j].route {
|
|
return keys[i].route < keys[j].route
|
|
}
|
|
if keys[i].method != keys[j].method {
|
|
return keys[i].method < keys[j].method
|
|
}
|
|
return keys[i].status < keys[j].status
|
|
})
|
|
for _, key := range keys {
|
|
value := c.http[key]
|
|
labels := fmt.Sprintf(`method="%s",route="%s",status="%d"`, escape(key.method), escape(key.route), key.status)
|
|
fmt.Fprintf(&output, "configcenter_http_requests_total{%s} %d\n", labels, value.count)
|
|
fmt.Fprintf(&output, "configcenter_http_request_duration_seconds_sum{%s} %s\n", labels, strconv.FormatFloat(value.durationSum, 'f', 6, 64))
|
|
}
|
|
c.mu.Unlock()
|
|
|
|
writeCounter(&output, "configcenter_publish_accepted_total", "Accepted configuration releases", c.publishAccepted.Load())
|
|
writeCounter(&output, "configcenter_outbox_applied_total", "Successfully applied outbox entries", c.outboxApplied.Load())
|
|
writeCounter(&output, "configcenter_outbox_failed_attempts_total", "Failed outbox apply attempts", c.outboxFailed.Load())
|
|
writeCounter(&output, "configcenter_runtime_reads_total", "Runtime configuration reads", c.runtimeReads.Load())
|
|
writeCounter(&output, "configcenter_gray_rule_matches_total", "Gray rules matched during runtime reads and watches", c.grayMatches.Load())
|
|
writeGauge(&output, "configcenter_watch_subscribers", "Current SSE watch subscribers", c.watchers.Load())
|
|
writeGauge(&output, "configcenter_outbox_pending", "Pending outbox entries", stats.Pending)
|
|
writeGauge(&output, "configcenter_outbox_processing", "Processing outbox entries", stats.Processing)
|
|
writeGauge(&output, "configcenter_outbox_failed", "Terminally failed outbox entries", stats.Failed)
|
|
_, _ = w.Write([]byte(output.String()))
|
|
}
|
|
}
|
|
|
|
func writeHelp(output *strings.Builder, name, help, metricType string) {
|
|
fmt.Fprintf(output, "# HELP %s %s\n# TYPE %s %s\n", name, help, name, metricType)
|
|
}
|
|
|
|
func writeCounter(output *strings.Builder, name, help string, value uint64) {
|
|
writeHelp(output, name, help, "counter")
|
|
fmt.Fprintf(output, "%s %d\n", name, value)
|
|
}
|
|
|
|
func writeGauge(output *strings.Builder, name, help string, value int64) {
|
|
writeHelp(output, name, help, "gauge")
|
|
fmt.Fprintf(output, "%s %d\n", name, value)
|
|
}
|
|
|
|
func escape(value string) string {
|
|
value = strings.ReplaceAll(value, `\`, `\\`)
|
|
value = strings.ReplaceAll(value, `"`, `\"`)
|
|
return strings.ReplaceAll(value, "\n", `\n`)
|
|
}
|