gen-statisticsitems.py.pre.in 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. #!@PYTHON@
  2. # Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
  3. #
  4. # Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH
  9. # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  10. # AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  11. # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  12. # LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  13. # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  14. # PERFORMANCE OF THIS SOFTWARE.
  15. """\
  16. This script generates spec file, docbook XML and some part of statistics code
  17. from statistics_msg_items.def.
  18. """
  19. import os
  20. import re
  21. import sys
  22. import json
  23. from xml.etree import ElementTree
  24. from xml.dom.minidom import parseString
  25. item_list = []
  26. localstatedir = '@@LOCALSTATEDIR@@'
  27. builddir = '@builddir@'
  28. srcdir = '@srcdir@'
  29. pre_suffix = '.pre'
  30. xmldocument_command_name = 'b10-auth'
  31. def need_generate(filepath, prepath, mtime):
  32. '''Check if we need to generate the specified file.
  33. To avoid unnecessary compilation, we skip (re)generating the file when
  34. the file already exists and newer than the base file, and definition file
  35. specified with mtime.
  36. '''
  37. if os.path.exists(filepath) and\
  38. (os.path.getmtime(filepath) > mtime and
  39. os.path.getmtime(filepath) > os.path.getmtime(prepath)):
  40. return False
  41. return True
  42. def import_definitions():
  43. '''Load statsitics items definitions from statistics_msg_items.def.
  44. statistics_msg_items.def defines a tree of message statistics items.
  45. Syntax:
  46. Each line describes a node; branch node for subset of counters,
  47. leaf node for a counter item.
  48. Each fields are separated with one or more field separator (Tab).
  49. Field separator in the head of a line is ignored.
  50. branch node:
  51. (item name)\t+(internal branch name)\t+(description of the item)\t+'='
  52. leaf node:
  53. (item name)\t+(internal item counter name)\t+(description of the item)
  54. Branch nodes contains leaf nodes and/or branch nodes. The end of
  55. a branch node is indicated with ';' as item name (first column).
  56. Internal branch name and internal item counter name must be unique.
  57. Returns mtime of statistics_msg_items.def. It will be used to check
  58. auto-generated files need to be regenerated.
  59. '''
  60. global item_list
  61. items_definition_file = srcdir + os.sep + 'statistics_msg_items.def'
  62. with open(items_definition_file, 'r') as item_definition:
  63. re_splitter = re.compile('\t+')
  64. l = item_list
  65. lp = None
  66. for line in item_definition.readlines():
  67. element = re_splitter.split(line.rstrip())
  68. # pop first element if it is empty to skip indent
  69. if element[0] == '':
  70. element.pop(0)
  71. # last element is '=': a branch node definition.
  72. if element[-1] == '=':
  73. l.append({'name': element[0], 'child': [], 'index': element[1],
  74. 'description': element[2], 'parent': lp})
  75. lp = l
  76. l = l[-1]['child']
  77. # last element is ';': end of a branch node.
  78. elif element[-1] == ';':
  79. l = lp
  80. lp = l[-1]['parent']
  81. # otherwise, a leaf node definition.
  82. else:
  83. l.append({'name': element[0], 'child': None,
  84. 'index': element[1], 'description': element[2],
  85. 'parent': lp})
  86. return os.path.getmtime(items_definition_file)
  87. def generate_specfile(specfile, def_mtime):
  88. '''Generate spec in specfile from skeleton (specfille+'.pre').
  89. If the specfile is newer than both skeleton and def_mtime, file generation
  90. will be skipped.
  91. This method reads the content of skeleton and appends statistics items
  92. definition into { "module_spec": { "statistics": } }.
  93. LOCALSTATEDIR is also expanded.
  94. Returns nothing.
  95. '''
  96. def convert_list(items, prefix=''):
  97. spec_list = []
  98. default_map = {}
  99. for item in items:
  100. full_item_name = prefix + item['name']
  101. if item['child'] is None:
  102. default_map[item['name']] = 0
  103. spec_list.append({
  104. 'item_name': item['name'],
  105. 'item_optional': False,
  106. 'item_type': 'integer',
  107. 'item_default': 0,
  108. 'item_title': full_item_name,
  109. 'item_description': item['description'],
  110. })
  111. else:
  112. child_spec_list, child_default_map = \
  113. convert_list(item['child'], full_item_name + '.')
  114. spec_list.append({
  115. 'item_name': item['name'],
  116. 'item_type': 'map',
  117. 'item_optional': False,
  118. 'item_title': full_item_name,
  119. 'item_description': item['description'],
  120. 'item_default': child_default_map,
  121. 'map_item_spec': child_spec_list,
  122. })
  123. default_map[item['name']] = child_default_map
  124. return spec_list, default_map
  125. item_spec_list, item_default_map = convert_list(item_list)
  126. statistics_spec_list = [{
  127. 'item_name': 'zones',
  128. 'item_type': 'named_set',
  129. 'item_optional': False,
  130. 'item_title': 'Zone statistics',
  131. 'item_description':
  132. 'Zone statistics items. ' +
  133. "Items for all zones are stored in '_SERVER_'.",
  134. 'item_default': { '_SERVER_': item_default_map },
  135. 'named_set_item_spec': {
  136. 'item_name': 'zone',
  137. 'item_type': 'map',
  138. 'item_optional': False,
  139. 'item_default': {},
  140. 'map_item_spec': item_spec_list,
  141. },
  142. }]
  143. if need_generate(builddir+os.sep+specfile,
  144. builddir+os.sep+specfile+pre_suffix, def_mtime):
  145. with open(builddir+os.sep+specfile+pre_suffix, 'r') as stats_pre:
  146. # split LOCALSTATEDIR to avoid substitution
  147. stats_pre_json = \
  148. json.loads(stats_pre.read().replace('@@LOCAL'+'STATEDIR@@',
  149. localstatedir))
  150. stats_pre_json['module_spec']['statistics'] = statistics_spec_list
  151. statistics_spec_json = json.dumps(stats_pre_json, sort_keys=True,
  152. indent=2)
  153. with open(builddir+os.sep+specfile, 'w') as stats_spec:
  154. stats_spec.write(statistics_spec_json)
  155. else:
  156. print('skip generating ' + specfile)
  157. return
  158. def generate_docfile(docfile, def_mtime):
  159. '''Generate docbook XML in docfile from skeleton (docfile+'.pre').
  160. If the docfile is newer than both skeleton and def_mtime, file generation
  161. will be skipped.
  162. This method reads the content of skeleton and replaces
  163. <!-- ### STATISTICS DATA PLACEHOLDER ### --> with statistics items
  164. definition. LOCALSTATEDIR is also expanded.
  165. Returns nothing.
  166. '''
  167. def convert_list(items, tree, prefix=''):
  168. '''
  169. Build XML tree from items.
  170. <varlistentry>
  171. <term>##item_full_name##</term>
  172. <listitem>
  173. <simpara>
  174. ##item_description##
  175. </simpara>
  176. </listitem>
  177. <varlistentry>
  178. xmldocument_command_name in item_description is surrounded with
  179. <command>.
  180. '''
  181. for item in items:
  182. full_item_name = prefix + item['name']
  183. if item['child'] is None:
  184. # the item is a leaf node: build varlistentry
  185. child_element = ElementTree.SubElement(tree, 'varlistentry')
  186. term = ElementTree.SubElement(child_element, 'term')
  187. term.text = full_item_name
  188. list_item = ElementTree.SubElement(child_element, 'listitem')
  189. sim_para = ElementTree.SubElement(list_item, 'simpara')
  190. sim_para.text = ''
  191. prev = None
  192. # put xmldocument_command_name in <command> node
  193. for word in item['description'].split():
  194. if word == xmldocument_command_name:
  195. command = ElementTree.SubElement(sim_para, 'command')
  196. command.text = word
  197. # append empty string for trailing text
  198. command.tail = ''
  199. prev = command
  200. else:
  201. if prev is None:
  202. sim_para.text += word + ' '
  203. else:
  204. prev.tail += word + ' '
  205. # remove extra trailing whitespaces
  206. sim_para.text = sim_para.text.rstrip()
  207. if prev is not None:
  208. prev.tail = prev.tail.rstrip()
  209. else:
  210. # the item is a branch node: call myself for child nodes
  211. convert_list(item['child'], tree, full_item_name + '.')
  212. return
  213. if need_generate(builddir+os.sep+docfile,
  214. srcdir+os.sep+docfile+pre_suffix, def_mtime):
  215. with open(srcdir+os.sep+docfile+pre_suffix, 'r') as doc_pre:
  216. # split LOCALSTATEDIR to avoid substitution
  217. doc_pre_xml = doc_pre.read().replace('@@LOCAL'+'STATEDIR@@',
  218. localstatedir)
  219. variable_tree = ElementTree.Element('variablelist')
  220. convert_list(item_list, variable_tree)
  221. pretty_xml = \
  222. parseString(ElementTree.tostring(variable_tree)).toprettyxml()
  223. # remove XML declaration
  224. pretty_xml = re.sub('<\?xml[^?]+\?>', '', pretty_xml)
  225. # remove extra whitespaces inside <command> and <term>
  226. pretty_xml = \
  227. re.sub(r'<(command|term)>\s+([^<\s]+)\s+</\1>', r'<\1>\2</\1>',
  228. pretty_xml)
  229. with open(builddir+os.sep+docfile, 'w') as doc:
  230. doc.write(doc_pre_xml.replace(
  231. '<!-- ### STATISTICS DATA PLACEHOLDER ### -->',
  232. pretty_xml))
  233. else:
  234. print('skip generating ' + docfile)
  235. return
  236. def generate_cxx(itemsfile, ccfile, utfile, def_mtime):
  237. '''Generate some part of statistics code in itemsfile, ccfile, utfile from
  238. skeleton (itemsfile+'.pre', ccfile+'.pre', utfile+'.pre').
  239. If the file is newer than both skeleton and def_mtime, file generation
  240. will be skipped.
  241. This method reads the content of skeleton and replaces
  242. // ### STATISTICS ITEMS DEFINITION ### with statistics items definition in
  243. ccfile and utfile,
  244. // ### STATISTICS ITEMS DECLARATION ### with statistics items declaration
  245. in itemsfile.
  246. Returns nothing.
  247. '''
  248. msg_counter_types = 'enum MSGCounterType {\n'
  249. item_names = ['// using -1 as counter_id to state it is not a '
  250. + 'counter item\n']
  251. item_names += ['const int NOT_ITEM = -1;\n', '\n']
  252. def convert_list(items, msg_counter_types, item_names_current, item_names):
  253. for item in items:
  254. if item['child'] is None:
  255. msg_counter_types += ' ' + item['index'] + ', ' +\
  256. '///< ' + item['description'] + '\n'
  257. item_names_current.append(' { "' + item['name'] +
  258. '", NULL, ' + item['index'] + ' },\n'
  259. )
  260. else:
  261. item_names_current_ = ['const struct CounterSpec ' +
  262. item['index'] + '[] = {\n']
  263. msg_counter_types, item_names_current_, item_names = \
  264. convert_list(item['child'], msg_counter_types,
  265. item_names_current_, item_names)
  266. item_names_current_.append(' { NULL, NULL, NOT_ITEM }\n' +
  267. '};\n')
  268. item_names.extend(item_names_current_)
  269. item_names_current.append(' { "' + item['name'] + '", ' +
  270. item['index'] + ', NOT_ITEM },\n')
  271. return msg_counter_types, item_names_current, item_names
  272. msg_counter_types, item_names_current, item_names = \
  273. convert_list(item_list, msg_counter_types, [], item_names)
  274. item_names.append('const struct CounterSpec msg_counter_tree[] = {\n')
  275. item_names.extend(item_names_current)
  276. item_names.append(' { NULL, NULL, NOT_ITEM }\n' +
  277. '};\n')
  278. msg_counter_types += \
  279. ' // End of counter types\n' +\
  280. ' MSG_COUNTER_TYPES ///< The number of defined counters\n' +\
  281. '};\n'
  282. item_decls = msg_counter_types
  283. item_defs = ''.join(item_names)
  284. if need_generate(builddir+os.sep+itemsfile,
  285. srcdir+os.sep+itemsfile+pre_suffix, def_mtime):
  286. with open(srcdir+os.sep+itemsfile+pre_suffix, 'r') \
  287. as statistics_items_h_pre:
  288. items_pre = statistics_items_h_pre.read()
  289. with open(builddir+os.sep+itemsfile, 'w') as statistics_items_h:
  290. statistics_items_h.write(items_pre.replace(
  291. '// ### STATISTICS ITEMS DECLARATION ###', item_decls))
  292. else:
  293. print('skip generating ' + itemsfile)
  294. if need_generate(builddir+os.sep+ccfile,
  295. srcdir+os.sep+ccfile+pre_suffix, def_mtime):
  296. with open(srcdir+os.sep+ccfile+pre_suffix, 'r') as statistics_cc_pre:
  297. items_pre = statistics_cc_pre.read()
  298. with open(builddir+os.sep+ccfile, 'w') as statistics_cc:
  299. statistics_cc.write(items_pre.replace(
  300. '// ### STATISTICS ITEMS DEFINITION ###', item_defs))
  301. else:
  302. print('skip generating ' + ccfile)
  303. if need_generate(builddir+os.sep+utfile,
  304. srcdir+os.sep+utfile+pre_suffix, def_mtime):
  305. with open(srcdir+os.sep+utfile+pre_suffix, 'r') \
  306. as statistics_ut_cc_pre:
  307. items_pre = statistics_ut_cc_pre.read()
  308. with open(builddir+os.sep+utfile, 'w') as statistics_ut_cc:
  309. statistics_ut_cc.write(items_pre.replace(
  310. '// ### STATISTICS ITEMS DEFINITION ###', item_defs))
  311. else:
  312. print('skip generating ' + utfile)
  313. return
  314. if __name__ == "__main__":
  315. try:
  316. def_mtime = import_definitions()
  317. generate_specfile('auth.spec', def_mtime)
  318. generate_docfile('b10-auth.xml', def_mtime)
  319. generate_cxx('statistics_items.h',
  320. 'statistics.cc',
  321. 'tests'+os.sep+'statistics_unittest.cc',
  322. def_mtime)
  323. except:
  324. sys.stderr.write('File generation failed due to exception: %s\n' %
  325. sys.exc_info()[1])
  326. exit(1)