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 }