bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/scollector/collectors/apache_mod_info_linux.go (about)

     1  package collectors
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"strconv"
     7  	"strings"
     8  	"time"
     9  
    10  	"golang.org/x/net/html"
    11  
    12  	"bosun.org/metadata"
    13  	"bosun.org/opentsdb"
    14  )
    15  
    16  const (
    17  	apacheModInfoBaseURL = "http://127.0.0.1/server-info"
    18  )
    19  
    20  func init() {
    21  	collectors = append(
    22  		collectors,
    23  		&IntervalCollector{
    24  			F:        c_apache_mod_info,
    25  			Enable:   enableURL(apacheModInfoBaseURL, "Apache Server Information"),
    26  			Interval: time.Minute * 30,
    27  		})
    28  }
    29  
    30  /* extractTimeouts processes the "?server" output and extracts timeout settings.
    31  More specifically it finds the line that looks like:
    32   "Timeouts: connection: 60 keep-alive: 15"
    33  and returns the two values contained therein.
    34  */
    35  func extractTimeouts(doc *html.Node) (int, int, error) {
    36  
    37  	// Walk the node tree.
    38  	// If the node is a "dt",
    39  	//   walk its children until we find contents "Timeouts:"
    40  	//   At which point we walk those children to find "connection".
    41  	// Else, keep walking.
    42  	//   Then continue walking Children to find contents starts with "connection":
    43  	//     Return contents
    44  
    45  	var walkSubtree func(*html.Node) string
    46  	var searchTimeouts func(*html.Node) string
    47  
    48  	walkSubtree = func(n *html.Node) string {
    49  		if n.Type == html.ElementNode && n.Data == "dt" {
    50  			if val := searchTimeouts(n); val != "" {
    51  				return val
    52  			}
    53  		} else {
    54  			for c := n.FirstChild; c != nil; c = c.NextSibling {
    55  				if val := walkSubtree(c); val != "" {
    56  					return val
    57  				}
    58  			}
    59  		}
    60  		return ""
    61  	}
    62  
    63  	searchTimeouts = func(n *html.Node) string {
    64  		if n.Type == html.TextNode && strings.HasPrefix(n.Data, "connection:") {
    65  			return n.Data
    66  		}
    67  		for c := n.FirstChild; c != nil; c = c.NextSibling {
    68  			if value := searchTimeouts(c); value != "" {
    69  				return value
    70  			}
    71  		}
    72  		return ""
    73  	}
    74  
    75  	original := walkSubtree(doc)
    76  	parts := strings.Fields(original)
    77  	if len(parts) < 4 {
    78  		return 0, 0, fmt.Errorf("fewer than 4 fields found on connection:/keep-alive line")
    79  	}
    80  	if (parts[0] != "connection:") || (parts[2] != "keep-alive:") {
    81  		return 0, 0, fmt.Errorf("format changed in connection:/keep-alive: line")
    82  	}
    83  
    84  	c, err := strconv.Atoi(parts[1])
    85  	if err != nil {
    86  		return 0, 0, fmt.Errorf("connection timeout is not an integer")
    87  	}
    88  	k, err := strconv.Atoi(parts[3])
    89  	if err != nil {
    90  		return 0, 0, fmt.Errorf("keep-alive value is not an integer")
    91  	}
    92  	return c, k, nil
    93  }
    94  
    95  /* extractMpmInfo processes the "?server" parse tree and extracts MPM settings.
    96  More specifically it finds the line that looks like:
    97   "MPM Information: Max Daemons: 64 Threaded: no Forked: yes"
    98  and returns the three values contained therein.
    99  */
   100  func extractMpmInfo(doc *html.Node) (int, bool, bool, error) {
   101  
   102  	// Walk the node tree.
   103  	// If the node is a "dt",
   104  	//   walk its children until we find contents "Timeouts:"
   105  	//   At which point we walk those children to find "connection".
   106  	// Else, keep walking.
   107  	//   Then continue walking Children to find contents starts with "connection":
   108  	//     Return contents
   109  
   110  	var walkSubtree func(*html.Node) string
   111  	var hasMpmInfo func(*html.Node) bool
   112  	var getMpmInfo func(*html.Node) string
   113  
   114  	walkSubtree = func(n *html.Node) string {
   115  		if n.Type == html.ElementNode && n.Data == "dt" {
   116  			if hasMpmInfo(n) {
   117  				if sib := n.FirstChild.NextSibling; sib != nil {
   118  					return getMpmInfo(sib)
   119  				}
   120  				return ""
   121  			}
   122  		} else {
   123  			for c := n.FirstChild; c != nil; c = c.NextSibling {
   124  				if val := walkSubtree(c); val != "" {
   125  					return val
   126  				}
   127  			}
   128  		}
   129  		return ""
   130  	}
   131  
   132  	hasMpmInfo = func(n *html.Node) bool {
   133  		if n.Type == html.TextNode && strings.HasPrefix(n.Data, "MPM Information:") {
   134  			return true
   135  		}
   136  		for c := n.FirstChild; c != nil; c = c.NextSibling {
   137  			if value := hasMpmInfo(c); value {
   138  				return true
   139  			}
   140  		}
   141  		return false
   142  	}
   143  
   144  	getMpmInfo = func(n *html.Node) string {
   145  		// Scan across the siblings:
   146  		for s := n; s != nil; s = s.NextSibling {
   147  			// if it is a text node, see if it what we want and return it.
   148  			if s.Type == html.TextNode {
   149  				if strings.HasPrefix(s.Data, "Max Daemons:") {
   150  					return s.Data
   151  				}
   152  			} else if s.Type == html.ElementNode {
   153  				// Resursively check the children:
   154  				for c := s.FirstChild; c != nil; c = c.NextSibling {
   155  					if val := getMpmInfo(c); val != "" {
   156  						return val
   157  					}
   158  				}
   159  			}
   160  		}
   161  		return ""
   162  	}
   163  
   164  	original := walkSubtree(doc)
   165  	parts := strings.Fields(original)
   166  	if len(parts) < 7 {
   167  		return 0, false, false, fmt.Errorf("fewer than 7 fields found on MPM Information line")
   168  	}
   169  	// Max Daemons: 64 Threaded: no Forked: yes
   170  	// 0   1        2  3         4  5       6
   171  	if (parts[0] != "Max") || (parts[1] != "Daemons:") || (parts[3] != "Threaded:") || (parts[5] != "Forked:") {
   172  		return 0, false, false, fmt.Errorf("wrong format found on MPM Information line")
   173  	}
   174  
   175  	max_daemons, err := strconv.Atoi(parts[2])
   176  	if err != nil {
   177  		return 0, false, false, fmt.Errorf("Max Daemons is not an integer: %v", parts[2])
   178  	}
   179  
   180  	threaded := parts[4] == "yes"
   181  	forked := parts[6] == "yes"
   182  
   183  	return max_daemons, threaded, forked, nil
   184  }
   185  
   186  func c_apache_mod_info() (opentsdb.MultiDataPoint, error) {
   187  	var md opentsdb.MultiDataPoint
   188  
   189  	resp, err := http.Get(apacheModInfoBaseURL + "?server")
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  	defer resp.Body.Close()
   194  	n, err := html.Parse(resp.Body)
   195  	if err != nil {
   196  		return nil, fmt.Errorf("unable to parse ?server status page")
   197  	}
   198  
   199  	connection_timeout, keepalive, err := extractTimeouts(n)
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  	Add(&md, "apache.server.timeout.connection", connection_timeout, nil, metadata.Gauge, metadata.Second, "")
   204  	Add(&md, "apache.server.timeout.keepalive", keepalive, nil, metadata.Gauge, metadata.Second, "")
   205  
   206  	max_daemons, is_threaded, is_forked, err := extractMpmInfo(n)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  	is_threaded_num := 0
   211  	if is_threaded {
   212  		is_threaded_num = 1
   213  	}
   214  	is_forked_num := 0
   215  	if is_forked {
   216  		is_forked_num = 1
   217  	}
   218  	Add(&md, "apache.mpm.daemons_max", max_daemons, nil, metadata.Gauge, metadata.Bool, "")
   219  	Add(&md, "apache.mpm.threaded", is_threaded_num, nil, metadata.Gauge, metadata.Bool, "")
   220  	Add(&md, "apache.mpm.forked", is_forked_num, nil, metadata.Gauge, metadata.Bool, "")
   221  
   222  	return md, nil
   223  }