stats.py.in 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. #!@PYTHON@
  2. # Copyright (C) 2010, 2011 Internet Systems Consortium.
  3. #
  4. # Permission to use, copy, modify, and distribute this software for any
  5. # purpose with or without fee is hereby granted, provided that the above
  6. # copyright notice and this permission notice appear in all copies.
  7. #
  8. # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
  9. # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
  10. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
  11. # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
  12. # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
  13. # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  14. # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
  15. # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. """
  17. Statistics daemon in BIND 10
  18. """
  19. import sys; sys.path.append ('@@PYTHONPATH@@')
  20. import os
  21. from time import time, strftime, gmtime
  22. from optparse import OptionParser, OptionValueError
  23. import isc
  24. import isc.util.process
  25. import isc.log
  26. from stats_messages import *
  27. isc.log.init("b10-stats")
  28. logger = isc.log.Logger("stats")
  29. # Some constants for debug levels, these should be removed when we
  30. # have #1074
  31. DBG_STATS_MESSAGING = 30
  32. # This is for boot_time of Stats
  33. _BASETIME = gmtime()
  34. # for setproctitle
  35. isc.util.process.rename()
  36. # If B10_FROM_SOURCE is set in the environment, we use data files
  37. # from a directory relative to that, otherwise we use the ones
  38. # installed on the system
  39. if "B10_FROM_SOURCE" in os.environ:
  40. SPECFILE_LOCATION = os.environ["B10_FROM_SOURCE"] + os.sep + \
  41. "src" + os.sep + "bin" + os.sep + "stats" + os.sep + "stats.spec"
  42. else:
  43. PREFIX = "@prefix@"
  44. DATAROOTDIR = "@datarootdir@"
  45. SPECFILE_LOCATION = "@datadir@" + os.sep + "@PACKAGE@" + os.sep + "stats.spec"
  46. SPECFILE_LOCATION = SPECFILE_LOCATION.replace("${datarootdir}", DATAROOTDIR)\
  47. .replace("${prefix}", PREFIX)
  48. def get_timestamp():
  49. """
  50. get current timestamp
  51. """
  52. return time()
  53. def get_datetime(gmt=None):
  54. """
  55. get current datetime
  56. """
  57. if not gmt: gmt = gmtime()
  58. return strftime("%Y-%m-%dT%H:%M:%SZ", gmt)
  59. def parse_spec(spec):
  60. """
  61. parse spec type data
  62. """
  63. if type(spec) is not list: return {}
  64. def _parse_spec(spec):
  65. item_type = spec['item_type']
  66. if item_type == "integer":
  67. return int(spec.get('item_default', 0))
  68. elif item_type == "real":
  69. return float(spec.get('item_default', 0.0))
  70. elif item_type == "boolean":
  71. return bool(spec.get('item_default', False))
  72. elif item_type == "string":
  73. return str(spec.get('item_default', ""))
  74. elif item_type == "list":
  75. return spec.get(
  76. "item_default",
  77. [ _parse_spec(s) for s in spec["list_item_spec"] ])
  78. elif item_type == "map":
  79. return spec.get(
  80. "item_default",
  81. dict([ (s["item_name"], _parse_spec(s)) for s in spec["map_item_spec"] ]) )
  82. else:
  83. return spec.get("item_default", None)
  84. return dict([ (s['item_name'], _parse_spec(s)) for s in spec ])
  85. class Callback():
  86. """
  87. A Callback handler class
  88. """
  89. def __init__(self, command=None, args=(), kwargs={}):
  90. self.command = command
  91. self.args = args
  92. self.kwargs = kwargs
  93. def __call__(self, *args, **kwargs):
  94. if not args: args = self.args
  95. if not kwargs: kwargs = self.kwargs
  96. if self.command: return self.command(*args, **kwargs)
  97. class StatsError(Exception):
  98. """Exception class for Stats class"""
  99. pass
  100. class Stats:
  101. """
  102. Main class of stats module
  103. """
  104. def __init__(self):
  105. self.running = False
  106. # create ModuleCCSession object
  107. self.mccs = isc.config.ModuleCCSession(SPECFILE_LOCATION,
  108. self.config_handler,
  109. self.command_handler)
  110. self.cc_session = self.mccs._session
  111. # get module spec
  112. self.module_name = self.mccs.get_module_spec().get_module_name()
  113. self.modules = {}
  114. self.statistics_data = {}
  115. # get commands spec
  116. self.commands_spec = self.mccs.get_module_spec().get_commands_spec()
  117. # add event handler related command_handler of ModuleCCSession
  118. self.callbacks = {}
  119. for cmd in self.commands_spec:
  120. # add prefix "command_"
  121. name = "command_" + cmd["command_name"]
  122. try:
  123. callback = getattr(self, name)
  124. kwargs = parse_spec(cmd["command_args"])
  125. self.callbacks[name] = Callback(command=callback, kwargs=kwargs)
  126. except AttributeError:
  127. raise StatsError(STATS_UNKNOWN_COMMAND_IN_SPEC, cmd["command_name"])
  128. self.mccs.start()
  129. def start(self):
  130. """
  131. Start stats module
  132. """
  133. self.running = True
  134. # TODO: should be added into new logging interface
  135. # if self.verbose:
  136. # sys.stdout.write("[b10-stats] starting\n")
  137. # request Bob to send statistics data
  138. logger.debug(DBG_STATS_MESSAGING, STATS_SEND_REQUEST_BOSS)
  139. cmd = isc.config.ccsession.create_command("sendstats", None)
  140. seq = self.cc_session.group_sendmsg(cmd, 'Boss')
  141. self.cc_session.group_recvmsg(True, seq)
  142. # initialized Statistics data
  143. errors = self.update_statistics_data(
  144. self.module_name,
  145. lname=self.cc_session.lname,
  146. boot_time=get_datetime(_BASETIME)
  147. )
  148. if errors:
  149. raise StatsError("stats spec file is incorrect")
  150. while self.running:
  151. self.mccs.check_command(False)
  152. def config_handler(self, new_config):
  153. """
  154. handle a configure from the cc channel
  155. """
  156. logger.debug(DBG_STATS_MESSAGING, STATS_RECEIVED_NEW_CONFIG,
  157. new_config)
  158. # do nothing currently
  159. return isc.config.create_answer(0)
  160. def command_handler(self, command, kwargs):
  161. """
  162. handle commands from the cc channel
  163. """
  164. name = 'command_' + command
  165. if name in self.callbacks:
  166. callback = self.callbacks[name]
  167. if kwargs:
  168. return callback(**kwargs)
  169. else:
  170. return callback()
  171. else:
  172. logger.error(STATS_RECEIVED_UNKNOWN_COMMAND, command)
  173. return isc.config.create_answer(1, "Unknown command: '"+str(command)+"'")
  174. def update_modules(self):
  175. """
  176. update information of each module
  177. """
  178. modules = {}
  179. seq = self.cc_session.group_sendmsg(
  180. isc.config.ccsession.create_command(
  181. isc.config.ccsession.COMMAND_GET_STATISTICS_SPEC),
  182. 'ConfigManager')
  183. (answer, env) = self.cc_session.group_recvmsg(False, seq)
  184. if answer:
  185. (rcode, value) = isc.config.ccsession.parse_answer(answer)
  186. if rcode == 0:
  187. for mod in value:
  188. spec = { "module_name" : mod }
  189. if value[mod] and type(value[mod]) is list:
  190. spec["statistics"] = value[mod]
  191. modules[mod] = isc.config.module_spec.ModuleSpec(spec)
  192. modules[self.module_name] = self.mccs.get_module_spec()
  193. self.modules = modules
  194. def get_statistics_data(self, owner=None, name=None):
  195. """
  196. return statistics data which stats module has of each module
  197. """
  198. self.update_statistics_data()
  199. if owner and name:
  200. try:
  201. return self.statistics_data[owner][name]
  202. except KeyError:
  203. pass
  204. elif owner:
  205. try:
  206. return self.statistics_data[owner]
  207. except KeyError:
  208. pass
  209. elif name:
  210. pass
  211. else:
  212. return self.statistics_data
  213. def update_statistics_data(self, owner=None, **data):
  214. """
  215. change statistics date of specified module into specified data
  216. """
  217. self.update_modules()
  218. statistics_data = {}
  219. for (name, module) in self.modules.items():
  220. value = parse_spec(module.get_statistics_spec())
  221. if module.validate_statistics(True, value):
  222. statistics_data[name] = value
  223. for (name, value) in self.statistics_data.items():
  224. if name in statistics_data:
  225. statistics_data[name].update(value)
  226. else:
  227. statistics_data[name] = value
  228. self.statistics_data = statistics_data
  229. if owner and data:
  230. errors = []
  231. try:
  232. if self.modules[owner].validate_statistics(False, data, errors):
  233. self.statistics_data[owner].update(data)
  234. return
  235. except KeyError:
  236. errors.append("unknown module name: " + str(owner))
  237. return errors
  238. def command_status(self):
  239. """
  240. handle status command
  241. """
  242. logger.debug(DBG_STATS_MESSAGING, STATS_RECEIVED_STATUS_COMMAND)
  243. return isc.config.create_answer(
  244. 0, "Stats is up. (PID " + str(os.getpid()) + ")")
  245. def command_shutdown(self):
  246. """
  247. handle shutdown command
  248. """
  249. logger.info(STATS_RECEIVED_SHUTDOWN_COMMAND)
  250. self.running = False
  251. return isc.config.create_answer(0)
  252. def command_show(self, owner=None, name=None):
  253. """
  254. handle show command
  255. """
  256. if (owner or name):
  257. logger.debug(DBG_STATS_MESSAGING,
  258. STATS_RECEIVED_SHOW_NAME_COMMAND,
  259. str(owner)+", "+str(name))
  260. else:
  261. logger.debug(DBG_STATS_MESSAGING,
  262. STATS_RECEIVED_SHOW_ALL_COMMAND)
  263. errors = self.update_statistics_data(
  264. self.module_name,
  265. timestamp=get_timestamp(),
  266. report_time=get_datetime()
  267. )
  268. if errors: raise StatsError("stats spec file is incorrect")
  269. ret = self.get_statistics_data(owner, name)
  270. if ret is not None:
  271. return isc.config.create_answer(0, ret)
  272. else:
  273. return isc.config.create_answer(
  274. 1, "specified arguments are incorrect: " \
  275. + "owner: " + str(owner) + ", name: " + str(name))
  276. def command_showschema(self, owner=None, name=None):
  277. """
  278. handle show command
  279. """
  280. # TODO: should be added into new logging interface
  281. # if self.verbose:
  282. # sys.stdout.write("[b10-stats] 'showschema' command received\n")
  283. self.update_modules()
  284. schema = {}
  285. schema_byname = {}
  286. for mod in self.modules:
  287. spec = self.modules[mod].get_statistics_spec()
  288. schema_byname[mod] = {}
  289. if spec:
  290. schema[mod] = spec
  291. for item in spec:
  292. schema_byname[mod][item['item_name']] = item
  293. if owner:
  294. try:
  295. if name:
  296. return isc.config.create_answer(0, schema_byname[owner][name])
  297. else:
  298. return isc.config.create_answer(0, schema[owner])
  299. except KeyError:
  300. pass
  301. else:
  302. if name:
  303. return isc.config.create_answer(1, "module name is not specified")
  304. else:
  305. return isc.config.create_answer(0, schema)
  306. return isc.config.create_answer(
  307. 1, "specified arguments are incorrect: " \
  308. + "owner: " + str(owner) + ", name: " + str(name))
  309. def command_set(self, owner, data):
  310. """
  311. handle set command
  312. """
  313. errors = self.update_statistics_data(owner, **data)
  314. if errors:
  315. return isc.config.create_answer(
  316. 1, "errors while setting statistics data: " \
  317. + ", ".join(errors))
  318. errors = self.update_statistics_data(
  319. self.module_name, last_update_time=get_datetime() )
  320. if errors:
  321. raise StatsError("stats spec file is incorrect")
  322. return isc.config.create_answer(0)
  323. if __name__ == "__main__":
  324. try:
  325. parser = OptionParser()
  326. parser.add_option(
  327. "-v", "--verbose", dest="verbose", action="store_true",
  328. help="display more about what is going on")
  329. (options, args) = parser.parse_args()
  330. if options.verbose:
  331. isc.log.init("b10-stats", "DEBUG", 99)
  332. stats = Stats()
  333. stats.start()
  334. except OptionValueError as ove:
  335. logger.fatal(STATS_BAD_OPTION_VALUE, ove)
  336. except SessionError as se:
  337. logger.fatal(STATS_CC_SESSION_ERROR, se)
  338. # TODO: should be added into new logging interface
  339. except StatsError as se:
  340. sys.exit("[b10-stats] %s" % se)
  341. except KeyboardInterrupt as kie:
  342. logger.info(STATS_STOPPED_BY_KEYBOARD)