stats.py.in 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  1. #!@PYTHON@
  2. # Copyright (C) 2010, 2011, 2012 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 errno
  24. import select
  25. import isc
  26. import isc.util.process
  27. import isc.log
  28. from isc.log_messages.stats_messages import *
  29. isc.log.init("b10-stats", buffer=True)
  30. logger = isc.log.Logger("stats")
  31. # Some constants for debug levels.
  32. DBG_STATS_MESSAGING = logger.DBGLVL_COMMAND
  33. # This is for boot_time of Stats
  34. _BASETIME = gmtime()
  35. # for setproctitle
  36. isc.util.process.rename()
  37. # If B10_FROM_SOURCE is set in the environment, we use data files
  38. # from a directory relative to that, otherwise we use the ones
  39. # installed on the system
  40. if "B10_FROM_SOURCE" in os.environ:
  41. SPECFILE_LOCATION = os.environ["B10_FROM_SOURCE"] + os.sep + \
  42. "src" + os.sep + "bin" + os.sep + "stats" + os.sep + "stats.spec"
  43. else:
  44. PREFIX = "@prefix@"
  45. DATAROOTDIR = "@datarootdir@"
  46. SPECFILE_LOCATION = "@datadir@" + os.sep + "@PACKAGE@" + os.sep + "stats.spec"
  47. SPECFILE_LOCATION = SPECFILE_LOCATION.replace("${datarootdir}", DATAROOTDIR)\
  48. .replace("${prefix}", PREFIX)
  49. def get_timestamp():
  50. """
  51. get current timestamp
  52. """
  53. return time()
  54. def get_datetime(gmt=None):
  55. """
  56. get current datetime
  57. """
  58. if not gmt: gmt = gmtime()
  59. return strftime("%Y-%m-%dT%H:%M:%SZ", gmt)
  60. def get_spec_defaults(spec):
  61. """
  62. extracts the default values of the items from spec specified in
  63. arg, and returns the dict-type variable which is a set of the item
  64. names and the default values
  65. """
  66. if type(spec) is not list: return {}
  67. def _get_spec_defaults(spec):
  68. item_type = spec['item_type']
  69. if item_type == "integer":
  70. return int(spec.get('item_default', 0))
  71. elif item_type == "real":
  72. return float(spec.get('item_default', 0.0))
  73. elif item_type == "boolean":
  74. return bool(spec.get('item_default', False))
  75. elif item_type == "string":
  76. return str(spec.get('item_default', ""))
  77. elif item_type == "list":
  78. return spec.get(
  79. "item_default",
  80. [ _get_spec_defaults(spec["list_item_spec"]) ])
  81. elif item_type == "map":
  82. return spec.get(
  83. "item_default",
  84. dict([ (s["item_name"], _get_spec_defaults(s)) for s in spec["map_item_spec"] ]) )
  85. elif item_type == "named_set":
  86. # in a named_set type, it returns {} as a default value
  87. return spec.get("item_default", {})
  88. else:
  89. return spec.get("item_default", None)
  90. return dict([ (s['item_name'], _get_spec_defaults(s)) for s in spec ])
  91. def _accum(a, b):
  92. """If the first arg is dict or list type, two values
  93. would be merged and accumlated. This is for internal use."""
  94. # If both of args are dict or list type, two
  95. # values are merged.
  96. if type(a) is dict and type(b) is dict:
  97. return dict([ (k, _accum(v, b[k])) \
  98. if k in b else (k, v) \
  99. for (k, v) in a.items() ] \
  100. + [ (k, v) \
  101. for (k, v) in b.items() \
  102. if k not in a ])
  103. elif type(a) is list and type(b) is list:
  104. return [ _accum(a[i], b[i]) \
  105. if len(b) > i else a[i] \
  106. for i in range(len(a)) ] \
  107. + [ b[i] \
  108. for i in range(len(b)) \
  109. if len(a) <= i ]
  110. # If both of args are integer or float type, two
  111. # values are added.
  112. elif (type(a) is int and type(b) is int) \
  113. or (type(a) is float or type(b) is float):
  114. return a + b
  115. # If both of args are string type,
  116. # values are compared and bigger one is returned.
  117. elif type(a) is str and type(b) is str:
  118. if a < b: return b
  119. return a
  120. # If the first arg is None type, the second value is returned.
  121. elif a is None:
  122. return b
  123. # Nothing matches above, the first arg is returned
  124. return a
  125. def merge_oldnew(old, new):
  126. """
  127. Merges two arguments. If old data contains the corresponding name
  128. against new data, the value of the name is replaced with the
  129. corresponding value in new data. Otherwise, the new date are added
  130. at same name or same id. Both old data and new data should be same
  131. data type. This method returns the merged result.
  132. """
  133. # If the first arg is dict or list type, two values
  134. # would be merged
  135. if type(old) is dict and type(new) is dict:
  136. return dict([ (k, merge_oldnew(old[k], v)) \
  137. if k in old else (k, v) \
  138. for (k, v) in new.items() ] \
  139. + [ (k, v) \
  140. for (k, v) in old.items() \
  141. if k not in new ])
  142. elif type(old) is list and type(new) is list:
  143. return [ merge_oldnew(old[i], new[i]) \
  144. if len(old) > i else new[i] \
  145. for i in range(len(new)) ] \
  146. + [ old[i] \
  147. for i in range(len(old)) \
  148. if len(new) <= i ]
  149. else:
  150. return new
  151. class Callback():
  152. """
  153. A Callback handler class
  154. """
  155. def __init__(self, command=None, args=(), kwargs={}):
  156. self.command = command
  157. self.args = args
  158. self.kwargs = kwargs
  159. def __call__(self, *args, **kwargs):
  160. if not args: args = self.args
  161. if not kwargs: kwargs = self.kwargs
  162. if self.command: return self.command(*args, **kwargs)
  163. class StatsError(Exception):
  164. """Exception class for Stats class"""
  165. pass
  166. class Stats:
  167. """
  168. Main class of stats module
  169. """
  170. def __init__(self):
  171. self.running = False
  172. # create ModuleCCSession object
  173. self.mccs = isc.config.ModuleCCSession(SPECFILE_LOCATION,
  174. self.config_handler,
  175. self.command_handler)
  176. self.cc_session = self.mccs._session
  177. # get module spec
  178. self.module_name = self.mccs.get_module_spec().get_module_name()
  179. self.modules = {}
  180. self.statistics_data = {}
  181. # statistics data by each mid
  182. self.statistics_data_bymid = {}
  183. # get commands spec
  184. self.commands_spec = self.mccs.get_module_spec().get_commands_spec()
  185. # add event handler related command_handler of ModuleCCSession
  186. self.callbacks = {}
  187. for cmd in self.commands_spec:
  188. # add prefix "command_"
  189. name = "command_" + cmd["command_name"]
  190. try:
  191. callback = getattr(self, name)
  192. kwargs = get_spec_defaults(cmd["command_args"])
  193. self.callbacks[name] = Callback(command=callback, kwargs=kwargs)
  194. except AttributeError:
  195. raise StatsError(STATS_UNKNOWN_COMMAND_IN_SPEC, cmd["command_name"])
  196. self.config = {}
  197. self.mccs.start()
  198. # setup my config
  199. self.config = dict([
  200. (itm['item_name'], self.mccs.get_value(itm['item_name'])[0])
  201. for itm in self.mccs.get_module_spec().get_config_spec()
  202. ])
  203. # set a absolute timestamp polling at next time
  204. self.next_polltime = get_timestamp() + self.get_interval()
  205. # initialized Statistics data
  206. self.update_modules()
  207. if self.update_statistics_data(
  208. self.module_name,
  209. self.cc_session.lname,
  210. {'lname': self.cc_session.lname,
  211. 'boot_time': get_datetime(_BASETIME),
  212. 'last_update_time': get_datetime()}):
  213. logger.warn(STATS_RECEIVED_INVALID_STATISTICS_DATA,
  214. self.module_name)
  215. # define the variable of the last time of polling
  216. self._lasttime_poll = 0.0
  217. def get_interval(self):
  218. """return the current value of 'poll-interval'"""
  219. return self.config['poll-interval']
  220. def do_polling(self):
  221. """Polls modules for statistics data. Return nothing. First
  222. search multiple instances of same module. Second requests
  223. each module to invoke 'getstats'. Finally updates internal
  224. statistics data every time it gets from each instance."""
  225. # It counts the number of instances of same module by
  226. # examining the third value from the array result of
  227. # 'show_processes' of Boss
  228. seq = self.cc_session.group_sendmsg(
  229. isc.config.ccsession.create_command("show_processes"),
  230. 'Boss')
  231. (answer, env) = self.cc_session.group_recvmsg(False, seq)
  232. modules = []
  233. if answer:
  234. (rcode, value) = isc.config.ccsession.parse_answer(answer)
  235. if rcode == 0 and type(value) is list:
  236. # NOTE: For example, the "show_processes" command
  237. # of Boss is assumed to return the response in this
  238. # format:
  239. # [
  240. # ...
  241. # [
  242. # 20061,
  243. # "b10-auth",
  244. # "Auth"
  245. # ],
  246. # [
  247. # 20103,
  248. # "b10-auth-2",
  249. # "Auth"
  250. # ]
  251. # ...
  252. # ]
  253. # If multiple instances of the same module are
  254. # running, the address names of them, which are at the
  255. # third element, must be also same. Thus, the value of
  256. # the third element of each outer element is read here
  257. # for counting multiple instances. This is a
  258. # workaround for counting the instances. This should
  259. # be fixed in another proper way in the future
  260. # release.
  261. modules = [ v[2] if type(v) is list and len(v) > 2 \
  262. else None for v in value ]
  263. # start requesting each module to collect statistics data
  264. sequences = []
  265. for (module_name, data) in self.get_statistics_data().items():
  266. # skip if module_name is 'Stats'
  267. if module_name == self.module_name:
  268. continue
  269. logger.debug(DBG_STATS_MESSAGING, STATS_SEND_STATISTICS_REQUEST,
  270. module_name)
  271. cmd = isc.config.ccsession.create_command(
  272. "getstats", None) # no argument
  273. seq = self.cc_session.group_sendmsg(cmd, module_name)
  274. sequences.append((module_name, seq))
  275. cnt = modules.count(module_name)
  276. if cnt > 1:
  277. sequences = sequences + [ (module_name, seq) \
  278. for i in range(cnt-1) ]
  279. # start receiving statistics data
  280. _statistics_data = []
  281. while len(sequences) > 0:
  282. try:
  283. (module_name, seq) = sequences.pop(0)
  284. answer, env = self.cc_session.group_recvmsg(
  285. False, seq)
  286. if answer:
  287. rcode, args = isc.config.ccsession.parse_answer(
  288. answer)
  289. if rcode == 0:
  290. _statistics_data.append(
  291. (module_name, env['from'], args))
  292. # skip this module if SessionTimeout raised
  293. except isc.cc.session.SessionTimeout:
  294. pass
  295. # update statistics data
  296. self.update_modules()
  297. while len(_statistics_data) > 0:
  298. (_module_name, _lname, _args) = _statistics_data.pop(0)
  299. if self.update_statistics_data(_module_name, _lname, _args):
  300. logger.warn(
  301. STATS_RECEIVED_INVALID_STATISTICS_DATA,
  302. _module_name)
  303. else:
  304. if self.update_statistics_data(
  305. self.module_name,
  306. self.cc_session.lname,
  307. {'last_update_time': get_datetime()}):
  308. logger.warn(
  309. STATS_RECEIVED_INVALID_STATISTICS_DATA,
  310. self.module_name)
  311. # if successfully done, set the last time of polling
  312. self._lasttime_poll = get_timestamp()
  313. def start(self):
  314. """
  315. Start stats module
  316. """
  317. logger.info(STATS_STARTING)
  318. def _check_command(nonblock=False):
  319. """check invoked command by waiting for 'poll-interval'
  320. seconds"""
  321. # backup original timeout
  322. orig_timeout = self.cc_session.get_timeout()
  323. # set cc-session timeout to half of a second(500ms)
  324. self.cc_session.set_timeout(500)
  325. try:
  326. answer, env = self.cc_session.group_recvmsg(nonblock)
  327. self.mccs.check_command_without_recvmsg(answer, env)
  328. except isc.cc.session.SessionTimeout:
  329. pass # waited for poll-interval seconds
  330. # restore timeout
  331. self.cc_session.set_timeout(orig_timeout)
  332. try:
  333. self.running = True
  334. while self.running:
  335. _check_command()
  336. now = get_timestamp()
  337. intval = self.get_interval()
  338. if intval > 0 and now >= self.next_polltime:
  339. # decide the next polling timestamp
  340. self.next_polltime += intval
  341. # adjust next time
  342. if self.next_polltime < now:
  343. self.next_polltime = now
  344. self.do_polling()
  345. finally:
  346. self.mccs.send_stopping()
  347. def config_handler(self, new_config):
  348. """
  349. handle a configure from the cc channel
  350. """
  351. logger.debug(DBG_STATS_MESSAGING, STATS_RECEIVED_NEW_CONFIG,
  352. new_config)
  353. errors = []
  354. if not self.mccs.get_module_spec().\
  355. validate_config(False, new_config, errors):
  356. return isc.config.ccsession.create_answer(
  357. 1, ", ".join(errors))
  358. if 'poll-interval' in new_config \
  359. and new_config['poll-interval'] < 0:
  360. return isc.config.ccsession.create_answer(
  361. 1, "Negative integer ignored")
  362. self.config.update(new_config)
  363. if 'poll-interval' in self.config:
  364. # update next polling timestamp
  365. self.next_polltime = get_timestamp() + self.get_interval()
  366. return isc.config.create_answer(0)
  367. def command_handler(self, command, kwargs):
  368. """
  369. handle commands from the cc channel
  370. """
  371. name = 'command_' + command
  372. if name in self.callbacks:
  373. callback = self.callbacks[name]
  374. if kwargs:
  375. return callback(**kwargs)
  376. else:
  377. return callback()
  378. else:
  379. logger.error(STATS_RECEIVED_UNKNOWN_COMMAND, command)
  380. return isc.config.create_answer(1, "Unknown command: '"+str(command)+"'")
  381. def update_modules(self):
  382. """
  383. updates information of each module. This method gets each
  384. module's information from the config manager and sets it into
  385. self.modules. If its getting from the config manager fails, it
  386. raises StatsError.
  387. """
  388. modules = {}
  389. seq = self.cc_session.group_sendmsg(
  390. isc.config.ccsession.create_command(
  391. isc.config.ccsession.COMMAND_GET_STATISTICS_SPEC),
  392. 'ConfigManager')
  393. (answer, env) = self.cc_session.group_recvmsg(False, seq)
  394. if answer:
  395. (rcode, value) = isc.config.ccsession.parse_answer(answer)
  396. if rcode == 0:
  397. for mod in value:
  398. spec = { "module_name" : mod }
  399. if value[mod] and type(value[mod]) is list:
  400. spec["statistics"] = value[mod]
  401. modules[mod] = isc.config.module_spec.ModuleSpec(spec)
  402. else:
  403. raise StatsError("Updating module spec fails: " + str(value))
  404. modules[self.module_name] = self.mccs.get_module_spec()
  405. self.modules = modules
  406. def get_statistics_data(self, owner=None, name=None):
  407. """
  408. returns statistics data which stats module has of each
  409. module. If it can't find specified statistics data, it raises
  410. StatsError.
  411. """
  412. self.update_modules()
  413. if owner and name:
  414. try:
  415. return {owner:{name:self.statistics_data[owner][name]}}
  416. except KeyError:
  417. pass
  418. elif owner:
  419. try:
  420. return {owner: self.statistics_data[owner]}
  421. except KeyError:
  422. pass
  423. elif name:
  424. pass
  425. else:
  426. return self.statistics_data
  427. raise StatsError("No statistics data found: "
  428. + "owner: " + str(owner) + ", "
  429. + "name: " + str(name))
  430. def update_statistics_data(self, owner=None, mid=None, data=None):
  431. """
  432. change statistics data of specified module into specified
  433. data. It updates information of each module first, and it
  434. updates statistics data. If specified data is invalid for
  435. statistics spec of specified owner, it returns a list of error
  436. messages. If there is no error or if neither owner nor data is
  437. specified in args, it returns None. The 'mid' argument is an identifier of
  438. the sender module in order for stats to identify which
  439. instance sends statistics data in the situation that multiple
  440. instances are working.
  441. """
  442. # Note:
  443. # The fix of #1751 is for multiple instances working. It is
  444. # assumed here that they send different statistics data with
  445. # each sender module id (mid). Stats should save their statistics data by
  446. # mid. The statistics data, which is the existing variable, is
  447. # preserved by accumlating from statistics data by the mid. This
  448. # is an ad-hoc fix because administrators can not see
  449. # statistics by each instance via bindctl or HTTP/XML. These
  450. # interfaces aren't changed in this fix.
  451. def _accum_bymodule(statistics_data_bymid):
  452. """This is an internal method for the superordinate
  453. method. It accumulates statistics data of each module id
  454. by module. It returns a accumulated result."""
  455. # FIXME: A issue might happen when consolidating
  456. # statistics of the multiple instances. If they have
  457. # different statistics data which are not for adding each
  458. # other, this might happen: If these are integer or float,
  459. # these are added each other. If these are string , these
  460. # are compared and consolidated into bigger one. If one
  461. # of them is None type , these might be consolidated
  462. # into not None-type one. Otherwise these are overwritten
  463. # into one of them.
  464. ret = {}
  465. for data in statistics_data_bymid.values():
  466. ret.update(_accum(data, ret))
  467. return ret
  468. # Firstly, it gets default statistics data in each spec file.
  469. statistics_data = {}
  470. for (name, module) in self.modules.items():
  471. value = get_spec_defaults(module.get_statistics_spec())
  472. if module.validate_statistics(True, value):
  473. statistics_data[name] = value
  474. self.statistics_data = statistics_data
  475. # If the "owner" and "data" arguments in this function are
  476. # specified, then the variable of statistics data of each module id
  477. # would be updated.
  478. errors = []
  479. if owner and data:
  480. _data = self.statistics_data_bymid.copy()
  481. try:
  482. for (_key, _val) in data.items():
  483. if self.modules[owner].validate_statistics(
  484. False, {_key: _val}, errors):
  485. if owner not in _data:
  486. _data[owner] = { mid: { _key: _val } }
  487. elif mid not in _data[owner]:
  488. _data[owner][mid] = { _key: _val }
  489. else:
  490. # merge recursively old value and new
  491. # value each other
  492. _data[owner][mid] = \
  493. merge_oldnew(_data[owner][mid],
  494. {_key: _val})
  495. continue
  496. # the key string might be a "xx/yy/zz[0]"
  497. # type. try it.
  498. if _key.find('/') >= 0 or \
  499. isc.cc.data.identifier_has_list_index(_key):
  500. # remove the last error
  501. if errors: errors.pop()
  502. # try updata and check validation in adavance
  503. __data = _data.copy()
  504. if owner not in _data:
  505. __data[owner] = {}
  506. if mid not in _data[owner]:
  507. __data[owner][mid] = {}
  508. # use the isc.cc.data.set method
  509. try:
  510. isc.cc.data.set(__data[owner][mid],
  511. _key, _val)
  512. if self.modules[owner].validate_statistics(
  513. False, __data[owner][mid], errors):
  514. _data = __data
  515. except Exception as e:
  516. errors.append(
  517. "%s: %s" % (e.__class__.__name__, e))
  518. except KeyError:
  519. errors.append("unknown module name: " + str(owner))
  520. if not errors:
  521. self.statistics_data_bymid = _data
  522. # Just consolidate statistics data of each module without
  523. # removing that of modules which have been already dead
  524. mlist = [ k for k in self.statistics_data_bymid.keys() ]
  525. for m in mlist:
  526. if self.statistics_data_bymid[m]:
  527. if m in self.statistics_data:
  528. # propagate the default values by times of
  529. # instances
  530. _len = len(self.statistics_data_bymid[m])
  531. for i in range(0, _len - 1):
  532. self.statistics_data[m] = _accum(
  533. self.statistics_data[m],
  534. self.statistics_data[m])
  535. # replace the default values with summaries of the
  536. # collected values of each module. But the default
  537. # values which are not included in collected
  538. # values are not replaced.
  539. self.statistics_data[m] = merge_oldnew(
  540. self.statistics_data[m],
  541. _accum_bymodule(
  542. self.statistics_data_bymid[m]))
  543. if errors: return errors
  544. def command_status(self):
  545. """
  546. handle status command
  547. """
  548. logger.debug(DBG_STATS_MESSAGING, STATS_RECEIVED_STATUS_COMMAND)
  549. return isc.config.create_answer(
  550. 0, "Stats is up. (PID " + str(os.getpid()) + ")")
  551. def command_shutdown(self, pid=None):
  552. """
  553. handle shutdown command
  554. The pid argument is ignored, it is here to match the signature.
  555. """
  556. logger.info(STATS_RECEIVED_SHUTDOWN_COMMAND)
  557. self.running = False
  558. return isc.config.create_answer(0)
  559. def command_show(self, owner=None, name=None):
  560. """
  561. handle show command
  562. """
  563. # decide if polling should be done based on the the last time of
  564. # polling. If more than one second has passed since the last
  565. # request to each module, the stats module requests each module
  566. # statistics data and then shows the latest result. Otherwise,
  567. # the stats module just shows statistics data which it has.
  568. if get_timestamp() - self._lasttime_poll > 1.0:
  569. self.do_polling()
  570. if owner or name:
  571. logger.debug(DBG_STATS_MESSAGING,
  572. STATS_RECEIVED_SHOW_NAME_COMMAND,
  573. str(owner)+", "+str(name))
  574. else:
  575. logger.debug(DBG_STATS_MESSAGING,
  576. STATS_RECEIVED_SHOW_ALL_COMMAND)
  577. errors = self.update_statistics_data(
  578. self.module_name,
  579. self.cc_session.lname,
  580. {'timestamp': get_timestamp(),
  581. 'report_time': get_datetime()}
  582. )
  583. if errors:
  584. raise StatsError("stats spec file is incorrect: "
  585. + ", ".join(errors))
  586. try:
  587. return isc.config.create_answer(
  588. 0, self.get_statistics_data(owner, name))
  589. except StatsError:
  590. return isc.config.create_answer(
  591. 1, "specified arguments are incorrect: " \
  592. + "owner: " + str(owner) + ", name: " + str(name))
  593. def command_showschema(self, owner=None, name=None):
  594. """
  595. handle show command
  596. """
  597. if owner or name:
  598. logger.debug(DBG_STATS_MESSAGING,
  599. STATS_RECEIVED_SHOWSCHEMA_NAME_COMMAND,
  600. str(owner)+", "+str(name))
  601. else:
  602. logger.debug(DBG_STATS_MESSAGING,
  603. STATS_RECEIVED_SHOWSCHEMA_ALL_COMMAND)
  604. self.update_modules()
  605. schema = {}
  606. schema_byname = {}
  607. for mod in self.modules:
  608. spec = self.modules[mod].get_statistics_spec()
  609. schema_byname[mod] = {}
  610. if spec:
  611. schema[mod] = spec
  612. for item in spec:
  613. schema_byname[mod][item['item_name']] = item
  614. if owner:
  615. try:
  616. if name:
  617. return isc.config.create_answer(0, {owner:[schema_byname[owner][name]]})
  618. else:
  619. return isc.config.create_answer(0, {owner:schema[owner]})
  620. except KeyError:
  621. pass
  622. else:
  623. if name:
  624. return isc.config.create_answer(1, "module name is not specified")
  625. else:
  626. return isc.config.create_answer(0, schema)
  627. return isc.config.create_answer(
  628. 1, "specified arguments are incorrect: " \
  629. + "owner: " + str(owner) + ", name: " + str(name))
  630. if __name__ == "__main__":
  631. try:
  632. parser = OptionParser()
  633. parser.add_option(
  634. "-v", "--verbose", dest="verbose", action="store_true",
  635. help="enable maximum debug logging")
  636. (options, args) = parser.parse_args()
  637. if options.verbose:
  638. isc.log.init("b10-stats", "DEBUG", 99, buffer=True)
  639. stats = Stats()
  640. stats.start()
  641. except OptionValueError as ove:
  642. logger.fatal(STATS_BAD_OPTION_VALUE, ove)
  643. sys.exit(1)
  644. except isc.cc.session.SessionError as se:
  645. logger.fatal(STATS_CC_SESSION_ERROR, se)
  646. sys.exit(1)
  647. except StatsError as se:
  648. logger.fatal(STATS_START_ERROR, se)
  649. sys.exit(1)
  650. except KeyboardInterrupt as kie:
  651. logger.info(STATS_STOPPED_BY_KEYBOARD)