1 #-*- coding: ISO-8859-1 -*-
2 # collect.py: the python collectd-unixsock module.
3 #
4 # Requires collectd to be configured with the unixsock plugin, like so:
5 #
6 # LoadPlugin unixsock
7 # <Plugin unixsock>
8 # SocketFile "/var/run/collectd-unixsock"
9 # SocketPerms "0775"
10 # </Plugin>
11 #
12 # Copyright (C) 2008 Clay Loveless <clay@killersoft.com>
13 #
14 # This software is provided 'as-is', without any express or implied
15 # warranty. In no event will the author be held liable for any damages
16 # arising from the use of this software.
17 #
18 # Permission is granted to anyone to use this software for any purpose,
19 # including commercial applications, and to alter it and redistribute it
20 # freely, subject to the following restrictions:
21 #
22 # 1. The origin of this software must not be misrepresented; you must not
23 # claim that you wrote the original software. If you use this software
24 # in a product, an acknowledgment in the product documentation would be
25 # appreciated but is not required.
26 # 2. Altered source versions must be plainly marked as such, and must not be
27 # misrepresented as being the original software.
28 # 3. This notice may not be removed or altered from any source distribution.
30 import socket
31 import sys
34 class Collectd():
36 def __init__(self, path='/var/run/collectd-unixsock', noisy=False):
37 self.noisy = noisy
38 self.path = path
39 self._sock = self._connect()
41 def flush(self, timeout=None, plugins=[], identifiers=[]):
42 """Send a FLUSH command.
44 Full documentation:
45 http://collectd.org/wiki/index.php/Plain_text_protocol#FLUSH
47 """
48 # have to pass at least one plugin or identifier
49 if not plugins and not identifiers:
50 return None
51 args = []
52 if timeout:
53 args.append("timeout=%s" % timeout)
54 if plugins:
55 plugin_args = map(lambda x: "plugin=%s" % x, plugins)
56 args.extend(plugin_args)
57 if identifiers:
58 identifier_args = map(lambda x: "identifier=%s" % x, identifiers)
59 args.extend(identifier_args)
60 return self._cmd('FLUSH %s' % ' '.join(args))
62 def getthreshold(self, identifier):
63 """Send a GETTHRESHOLD command.
65 Full documentation:
66 http://collectd.org/wiki/index.php/Plain_text_protocol#GETTHRESHOLD
68 """
69 numvalues = self._cmd('GETTHRESHOLD "%s"' % identifier)
70 lines = []
71 if not numvalues or numvalues < 0:
72 raise KeyError("Identifier '%s' not found" % identifier)
73 lines = self._readlines(numvalues)
74 return lines
76 def getval(self, identifier, flush_after=True):
77 """Send a GETVAL command.
79 Also flushes the identifier if flush_after is True.
81 Full documentation:
82 http://collectd.org/wiki/index.php/Plain_text_protocol#GETVAL
84 """
85 numvalues = self._cmd('GETVAL "%s"' % identifier)
86 lines = []
87 if not numvalues or numvalues < 0:
88 raise KeyError("Identifier '%s' not found" % identifier)
89 lines = self._readlines(numvalues)
90 if flush_after:
91 self.flush(identifiers=[identifier])
92 return lines
94 def listval(self):
95 """Send a LISTVAL command.
97 Full documentation:
98 http://collectd.org/wiki/index.php/Plain_text_protocol#LISTVAL
100 """
101 numvalues = self._cmd('LISTVAL')
102 lines = []
103 if numvalues:
104 lines = self._readlines(numvalues)
105 return lines
107 def putnotif(self, message, options={}):
108 """Send a PUTNOTIF command.
110 Options must be passed as a Python dictionary. Example:
111 options={'severity': 'failure', 'host': 'example.com'}
113 Full documentation:
114 http://collectd.org/wiki/index.php/Plain_text_protocol#PUTNOTIF
116 """
117 args = []
118 if options:
119 options_args = map(lambda x: "%s=%s" % (x, options[x]), options)
120 args.extend(options_args)
121 args.append('message="%s"' % message)
122 return self._cmd('PUTNOTIF %s' % ' '.join(args))
124 def putval(self, identifier, values, options={}):
125 """Send a PUTVAL command.
127 Options must be passed as a Python dictionary. Example:
128 options={'interval': 10}
130 Full documentation:
131 http://collectd.org/wiki/index.php/Plain_text_protocol#PUTVAL
133 """
134 args = []
135 args.append('"%s"' % identifier)
136 if options:
137 options_args = map(lambda x: "%s=%s" % (x, options[x]), options)
138 args.extend(options_args)
139 values = map(str, values)
140 args.append(':'.join(values))
141 return self._cmd('PUTVAL %s' % ' '.join(args))
143 def _cmd(self, c):
144 try:
145 return self._cmdattempt(c)
146 except socket.error, (errno, errstr):
147 sys.stderr.write("[error] Sending to socket failed: [%d] %s\n"
148 % (errno, errstr))
149 self._sock = self._connect()
150 return self._cmdattempt(c)
152 def _cmdattempt(self, c):
153 if self.noisy:
154 print "[send] %s" % c
155 if not self._sock:
156 sys.stderr.write("[error] Socket unavailable. Can not send.")
157 return False
158 self._sock.send(c + "\n")
159 status_message = self._readline()
160 if self.noisy:
161 print "[receive] %s" % status_message
162 if not status_message:
163 return None
164 code, message = status_message.split(' ', 1)
165 if int(code):
166 return int(code)
167 return False
169 def _connect(self):
170 try:
171 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
172 sock.connect(self.path)
173 if self.noisy:
174 print "[socket] connected to %s" % self.path
175 return sock
176 except socket.error, (errno, errstr):
177 sys.stderr.write("[error] Connecting to socket failed: [%d] %s"
178 % (errno, errstr))
179 return None
181 def _readline(self):
182 """Read single line from socket"""
183 if not self._sock:
184 sys.stderr.write("[error] Socket unavailable. Can not read.")
185 return None
186 try:
187 data = ''
188 buf = []
189 recv = self._sock.recv
190 while data != "\n":
191 data = recv(1)
192 if not data:
193 break
194 if data != "\n":
195 buf.append(data)
196 return ''.join(buf)
197 except socket.error, (errno, errstr):
198 sys.stderr.write("[error] Reading from socket failed: [%d] %s"
199 % (errno, errstr))
200 self._sock = self._connect()
201 return None
203 def _readlines(self, sizehint=0):
204 """Read multiple lines from socket"""
205 total = 0
206 list = []
207 while True:
208 line = self._readline()
209 if not line:
210 break
211 list.append(line)
212 total = len(list)
213 if sizehint and total >= sizehint:
214 break
215 return list
217 def __del__(self):
218 if not self._sock:
219 return
220 try:
221 self._sock.close()
222 except socket.error, (errno, errstr):
223 sys.stderr.write("[error] Closing socket failed: [%d] %s"
224 % (errno, errstr))
227 if __name__ == '__main__':
228 """Collect values from socket and dump to STDOUT"""
230 c = Collectd('/var/run/collectd-unixsock', noisy=True)
231 list = c.listval()
232 for val in list:
233 stamp, identifier = val.split()
234 print "\n%s" % identifier
235 print "\tUpdate time: %s" % stamp
237 values = c.getval(identifier)
238 print "\tValue list: %s" % ', '.join(values)
240 # don't fetch thresholds by default because collectd will crash
241 # if there is no treshold for the given identifier
242 #thresholds = c.getthreshold(identifier)
243 #print "\tThresholds: %s" % ', '.join(thresholds)