contrib/format.sh: Switch to custom domain name.
[collectd.git] / contrib / collectd_unixsock.py
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.
29
30 import socket
31 import sys
32
33
34 class Collectd():
35
36     def __init__(self, path='/var/run/collectd-unixsock', noisy=False):
37         self.noisy = noisy
38         self.path = path
39         self._sock = self._connect()
40
41     def flush(self, timeout=None, plugins=[], identifiers=[]):
42         """Send a FLUSH command.
43
44         Full documentation:
45             http://collectd.org/wiki/index.php/Plain_text_protocol#FLUSH
46
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))
61
62     def getthreshold(self, identifier):
63         """Send a GETTHRESHOLD command.
64
65         Full documentation:
66             http://collectd.org/wiki/index.php/Plain_text_protocol#GETTHRESHOLD
67
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
75
76     def getval(self, identifier, flush_after=True):
77         """Send a GETVAL command.
78
79         Also flushes the identifier if flush_after is True.
80
81         Full documentation:
82             http://collectd.org/wiki/index.php/Plain_text_protocol#GETVAL
83
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
93
94     def listval(self):
95         """Send a LISTVAL command.
96
97         Full documentation:
98             http://collectd.org/wiki/index.php/Plain_text_protocol#LISTVAL
99
100         """
101         numvalues = self._cmd('LISTVAL')
102         lines = []
103         if numvalues:
104             lines = self._readlines(numvalues)
105         return lines
106
107     def putnotif(self, message, options={}):
108         """Send a PUTNOTIF command.
109
110         Options must be passed as a Python dictionary. Example:
111           options={'severity': 'failure', 'host': 'example.com'}
112
113         Full documentation:
114             http://collectd.org/wiki/index.php/Plain_text_protocol#PUTNOTIF
115
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))
123
124     def putval(self, identifier, values, options={}):
125         """Send a PUTVAL command.
126
127         Options must be passed as a Python dictionary. Example:
128           options={'interval': 10}
129
130         Full documentation:
131             http://collectd.org/wiki/index.php/Plain_text_protocol#PUTVAL
132
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))
142
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)
151
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
168
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
180
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
202
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
216
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))
225
226
227 if __name__ == '__main__':
228     """Collect values from socket and dump to STDOUT"""
229
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
236
237         values = c.getval(identifier)
238         print "\tValue list: %s" % ', '.join(values)
239
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)