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

Revision 8d6f67693835bd19cb2141a5f8dfbae02426bd47, 9.4 kB (checked in by Brian Clapper <bclapper@omniti.com>, 2 years ago)

stop using global vars, use local metric_data and pass that around

  • Property mode set to 100644
Line 
1 -- Copyright (c) 2009, 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>redis</name>
37   <description><para>Redis check.</para></description>
38   <loader>lua</loader>
39   <object>noit.module.redis</object>
40   <moduleconfig />
41   <checkconfig>
42     <parameter name="port" required="required" default="6379" allowed="\d+">
43         Specifies the port on which redis is running.
44     </parameter>
45     <parameter name="command" required="required" default="INFO" allowed=".+">
46             Command to send to redis server.
47     </parameter>
48   </checkconfig>
49   <examples>
50     <example>
51       <title>Checking Redis</title>
52       <para>This example checks Redis by issuing the INFO command</para>
53       <programlisting><![CDATA[
54       <noit>
55         <modules>
56           <loader image="lua" name="lua">
57             <config><directory>/opt/reconnoiter/libexec/modules-lua/?.lua</directory></config>
58           </loader>
59           <module loader="lua" name="redis" object="noit.module.redis" />
60         </modules>
61         <checks>
62           <check uuid="052852f2-fd09-4751-8889-a313a70c3c9c" module="redis" target="127.0.0.1" />
63         </checks>
64       </noit>
65     ]]></programlisting>
66     </example>
67   </examples>
68
69 </module>
70 ]=]);
71   return 0
72 end
73
74 function init(module)
75   return 0
76 end
77
78 function config(module, options)
79   return 0
80 end
81
82 function initiate(module, check)
83   local host = check.config.host or check.target_ip or check.target
84   local port = check.config.port or 6379
85
86   -- default to bad
87   check.bad();
88   check.unavailable();
89
90   local redis_comm = build_redis_command(check.config.command or "info")
91
92   local conn = noit.socket(host)
93   local rv, err = conn:connect(host, port)
94   if ( rv ~= 0 ) then
95     check.status(err or "connect error")
96     return
97   end
98
99   conn:write(redis_comm)
100   local metric_count = 0
101
102   if ( check.config.command ~= nil and check.config.command:upper() ~= "INFO" ) then
103     metric_count = get_command_metrics(conn, check)
104   else
105     metric_count = get_info_metrics(conn, check)
106   end
107
108   if ( metric_count > 0 ) then
109       check.status(string.format("%d stats", metric_count))
110       check.available()
111       check.good()
112   end
113 end
114
115 function build_redis_command(command)
116   local redis_comm = "*"
117   local comm_list = string.split(command, "%s+")
118
119   redis_comm = redis_comm .. table.getn(comm_list) .. "\r\n"
120
121   for c in ipairs(comm_list) do
122     redis_comm = redis_comm .. "$" .. comm_list[c]:len() .. "\r\n" .. comm_list[c] .. "\r\n"
123   end
124
125   return redis_comm
126 end
127
128 function get_info_metrics(conn, check)
129   local count = 0
130   local redis_result
131   local result_len = conn:read("\r\n")
132   local metric_data = {}
133   metric_data["metric_name"] = nil
134   result_len = string.sub(result_len, 2, -2)
135   redis_result = conn:read(result_len)
136
137   local list = string.split(redis_result, "\r\n")
138
139   for line in pairs(list) do
140     if ( list[line] == "" or list[line] == nil) then
141       break
142     end
143     kv = string.split(list[line], ":")
144
145     -- see if this is db* data
146     if ( string.find(kv[1], "^db%d+$") ) then
147       db_metrics = string.split(kv[2], ",")
148       for idx in pairs(db_metrics) do
149         count = count + 1
150         met = string.split(db_metrics[idx], "=")
151         metric_data["metric_name"] = kv[1] .. "`" .. met[1]
152         add_check_metric(met[2], check, metric_data)
153       end
154     elseif ( string.find(kv[1], "^allocation_stats$") ) then
155       alloc_metrics = string.split(kv[2], ",")
156       for idx in pairs(alloc_metrics) do
157         count = count + 1
158         met = string.split(alloc_metrics[idx], "=")
159         if ( 3 == table.getn(met) ) then
160             check.metric_int32("allocation_stats`" .. met[2], met[3])
161         else
162             check.metric_int32("allocation_stats`" .. met[1], met[2])
163         end
164       end
165     else
166       count = count + 1
167       metric_data["metric_name"] = kv[1]
168       add_check_metric(kv[2], check, metric_data)
169     end
170   end
171
172   return count
173 end
174
175 function get_command_metrics(conn, check)
176   -- the only metric name we know is what we are sending to redis
177   local metric_data = {}
178   metric_data["hash_key"] = nil
179   metric_data["metric_name"] = nil
180   metric_data["need_key"] = 0
181   metric_data["names"] = {}
182
183   local cs = string.split(check.config.command, "%s+")
184   if ( string.find(cs[1]:upper(), "HGET") ) then
185     metric_data["hash_key"] = cs[2]
186   elseif ( string.find(cs[1]:upper(), "MGET") ) then
187     for idx in pairs(cs) do
188       if ( idx > 1 ) then
189         if ( string.find(cs[1]:upper(), "HMGET") and idx > 2 ) then
190           metric_data["metric_names"][idx-3] = cs[2] .. '`' .. cs[idx]
191         else
192           metric_data["metric_names"][idx-2] = cs[idx]
193         end
194       end
195     end
196   end
197   metric_data["metric_name"] = cs[2]
198   return read_redis_response(conn, check, metric_data)
199 end
200
201 function read_redis_response(conn, check, metric_data)
202   local response_type = conn:read(1);
203   if ( response_type == "+" ) then
204     return redis_response_string(conn, check, metric_data)
205   elseif ( response_type == "-" ) then
206     return redis_response_error(conn, check, metric_data)
207   elseif ( response_type == ":" ) then
208     return redis_response_integer(conn, check, metric_data)
209   elseif ( response_type == "$" ) then
210     return redis_bulk_response(conn, check, metric_data)
211   elseif ( response_type == "*" ) then
212     return redis_multibulk_response(conn, check, metric_data)
213   end
214 end
215
216 function redis_bulk_response(conn, check, metric_data)
217   local response
218   local response_len = conn:read("\r\n")
219   response_len = string.sub(response_len, 1, -2)
220   if ( -1 == tonumber(response_len) ) then
221     return redis_response_null(check)
222   else
223     response = conn:read(response_len)
224     if ( 1 == metric_data["need_key"] ) then
225       metric_data["metric_name"] = metric_data["hash_key"] .. '`' .. response
226     else
227       add_check_metric(response, check, metric_data)
228     end
229     -- clean out rest of response
230     conn:read("\r\n")
231     return 1
232   end
233 end
234
235 function redis_multibulk_response(conn, check, metric_data)
236   local responses = conn:read("\r\n")
237   responses = string.sub(responses, 1, -2)
238
239   for i = 1, responses, 1 do
240     if ( metric_data["hash_key"] ~= nil ) then
241       metric_data["need_key"] = (metric_data["need_key"] + 1) % 2
242     else
243       metric_data["metric_name"] = metric_data["metric_name"] .. '`' .. (i-1)
244       if ( metric_data["metric_names"][i-1] ) then
245         metric_data["metric_name"] = metric_data["metric_names"][i-1]
246       end
247     end
248     read_redis_response(conn, check, metric_data)
249   end
250   return tonumber(responses)
251 end
252
253 function redis_response_integer(conn, check, metric_data)
254   local response = conn:read("\r\n")
255   response = string.sub(response, 1, -2)
256   add_check_metric(response, check, metric_data)
257   return 1
258 end
259
260 function redis_response_string(conn, check, metric_data)
261   local response = conn:read("\r\n")
262   response = string.sub(response, 1, -2)
263   add_check_metric(response, check, metric_data)
264   return 1
265 end
266
267 function redis_response_error(conn, check)
268   local response = conn:read("\r\n")
269   response = string.sub(response, 1, -2)
270   check.status(response)
271   check.bad()
272   return 0
273 end
274
275 function redis_response_null(check, metric_data)
276   check.metric_string(metric_data["metric_name"], nil)
277   return 1
278 end
279
280 function add_check_metric(value, check, metric_data)
281   if ( string.find(value, "^%d+$") ) then
282     check.metric_uint64(metric_data["metric_name"], value)
283   elseif ( string.find(value, "^%d+?.%d+$") ) then
284     check.metric_double(metric_data["metric_name"], value)
285   else
286     check.metric(metric_data["metric_name"], value)
287   end
288 end
289
290 -- from http://www.wellho.net/resources/ex.php4?item=u108/split
291 function string:split(delimiter)
292   local result = { }
293   local from  = 1
294   local delim_from, delim_to = string.find( self, delimiter, from  )
295   while delim_from do
296     table.insert( result, string.sub( self, from , delim_from-1 ) )
297     from  = delim_to + 1
298     delim_from, delim_to = string.find( self, delimiter, from  )
299   end
300   table.insert( result, string.sub( self, from  ) )
301   return result
302 end
Note: See TracBrowser for help on using the browser.