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

     1  package mpnginx
     2  
     3  import (
     4  	"bufio"
     5  	"flag"
     6  	"fmt"
     7  	"io"
     8  
     9  	"errors"
    10  	"net/http"
    11  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  
    15  	mp "github.com/mackerelio/go-mackerel-plugin-helper"
    16  )
    17  
    18  var graphdef = map[string]mp.Graphs{
    19  	"nginx.connections": {
    20  		Label: "Nginx Connections",
    21  		Unit:  "integer",
    22  		Metrics: []mp.Metrics{
    23  			{Name: "connections", Label: "Active connections", Diff: false},
    24  		},
    25  	},
    26  	"nginx.requests": {
    27  		Label: "Nginx requests",
    28  		Unit:  "float",
    29  		Metrics: []mp.Metrics{
    30  			{Name: "accepts", Label: "Accepted connections", Diff: true, Type: "uint64"},
    31  			{Name: "handled", Label: "Handled connections", Diff: true, Type: "uint64"},
    32  			{Name: "requests", Label: "Handled requests", Diff: true, Type: "uint64"},
    33  		},
    34  	},
    35  	"nginx.queue": {
    36  		Label: "Nginx connection status",
    37  		Unit:  "integer",
    38  		Metrics: []mp.Metrics{
    39  			{Name: "reading", Label: "Reading", Diff: false},
    40  			{Name: "writing", Label: "Writing", Diff: false},
    41  			{Name: "waiting", Label: "Waiting", Diff: false},
    42  		},
    43  	},
    44  }
    45  
    46  type stringSlice []string
    47  
    48  func (s *stringSlice) Set(v string) error {
    49  	*s = append(*s, v)
    50  	return nil
    51  }
    52  
    53  func (s *stringSlice) String() string {
    54  	return fmt.Sprintf("%v", *s)
    55  }
    56  
    57  // NginxPlugin mackerel plugin for Nginx
    58  type NginxPlugin struct {
    59  	URI    string
    60  	Header stringSlice
    61  }
    62  
    63  // % wget -qO- http://localhost:8080/nginx_status
    64  // Active connections: 123
    65  // server accepts handled requests
    66  //  1693613501 1693613501 7996986318
    67  // Reading: 66 Writing: 16 Waiting: 41
    68  
    69  // FetchMetrics interface for mackerelplugin
    70  func (n NginxPlugin) FetchMetrics() (map[string]interface{}, error) {
    71  	req, err := http.NewRequest("GET", n.URI, nil)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	for _, h := range n.Header {
    76  		kv := strings.SplitN(h, ":", 2)
    77  		var k, v string
    78  		k = strings.TrimSpace(kv[0])
    79  		if len(kv) == 2 {
    80  			v = strings.TrimSpace(kv[1])
    81  		}
    82  		if http.CanonicalHeaderKey(k) == "Host" {
    83  			req.Host = v
    84  		} else {
    85  			req.Header.Set(k, v)
    86  		}
    87  	}
    88  
    89  	// set default User-Agent unless specified by n.Header
    90  	if _, ok := req.Header["User-Agent"]; !ok {
    91  		req.Header.Set("User-Agent", "mackerel-plugin-nginx")
    92  	}
    93  
    94  	resp, err := http.DefaultClient.Do(req)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  	defer resp.Body.Close()
    99  
   100  	return n.parseStats(resp.Body)
   101  }
   102  
   103  func (n NginxPlugin) parseStats(body io.Reader) (map[string]interface{}, error) {
   104  	stat := make(map[string]interface{})
   105  
   106  	r := bufio.NewReader(body)
   107  	line, _, err := r.ReadLine()
   108  	if err != nil {
   109  		return nil, errors.New("cannot get values")
   110  	}
   111  	re := regexp.MustCompile("Active connections: ([0-9]+)")
   112  	res := re.FindStringSubmatch(string(line))
   113  	if res == nil || len(res) != 2 {
   114  		return nil, errors.New("cannot get values")
   115  	}
   116  	stat["connections"], err = strconv.ParseFloat(res[1], 64)
   117  	if err != nil {
   118  		return nil, errors.New("cannot get values")
   119  	}
   120  
   121  	_, _, err = r.ReadLine()
   122  	if err != nil {
   123  		return nil, errors.New("cannot get values")
   124  	}
   125  
   126  	line, _, err = r.ReadLine()
   127  	if err != nil {
   128  		return nil, errors.New("cannot get values")
   129  	}
   130  	re = regexp.MustCompile("([0-9]+) ([0-9]+) ([0-9]+)")
   131  	res = re.FindStringSubmatch(string(line))
   132  	if res == nil || len(res) != 4 {
   133  		return nil, errors.New("cannot get values")
   134  	}
   135  	stat["accepts"], err = strconv.ParseFloat(res[1], 64)
   136  	if err != nil {
   137  		return nil, errors.New("cannot get values")
   138  	}
   139  	stat["handled"], err = strconv.ParseFloat(res[2], 64)
   140  	if err != nil {
   141  		return nil, errors.New("cannot get values")
   142  	}
   143  	stat["requests"], err = strconv.ParseFloat(res[3], 64)
   144  	if err != nil {
   145  		return nil, errors.New("cannot get values")
   146  	}
   147  
   148  	line, _, err = r.ReadLine()
   149  	if err != nil {
   150  		return nil, errors.New("cannot get values")
   151  	}
   152  	re = regexp.MustCompile("Reading: ([0-9]+) Writing: ([0-9]+) Waiting: ([0-9]+)")
   153  	res = re.FindStringSubmatch(string(line))
   154  	if res == nil || len(res) != 4 {
   155  		return nil, errors.New("cannot get values")
   156  	}
   157  	stat["reading"], err = strconv.ParseFloat(res[1], 64)
   158  	if err != nil {
   159  		return nil, errors.New("cannot get values")
   160  	}
   161  	stat["writing"], err = strconv.ParseFloat(res[2], 64)
   162  	if err != nil {
   163  		return nil, errors.New("cannot get values")
   164  	}
   165  	stat["waiting"], err = strconv.ParseFloat(res[3], 64)
   166  	if err != nil {
   167  		return nil, errors.New("cannot get values")
   168  	}
   169  	return stat, nil
   170  }
   171  
   172  // GraphDefinition interface for mackerelplugin
   173  func (n NginxPlugin) GraphDefinition() map[string]mp.Graphs {
   174  	return graphdef
   175  }
   176  
   177  // Do the plugin
   178  func Do() {
   179  	optURI := flag.String("uri", "", "URI")
   180  	optScheme := flag.String("scheme", "http", "Scheme")
   181  	optHost := flag.String("host", "localhost", "Hostname")
   182  	optPort := flag.String("port", "8080", "Port")
   183  	optPath := flag.String("path", "/nginx_status", "Path")
   184  	optTempfile := flag.String("tempfile", "", "Temp file name")
   185  	optHeader := &stringSlice{}
   186  	flag.Var(optHeader, "header", "Set http header (e.g. \"Host: servername\")")
   187  	flag.Parse()
   188  
   189  	var nginx NginxPlugin
   190  	if *optURI != "" {
   191  		nginx.URI = *optURI
   192  	} else {
   193  		nginx.URI = fmt.Sprintf("%s://%s:%s%s", *optScheme, *optHost, *optPort, *optPath)
   194  	}
   195  	nginx.Header = *optHeader
   196  
   197  	helper := mp.NewMackerelPlugin(nginx)
   198  	helper.Tempfile = *optTempfile
   199  	helper.Run()
   200  }