323 lines
10 KiB
Go
323 lines
10 KiB
Go
// Licensed to Elasticsearch B.V. under one or more contributor
|
|
// license agreements. See the NOTICE file distributed with
|
|
// this work for additional information regarding copyright
|
|
// ownership. Elasticsearch B.V. licenses this file to you under
|
|
// the Apache License, Version 2.0 (the "License"); you may
|
|
// not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package haproxy
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/csv"
|
|
"io"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/elastic/beats/metricbeat/mb/parse"
|
|
|
|
"github.com/gocarina/gocsv"
|
|
"github.com/mitchellh/mapstructure"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// HostParser is used for parsing the configured HAProxy hosts.
|
|
var HostParser = parse.URLHostParserBuilder{DefaultScheme: "tcp"}.Build()
|
|
|
|
// Stat is an instance of the HAProxy stat information
|
|
type Stat struct {
|
|
PxName string `csv:"# pxname"`
|
|
SvName string `csv:"svname"`
|
|
Qcur string `csv:"qcur"`
|
|
Qmax string `csv:"qmax"`
|
|
Scur string `csv:"scur"`
|
|
Smax string `csv:"smax"`
|
|
Slim string `csv:"slim"`
|
|
Stot string `csv:"stot"`
|
|
Bin string `csv:"bin"`
|
|
Bout string `csv:"bout"`
|
|
Dreq string `csv:"dreq"`
|
|
Dresp string `csv:"dresp"`
|
|
Ereq string `csv:"ereq"`
|
|
Econ string `csv:"econ"`
|
|
Eresp string `csv:"eresp"`
|
|
Wretr string `csv:"wretr"`
|
|
Wredis string `csv:"wredis"`
|
|
Status string `csv:"status"`
|
|
Weight string `csv:"weight"`
|
|
Act string `csv:"act"`
|
|
Bck string `csv:"bck"`
|
|
ChkFail string `csv:"chkfail"`
|
|
ChkDown string `csv:"chkdown"`
|
|
Lastchg string `csv:"lastchg"`
|
|
Downtime string `csv:"downtime"`
|
|
Qlimit string `csv:"qlimit"`
|
|
Pid string `csv:"pid"`
|
|
Iid string `csv:"iid"`
|
|
Sid string `csv:"sid"`
|
|
Throttle string `csv:"throttle"`
|
|
Lbtot string `csv:"lbtot"`
|
|
Tracked string `csv:"tracked"`
|
|
Type string `csv:"type"`
|
|
Rate string `csv:"rate"`
|
|
RateLim string `csv:"rate_lim"`
|
|
RateMax string `csv:"rate_max"`
|
|
CheckStatus string `csv:"check_status"`
|
|
CheckCode string `csv:"check_code"`
|
|
CheckDuration string `csv:"check_duration"`
|
|
Hrsp1xx string `csv:"hrsp_1xx"`
|
|
Hrsp2xx string `csv:"hrsp_2xx"`
|
|
Hrsp3xx string `csv:"hrsp_3xx"`
|
|
Hrsp4xx string `csv:"hrsp_4xx"`
|
|
Hrsp5xx string `csv:"hrsp_5xx"`
|
|
HrspOther string `csv:"hrsp_other"`
|
|
Hanafail string `csv:"hanafail"`
|
|
ReqRate string `csv:"req_rate"`
|
|
ReqRateMax string `csv:"req_rate_max"`
|
|
ReqTot string `csv:"req_tot"`
|
|
CliAbrt string `csv:"cli_abrt"`
|
|
SrvAbrt string `csv:"srv_abrt"`
|
|
CompIn string `csv:"comp_in"`
|
|
CompOut string `csv:"comp_out"`
|
|
CompByp string `csv:"comp_byp"`
|
|
CompRsp string `csv:"comp_rsp"`
|
|
LastSess string `csv:"lastsess"`
|
|
LastChk string `csv:"last_chk"`
|
|
LastAgt string `csv:"last_agt"`
|
|
Qtime string `csv:"qtime"`
|
|
Ctime string `csv:"ctime"`
|
|
Rtime string `csv:"rtime"`
|
|
Ttime string `csv:"ttime"`
|
|
}
|
|
|
|
type Info struct {
|
|
Name string `mapstructure:"Name"`
|
|
Version string `mapstructure:"Version"`
|
|
ReleaseDate string `mapstructure:"Release_date"`
|
|
Nbproc string `mapstructure:"Nbproc"`
|
|
ProcessNum string `mapstructure:"Process_num"`
|
|
Pid string `mapstructure:"Pid"`
|
|
Uptime string `mapstructure:"Uptime"`
|
|
UptimeSec string `mapstructure:"Uptime_sec"`
|
|
MemMax string `mapstructure:"Memmax_MB"`
|
|
UlimitN string `mapstructure:"Ulimit-n"`
|
|
Maxsock string `mapstructure:"Maxsock"`
|
|
Maxconn string `mapstructure:"Maxconn"`
|
|
HardMaxconn string `mapstructure:"Hard_maxconn"`
|
|
CurrConns string `mapstructure:"CurrConns"`
|
|
CumConns string `mapstructure:"CumConns"`
|
|
CumReq string `mapstructure:"CumReq"`
|
|
MaxSslConns string `mapstructure:"MaxSslConns"`
|
|
CurrSslConns string `mapstructure:"CurrSslConns"`
|
|
CumSslConns string `mapstructure:"CumSslConns"`
|
|
Maxpipes string `mapstructure:"Maxpipes"`
|
|
PipesUsed string `mapstructure:"PipesUsed"`
|
|
PipesFree string `mapstructure:"PipesFree"`
|
|
ConnRate string `mapstructure:"ConnRate"`
|
|
ConnRateLimit string `mapstructure:"ConnRateLimit"`
|
|
MaxConnRate string `mapstructure:"MaxConnRate"`
|
|
SessRate string `mapstructure:"SessRate"`
|
|
SessRateLimit string `mapstructure:"SessRateLimit"`
|
|
MaxSessRate string `mapstructure:"MaxSessRate"`
|
|
SslRate string `mapstructure:"SslRate"`
|
|
SslRateLimit string `mapstructure:"SslRateLimit"`
|
|
MaxSslRate string `mapstructure:"MaxSslRate"`
|
|
SslFrontendKeyRate string `mapstructure:"SslFrontendKeyRate"`
|
|
SslFrontendMaxKeyRate string `mapstructure:"SslFrontendMaxKeyRate"`
|
|
SslFrontendSessionReusePct string `mapstructure:"SslFrontendSessionReuse_pct"`
|
|
SslBackendKeyRate string `mapstructure:"SslBackendKeyRate"`
|
|
SslBackendMaxKeyRate string `mapstructure:"SslBackendMaxKeyRate"`
|
|
SslCacheLookups string `mapstructure:"SslCacheLookups"`
|
|
SslCacheMisses string `mapstructure:"SslCacheMisses"`
|
|
CompressBpsIn string `mapstructure:"CompressBpsIn"`
|
|
CompressBpsOut string `mapstructure:"CompressBpsOut"`
|
|
CompressBpsRateLim string `mapstructure:"CompressBpsRateLim"`
|
|
ZlibMemUsage string `mapstructure:"ZlibMemUsage"`
|
|
MaxZlibMemUsage string `mapstructure:"MaxZlibMemUsage"`
|
|
Tasks string `mapstructure:"Tasks"`
|
|
RunQueue string `mapstructure:"Run_queue"`
|
|
IdlePct string `mapstructure:"Idle_pct"`
|
|
Node string `mapstructure:"Node"`
|
|
Description string `mapstructure:"Description"`
|
|
}
|
|
|
|
// Client is an instance of the HAProxy client
|
|
type clientProto interface {
|
|
Stat() (*bytes.Buffer, error)
|
|
Info() (*bytes.Buffer, error)
|
|
}
|
|
|
|
type Client struct {
|
|
proto clientProto
|
|
}
|
|
|
|
// NewHaproxyClient returns a new instance of HaproxyClient
|
|
func NewHaproxyClient(address string) (*Client, error) {
|
|
u, err := url.Parse(address)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "invalid url")
|
|
}
|
|
|
|
switch u.Scheme {
|
|
case "tcp":
|
|
return &Client{&unixProto{Network: u.Scheme, Address: u.Host}}, nil
|
|
case "unix":
|
|
return &Client{&unixProto{Network: u.Scheme, Address: u.Path}}, nil
|
|
case "http", "https":
|
|
return &Client{&httpProto{URL: u}}, nil
|
|
default:
|
|
return nil, errors.Errorf("invalid protocol scheme: %s", u.Scheme)
|
|
}
|
|
}
|
|
|
|
// GetStat returns the result from the 'show stat' command
|
|
func (c *Client) GetStat() ([]*Stat, error) {
|
|
runResult, err := c.proto.Stat()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var statRes []*Stat
|
|
csvReader := csv.NewReader(runResult)
|
|
csvReader.TrailingComma = true
|
|
|
|
err = gocsv.UnmarshalCSV(csvReader, &statRes)
|
|
if err != nil {
|
|
return nil, errors.Errorf("error parsing CSV: %s", err)
|
|
}
|
|
|
|
return statRes, nil
|
|
}
|
|
|
|
// GetInfo returns the result from the 'show stat' command
|
|
func (c *Client) GetInfo() (*Info, error) {
|
|
res, err := c.proto.Info()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if b, err := ioutil.ReadAll(res); err == nil {
|
|
|
|
resultMap := map[string]interface{}{}
|
|
|
|
for _, ln := range strings.Split(string(b), "\n") {
|
|
|
|
ln := strings.TrimSpace(ln)
|
|
if ln == "" {
|
|
continue
|
|
}
|
|
|
|
parts := strings.Split(ln, ":")
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
|
|
resultMap[parts[0]] = strings.TrimSpace(parts[1])
|
|
}
|
|
|
|
var result *Info
|
|
|
|
if err := mapstructure.Decode(resultMap, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
type unixProto struct {
|
|
Network string
|
|
Address string
|
|
}
|
|
|
|
// Run sends a designated command to the haproxy stats socket
|
|
func (p *unixProto) run(cmd string) (*bytes.Buffer, error) {
|
|
var conn net.Conn
|
|
response := bytes.NewBuffer(nil)
|
|
|
|
conn, err := net.Dial(p.Network, p.Address)
|
|
if err != nil {
|
|
return response, err
|
|
}
|
|
defer conn.Close()
|
|
|
|
_, err = conn.Write([]byte(cmd + "\n"))
|
|
if err != nil {
|
|
return response, err
|
|
}
|
|
|
|
_, err = io.Copy(response, conn)
|
|
if err != nil {
|
|
return response, err
|
|
}
|
|
|
|
if strings.HasPrefix(response.String(), "Unknown command") {
|
|
return response, errors.Errorf("unknown command: %s", cmd)
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
func (p *unixProto) Stat() (*bytes.Buffer, error) {
|
|
return p.run("show stat")
|
|
}
|
|
|
|
func (p *unixProto) Info() (*bytes.Buffer, error) {
|
|
return p.run("show info")
|
|
}
|
|
|
|
type httpProto struct {
|
|
URL *url.URL
|
|
}
|
|
|
|
func (p *httpProto) Stat() (*bytes.Buffer, error) {
|
|
url := p.URL.String()
|
|
// Force csv format
|
|
if !strings.HasSuffix(url, ";csv") {
|
|
url += ";csv"
|
|
}
|
|
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if p.URL.User != nil {
|
|
password, _ := p.URL.User.Password()
|
|
req.SetBasicAuth(p.URL.User.Username(), password)
|
|
}
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return nil, errors.Errorf("couldn't connect: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, errors.Errorf("invalid response: %s", resp.Status)
|
|
}
|
|
|
|
d, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, errors.Errorf("couldn't read response body: %v", err)
|
|
}
|
|
return bytes.NewBuffer(d), nil
|
|
}
|
|
|
|
func (p *httpProto) Info() (*bytes.Buffer, error) {
|
|
return nil, errors.New("not supported")
|
|
}
|