github.com/mackerelio/mackerel-agent-plugins@v0.89.3/mackerel-plugin-haproxy/lib/haproxy.go (about)

     1  package mphaproxy
     2  
     3  import (
     4  	"bufio"
     5  	"encoding/csv"
     6  	"errors"
     7  	"flag"
     8  	"fmt"
     9  	"io"
    10  	"net"
    11  	"net/http"
    12  	"os"
    13  	"strconv"
    14  	"time"
    15  
    16  	mp "github.com/mackerelio/go-mackerel-plugin"
    17  )
    18  
    19  var graphdef = map[string]mp.Graphs{
    20  	"haproxy.total.sessions": {
    21  		Label: "HAProxy Total Sessions",
    22  		Unit:  "integer",
    23  		Metrics: []mp.Metrics{
    24  			{Name: "sessions", Label: "Sessions", Diff: true},
    25  		},
    26  	},
    27  	"haproxy.total.bytes": {
    28  		Label: "HAProxy Total Bytes",
    29  		Unit:  "integer",
    30  		Metrics: []mp.Metrics{
    31  			{Name: "bytes_in", Label: "Bytes In", Diff: true},
    32  			{Name: "bytes_out", Label: "Bytes Out", Diff: true},
    33  		},
    34  	},
    35  	"haproxy.total.connection_errors": {
    36  		Label: "HAProxy Total Connection Errors",
    37  		Unit:  "integer",
    38  		Metrics: []mp.Metrics{
    39  			{Name: "connection_errors", Label: "Connection Errors", Diff: true},
    40  		},
    41  	},
    42  }
    43  
    44  // HAProxyPlugin mackerel plugin for haproxy
    45  type HAProxyPlugin struct {
    46  	URI      string
    47  	Username string
    48  	Password string
    49  	Socket   string
    50  }
    51  
    52  // FetchMetrics interface for mackerelplugin
    53  func (p HAProxyPlugin) FetchMetrics() (map[string]float64, error) {
    54  	var metrics map[string]float64
    55  	var err error
    56  	if p.Socket == "" {
    57  		metrics, err = p.fetchMetricsFromTCP()
    58  	} else {
    59  		metrics, err = p.fetchMetricsFromSocket()
    60  	}
    61  	return metrics, err
    62  }
    63  
    64  func (p HAProxyPlugin) fetchMetricsFromTCP() (map[string]float64, error) {
    65  	client := &http.Client{
    66  		Timeout: time.Duration(5) * time.Second,
    67  	}
    68  
    69  	requestURI := p.URI + ";csv;norefresh"
    70  	req, err := http.NewRequest("GET", requestURI, nil)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	if p.Username != "" {
    75  		req.SetBasicAuth(p.Username, p.Password)
    76  	}
    77  	req.Header.Set("User-Agent", "mackerel-plugin-haproxy")
    78  
    79  	resp, err := client.Do(req)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	defer resp.Body.Close()
    84  
    85  	if resp.StatusCode != 200 {
    86  		return nil, fmt.Errorf("Request failed. Status: %s, URI: %s", resp.Status, requestURI) // nolint
    87  	}
    88  
    89  	return p.parseStats(resp.Body)
    90  }
    91  
    92  func (p HAProxyPlugin) fetchMetricsFromSocket() (map[string]float64, error) {
    93  	client, err := net.Dial("unix", p.Socket)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	defer client.Close()
    98  
    99  	fmt.Fprintln(client, "show stat")
   100  
   101  	return p.parseStats(bufio.NewReader(client))
   102  }
   103  
   104  func (p HAProxyPlugin) parseStats(statsBody io.Reader) (map[string]float64, error) {
   105  	stat := make(map[string]float64)
   106  	reader := csv.NewReader(statsBody)
   107  
   108  	for {
   109  		columns, err := reader.Read()
   110  		if err == io.EOF {
   111  			break
   112  		}
   113  
   114  		if len(columns) < 60 {
   115  			return nil, errors.New("length of stats csv is too short (specified uri/socket may be wrong)")
   116  		}
   117  
   118  		if columns[1] != "BACKEND" {
   119  			continue
   120  		}
   121  
   122  		var data float64
   123  
   124  		data, err = strconv.ParseFloat(columns[7], 64)
   125  		if err != nil {
   126  			return nil, errors.New("cannot get values")
   127  		}
   128  		stat["sessions"] += data
   129  
   130  		data, err = strconv.ParseFloat(columns[8], 64)
   131  		if err != nil {
   132  			return nil, errors.New("cannot get values")
   133  		}
   134  		stat["bytes_in"] += data
   135  
   136  		data, err = strconv.ParseFloat(columns[9], 64)
   137  		if err != nil {
   138  			return nil, errors.New("cannot get values")
   139  		}
   140  		stat["bytes_out"] += data
   141  
   142  		data, err = strconv.ParseFloat(columns[13], 64)
   143  		if err != nil {
   144  			return nil, errors.New("cannot get values")
   145  		}
   146  		stat["connection_errors"] += data
   147  	}
   148  
   149  	return stat, nil
   150  }
   151  
   152  // GraphDefinition interface for mackerelplugin
   153  func (p HAProxyPlugin) GraphDefinition() map[string]mp.Graphs {
   154  	return graphdef
   155  }
   156  
   157  // Do the plugin
   158  func Do() {
   159  	optURI := flag.String("uri", "", "URI")
   160  	optScheme := flag.String("scheme", "http", "Scheme")
   161  	optHost := flag.String("host", "localhost", "Hostname")
   162  	optPort := flag.String("port", "80", "Port")
   163  	optPath := flag.String("path", "/", "Path")
   164  	optUsername := flag.String("username", "", "Username for Basic Auth")
   165  	optPassword := flag.String("password", os.Getenv("HAPROXY_PASSWORD"), "Password for Basic Auth")
   166  	optTempfile := flag.String("tempfile", "", "Temp file name")
   167  	optSocket := flag.String("socket", "", "Unix Domain Socket")
   168  	flag.Parse()
   169  
   170  	var haproxy HAProxyPlugin
   171  	if *optURI != "" {
   172  		haproxy.URI = *optURI
   173  	} else {
   174  		haproxy.URI = fmt.Sprintf("%s://%s:%s%s", *optScheme, *optHost, *optPort, *optPath)
   175  	}
   176  
   177  	if *optUsername != "" {
   178  		haproxy.Username = *optUsername
   179  	}
   180  
   181  	if *optPassword != "" {
   182  		haproxy.Password = *optPassword
   183  	}
   184  
   185  	if *optSocket != "" {
   186  		haproxy.Socket = *optSocket
   187  	}
   188  
   189  	helper := mp.NewMackerelPlugin(haproxy)
   190  	helper.Tempfile = *optTempfile
   191  
   192  	helper.Run()
   193  }