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

     1  package mpapache2
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"log"
     8  	"net/http"
     9  	"os"
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  
    14  	mp "github.com/mackerelio/go-mackerel-plugin-helper"
    15  	"github.com/urfave/cli"
    16  )
    17  
    18  // Apache2Plugin for fetching metrics
    19  type Apache2Plugin struct {
    20  	Host        string
    21  	Port        uint16
    22  	Path        string
    23  	Header      []string
    24  	Tempfile    string
    25  	Prefix      string
    26  	LabelPrefix string
    27  }
    28  
    29  // MetricKeyPrefix interface for PluginWithPrefix
    30  func (c Apache2Plugin) MetricKeyPrefix() string {
    31  	if c.Prefix == "" {
    32  		c.Prefix = "apache2"
    33  	}
    34  	return c.Prefix
    35  }
    36  
    37  // GraphDefinition Graph definition
    38  func (c Apache2Plugin) GraphDefinition() map[string]mp.Graphs {
    39  	labelPrefix := c.LabelPrefix
    40  
    41  	// metric value structure
    42  	var graphdef = map[string]mp.Graphs{
    43  		"workers": {
    44  			Label: (labelPrefix + " Workers"),
    45  			Unit:  "integer",
    46  			Metrics: []mp.Metrics{
    47  				{Name: "busy_workers", Label: "Busy Workers", Diff: false, Stacked: true},
    48  				{Name: "idle_workers", Label: "Idle Workers", Diff: false, Stacked: true},
    49  			},
    50  		},
    51  		"bytes": {
    52  			Label: (labelPrefix + " Bytes"),
    53  			Unit:  "bytes",
    54  			Metrics: []mp.Metrics{
    55  				{Name: "bytes_sent", Label: "Bytes Sent", Diff: true, Type: "uint64"},
    56  			},
    57  		},
    58  		"cpu": {
    59  			Label: (labelPrefix + " CPU Load"),
    60  			Unit:  "float",
    61  			Metrics: []mp.Metrics{
    62  				{Name: "cpu_load", Label: "CPU Load", Diff: false},
    63  			},
    64  		},
    65  		"req": {
    66  			Label: (labelPrefix + " Requests"),
    67  			Unit:  "integer",
    68  			Metrics: []mp.Metrics{
    69  				{Name: "requests", Label: "Requests", Diff: true, Type: "uint64"},
    70  			},
    71  		},
    72  		"scoreboard": {
    73  			Label: (labelPrefix + " Scoreboard"),
    74  			Unit:  "integer",
    75  			Metrics: []mp.Metrics{
    76  				{Name: "score-_", Label: "Waiting for connection", Diff: false, Stacked: true},
    77  				{Name: "score-S", Label: "Starting up", Diff: false, Stacked: true},
    78  				{Name: "score-R", Label: "Reading request", Diff: false, Stacked: true},
    79  				{Name: "score-W", Label: "Sending reply", Diff: false, Stacked: true},
    80  				{Name: "score-K", Label: "Keepalive", Diff: false, Stacked: true},
    81  				{Name: "score-D", Label: "DNS lookup", Diff: false, Stacked: true},
    82  				{Name: "score-C", Label: "Closing connection", Diff: false, Stacked: true},
    83  				{Name: "score-L", Label: "Logging", Diff: false, Stacked: true},
    84  				{Name: "score-G", Label: "Gracefully finishing", Diff: false, Stacked: true},
    85  				{Name: "score-I", Label: "Idle cleanup", Diff: false, Stacked: true},
    86  				{Name: "score-", Label: "Open slot", Diff: false, Stacked: true},
    87  			},
    88  		},
    89  	}
    90  	return graphdef
    91  }
    92  
    93  // main function
    94  func doMain(c *cli.Context) error {
    95  
    96  	var apache2 Apache2Plugin
    97  
    98  	apache2.Host = c.String("http_host")
    99  	apache2.Port = uint16(c.Int("http_port"))
   100  	apache2.Path = c.String("status_page")
   101  	apache2.Header = c.StringSlice("header")
   102  	apache2.Prefix = c.String("metric-key-prefix")
   103  	apache2.LabelPrefix = c.String("metric-label-prefix")
   104  
   105  	helper := mp.NewMackerelPlugin(apache2)
   106  	helper.Tempfile = c.String("tempfile")
   107  
   108  	helper.Run()
   109  	return nil
   110  }
   111  
   112  // FetchMetrics fetch the metrics
   113  func (c Apache2Plugin) FetchMetrics() (map[string]interface{}, error) {
   114  	data, err := getApache2Metrics(c.Host, c.Port, c.Path, c.Header)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	stat := make(map[string]interface{})
   120  	errStat := parseApache2Status(data, &stat)
   121  	if errStat != nil {
   122  		return nil, errStat
   123  	}
   124  	errScore := parseApache2Scoreboard(data, &stat)
   125  	if errScore != nil {
   126  		return nil, errScore
   127  	}
   128  
   129  	return stat, nil
   130  }
   131  
   132  var scoreboardLine = regexp.MustCompile("Scoreboard(.*)")
   133  
   134  // parsing scoreboard from server-status?auto
   135  func parseApache2Scoreboard(str string, p *map[string]interface{}) error {
   136  	for _, line := range strings.Split(str, "\n") {
   137  		if !scoreboardLine.MatchString(line) {
   138  			continue
   139  		}
   140  		record := strings.Split(line, ":")
   141  		for _, sb := range strings.Split(strings.Trim(record[1], " "), "") {
   142  			if sb == "." {
   143  				sb = ""
   144  			}
   145  			name := fmt.Sprintf("score-%s", sb)
   146  			c, assert := (*p)[name].(float64)
   147  			if !assert {
   148  				c = 0.0
   149  			}
   150  			(*p)[name] = c + 1.0
   151  		}
   152  		return nil
   153  	}
   154  
   155  	return errors.New("scoreboard data is not found")
   156  }
   157  
   158  // parsing metrics from server-status?auto
   159  func parseApache2Status(str string, p *map[string]interface{}) error {
   160  	Params := map[string]string{
   161  		"Total Accesses": "requests",
   162  		"Total kBytes":   "bytes_sent",
   163  		"CPULoad":        "cpu_load",
   164  		"BusyWorkers":    "busy_workers",
   165  		"IdleWorkers":    "idle_workers"}
   166  
   167  	for _, line := range strings.Split(str, "\n") {
   168  		record := strings.Split(line, ":")
   169  		_, assert := Params[record[0]]
   170  		if !assert {
   171  			continue
   172  		}
   173  		var errParse error
   174  		(*p)[Params[record[0]]], errParse = strconv.ParseFloat(strings.Trim(record[1], " "), 64)
   175  		if errParse != nil {
   176  			return errParse
   177  		}
   178  	}
   179  
   180  	if len(*p) == 0 {
   181  		return errors.New("status data not found")
   182  	}
   183  
   184  	return nil
   185  }
   186  
   187  // Getting apache2 status from server-status module data.
   188  func getApache2Metrics(host string, port uint16, path string, header []string) (string, error) {
   189  	uri := "http://" + host + ":" + strconv.FormatUint(uint64(port), 10) + path
   190  	req, err := http.NewRequest("GET", uri, nil)
   191  	if err != nil {
   192  		return "", err
   193  	}
   194  	for _, h := range header {
   195  		kv := strings.SplitN(h, ":", 2)
   196  		var k, v string
   197  		k = strings.TrimSpace(kv[0])
   198  		if len(kv) == 2 {
   199  			v = strings.TrimSpace(kv[1])
   200  		}
   201  		if http.CanonicalHeaderKey(k) == "Host" {
   202  			req.Host = v
   203  		} else {
   204  			req.Header.Set(k, v)
   205  		}
   206  	}
   207  
   208  	// set default User-Agent unless specified by header
   209  	if _, ok := req.Header["User-Agent"]; !ok {
   210  		req.Header.Set("User-Agent", "mackerel-plugin-apache2")
   211  	}
   212  
   213  	resp, err := http.DefaultClient.Do(req)
   214  	if err != nil {
   215  		return "", err
   216  	}
   217  	defer resp.Body.Close()
   218  	if resp.StatusCode != http.StatusOK {
   219  		return "", fmt.Errorf("HTTP status error: %d", resp.StatusCode)
   220  	}
   221  	body, err := io.ReadAll(resp.Body)
   222  	if err != nil {
   223  		return "", err
   224  	}
   225  	return string(body[:]), nil
   226  }
   227  
   228  // Do the plugin
   229  func Do() {
   230  	app := cli.NewApp()
   231  	app.Name = "apache2_metrics"
   232  	app.Usage = "Get metrics from apache2."
   233  	app.Author = "Yuichiro Saito"
   234  	app.Email = "saito@heartbeats.jp"
   235  	app.Flags = flags
   236  	app.Action = doMain
   237  
   238  	err := app.Run(os.Args)
   239  	if err != nil {
   240  		log.Fatalln(err)
   241  	}
   242  }