Skip to content

Commit 3d7d8ae

Browse files
author
Thibault Gilles
committed
Add stats server
1 parent 5c5a5fa commit 3d7d8ae

File tree

6 files changed

+199
-17
lines changed

6 files changed

+199
-17
lines changed

consul/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
type Config struct {
1010
ServiceName string
11+
ServiceID string
1112
CAsPool *x509.CertPool
1213
Downstream Downstream
1314
Upstreams []Upstream

consul/watcher.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ func (w *Watcher) genCfg() Config {
325325

326326
config := Config{
327327
ServiceName: w.serviceName,
328+
ServiceID: w.service,
328329
CAsPool: w.certCAPool,
329330
Downstream: Downstream{
330331
LocalBindAddress: w.downstream.LocalBindAddress,

haproxy/haproxy.go

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"net"
66
"net/http"
77
"os/exec"
8+
"strconv"
9+
"sync"
810
"syscall"
911
"time"
1012

@@ -13,6 +15,7 @@ import (
1315
spoe "github.com/criteo/haproxy-spoe-go"
1416
"github.com/haproxytech/models"
1517
"github.com/hashicorp/consul/api"
18+
"github.com/prometheus/client_golang/prometheus/promhttp"
1619
log "github.com/sirupsen/logrus"
1720
"gopkg.in/mcuadros/go-syslog.v2"
1821
)
@@ -78,20 +81,25 @@ func (h *HAProxy) Run(sd *lib.Shutdown) error {
7881
return err
7982
}
8083

81-
//go (&Stats{h.dataplaneClient}).Run()
82-
8384
err = h.init()
8485
if err != nil {
8586
return err
8687
}
8788

89+
var statsOnce sync.Once
8890
for {
8991
select {
9092
case c := <-h.cfgC:
9193
err := h.handleChange(c)
9294
if err != nil {
9395
log.Error(err)
9496
}
97+
statsOnce.Do(func() {
98+
err := h.startStats()
99+
if err != nil {
100+
log.Error(err)
101+
}
102+
})
95103
case <-sd.Stop:
96104
return nil
97105
}
@@ -260,3 +268,57 @@ func (h *HAProxy) startDataplane(sd *lib.Shutdown, haCmd *exec.Cmd) error {
260268

261269
return nil
262270
}
271+
272+
func (h *HAProxy) startStats() error {
273+
if h.opts.StatsListenAddr == "" {
274+
return nil
275+
}
276+
go func() {
277+
if !h.opts.StatsRegisterService {
278+
return
279+
}
280+
281+
_, portStr, err := net.SplitHostPort(h.opts.StatsListenAddr)
282+
if err != nil {
283+
log.Errorf("cannot parse stats listen addr: %s", err)
284+
}
285+
port, _ := strconv.Atoi(portStr)
286+
287+
reg := func() {
288+
err = h.consulClient.Agent().ServiceRegister(&api.AgentServiceRegistration{
289+
ID: fmt.Sprintf("%s-connect-stats", h.currentCfg.ServiceID),
290+
Name: fmt.Sprintf("%s-connect-stats", h.currentCfg.ServiceName),
291+
Port: port,
292+
Checks: api.AgentServiceChecks{
293+
&api.AgentServiceCheck{
294+
HTTP: fmt.Sprintf("http://localhost:%d/metrics", port),
295+
Interval: (10 * time.Second).String(),
296+
DeregisterCriticalServiceAfter: time.Minute.String(),
297+
},
298+
},
299+
Tags: []string{"connect-stats"},
300+
})
301+
if err != nil {
302+
log.Errorf("cannot register stats service: %s", err)
303+
}
304+
}
305+
306+
reg()
307+
308+
for range time.Tick(time.Minute) {
309+
reg()
310+
}
311+
}()
312+
go (&Stats{
313+
dpapi: h.dataplaneClient,
314+
service: h.currentCfg.ServiceName,
315+
}).Run()
316+
go func() {
317+
http.Handle("/metrics", promhttp.Handler())
318+
319+
log.Infof("Starting stats server at %s", h.opts.StatsListenAddr)
320+
http.ListenAndServe(h.opts.StatsListenAddr, nil)
321+
}()
322+
323+
return nil
324+
}

haproxy/options.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package haproxy
22

33
type Options struct {
4-
HAProxyBin string
5-
DataplaneBin string
6-
ConfigBaseDir string
7-
SPOEAddress string
8-
EnableIntentions bool
4+
HAProxyBin string
5+
DataplaneBin string
6+
ConfigBaseDir string
7+
SPOEAddress string
8+
EnableIntentions bool
9+
StatsListenAddr string
10+
StatsRegisterService bool
911
}

haproxy/stats.go

Lines changed: 118 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,81 @@
11
package haproxy
22

33
import (
4+
"strings"
45
"time"
56

6-
"github.com/davecgh/go-spew/spew"
77
"github.com/haproxytech/models"
88
"github.com/prometheus/client_golang/prometheus"
99
"github.com/prometheus/client_golang/prometheus/promauto"
1010
log "github.com/sirupsen/logrus"
1111
)
1212

1313
var (
14-
opsProcessed = promauto.NewGaugeVec(prometheus.GaugeOpts{
15-
Name: "http_requests_total",
14+
upMetric = promauto.NewGaugeVec(prometheus.GaugeOpts{
15+
Name: "haproxy_connect_up",
16+
Help: "The total number of http requests",
17+
}, []string{"service"})
18+
19+
reqOutRate = promauto.NewGaugeVec(prometheus.GaugeOpts{
20+
Name: "haproxy_connect_http_request_out_rate",
21+
Help: "The total number of http requests",
22+
}, []string{"service", "target"})
23+
reqInRate = promauto.NewGaugeVec(prometheus.GaugeOpts{
24+
Name: "haproxy_connect_http_request_in_rate",
25+
Help: "The total number of http requests",
26+
}, []string{"service"})
27+
resInTotal = promauto.NewGaugeVec(prometheus.GaugeOpts{
28+
Name: "haproxy_connect_http_response_in_total",
29+
Help: "The total number of http requests",
30+
}, []string{"service", "code"})
31+
resOutTotal = promauto.NewGaugeVec(prometheus.GaugeOpts{
32+
Name: "haproxy_connect_http_response_out_total",
33+
Help: "The total number of http requests",
34+
}, []string{"service", "target", "code"})
35+
36+
resTimeIn = promauto.NewGaugeVec(prometheus.GaugeOpts{
37+
Name: "haproxy_connect_http_response_in_avg_time_second",
38+
Help: "The total number of http requests",
39+
}, []string{"service"})
40+
resTimeOut = promauto.NewGaugeVec(prometheus.GaugeOpts{
41+
Name: "haproxy_connect_http_response_out_avg_time_second",
42+
Help: "The total number of http requests",
43+
}, []string{"service", "target"})
44+
45+
connOutCount = promauto.NewGaugeVec(prometheus.GaugeOpts{
46+
Name: "haproxy_connect_connection_out_rate",
47+
Help: "The total number of http requests",
48+
}, []string{"service", "target"})
49+
connInCount = promauto.NewGaugeVec(prometheus.GaugeOpts{
50+
Name: "haproxy_connect_connection_in_count",
51+
Help: "The total number of http requests",
52+
}, []string{"service"})
53+
54+
bytesInOut = promauto.NewGaugeVec(prometheus.GaugeOpts{
55+
Name: "haproxy_connect_bytes_in_out_total",
56+
Help: "The total number of http requests",
57+
}, []string{"service", "target"})
58+
bytesOutOut = promauto.NewGaugeVec(prometheus.GaugeOpts{
59+
Name: "haproxy_connect_bytes_out_out_total",
60+
Help: "The total number of http requests",
61+
}, []string{"service", "target"})
62+
bytesInIn = promauto.NewGaugeVec(prometheus.GaugeOpts{
63+
Name: "haproxy_connect_bytes_in_in_total",
64+
Help: "The total number of http requests",
65+
}, []string{"service"})
66+
bytesOutIn = promauto.NewGaugeVec(prometheus.GaugeOpts{
67+
Name: "haproxy_connect_bytes_out_in_total",
1668
Help: "The total number of http requests",
1769
}, []string{"service"})
1870
)
1971

2072
type Stats struct {
21-
dpapi *dataplaneClient
73+
service string
74+
dpapi *dataplaneClient
2275
}
2376

2477
func (s *Stats) Run() {
78+
upMetric.WithLabelValues(s.service).Set(1)
2579
for {
2680
time.Sleep(time.Second)
2781
stats, err := s.dpapi.Stats()
@@ -34,7 +88,65 @@ func (s *Stats) Run() {
3488
}
3589

3690
func (s *Stats) handle(stats []models.NativeStat) {
37-
for _, s := range stats {
38-
spew.Dump(s)
91+
for _, stats := range stats {
92+
switch stats.Type {
93+
case models.NativeStatTypeFrontend:
94+
s.handleFrontend(stats)
95+
case models.NativeStatTypeBackend:
96+
s.handlebackend(stats)
97+
case models.NativeStatTypeServer:
98+
s.handleServer(stats)
99+
}
100+
}
101+
}
102+
103+
func statVal(i *int64) float64 {
104+
if i == nil {
105+
return 0
106+
}
107+
return float64(*i)
108+
}
109+
110+
func (s *Stats) handleFrontend(stats models.NativeStat) {
111+
targetService := strings.TrimPrefix(stats.Name, "front_")
112+
113+
if targetService == "downstream" {
114+
reqInRate.WithLabelValues(s.service).Set(statVal(stats.Stats.Rate))
115+
connInCount.WithLabelValues(s.service).Set(statVal(stats.Stats.Scur))
116+
bytesInIn.WithLabelValues(s.service).Set(statVal(stats.Stats.Bin))
117+
bytesOutIn.WithLabelValues(s.service).Set(statVal(stats.Stats.Bout))
118+
119+
resInTotal.WithLabelValues(s.service, "1xx").Set(statVal(stats.Stats.Hrsp1xx))
120+
resInTotal.WithLabelValues(s.service, "2xx").Set(statVal(stats.Stats.Hrsp2xx))
121+
resInTotal.WithLabelValues(s.service, "3xx").Set(statVal(stats.Stats.Hrsp3xx))
122+
resInTotal.WithLabelValues(s.service, "4xx").Set(statVal(stats.Stats.Hrsp4xx))
123+
resInTotal.WithLabelValues(s.service, "5xx").Set(statVal(stats.Stats.Hrsp5xx))
124+
resInTotal.WithLabelValues(s.service, "other").Set(statVal(stats.Stats.HrspOther))
125+
} else {
126+
reqOutRate.WithLabelValues(s.service, targetService).Set(statVal(stats.Stats.Rate))
127+
connOutCount.WithLabelValues(s.service, targetService).Set(statVal(stats.Stats.Scur))
128+
bytesInOut.WithLabelValues(s.service, targetService).Set(statVal(stats.Stats.Bin))
129+
bytesOutOut.WithLabelValues(s.service, targetService).Set(statVal(stats.Stats.Bout))
130+
131+
resOutTotal.WithLabelValues(s.service, targetService, "1xx").Set(statVal(stats.Stats.Hrsp1xx))
132+
resOutTotal.WithLabelValues(s.service, targetService, "2xx").Set(statVal(stats.Stats.Hrsp2xx))
133+
resOutTotal.WithLabelValues(s.service, targetService, "3xx").Set(statVal(stats.Stats.Hrsp3xx))
134+
resOutTotal.WithLabelValues(s.service, targetService, "4xx").Set(statVal(stats.Stats.Hrsp4xx))
135+
resOutTotal.WithLabelValues(s.service, targetService, "5xx").Set(statVal(stats.Stats.Hrsp5xx))
136+
resOutTotal.WithLabelValues(s.service, targetService, "other").Set(statVal(stats.Stats.HrspOther))
39137
}
40138
}
139+
140+
func (s *Stats) handlebackend(stats models.NativeStat) {
141+
targetService := strings.TrimPrefix(stats.Name, "back_")
142+
143+
if targetService == "downstream" {
144+
resTimeIn.WithLabelValues(s.service).Set(statVal(stats.Stats.Ttime) / 1000)
145+
} else {
146+
resTimeOut.WithLabelValues(s.service, targetService).Set(statVal(stats.Stats.Ttime) / 1000)
147+
}
148+
}
149+
150+
func (s *Stats) handleServer(stats models.NativeStat) {
151+
resTimeOut.WithLabelValues(s.service, stats.Name).Set(statVal(stats.Stats.Ttime) / 1000)
152+
}

main.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ func main() {
2121
haproxyBin := flag.String("haproxy", "haproxy", "Haproxy binary path")
2222
dataplaneBin := flag.String("dataplane", "dataplane-api", "Dataplane binary path")
2323
haproxyCfgBasePath := flag.String("haproxy-cfg-base-path", "/tmp", "Haproxy binary path")
24+
statsListenAddr := flag.String("stats-addr", "", "Listen addr for stats server")
25+
statsServiceRegister := flag.Bool("stats-service-register", false, "Register a consul service for connect stats")
2426
enableIntentions := flag.Bool("enable-intentions", false, "Enable Connect intentions")
2527
token := flag.String("token", "", "Consul ACL token")
2628
flag.Parse()
@@ -45,10 +47,12 @@ func main() {
4547
}()
4648

4749
hap := haproxy.New(consulClient, watcher.C, haproxy.Options{
48-
HAProxyBin: *haproxyBin,
49-
DataplaneBin: *dataplaneBin,
50-
ConfigBaseDir: *haproxyCfgBasePath,
51-
EnableIntentions: *enableIntentions,
50+
HAProxyBin: *haproxyBin,
51+
DataplaneBin: *dataplaneBin,
52+
ConfigBaseDir: *haproxyCfgBasePath,
53+
EnableIntentions: *enableIntentions,
54+
StatsListenAddr: *statsListenAddr,
55+
StatsRegisterService: *statsServiceRegister,
5256
})
5357
sd.Add(1)
5458
go func() {

0 commit comments

Comments
 (0)