Skip to content

Commit 63804bb

Browse files
committed
display statistics from prometheus endpoint
1 parent 14442e7 commit 63804bb

4 files changed

Lines changed: 345 additions & 66 deletions

File tree

cmd/localproxyd/daemon.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,14 +158,14 @@ func (d *Daemon) initEnvoy() error {
158158
if err := d.envoyMgr.Start(); err != nil {
159159
return err
160160
}
161-
d.statsScraper = envoy.NewStatsScraper("http://127.0.0.1:9901", "", 10*time.Second)
161+
d.statsScraper = envoy.NewStatsScraper("http://127.0.0.1:9901", 10*time.Second)
162162
d.statsScraper.Start()
163163
return nil
164164
}
165165

166166
func (d *Daemon) initRouting() error {
167167
basePaths := d.getBasePaths()
168-
d.dashboardServer = proxy.NewDashboardServer(basePaths, d.config.TraceProcessLogs)
168+
d.dashboardServer = proxy.NewDashboardServer(basePaths, d.config.TraceProcessLogs, d.statsScraper)
169169
d.routeRegistry = registry.NewRouteRegistry(d.onRoutesChanged)
170170

171171
certPath, keyPath, _ := d.certMgr.GetCert("localhost")

internal/envoy/scraper.go

Lines changed: 180 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,62 @@ package envoy
22

33
import (
44
"context"
5-
"log"
65
"net/http"
7-
"sort"
86
"strings"
7+
"sync"
98
"time"
109

1110
"github.com/prometheus/common/expfmt"
1211
"github.com/prometheus/common/model"
1312
)
1413

15-
type Metric struct {
16-
Name string
17-
Labels map[string]string
18-
Value float64
14+
type ClusterStats struct {
15+
Name string
16+
TCPBytesReceived uint64
17+
TCPBytesSent uint64
18+
TCPBytesReceivedRate float64
19+
TCPBytesSentRate float64
20+
LastTrafficReceived time.Time
21+
HTTPRequestsTotal uint64
22+
HTTPRequestsRate float64
23+
HTTP2xx uint64
24+
HTTP4xx uint64
25+
HTTP5xx uint64
26+
HTTP1Connections uint64
27+
HTTP2Connections uint64
28+
HTTP3Connections uint64
29+
ActiveConnections uint64
30+
ConnectionsTotal uint64
31+
DisconnectsLocal uint64
32+
DisconnectsRemote uint64
1933
}
2034

21-
func (m Metric) String() string {
22-
if len(m.Labels) == 0 {
23-
return m.Name
24-
}
25-
pairs := make([]string, 0, len(m.Labels))
26-
for k, v := range m.Labels {
27-
pairs = append(pairs, k+"="+v)
28-
}
29-
sort.Strings(pairs)
30-
return m.Name + "{" + strings.Join(pairs, ",") + "}"
35+
type GlobalStats struct {
36+
DownstreamHTTP1 uint64
37+
DownstreamHTTP2 uint64
38+
DownstreamHTTP3 uint64
3139
}
3240

3341
type StatsScraper struct {
34-
adminURL string
35-
filter string
36-
interval time.Duration
37-
ctx context.Context
38-
cancel context.CancelFunc
42+
adminURL string
43+
interval time.Duration
44+
ctx context.Context
45+
cancel context.CancelFunc
46+
mu sync.RWMutex
47+
clusters map[string]*ClusterStats
48+
globalStats GlobalStats
49+
prevScrape map[string]*ClusterStats
50+
lastScrapeAt time.Time
3951
}
4052

41-
func NewStatsScraper(adminURL, filter string, interval time.Duration) *StatsScraper {
53+
func NewStatsScraper(adminURL string, interval time.Duration) *StatsScraper {
4254
ctx, cancel := context.WithCancel(context.Background())
4355
return &StatsScraper{
4456
adminURL: adminURL,
45-
filter: filter,
4657
interval: interval,
4758
ctx: ctx,
4859
cancel: cancel,
60+
clusters: make(map[string]*ClusterStats),
4961
}
5062
}
5163

@@ -57,6 +69,24 @@ func (s *StatsScraper) Stop() {
5769
s.cancel()
5870
}
5971

72+
func (s *StatsScraper) GetClusterStats() map[string]*ClusterStats {
73+
s.scrape()
74+
s.mu.RLock()
75+
defer s.mu.RUnlock()
76+
result := make(map[string]*ClusterStats, len(s.clusters))
77+
for k, v := range s.clusters {
78+
copy := *v
79+
result[k] = &copy
80+
}
81+
return result
82+
}
83+
84+
func (s *StatsScraper) GetGlobalStats() GlobalStats {
85+
s.mu.RLock()
86+
defer s.mu.RUnlock()
87+
return s.globalStats
88+
}
89+
6090
func (s *StatsScraper) loop() {
6191
ticker := time.NewTicker(s.interval)
6292
defer ticker.Stop()
@@ -73,59 +103,154 @@ func (s *StatsScraper) loop() {
73103
}
74104

75105
func (s *StatsScraper) scrape() {
76-
metrics, err := scrapeEnvoyStats(s.adminURL, s.filter)
106+
metrics, globalStats, err := scrapeEnvoyStats(s.adminURL)
77107
if err != nil {
78-
log.Printf("stats scraper: failed to scrape: %v", err)
79108
return
80109
}
110+
111+
now := time.Now()
112+
clusters := make(map[string]*ClusterStats)
113+
81114
for _, m := range metrics {
82-
log.Printf("stats: %s = %f", m.String(), m.Value)
115+
clusterName := m.Labels["envoy_cluster_name"]
116+
if clusterName == "" {
117+
continue
118+
}
119+
120+
stats, ok := clusters[clusterName]
121+
if !ok {
122+
stats = &ClusterStats{Name: clusterName}
123+
clusters[clusterName] = stats
124+
}
125+
126+
val := uint64(m.Value)
127+
switch m.Name {
128+
case "envoy_cluster_upstream_cx_rx_bytes_total":
129+
stats.TCPBytesReceived = val
130+
case "envoy_cluster_upstream_cx_tx_bytes_total":
131+
stats.TCPBytesSent = val
132+
case "envoy_cluster_upstream_rq_total":
133+
stats.HTTPRequestsTotal = val
134+
case "envoy_cluster_upstream_rq_xx":
135+
code := m.Labels["envoy_response_code_class"]
136+
switch code {
137+
case "2":
138+
stats.HTTP2xx = val
139+
case "4":
140+
stats.HTTP4xx = val
141+
case "5":
142+
stats.HTTP5xx = val
143+
}
144+
case "envoy_cluster_upstream_cx_active":
145+
stats.ActiveConnections = val
146+
case "envoy_cluster_upstream_cx_total":
147+
stats.ConnectionsTotal = val
148+
case "envoy_cluster_upstream_cx_destroy_local":
149+
stats.DisconnectsLocal = val
150+
case "envoy_cluster_upstream_cx_destroy_remote":
151+
stats.DisconnectsRemote = val
152+
}
83153
}
154+
155+
s.mu.Lock()
156+
defer s.mu.Unlock()
157+
158+
if s.prevScrape != nil && !s.lastScrapeAt.IsZero() {
159+
elapsed := now.Sub(s.lastScrapeAt).Seconds()
160+
if elapsed > 0 {
161+
for name, curr := range clusters {
162+
if prev, ok := s.prevScrape[name]; ok {
163+
curr.TCPBytesReceivedRate = float64(curr.TCPBytesReceived-prev.TCPBytesReceived) / elapsed
164+
curr.TCPBytesSentRate = float64(curr.TCPBytesSent-prev.TCPBytesSent) / elapsed
165+
curr.HTTPRequestsRate = float64(curr.HTTPRequestsTotal-prev.HTTPRequestsTotal) / elapsed
166+
167+
if curr.TCPBytesReceived > prev.TCPBytesReceived {
168+
curr.LastTrafficReceived = now
169+
} else if !prev.LastTrafficReceived.IsZero() {
170+
curr.LastTrafficReceived = prev.LastTrafficReceived
171+
}
172+
}
173+
}
174+
}
175+
}
176+
177+
s.prevScrape = s.clusters
178+
s.clusters = clusters
179+
s.globalStats = globalStats
180+
s.lastScrapeAt = now
84181
}
85182

86-
func scrapeEnvoyStats(adminURL string, filter string) ([]Metric, error) {
183+
type rawMetric struct {
184+
Name string
185+
Labels map[string]string
186+
Value float64
187+
}
188+
189+
func scrapeEnvoyStats(adminURL string) ([]rawMetric, GlobalStats, error) {
87190
resp, err := http.Get(adminURL + "/stats?format=prometheus")
88191
if err != nil {
89-
return nil, err
192+
return nil, GlobalStats{}, err
90193
}
91194
defer resp.Body.Close()
92195

93196
parser := expfmt.NewTextParser(model.UTF8Validation)
94197
families, err := parser.TextToMetricFamilies(resp.Body)
95198
if err != nil {
96-
return nil, err
199+
return nil, GlobalStats{}, err
97200
}
98201

99-
var result []Metric
202+
var result []rawMetric
203+
var global GlobalStats
204+
100205
for name, family := range families {
101-
if filter != "" && !strings.Contains(name, filter) {
102-
continue
103-
}
104-
for _, m := range family.Metric {
105-
var value float64
106-
if m.Counter != nil {
107-
value = m.Counter.GetValue()
108-
} else if m.Gauge != nil {
109-
value = m.Gauge.GetValue()
110-
} else if m.Histogram != nil {
111-
value = float64(m.Histogram.GetSampleCount())
112-
} else if m.Summary != nil {
113-
value = float64(m.Summary.GetSampleCount())
114-
} else {
115-
continue
116-
}
206+
if strings.HasPrefix(name, "envoy_cluster_") {
207+
for _, m := range family.Metric {
208+
var value float64
209+
if m.Counter != nil {
210+
value = m.Counter.GetValue()
211+
} else if m.Gauge != nil {
212+
value = m.Gauge.GetValue()
213+
} else {
214+
continue
215+
}
216+
217+
labels := make(map[string]string)
218+
for _, lp := range m.Label {
219+
labels[lp.GetName()] = lp.GetValue()
220+
}
117221

118-
labels := make(map[string]string)
119-
for _, lp := range m.Label {
120-
labels[lp.GetName()] = lp.GetValue()
222+
result = append(result, rawMetric{
223+
Name: name,
224+
Labels: labels,
225+
Value: value,
226+
})
121227
}
228+
}
122229

123-
result = append(result, Metric{
124-
Name: name,
125-
Labels: labels,
126-
Value: value,
127-
})
230+
switch name {
231+
case "envoy_http_downstream_cx_http1_total", "envoy_http_downstream_cx_http2_total", "envoy_http_downstream_cx_http3_total":
232+
for _, m := range family.Metric {
233+
if m.Counter == nil {
234+
continue
235+
}
236+
val := uint64(m.Counter.GetValue())
237+
for _, lp := range m.Label {
238+
if lp.GetName() == "envoy_http_conn_manager_prefix" {
239+
prefix := lp.GetValue()
240+
if prefix == "https_ingress" || prefix == "http_ingress" || prefix == "http3_ingress" {
241+
switch name {
242+
case "envoy_http_downstream_cx_http1_total":
243+
global.DownstreamHTTP1 += val
244+
case "envoy_http_downstream_cx_http2_total":
245+
global.DownstreamHTTP2 += val
246+
case "envoy_http_downstream_cx_http3_total":
247+
global.DownstreamHTTP3 += val
248+
}
249+
}
250+
}
251+
}
252+
}
128253
}
129254
}
130-
return result, nil
255+
return result, global, nil
131256
}

0 commit comments

Comments
 (0)