root/src/modules-lua/noit/module/resmon.lua

Revision b0d6e990b8686a9a92908a916b6128d02974b7f1, 9.1 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 4 years ago)

auto-detect the strings because JSON _BLOWS CHUNKS_ not supporting large integer types.

  • Property mode set to 100644
Line 
1 -- Copyright (c) 2008, OmniTI Computer Consulting, Inc.
2 -- All rights reserved.
3 --
4 -- Redistribution and use in source and binary forms, with or without
5 -- modification, are permitted provided that the following conditions are
6 -- met:
7 --
8 --     * Redistributions of source code must retain the above copyright
9 --       notice, this list of conditions and the following disclaimer.
10 --     * Redistributions in binary form must reproduce the above
11 --       copyright notice, this list of conditions and the following
12 --       disclaimer in the documentation and/or other materials provided
13 --       with the distribution.
14 --     * Neither the name OmniTI Computer Consulting, Inc. nor the names
15 --       of its contributors may be used to endorse or promote products
16 --       derived from this software without specific prior written
17 --       permission.
18 --
19 -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 -- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 -- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 -- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 -- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 -- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 -- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 -- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 -- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 -- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 -- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31 module(..., package.seeall)
32
33 function onload(image)
34   image.xml_description([=[
35 <module>
36   <name>resmon</name>
37   <description><para>The resmon module performs services checks against an HTTP server serving with Resmon XML or JSON.</para>
38   <para><ulink url="https://labs.omniti.com/trac/resmon"><citetitle>Resmon</citetitle></ulink> is a light-weight resource monitor that exposes health of services over HTTP in XML.</para>
39   <para>This module rides on the http module and provides a secondary phase of XML parsing on the contents that extracts Resmon status messages into metrics that can be trended.</para>
40   </description>
41   <loader>lua</loader>
42   <object>noit.module.resmon</object>
43   <checkconfig>
44     <parameter name="url"
45                required="required"
46                allowed=".+">The URL including schema and hostname (as you would type into a browser's location bar).</parameter>
47     <parameter name="port"
48                required="optional"
49                allowed="\d+">The TCP port can be specified to overide the default of 81.</parameter>
50   </checkconfig>
51   <examples>
52     <example>
53       <title>Checking resmon services on OmniTI Labs.</title>
54       <para>This example checks the Resmon service on OmniTI Labs.</para>
55       <programlisting><![CDATA[
56       <noit>
57         <modules>
58           <loader image="lua" name="lua">
59             <config><directory>/opt/reconnoiter/libexec/modules-lua/?.lua</directory></config>
60           </loader>
61           <module loader="lua" name="resmon" object="noit.module.resmon"/>
62         </modules>
63         <checks>
64           <labs target="8.8.38.5" module="resmon">
65             <check uuid="36b8ba72-7968-11dd-a67f-d39a2cc3f9de">
66               <config>
67                 <auth_user>foo</auth_user>
68                 <auth_password>bar</auth_password>
69               </config>
70             </check>
71           </labs>
72         </checks>
73       </noit>
74     ]]></programlisting>
75     </example>
76   </examples>
77 </module>
78 ]=]);
79   return 0
80 end
81
82 function init(module)
83   return 0
84 end
85
86 function config(module, options)
87   return 0
88 end
89
90 local HttpClient = require 'noit.HttpClient'
91
92 function json_metric(check, prefix, o)
93     local cnt = 1
94     if type(o) == "table" then
95         cnt = 0
96         for k, v in pairs(o) do
97             cnt = cnt + json_metric(check, prefix and (prefix .. '`' .. k) or k, v)
98         end
99     elseif type(o) == "string" then
100         check.metric(prefix, o)
101     elseif type(o) == "number" then
102         check.metric_double(prefix, o)
103     elseif type(o) == "boolean" then
104         check.metric_int32(prefix, o and 1 or 0)
105     end
106     return cnt
107 end
108
109 function json_to_metrics(check, doc)
110     local services = 0
111     check.available()
112     local data = doc:document()
113     services = json_metric(check, nil, data)
114     check.metric_uint32("services", services)
115     if services > 0 then check.good() else check.bad() end
116     check.status("services=" .. services)
117 end
118
119 function xml_to_metrics(check, doc)
120     check.available()
121
122     local services = 0
123     local result
124     for result in doc:xpath("/ResmonResults/ResmonResult") do
125         services = services + 1
126         local module = result:attr("module") or "DUMMY"
127         local service = result:attr("service") or "DUMMY"
128         local prefix = module .. '`' .. service .. '`'
129         local obj
130         obj = (doc:xpath("last_runtime_seconds", result))()
131         local ds = tonumber(obj and obj:contents())
132         if ds ~= nil then
133             ds = math.floor(ds * 1000)
134             check.metric_uint32(prefix .. "duration", ds)
135         end
136         obj = (doc:xpath("state", result))()
137         if obj ~= nil then
138             check.metric_string(prefix .. "state", obj and obj:contents())
139         end
140         local metrics = 0
141         for metric in doc:xpath("metric", result) do
142             metrics = metrics + 1
143             local name = metric:attr("name") or "DUMMY"
144             local type = metric:attr("type") or "DUMMY"
145             if type == 'i' then
146                 check.metric_int32(prefix .. name, metric and metric:contents())
147             elseif type == 'I' then
148                 check.metric_uint32(prefix .. name, metric and metric:contents())
149             elseif type == 'l' then
150                 check.metric_int64(prefix .. name, metric and metric:contents())
151             elseif type == 'L' then
152                 check.metric_uint64(prefix .. name, metric and metric:contents())
153             elseif type == 'n' then
154                 check.metric_double(prefix .. name, metric and metric:contents())
155             elseif type == 's' then
156                 check.metric_string(prefix .. name, metric and metric:contents())
157             else
158                 check.metric(prefix .. name, metric and metric:contents())
159             end
160         end
161         if metrics == 0 then
162             local message = (doc:xpath("message", result))()
163             check.metric_string(prefix .. "message", message and message:contents())
164         end
165     end
166     check.metric_uint32("services", services)
167     local status = ''
168     if services > 0 then check.good() else check.bad() end
169     check.status("services=" .. services)
170 end
171
172 function initiate(module, check)
173     local url = check.config.url or 'http:///'
174     local schema, host, uri = string.match(url, "^(https?)://([^/]*)(.+)$");
175     local port
176     local use_ssl = false
177     local codere = noit.pcre(check.config.code or '^200$')
178     local good = false
179     local starttime = noit.timeval.now()
180
181     local user = check.config.auth_user or nil
182     local pass = check.config.auth_password or nil
183     local encoded = nil
184     if (user ~= nil and pass ~= nil) then
185         encoded = noit.base64_encode(user .. ':' .. pass)
186     end
187
188     -- assume the worst.
189     check.bad()
190     check.unavailable()
191
192     if host == nil then host = check.target end
193     if schema == nil then
194         schema = 'http'
195         uri = '/'
196     end
197     if schema == 'http' then
198         port = check.config.port or 81
199     elseif schema == 'https' then
200         port = check.config.port or 443
201         use_ssl = true
202     else
203         error(schema .. " not supported")
204     end
205
206     local output = ''
207
208     -- callbacks from the HttpClient
209     local callbacks = { }
210     local hdrs_in = { }
211     callbacks.consume = function (str) output = output .. str end
212     callbacks.headers = function (t) hdrs_in = t end
213     local client = HttpClient:new(callbacks)
214     local rv, err = client:connect(check.target, port, use_ssl)
215    
216     if rv ~= 0 then
217         check.status(err or "unknown error")
218         return
219     end
220
221     -- perform the request
222     local headers = {}
223     headers.Host = host
224     if encoded ~= nil then
225         headers["Authorization"] = "Basic " .. encoded
226     end
227     client:do_request("GET", uri, headers)
228     client:get_response()
229
230     local jsondoc = nil
231     if string.find(hdrs_in["content-type"] or '', 'json') ~= nil or
232        string.find(hdrs_in["content-type"] or '', 'javascript') ~= nil then
233         jsondoc = noit.parsejson(output)
234         if jsondoc == nil then
235             noit.log("error", "bad json: %s", output)
236             check.status("json parse error")
237             return
238         end
239     end
240
241     if jsondoc ~= nil then
242         json_to_metrics(check, jsondoc)
243         return
244     end
245
246     -- try xml by "default" (assuming no json-specific content header)
247
248     -- parse the xml doc
249     local doc = noit.parsexml(output)
250     if doc == nil then
251         jsondoc = noit.parsejson(output)
252         if jsondoc == nil then
253             noit.log("error", "bad xml: %s", output)
254             check.status("xml parse error")
255             return
256         end
257         json_to_metrics(check, jsondoc)
258         return
259     end
260    
261     xml_to_metrics(check, doc)
262 end
263
Note: See TracBrowser for help on using the browser.