Ticket #191: stats.py,cover

File stats.py,cover, 14.7 KB (added by naokikambe, 9 years ago)

An annotate source file of stats.py

Line 
1  #!/usr/local/bin/python3
2 
3  # Copyright (C) 2010  Internet Systems Consortium.
4  #
5  # Permission to use, copy, modify, and distribute this software for any
6  # purpose with or without fee is hereby granted, provided that the above
7  # copyright notice and this permission notice appear in all copies.
8  #
9  # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
10  # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
11  # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
12  # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
13  # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
14  # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
15  # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
16  # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 
18  # $Id$
19> __version__ = "$Revision$"
20 
21> import sys; sys.path.append ('@@PYTHONPATH@@')
22> import os
23> import signal
24> import select
25> import time
26> from optparse import OptionParser, OptionValueError
27> from collections import defaultdict
28> from isc.config.ccsession import ModuleCCSession, create_answer
29> from isc.cc import Session, SessionError
30 
31  # If B10_FROM_SOURCE is set in the environment, we use data files
32  # from a directory relative to that, otherwise we use the ones
33  # installed on the system
34> if "B10_FROM_SOURCE" in os.environ:
35!     SPECFILE_LOCATION = os.environ["B10_FROM_SOURCE"] + "/src/bin/stats/stats.spec"
36> else:
37>     PREFIX = "/home/kambe/bind10"
38>     DATAROOTDIR = "${prefix}/share"
39>     SPECFILE_LOCATION = "${datarootdir}/bind10-devel/stats.spec".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
40 
41> class Singleton(type):
42>     """
43>     A abstract class of singleton pattern
44>     """
45>     def __init__(self, *args):
46!         type.__init__(self, *args)
47!         self._instances = {}
48 
49>     def __call__(self, *args):
50!         if not args in self._instances:
51!             self._instances[args] = type.__call__(self, *args)
52!         return self._instances[args]
53 
54> class Callback():
55>     """
56>     A Callback handler class
57>     """
58>     def __init__(self, name, callback=None, *args, **kwargs):
59>         self.name=name
60>         self.callback=callback
61>         self.args=args
62>         self.kwargs=kwargs
63 
64>     def __call__(self, *args, **kwargs):
65>         if args:
66>             self.args = args
67>         if kwargs:
68!             self.kwargs.update(kwargs)
69>         if self.callback:
70>             return self.callback(*self.args, **self.kwargs)
71 
72> class Subject():
73>     """
74>     A abstract subject class of observer pattern
75>     """
76>     def __init__(self):
77>         self._listeners = []
78 
79>     def attach(self, listener):
80>         if not listener in self._listeners:
81>             self._listeners.append(listener)
82 
83>     def detach(self, listener):
84!         try:
85!             self._listeners.remove(listener)
86!         except ValueError:
87!             pass
88 
89>     def notify(self, event, modifier=None):
90>         for listener in self._listeners:
91>             if modifier != listener:
92>                 listener.update(event)
93 
94> class Listener():
95>     """
96>     A abstract listener class of observer pattern
97>     """
98>     def __init__(self, subject):
99>         self.subject = subject
100>         self.subject.attach(self)
101>         self.events = {}
102 
103>     def update(self, name):
104>         if name in self.events:
105>             callback = self.events[name]
106>             return callback()
107 
108>     def add_event(self, event):
109>         self.events[event.name]=event
110 
111> class SessionSubject(Subject):
112>     """
113>     A concrete subject class which creates CC session object
114>     """
115>     __metaclass__ = Singleton
116>     def __init__(self, session=None, verbose=False):
117>         Subject.__init__(self)
118>         self.verbose = verbose
119>         if session is None:
120!             self.session = Session()
121>         else:
122>             self.session = session
123>         self.running = False
124 
125>     def start(self):
126>         self.running = True
127>         self.notify('start')
128 
129>     def stop(self):
130>         self.running = False
131>         self.notify('stop')
132 
133>     def check(self):
134>         self.notify('check')
135 
136> class CCSessionListener(Listener):
137>     """
138>     A concrete listener class which creates SessionSubject object and
139>     ModuleCCSession object
140>     """
141>     def __init__(self, subject, verbose=False):
142>         Listener.__init__(self, subject)
143>         self.verbose = verbose
144>         self.subject = subject
145>         self.session = subject.session
146>         self.boot_time = get_datetime()
147 
148          # create ModuleCCSession object
149>         self.cc_session = ModuleCCSession(SPECFILE_LOCATION,
150>                                           self.config_handler,
151>                                           self.command_handler,
152>                                           self.session)
153 
154          # initialize internal data
155>         self.config_spec = self.cc_session.get_module_spec().get_config_spec()
156>         self.stats_spec = self.config_spec
157>         self.stats_data = self.initialize_data(self.stats_spec)
158 
159          # add event handler invoked via SessionSubject object
160>         self.add_event(Callback('start', self.start))
161>         self.add_event(Callback('stop', self.stop))
162>         self.add_event(Callback('check', self.check))
163          # don't add 'command_' suffix to the special commands in
164          # order to prevent executing internal command via bindctl
165 
166          # get commands spec
167>         self.commands_spec = self.cc_session.get_module_spec().get_commands_spec()
168 
169          # add event handler related command_handler of ModuleCCSession
170          # invoked via bindctl
171>         for cmd in self.commands_spec:
172>             try:
173                  # add prefix "command_"
174>                 name = "command_" + cmd["command_name"]
175>                 callback = eval("self." + name)
176>                 kwargs = self.initialize_data(cmd["command_args"])
177>                 self.add_event(Callback(name, callback, **kwargs))
178!             except AttributeError as ae:
179!                 sys.stderr.write("[b10-stats] Caught undefined command while parsing spec file: "
180!                                  +str(cmd["command_name"])+"\n")
181 
182>     def start(self):
183>         """
184>         start the cc chanel
185>         """
186          # set initial value
187>         self.stats_data['stats.boot_time'] = self.boot_time
188>         self.stats_data['stats.start_time'] = get_datetime()
189>         self.stats_data['stats.last_update_time'] = get_datetime()
190>         self.stats_data['stats.lname'] = self.session.lname
191>         return self.cc_session.start()
192 
193>     def stop(self):
194>         """
195>         stop the cc chanel
196>         """
197>         return self.cc_session.close()
198 
199>     def check(self):
200>         """
201>         check the cc chanel
202>         """
203>         return self.cc_session.check_command()
204 
205>     def config_handler(self, new_config):
206>         """
207>         handle a configure from the cc channel
208>         """
209!         if self.verbose:
210!             sys.stdout.write("[b10-stats] newconfig received: "+str(new_config)+"\n")
211 
212          # do nothing currently
213!         return create_answer(0)
214 
215>     def command_handler(self, command, *args, **kwargs):
216>         """
217>         handle commands from the cc channel
218>         """
219          # add 'command_' suffix in order to executing command via bindctl
220>         name = 'command_' + command
221         
222>         if name in self.events:
223>             event = self.events[name]
224>             return event(*args, **kwargs)
225>         else:
226>             return self.command_unknown(command, args)
227 
228>     def command_shutdown(self, args):
229>         """
230>         handle shutdown command
231>         """
232>         if self.verbose:
233!             sys.stdout.write("[b10-stats] 'shutdown' command received\n")
234>         self.subject.running = False
235>         return create_answer(0)
236 
237>     def command_set(self, args, stats_data={}):
238>         """
239>         handle set command
240>         """
241>         if self.verbose:
242!             sys.stdout.write("[b10-stats] 'set' command received, args: "+str(args)+"\n")
243 
244          # 'args' must be dictionary type
245>         self.stats_data.update(args['stats_data'])
246 
247          # overwrite "stats.LastUpdateTime"
248>         self.stats_data['stats.last_update_time'] = get_datetime()
249 
250>         return create_answer(0)
251 
252>     def command_increase(self, args, stats_data={}):
253>         """
254>         handle increase command
255>         """
256>         if self.verbose:
257!             sys.stdout.write("[b10-stats] 'increase' command received, args: "+str(args)+"\n")
258 
259          # 'args' must be dictionary type
260>         stats_data.update(args['stats_data'])
261 
262          # A internal private function increase for each element, which
263          # is called recursively
264>         def __increase_data(data, args):
265>             if type(data) == list:
266!                 mx = max(len(data), len(args))
267!                 for i in mx:
268!                     if i in data and i in args:
269!                         data[i] = __increase_data(data[i], args[i])
270!                     elif i in args:
271!                         data.extend(args[i:])
272!                         break
273>             elif type(data) == dict:
274>                 for k in args.keys():
275>                     if k in data:
276>                         data[k] = __increase_data(data[k], args[k])
277!                     else:
278!                         data[k] = args[k]
279>             else:
280>                 data = data + args
281>             return data
282 
283          # call the internal function
284>         self.stats_data = __increase_data(self.stats_data, stats_data)
285 
286          # overwrite "stats.LastUpdateTime"
287>         self.stats_data['stats.last_update_time'] = get_datetime()
288 
289>         return create_answer(0)
290 
291>     def command_remove(self, args, stats_item_name=''):
292>         """
293>         handle remove command
294>         """
295>         if self.verbose:
296!             sys.stdout.write("[b10-stats] 'remove' command received, args: "+str(args)+"\n")
297 
298          # 'args' must be dictionary type
299>         if args and args['stats_item_name'] in self.stats_data:
300>             stats_item_name = args['stats_item_name']
301 
302          # just remove one item
303>         self.stats_data.pop(stats_item_name)
304 
305>         return create_answer(0)
306 
307>     def command_show(self, args, stats_item_name=''):
308>         """
309>         handle show command
310>         """
311>         if self.verbose:
312!             sys.stdout.write("[b10-stats] 'show' command received, args: "+str(args)+"\n")
313 
314          # always overwrite 'report_time' and 'stats.timestamp'
315          # if "show" command invoked
316>         self.stats_data['report_time'] = get_datetime()
317>         self.stats_data['stats.timestamp'] = get_timestamp()
318 
319          # if with args
320>         if args and args['stats_item_name'] in self.stats_data:
321>             stats_item_name = args['stats_item_name']
322>             return create_answer(0, {stats_item_name: self.stats_data[stats_item_name]})
323 
324>         return create_answer(0, self.stats_data)
325 
326>     def command_reset(self, args):
327>         """
328>         handle reset command
329>         """
330>         if self.verbose:
331!             sys.stdout.write("[b10-stats] 'reset' command received\n")
332 
333          # re-initialize internal variables
334>         self.stats_data = self.initialize_data(self.stats_spec)
335 
336          # reset initial value
337>         self.stats_data['stats.boot_time'] = self.boot_time
338>         self.stats_data['stats.start_time'] = get_datetime()
339>         self.stats_data['stats.last_update_time'] = get_datetime()
340>         self.stats_data['stats.lname'] = self.session.lname
341 
342>         return create_answer(0)
343 
344>     def command_status(self, args):
345>         """
346>         handle status command
347>         """
348>         if self.verbose:
349!             sys.stdout.write("[b10-stats] 'status' command received\n")
350          # just return "I'm alive."
351>         return create_answer(0, "I'm alive.")
352 
353>     def command_unknown(self, command, args):
354>         """
355>         handle an unknown command
356>         """
357>         if self.verbose:
358!             sys.stdout.write("[b10-stats] Unknown command received: '"
359!                              + str(command) + "'\n")
360>         return create_answer(1, "Unknown command: '"+str(command)+"'")
361 
362>     def initialize_data(self, spec):
363>         """
364>         initialize stats data
365>         """
366>         def __get_init_val(spec):
367>             if spec['item_type'] == 'null':
368!                 return None
369>             elif spec['item_type'] == 'boolean':
370!                 return bool(spec.get('item_default', False))
371>             elif spec['item_type'] == 'string':
372>                 return str(spec.get('item_default', ''))
373>             elif spec['item_type'] in set(['number', 'integer']):
374>                 return int(spec.get('item_default', 0))
375>             elif spec['item_type'] in set(['float', 'double', 'real']):
376>                 return float(spec.get('item_default', 0.0))
377>             elif spec['item_type'] in set(['list', 'array']):
378!                 return spec.get('item_default',
379!                                 [ __get_init_val(spec['list_item_spec']) ])
380>             elif spec['item_type'] in set(['map', 'object']):
381>                 return spec.get('item_default',
382>                                 dict([
383>                             (s['item_name'], __get_init_val(s))
384>                             for s in spec['map_item_spec']
385>                             ]) )
386!             else:
387!                 return spec.get('item_default')
388>         return dict([ (s['item_name'], __get_init_val(s)) for s in spec ])
389 
390> def get_timestamp():
391>     """
392>     get current timestamp
393>     """
394>     return time.time()
395 
396> def get_datetime():
397>     """
398>     get current datetime
399>     """
400>     return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
401 
402> def main():
403!     try:
404!         parser = OptionParser()
405!         parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
406!                       help="display more about what is going on")
407!         (options, args) = parser.parse_args()
408!         subject = SessionSubject(verbose=options.verbose)
409!         listener = CCSessionListener(subject, verbose=options.verbose)
410!         subject.start()
411!         while subject.running:
412!             subject.check()
413!         subject.stop()
414!     except OptionValueError:
415!         sys.stderr.write("[b10-stats] Error parsing options\n")
416!     except SessionError as se:
417!         sys.stderr.write("[b10-stats] Error creating Stats module, "
418!               + "is the command channel daemon running?\n")
419!     except KeyboardInterrupt as kie:
420!         sys.stderr.write("[b10-stats] Interrupted, exiting\n")
421 
422> if __name__ == "__main__":
423!     main()