@@ -2,50 +2,62 @@ package envoy
22
33import (
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
3341type 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+
6090func (s * StatsScraper ) loop () {
6191 ticker := time .NewTicker (s .interval )
6292 defer ticker .Stop ()
@@ -73,59 +103,154 @@ func (s *StatsScraper) loop() {
73103}
74104
75105func (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