Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
128
plugins/inputs/haproxy/README.md
Normal file
128
plugins/inputs/haproxy/README.md
Normal file
|
@ -0,0 +1,128 @@
|
|||
# HAProxy Input Plugin
|
||||
|
||||
This plugin gathers statistics of [HAProxy][haproxy] servers using sockets or
|
||||
the HTTP protocol.
|
||||
|
||||
⭐ Telegraf v0.1.5
|
||||
🏷️ network, server
|
||||
💻 all
|
||||
|
||||
[haproxy]: http://www.haproxy.org/
|
||||
|
||||
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
|
||||
|
||||
In addition to the plugin-specific configuration settings, plugins support
|
||||
additional global and plugin configuration settings. These settings are used to
|
||||
modify metrics, tags, and field or create aliases and configure ordering, etc.
|
||||
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
|
||||
|
||||
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml @sample.conf
|
||||
# Read metrics of HAProxy, via stats socket or http endpoints
|
||||
[[inputs.haproxy]]
|
||||
## List of stats endpoints. Metrics can be collected from both http and socket
|
||||
## endpoints. Examples of valid endpoints:
|
||||
## - http://myhaproxy.com:1936/haproxy?stats
|
||||
## - https://myhaproxy.com:8000/stats
|
||||
## - socket:/run/haproxy/admin.sock
|
||||
## - /run/haproxy/*.sock
|
||||
## - tcp://127.0.0.1:1936
|
||||
##
|
||||
## Server addresses not starting with 'http://', 'https://', 'tcp://' will be
|
||||
## treated as possible sockets. When specifying local socket, glob patterns are
|
||||
## supported.
|
||||
servers = ["http://myhaproxy.com:1936/haproxy?stats"]
|
||||
|
||||
## By default, some of the fields are renamed from what haproxy calls them.
|
||||
## Setting this option to true results in the plugin keeping the original
|
||||
## field names.
|
||||
# keep_field_names = false
|
||||
|
||||
## Optional TLS Config
|
||||
# tls_ca = "/etc/telegraf/ca.pem"
|
||||
# tls_cert = "/etc/telegraf/cert.pem"
|
||||
# tls_key = "/etc/telegraf/key.pem"
|
||||
## Use TLS but skip chain & host verification
|
||||
# insecure_skip_verify = false
|
||||
```
|
||||
|
||||
### HAProxy Configuration
|
||||
|
||||
The following information may be useful when getting started, but please consult
|
||||
the HAProxy documentation for complete and up to date instructions.
|
||||
|
||||
The [`stats enable`][4] option can be used to add unauthenticated access over
|
||||
HTTP using the default settings. To enable the unix socket begin by reading
|
||||
about the [`stats socket`][5] option.
|
||||
|
||||
[4]: https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#4-stats%20enable
|
||||
[5]: https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#3.1-stats%20socket
|
||||
|
||||
### servers
|
||||
|
||||
Server addresses must explicitly start with 'http' if you wish to use HAProxy
|
||||
status page. Otherwise, addresses will be assumed to be an UNIX socket and any
|
||||
protocol (if present) will be discarded.
|
||||
|
||||
When using socket names, wildcard expansion is supported so plugin can gather
|
||||
stats from multiple sockets at once.
|
||||
|
||||
To use HTTP Basic Auth add the username and password in the userinfo section of
|
||||
the URL: `http://user:password@1.2.3.4/haproxy?stats`. The credentials are sent
|
||||
via the `Authorization` header and not using the request URL.
|
||||
|
||||
### keep_field_names
|
||||
|
||||
By default, some of the fields are renamed from what haproxy calls them.
|
||||
Setting the `keep_field_names` parameter to `true` will result in the plugin
|
||||
keeping the original field names.
|
||||
|
||||
The following renames are made:
|
||||
|
||||
- `pxname` -> `proxy`
|
||||
- `svname` -> `sv`
|
||||
- `act` -> `active_servers`
|
||||
- `bck` -> `backup_servers`
|
||||
- `cli_abrt` -> `cli_abort`
|
||||
- `srv_abrt` -> `srv_abort`
|
||||
- `hrsp_1xx` -> `http_response.1xx`
|
||||
- `hrsp_2xx` -> `http_response.2xx`
|
||||
- `hrsp_3xx` -> `http_response.3xx`
|
||||
- `hrsp_4xx` -> `http_response.4xx`
|
||||
- `hrsp_5xx` -> `http_response.5xx`
|
||||
- `hrsp_other` -> `http_response.other`
|
||||
|
||||
## Metrics
|
||||
|
||||
For more details about collected metrics reference the [HAProxy CSV format
|
||||
documentation][6].
|
||||
|
||||
- haproxy
|
||||
- tags:
|
||||
- `server` - address of the server data was gathered from
|
||||
- `proxy` - proxy name
|
||||
- `sv` - service name
|
||||
- `type` - proxy session type
|
||||
- fields:
|
||||
- `status` (string)
|
||||
- `check_status` (string)
|
||||
- `last_chk` (string)
|
||||
- `mode` (string)
|
||||
- `tracked` (string)
|
||||
- `agent_status` (string)
|
||||
- `last_agt` (string)
|
||||
- `addr` (string)
|
||||
- `cookie` (string)
|
||||
- `lastsess` (int)
|
||||
- **all other stats** (int)
|
||||
|
||||
[6]: https://cbonte.github.io/haproxy-dconv/1.8/management.html#9.1
|
||||
|
||||
## Example Output
|
||||
|
||||
```text
|
||||
haproxy,server=/run/haproxy/admin.sock,proxy=public,sv=FRONTEND,type=frontend http_response.other=0i,req_rate_max=1i,comp_byp=0i,status="OPEN",rate_lim=0i,dses=0i,req_rate=0i,comp_rsp=0i,bout=9287i,comp_in=0i,mode="http",smax=1i,slim=2000i,http_response.1xx=0i,conn_rate=0i,dreq=0i,ereq=0i,iid=2i,rate_max=1i,http_response.2xx=1i,comp_out=0i,intercepted=1i,stot=2i,pid=1i,http_response.5xx=1i,http_response.3xx=0i,http_response.4xx=0i,conn_rate_max=1i,conn_tot=2i,dcon=0i,bin=294i,rate=0i,sid=0i,req_tot=2i,scur=0i,dresp=0i 1513293519000000000
|
||||
```
|
283
plugins/inputs/haproxy/haproxy.go
Normal file
283
plugins/inputs/haproxy/haproxy.go
Normal file
|
@ -0,0 +1,283 @@
|
|||
//go:generate ../../../tools/readme_config_includer/generator
|
||||
package haproxy
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/plugins/common/tls"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
//go:embed sample.conf
|
||||
var sampleConfig string
|
||||
|
||||
var (
|
||||
typeNames = []string{"frontend", "backend", "server", "listener"}
|
||||
fieldRenames = map[string]string{
|
||||
"pxname": "proxy",
|
||||
"svname": "sv",
|
||||
"act": "active_servers",
|
||||
"bck": "backup_servers",
|
||||
"cli_abrt": "cli_abort",
|
||||
"srv_abrt": "srv_abort",
|
||||
"hrsp_1xx": "http_response.1xx",
|
||||
"hrsp_2xx": "http_response.2xx",
|
||||
"hrsp_3xx": "http_response.3xx",
|
||||
"hrsp_4xx": "http_response.4xx",
|
||||
"hrsp_5xx": "http_response.5xx",
|
||||
"hrsp_other": "http_response.other",
|
||||
}
|
||||
)
|
||||
|
||||
// CSV format: https://cbonte.github.io/haproxy-dconv/1.5/configuration.html#9.1
|
||||
|
||||
type HAProxy struct {
|
||||
Servers []string `toml:"servers"`
|
||||
KeepFieldNames bool `toml:"keep_field_names"`
|
||||
Username string `toml:"username"`
|
||||
Password string `toml:"password"`
|
||||
tls.ClientConfig
|
||||
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func (*HAProxy) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (h *HAProxy) Gather(acc telegraf.Accumulator) error {
|
||||
if len(h.Servers) == 0 {
|
||||
return h.gatherServer("http://127.0.0.1:1936/haproxy?stats", acc)
|
||||
}
|
||||
|
||||
endpoints := make([]string, 0, len(h.Servers))
|
||||
|
||||
for _, endpoint := range h.Servers {
|
||||
if strings.HasPrefix(endpoint, "http://") || strings.HasPrefix(endpoint, "https://") || strings.HasPrefix(endpoint, "tcp://") {
|
||||
endpoints = append(endpoints, endpoint)
|
||||
continue
|
||||
}
|
||||
|
||||
socketPath := getSocketAddr(endpoint)
|
||||
|
||||
matches, err := filepath.Glob(socketPath)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(matches) == 0 {
|
||||
endpoints = append(endpoints, socketPath)
|
||||
} else {
|
||||
endpoints = append(endpoints, matches...)
|
||||
}
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(endpoints))
|
||||
for _, server := range endpoints {
|
||||
go func(serv string) {
|
||||
defer wg.Done()
|
||||
if err := h.gatherServer(serv, acc); err != nil {
|
||||
acc.AddError(err)
|
||||
}
|
||||
}(server)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HAProxy) gatherServerSocket(addr string, acc telegraf.Accumulator) error {
|
||||
var network, address string
|
||||
if strings.HasPrefix(addr, "tcp://") {
|
||||
network = "tcp"
|
||||
address = strings.TrimPrefix(addr, "tcp://")
|
||||
} else {
|
||||
network = "unix"
|
||||
address = getSocketAddr(addr)
|
||||
}
|
||||
|
||||
c, err := net.Dial(network, address)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not connect to '%s://%s': %w", network, address, err)
|
||||
}
|
||||
|
||||
_, errw := c.Write([]byte("show stat\n"))
|
||||
if errw != nil {
|
||||
return fmt.Errorf("could not write to socket '%s://%s': %w", network, address, errw)
|
||||
}
|
||||
|
||||
return h.importCsvResult(c, acc, address)
|
||||
}
|
||||
|
||||
func (h *HAProxy) gatherServer(addr string, acc telegraf.Accumulator) error {
|
||||
if !strings.HasPrefix(addr, "http") {
|
||||
return h.gatherServerSocket(addr, acc)
|
||||
}
|
||||
|
||||
if h.client == nil {
|
||||
tlsCfg, err := h.ClientConfig.TLSConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tr := &http.Transport{
|
||||
ResponseHeaderTimeout: 3 * time.Second,
|
||||
TLSClientConfig: tlsCfg,
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: tr,
|
||||
Timeout: 4 * time.Second,
|
||||
}
|
||||
h.client = client
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(addr, ";csv") {
|
||||
addr += "/;csv"
|
||||
}
|
||||
|
||||
u, err := url.Parse(addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable parse server address %q: %w", addr, err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", addr, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create new request %q: %w", addr, err)
|
||||
}
|
||||
if u.User != nil {
|
||||
p, _ := u.User.Password()
|
||||
req.SetBasicAuth(u.User.Username(), p)
|
||||
u.User = &url.Userinfo{}
|
||||
addr = u.String()
|
||||
}
|
||||
|
||||
if h.Username != "" || h.Password != "" {
|
||||
req.SetBasicAuth(h.Username, h.Password)
|
||||
}
|
||||
|
||||
res, err := h.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to connect to haproxy server %q: %w", addr, err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
return fmt.Errorf("unable to get valid stat result from %q, http response code : %d", addr, res.StatusCode)
|
||||
}
|
||||
|
||||
if err := h.importCsvResult(res.Body, acc, u.Host); err != nil {
|
||||
return fmt.Errorf("unable to parse stat result from %q: %w", addr, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSocketAddr(sock string) string {
|
||||
socketAddr := strings.Split(sock, ":")
|
||||
|
||||
if len(socketAddr) >= 2 {
|
||||
return socketAddr[1]
|
||||
}
|
||||
return socketAddr[0]
|
||||
}
|
||||
|
||||
func (h *HAProxy) importCsvResult(r io.Reader, acc telegraf.Accumulator, host string) error {
|
||||
csvr := csv.NewReader(r)
|
||||
now := time.Now()
|
||||
|
||||
headers, err := csvr.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(headers[0]) <= 2 || headers[0][:2] != "# " {
|
||||
return errors.New("did not receive standard haproxy headers")
|
||||
}
|
||||
headers[0] = headers[0][2:]
|
||||
|
||||
for {
|
||||
row, err := csvr.Read()
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fields := make(map[string]interface{})
|
||||
tags := map[string]string{
|
||||
"server": host,
|
||||
}
|
||||
|
||||
if len(row) != len(headers) {
|
||||
return fmt.Errorf("number of columns does not match number of headers. headers=%d columns=%d", len(headers), len(row))
|
||||
}
|
||||
for i, v := range row {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
colName := headers[i]
|
||||
fieldName := colName
|
||||
if !h.KeepFieldNames {
|
||||
if fieldRename, ok := fieldRenames[colName]; ok {
|
||||
fieldName = fieldRename
|
||||
}
|
||||
}
|
||||
|
||||
switch colName {
|
||||
case "pxname", "svname":
|
||||
tags[fieldName] = v
|
||||
case "type":
|
||||
vi, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse type value %q", v)
|
||||
}
|
||||
if vi >= int64(len(typeNames)) {
|
||||
return fmt.Errorf("received unknown type value: %d", vi)
|
||||
}
|
||||
tags[fieldName] = typeNames[vi]
|
||||
case "check_desc", "agent_desc":
|
||||
// do nothing. These fields are just a more verbose description of the check_status & agent_status fields
|
||||
case "status", "check_status", "last_chk", "mode", "tracked", "agent_status", "last_agt", "addr", "cookie":
|
||||
// these are string fields
|
||||
fields[fieldName] = v
|
||||
case "lastsess":
|
||||
vi, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
// TODO log the error. And just once (per column) so we don't spam the log
|
||||
continue
|
||||
}
|
||||
fields[fieldName] = vi
|
||||
default:
|
||||
vi, err := strconv.ParseUint(v, 10, 64)
|
||||
if err != nil {
|
||||
// TODO log the error. And just once (per column) so we don't spam the log
|
||||
continue
|
||||
}
|
||||
fields[fieldName] = vi
|
||||
}
|
||||
}
|
||||
acc.AddFields("haproxy", fields, tags, now)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add("haproxy", func() telegraf.Input {
|
||||
return &HAProxy{}
|
||||
})
|
||||
}
|
344
plugins/inputs/haproxy/haproxy_test.go
Normal file
344
plugins/inputs/haproxy/haproxy_test.go
Normal file
|
@ -0,0 +1,344 @@
|
|||
package haproxy
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
func serverSocket(l net.Listener) {
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
go func(c net.Conn) {
|
||||
defer c.Close()
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
n, err := c.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data := buf[:n]
|
||||
if string(data) == "show stat\n" {
|
||||
c.Write(csvOutputSample) //nolint:errcheck // we return anyway
|
||||
}
|
||||
}(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHaproxyGeneratesMetricsWithAuthentication(t *testing.T) {
|
||||
// We create a fake server to return test data
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
username, password, ok := r.BasicAuth()
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
if _, err := fmt.Fprint(w, "Unauthorized"); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if username == "user" && password == "password" {
|
||||
if _, err := fmt.Fprint(w, string(csvOutputSample)); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
if _, err := fmt.Fprint(w, "Unauthorized"); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
// Now we tested again above server, with our authentication data
|
||||
r := &HAProxy{
|
||||
Servers: []string{strings.Replace(ts.URL, "http://", "http://user:password@", 1)},
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
|
||||
err := r.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
tags := map[string]string{
|
||||
"server": ts.Listener.Addr().String(),
|
||||
"proxy": "git",
|
||||
"sv": "www",
|
||||
"type": "server",
|
||||
}
|
||||
|
||||
fields := haproxyGetFieldValues()
|
||||
acc.AssertContainsTaggedFields(t, "haproxy", fields, tags)
|
||||
|
||||
// Here, we should get error because we don't pass authentication data
|
||||
r = &HAProxy{
|
||||
Servers: []string{ts.URL},
|
||||
}
|
||||
|
||||
require.NoError(t, r.Gather(&acc))
|
||||
require.NotEmpty(t, acc.Errors)
|
||||
}
|
||||
|
||||
func TestHaproxyGeneratesMetricsWithoutAuthentication(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
if _, err := fmt.Fprint(w, string(csvOutputSample)); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
r := &HAProxy{
|
||||
Servers: []string{ts.URL},
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
|
||||
require.NoError(t, r.Gather(&acc))
|
||||
|
||||
tags := map[string]string{
|
||||
"server": ts.Listener.Addr().String(),
|
||||
"proxy": "git",
|
||||
"sv": "www",
|
||||
"type": "server",
|
||||
}
|
||||
|
||||
fields := haproxyGetFieldValues()
|
||||
acc.AssertContainsTaggedFields(t, "haproxy", fields, tags)
|
||||
}
|
||||
|
||||
func TestHaproxyGeneratesMetricsUsingSocket(t *testing.T) {
|
||||
var randomNumber int64
|
||||
var sockets [5]net.Listener
|
||||
|
||||
// The Maximum length of the socket path is 104/108 characters, path created with t.TempDir() is too long for some cases
|
||||
// (it combines test name with subtest name and some random numbers in the path). Therefore, in this case, it is safer to stick with `os.MkdirTemp()`.
|
||||
//nolint:usetesting // Ignore "os.TempDir() could be replaced by t.TempDir() in TestHaproxyGeneratesMetricsUsingSocket" finding.
|
||||
tempDir := os.TempDir()
|
||||
_globmask := filepath.Join(tempDir, "test-haproxy*.sock")
|
||||
_badmask := filepath.Join(tempDir, "test-fail-haproxy*.sock")
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
require.NoError(t, binary.Read(rand.Reader, binary.LittleEndian, &randomNumber))
|
||||
sockname := filepath.Join(tempDir, fmt.Sprintf("test-haproxy%d.sock", randomNumber))
|
||||
|
||||
sock, err := net.Listen("unix", sockname)
|
||||
require.NoError(t, err, "Cannot initialize socket")
|
||||
|
||||
sockets[i] = sock
|
||||
defer sock.Close() //nolint:revive,gocritic // done on purpose, closing will be executed properly
|
||||
|
||||
go serverSocket(sock)
|
||||
}
|
||||
|
||||
r := &HAProxy{
|
||||
Servers: []string{_globmask},
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
|
||||
err := r.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
fields := haproxyGetFieldValues()
|
||||
|
||||
for _, sock := range sockets {
|
||||
tags := map[string]string{
|
||||
"server": getSocketAddr(sock.Addr().String()),
|
||||
"proxy": "git",
|
||||
"sv": "www",
|
||||
"type": "server",
|
||||
}
|
||||
|
||||
acc.AssertContainsTaggedFields(t, "haproxy", fields, tags)
|
||||
}
|
||||
|
||||
// This mask should not match any socket
|
||||
r.Servers = []string{_badmask}
|
||||
|
||||
require.NoError(t, r.Gather(&acc))
|
||||
require.NotEmpty(t, acc.Errors)
|
||||
}
|
||||
|
||||
func TestHaproxyGeneratesMetricsUsingTcp(t *testing.T) {
|
||||
l, err := net.Listen("tcp", "localhost:8192")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
go serverSocket(l)
|
||||
|
||||
r := &HAProxy{
|
||||
Servers: []string{"tcp://" + l.Addr().String()},
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
require.NoError(t, r.Gather(&acc))
|
||||
|
||||
fields := haproxyGetFieldValues()
|
||||
|
||||
tags := map[string]string{
|
||||
"server": l.Addr().String(),
|
||||
"proxy": "git",
|
||||
"sv": "www",
|
||||
"type": "server",
|
||||
}
|
||||
|
||||
acc.AssertContainsTaggedFields(t, "haproxy", fields, tags)
|
||||
|
||||
require.NoError(t, r.Gather(&acc))
|
||||
}
|
||||
|
||||
// When not passing server config, we default to localhost
|
||||
// We just want to make sure we did request stat from localhost
|
||||
func TestHaproxyDefaultGetFromLocalhost(t *testing.T) {
|
||||
r := &HAProxy{}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
|
||||
err := r.Gather(&acc)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "127.0.0.1:1936/haproxy?stats/;csv")
|
||||
}
|
||||
|
||||
func TestHaproxyKeepFieldNames(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
if _, err := fmt.Fprint(w, string(csvOutputSample)); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
r := &HAProxy{
|
||||
Servers: []string{ts.URL},
|
||||
KeepFieldNames: true,
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
|
||||
require.NoError(t, r.Gather(&acc))
|
||||
|
||||
tags := map[string]string{
|
||||
"server": ts.Listener.Addr().String(),
|
||||
"pxname": "git",
|
||||
"svname": "www",
|
||||
"type": "server",
|
||||
}
|
||||
|
||||
fields := haproxyGetFieldValues()
|
||||
fields["act"] = fields["active_servers"]
|
||||
delete(fields, "active_servers")
|
||||
fields["bck"] = fields["backup_servers"]
|
||||
delete(fields, "backup_servers")
|
||||
fields["cli_abrt"] = fields["cli_abort"]
|
||||
delete(fields, "cli_abort")
|
||||
fields["srv_abrt"] = fields["srv_abort"]
|
||||
delete(fields, "srv_abort")
|
||||
fields["hrsp_1xx"] = fields["http_response.1xx"]
|
||||
delete(fields, "http_response.1xx")
|
||||
fields["hrsp_2xx"] = fields["http_response.2xx"]
|
||||
delete(fields, "http_response.2xx")
|
||||
fields["hrsp_3xx"] = fields["http_response.3xx"]
|
||||
delete(fields, "http_response.3xx")
|
||||
fields["hrsp_4xx"] = fields["http_response.4xx"]
|
||||
delete(fields, "http_response.4xx")
|
||||
fields["hrsp_5xx"] = fields["http_response.5xx"]
|
||||
delete(fields, "http_response.5xx")
|
||||
fields["hrsp_other"] = fields["http_response.other"]
|
||||
delete(fields, "http_response.other")
|
||||
|
||||
acc.AssertContainsTaggedFields(t, "haproxy", fields, tags)
|
||||
}
|
||||
|
||||
func mustReadSampleOutput() []byte {
|
||||
filePath := "testdata/sample_output.csv"
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("could not read from file %s: %w", filePath, err))
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func haproxyGetFieldValues() map[string]interface{} {
|
||||
fields := map[string]interface{}{
|
||||
"active_servers": uint64(1),
|
||||
"backup_servers": uint64(0),
|
||||
"bin": uint64(5228218),
|
||||
"bout": uint64(303747244),
|
||||
"check_code": uint64(200),
|
||||
"check_duration": uint64(3),
|
||||
"check_fall": uint64(3),
|
||||
"check_health": uint64(4),
|
||||
"check_rise": uint64(2),
|
||||
"check_status": "L7OK",
|
||||
"chkdown": uint64(84),
|
||||
"chkfail": uint64(559),
|
||||
"cli_abort": uint64(690),
|
||||
"ctime": uint64(1),
|
||||
"downtime": uint64(3352),
|
||||
"dresp": uint64(0),
|
||||
"econ": uint64(0),
|
||||
"eresp": uint64(21),
|
||||
"http_response.1xx": uint64(0),
|
||||
"http_response.2xx": uint64(5668),
|
||||
"http_response.3xx": uint64(8710),
|
||||
"http_response.4xx": uint64(140),
|
||||
"http_response.5xx": uint64(0),
|
||||
"http_response.other": uint64(0),
|
||||
"iid": uint64(4),
|
||||
"last_chk": "OK",
|
||||
"lastchg": uint64(1036557),
|
||||
"lastsess": int64(1342),
|
||||
"lbtot": uint64(9481),
|
||||
"mode": "http",
|
||||
"pid": uint64(1),
|
||||
"qcur": uint64(0),
|
||||
"qmax": uint64(0),
|
||||
"qtime": uint64(1268),
|
||||
"rate": uint64(0),
|
||||
"rate_max": uint64(2),
|
||||
"rtime": uint64(2908),
|
||||
"sid": uint64(1),
|
||||
"scur": uint64(0),
|
||||
"slim": uint64(2),
|
||||
"smax": uint64(2),
|
||||
"srv_abort": uint64(0),
|
||||
"status": "UP",
|
||||
"stot": uint64(14539),
|
||||
"ttime": uint64(4500),
|
||||
"weight": uint64(1),
|
||||
"wredis": uint64(0),
|
||||
"wretr": uint64(0),
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
// Can obtain from official haproxy demo: 'http://demo.haproxy.org/;csv'
|
||||
var csvOutputSample = mustReadSampleOutput()
|
26
plugins/inputs/haproxy/sample.conf
Normal file
26
plugins/inputs/haproxy/sample.conf
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Read metrics of HAProxy, via stats socket or http endpoints
|
||||
[[inputs.haproxy]]
|
||||
## List of stats endpoints. Metrics can be collected from both http and socket
|
||||
## endpoints. Examples of valid endpoints:
|
||||
## - http://myhaproxy.com:1936/haproxy?stats
|
||||
## - https://myhaproxy.com:8000/stats
|
||||
## - socket:/run/haproxy/admin.sock
|
||||
## - /run/haproxy/*.sock
|
||||
## - tcp://127.0.0.1:1936
|
||||
##
|
||||
## Server addresses not starting with 'http://', 'https://', 'tcp://' will be
|
||||
## treated as possible sockets. When specifying local socket, glob patterns are
|
||||
## supported.
|
||||
servers = ["http://myhaproxy.com:1936/haproxy?stats"]
|
||||
|
||||
## By default, some of the fields are renamed from what haproxy calls them.
|
||||
## Setting this option to true results in the plugin keeping the original
|
||||
## field names.
|
||||
# keep_field_names = false
|
||||
|
||||
## Optional TLS Config
|
||||
# tls_ca = "/etc/telegraf/ca.pem"
|
||||
# tls_cert = "/etc/telegraf/cert.pem"
|
||||
# tls_key = "/etc/telegraf/key.pem"
|
||||
## Use TLS but skip chain & host verification
|
||||
# insecure_skip_verify = false
|
15
plugins/inputs/haproxy/testdata/sample_output.csv
vendored
Normal file
15
plugins/inputs/haproxy/testdata/sample_output.csv
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
|
||||
# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,comp_in,comp_out,comp_byp,comp_rsp,lastsess,last_chk,last_agt,qtime,ctime,rtime,ttime,agent_status,agent_code,agent_duration,check_desc,agent_desc,check_rise,check_fall,check_health,agent_rise,agent_fall,agent_health,addr,cookie,mode,algo,conn_rate,conn_rate_max,conn_tot,intercepted,dcon,dses,
|
||||
http-in,FRONTEND,,,3,100,100,2639994,813557487,65937668635,505252,0,47567,,,,,OPEN,,,,,,,,,1,2,0,,,,0,1,0,157,,,,0,1514640,606647,136264,496535,14948,,1,155,2754255,,,36370569635,17435137766,0,642264,,,,,,,,,,,,,,,,,,,,,http,,1,157,2649922,339471,0,0,
|
||||
http-in,IPv4-direct,,,3,41,100,349801,57445827,1503928881,269899,0,287,,,,,OPEN,,,,,,,,,1,2,1,,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,http,,,,,,0,0,
|
||||
http-in,IPv4-cached,,,0,33,100,1786155,644395819,57905460294,60511,0,1,,,,,OPEN,,,,,,,,,1,2,2,,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,http,,,,,,0,0,
|
||||
http-in,IPv6-direct,,,0,100,100,325619,92414745,6205208728,3399,0,47279,,,,,OPEN,,,,,,,,,1,2,3,,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,http,,,,,,0,0,
|
||||
http-in,local,,,0,0,100,0,0,0,0,0,0,,,,,OPEN,,,,,,,,,1,2,4,,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,http,,,,,,0,0,
|
||||
http-in,local-https,,,0,5,100,188347,19301096,323070732,171443,0,0,,,,,OPEN,,,,,,,,,1,2,5,,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,http,,,,,,0,0,
|
||||
www,www,0,0,0,20,20,1719698,672044109,64806076656,,0,,0,5285,22,0,UP,1,1,0,561,84,1036557,3356,,1,3,1,,1715117,,2,0,,45,L7OK,200,5,671,1144889,481714,87038,4,0,,,,,105016,167,,,,,5,OK,,0,5,16,1167,,,,Layer7 check passed,,2,3,4,,,,,,http,,,,,,,,
|
||||
www,bck,0,0,0,10,10,1483,537137,7544118,,0,,0,0,0,0,UP,1,0,1,4,0,5218087,0,,1,3,2,,1371,,2,0,,17,L7OK,200,2,0,629,99,755,0,0,,,,,16,0,,,,,1036557,OK,,756,1,13,1184,,,,Layer7 check passed,,2,5,6,,,,,,http,,,,,,,,
|
||||
www,BACKEND,0,25,0,46,100,1721835,674684790,64813732170,314,0,,130,5285,22,0,UP,1,1,1,,0,5218087,0,,1,3,0,,1716488,,1,0,,45,,,,0,1145518,481813,88664,5719,121,,,,1721835,105172,167,35669268059,17250148556,0,556042,5,,,0,5,16,1167,,,,,,,,,,,,,,http,,,,,,,,
|
||||
git,www,0,0,0,2,2,14539,5228218,303747244,,0,,0,21,0,0,UP,1,1,0,559,84,1036557,3352,,1,4,1,,9481,,2,0,,2,L7OK,200,3,0,5668,8710,140,0,0,,,,,690,0,,,,,1342,OK,,1268,1,2908,4500,,,,Layer7 check passed,,2,3,4,,,,,,http,,,,,,,,
|
||||
git,bck,0,0,0,0,2,0,0,0,,0,,0,0,0,0,UP,1,0,1,2,0,5218087,0,,1,4,2,,0,,2,0,,0,L7OK,200,2,0,0,0,0,0,0,,,,,0,0,,,,,-1,OK,,0,0,0,0,,,,Layer7 check passed,,2,3,4,,,,,,http,,,,,,,,
|
||||
git,BACKEND,0,6,0,8,2,14541,8082393,303747668,0,0,,2,21,0,0,UP,1,1,1,,0,5218087,0,,1,4,0,,9481,,1,0,,7,,,,0,5668,8710,140,23,0,,,,14541,690,0,133458298,38104818,0,4379,1342,,,1268,1,2908,4500,,,,,,,,,,,,,,http,,,,,,,,
|
||||
demo,BACKEND,0,0,1,5,20,24063,7876647,659864417,48,0,,1,0,0,0,UP,0,0,0,,0,5218087,,,1,17,0,,0,,1,1,,26,,,,0,23983,21,0,1,57,,,,24062,111,0,567843278,146884392,0,1083,0,,,2706,0,0,887,,,,,,,,,,,,,,http,,,,,,,,
|
|
Loading…
Add table
Add a link
Reference in a new issue