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

     1  //go:build linux
     2  
     3  package mpmailq
     4  
     5  import (
     6  	"bufio"
     7  	"flag"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"os/exec"
    12  	"regexp"
    13  	"strconv"
    14  	"strings"
    15  
    16  	mp "github.com/mackerelio/go-mackerel-plugin-helper"
    17  )
    18  
    19  type mailq struct {
    20  	command string
    21  	args    []string
    22  	line    int
    23  	pattern string
    24  }
    25  
    26  var mailqFormats = map[string]mailq{
    27  	"postfix": {
    28  		command: "postqueue",
    29  		args:    []string{"-p"},
    30  		line:    -1,
    31  		pattern: `-- \d+ Kbytes in (\d+) (?:Request|Requests)\.`,
    32  	},
    33  	"qmail": {
    34  		command: "qmail-qstat",
    35  		pattern: `messages in queue: (\d+)`,
    36  	},
    37  	"exim": {
    38  		command: "exim",
    39  		args:    []string{"-bpc"},
    40  		pattern: `(\d+)`,
    41  	},
    42  }
    43  
    44  type plugin struct {
    45  	path                   string
    46  	mailq                  mailq
    47  	keyPrefix, labelPrefix string
    48  }
    49  
    50  func (format *mailq) parse(rd io.Reader) (count uint64, err error) {
    51  	l, err := getNthLine(rd, format.line)
    52  	if err != nil {
    53  		return
    54  	}
    55  
    56  	m := regexp.MustCompile(format.pattern).FindStringSubmatch(l)
    57  	if m != nil {
    58  		count, err = strconv.ParseUint(m[1], 10, 64)
    59  	}
    60  
    61  	return
    62  }
    63  
    64  func (p *plugin) fetchMailqCount() (count uint64, err error) {
    65  
    66  	var path string
    67  	if p.path != "" {
    68  		path = p.path
    69  	} else {
    70  		path, err = exec.LookPath(p.mailq.command)
    71  		if err != nil {
    72  			return
    73  		}
    74  	}
    75  
    76  	cmd := exec.Cmd{
    77  		Path: path,
    78  		Args: append([]string{p.mailq.command}, p.mailq.args...),
    79  	}
    80  
    81  	if p.path != "" {
    82  		cmd.Path = p.path
    83  	}
    84  
    85  	stdout, err := cmd.StdoutPipe()
    86  	if err != nil {
    87  		return
    88  	}
    89  
    90  	err = cmd.Start()
    91  	if err != nil {
    92  		return
    93  	}
    94  
    95  	count, err = p.mailq.parse(stdout)
    96  	if err != nil {
    97  		cmd.Wait() // nolint
    98  		return
    99  	}
   100  
   101  	err = cmd.Wait()
   102  	return
   103  }
   104  
   105  func (p *plugin) FetchMetrics() (map[string]interface{}, error) {
   106  	count, err := p.fetchMailqCount()
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	return map[string]interface{}{"count": count}, nil
   112  }
   113  
   114  func (p *plugin) GraphDefinition() map[string]mp.Graphs {
   115  	return map[string]mp.Graphs{
   116  		p.keyPrefix: {
   117  			Label: p.labelPrefix + " Count",
   118  			Unit:  "integer",
   119  			Metrics: []mp.Metrics{
   120  				{Name: "count", Label: "count", Type: "uint64"},
   121  			},
   122  		},
   123  	}
   124  }
   125  
   126  func getNthLine(rd io.Reader, nth int) (string, error) {
   127  	var result string
   128  
   129  	brd := bufio.NewReader(rd)
   130  
   131  	if nth >= 0 { // get nth-first line (0 is the first line)
   132  		for i := 0; ; i++ {
   133  			s, err := brd.ReadString('\n')
   134  			if err == nil || err == io.EOF {
   135  				if i == nth {
   136  					result = strings.TrimRight(s, "\n")
   137  				}
   138  				if err == io.EOF {
   139  					break
   140  				}
   141  			} else {
   142  				return "", err
   143  			}
   144  		}
   145  	} else { // get nth-last line (-1 is the last line, -2 is the second last line)
   146  		buffer := make([]string, -nth)
   147  		i := 0
   148  		for {
   149  			s, err := brd.ReadString('\n')
   150  			if err == nil || err == io.EOF {
   151  				if s != "" {
   152  					buffer[i%-nth] = strings.TrimRight(s, "\n")
   153  					i++
   154  				}
   155  
   156  				if err == io.EOF {
   157  					result = buffer[i%-nth]
   158  					break
   159  				}
   160  			} else {
   161  				return "", err
   162  			}
   163  		}
   164  	}
   165  
   166  	return result, nil
   167  }
   168  
   169  // Do the plugin
   170  func Do() {
   171  	var mtas []string
   172  	for k := range mailqFormats {
   173  		mtas = append(mtas, k)
   174  	}
   175  
   176  	mta := flag.String("mta", "", fmt.Sprintf("type of MTA (one of %v)", mtas))
   177  	flag.StringVar(mta, "M", "", "shorthand for -mta")
   178  	command := flag.String("command", "", "path to queue-printing command (guessed by -M flag if not given)")
   179  	flag.StringVar(command, "c", "", "shorthand for -command")
   180  	tempfile := flag.String("tempfile", "", "path to tempfile")
   181  	keyPrefix := flag.String("metric-key-prefix", "mailq", "prefix to metric key")
   182  	labelPrefix := flag.String("metric-label-prefix", "Mailq", "prefix to metric label")
   183  
   184  	flag.Parse()
   185  
   186  	if format, ok := mailqFormats[*mta]; *mta == "" || !ok {
   187  		fmt.Fprintf(os.Stderr, "Unknown MTA: %s\n", *mta)
   188  		flag.PrintDefaults()
   189  		os.Exit(1)
   190  	} else {
   191  		plugin := &plugin{
   192  			path:        *command,
   193  			mailq:       format,
   194  			keyPrefix:   *keyPrefix,
   195  			labelPrefix: *labelPrefix,
   196  		}
   197  		helper := mp.NewMackerelPlugin(plugin)
   198  		helper.Tempfile = *tempfile
   199  		helper.Run()
   200  	}
   201  }