gen-statisticsitems.py.pre.in 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  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. item_list = []
  25. localstatedir = '@@LOCALSTATEDIR@@'
  26. builddir = '@builddir@'
  27. srcdir = '@srcdir@'
  28. pre_suffix = '.pre'
  29. xmldocument_command_name = 'b10-auth'
  30. def need_generate(filepath, prepath, mtime):
  31. '''Check if we need to generate the specified file.
  32. To avoid unnecessary compilation, we skip (re)generating the file when
  33. the file already exists and newer than the base file, and definition file
  34. specified with mtime.
  35. '''
  36. if os.path.exists(filepath) and\
  37. (os.path.getmtime(filepath) > mtime and
  38. os.path.getmtime(filepath) > os.path.getmtime(prepath)):
  39. return False
  40. return True
  41. def import_definitions():
  42. '''Load statsitics items definitions from statistics_msg_items.def.
  43. statistics_msg_items.def defines a tree of message statistics items.
  44. Syntax:
  45. Each line describes a node; branch node for subset of counters,
  46. leaf node for a counter item.
  47. Each fields are separated with one or more field separator (Tab).
  48. Field separator in the head of a line is ignored.
  49. branch node:
  50. (item name)\t+(internal branch name)\t+(description of the item)\t+'='
  51. leaf node:
  52. (item name)\t+(internal item counter name)\t+(description of the item)
  53. Branch nodes contain leaf nodes and/or branch nodes. The end of
  54. a branch node is indicated with ';' as item name (first column).
  55. Internal branch name and internal item counter name must be unique.
  56. Returns mtime of statistics_msg_items.def. It will be used to check
  57. auto-generated files need to be regenerated.
  58. '''
  59. global item_list
  60. items_definition_file = srcdir + os.sep + 'statistics_msg_items.def'
  61. with open(items_definition_file, 'r') as item_definition:
  62. re_splitter = re.compile('\t+')
  63. l = item_list
  64. lp = None
  65. for line in item_definition.readlines():
  66. element = re_splitter.split(line.rstrip())
  67. # pop first element if it is empty to skip indent
  68. if element[0] == '':
  69. element.pop(0)
  70. # last element is '=': a branch node definition.
  71. if element[-1] == '=':
  72. l.append({'name': element[0], 'child': [], 'index': element[1],
  73. 'description': element[2], 'parent': lp})
  74. lp = l
  75. l = l[-1]['child']
  76. # last element is ';': end of a branch node.
  77. elif element[-1] == ';':
  78. l = lp
  79. lp = l[-1]['parent']
  80. # otherwise, a leaf node definition.
  81. else:
  82. l.append({'name': element[0], 'child': None,
  83. 'index': element[1], 'description': element[2],
  84. 'parent': lp})
  85. return os.path.getmtime(items_definition_file)
  86. def generate_specfile(specfile, def_mtime):
  87. '''Generate spec in specfile from skeleton (specfille+'.pre').
  88. If the specfile is newer than both skeleton and def_mtime, file generation
  89. will be skipped.
  90. This method reads the content of skeleton and appends statistics items
  91. definition into { "module_spec": { "statistics": } }.
  92. LOCALSTATEDIR is also expanded.
  93. Returns nothing.
  94. '''
  95. def convert_list(items, prefix=''):
  96. spec_list = []
  97. default_map = {}
  98. for item in items:
  99. full_item_name = prefix + item['name']
  100. if item['child'] is None:
  101. default_map[item['name']] = 0
  102. spec_list.append({
  103. 'item_name': item['name'],
  104. 'item_optional': False,
  105. 'item_type': 'integer',
  106. 'item_default': 0,
  107. 'item_title': full_item_name,
  108. 'item_description': item['description'],
  109. })
  110. else:
  111. child_spec_list, child_default_map = \
  112. convert_list(item['child'], full_item_name + '.')
  113. spec_list.append({
  114. 'item_name': item['name'],
  115. 'item_type': 'map',
  116. 'item_optional': False,
  117. 'item_title': full_item_name,
  118. 'item_description': item['description'],
  119. 'item_default': child_default_map,
  120. 'map_item_spec': child_spec_list,
  121. })
  122. default_map[item['name']] = child_default_map
  123. return spec_list, default_map
  124. item_spec_list, item_default_map = convert_list(item_list)
  125. statistics_spec_list = [{
  126. 'item_name': 'zones',
  127. 'item_type': 'named_set',
  128. 'item_optional': False,
  129. 'item_title': 'Zone statistics',
  130. 'item_description':
  131. 'Zone statistics items. ' +
  132. "Items for all zones are stored in '_SERVER_'.",
  133. 'item_default': { '_SERVER_': item_default_map },
  134. 'named_set_item_spec': {
  135. 'item_name': 'zone',
  136. 'item_type': 'map',
  137. 'item_optional': False,
  138. 'item_default': {},
  139. 'map_item_spec': item_spec_list,
  140. },
  141. }]
  142. if need_generate(builddir+os.sep+specfile,
  143. builddir+os.sep+specfile+pre_suffix, def_mtime):
  144. with open(builddir+os.sep+specfile+pre_suffix, 'r') as stats_pre:
  145. # split LOCALSTATEDIR to avoid substitution
  146. stats_pre_json = \
  147. json.loads(stats_pre.read().replace('@@LOCAL'+'STATEDIR@@',
  148. localstatedir))
  149. stats_pre_json['module_spec']['statistics'] = statistics_spec_list
  150. statistics_spec_json = json.dumps(stats_pre_json, sort_keys=True,
  151. indent=2)
  152. with open(builddir+os.sep+specfile, 'w') as stats_spec:
  153. stats_spec.write(statistics_spec_json)
  154. else:
  155. print('skip generating ' + specfile)
  156. return
  157. def generate_docfile(docfile, def_mtime):
  158. '''Generate docbook XML in docfile from skeleton (docfile+'.pre').
  159. If the docfile is newer than both skeleton and def_mtime, file generation
  160. will be skipped.
  161. This method reads the content of skeleton and replaces
  162. <!-- ### STATISTICS DATA PLACEHOLDER ### --> with statistics items
  163. definition. LOCALSTATEDIR is also expanded.
  164. Returns nothing.
  165. '''
  166. def convert_list(items, tree, prefix=''):
  167. '''
  168. Build XML tree from items.
  169. <varlistentry>
  170. <term>##item_full_name##</term>
  171. <listitem><simpara>##item_description##</simpara></listitem>
  172. </varlistentry>
  173. xmldocument_command_name in item_description is put inside <command>
  174. element.
  175. '''
  176. for item in items:
  177. full_item_name = prefix + item['name']
  178. if item['child'] is None:
  179. # the item is a leaf node: build varlistentry
  180. child_element = ElementTree.SubElement(tree, 'varlistentry')
  181. term = ElementTree.SubElement(child_element, 'term')
  182. term.text = full_item_name
  183. list_item = ElementTree.SubElement(child_element, 'listitem')
  184. sim_para = ElementTree.SubElement(list_item, 'simpara')
  185. sim_para.text = ''
  186. prev = None
  187. # put xmldocument_command_name in <command> node
  188. for word in item['description'].split():
  189. if word == xmldocument_command_name:
  190. command = ElementTree.SubElement(sim_para, 'command')
  191. command.text = word
  192. # at this point command.tail is None
  193. # append a space as trailing text for the next word
  194. # so it can be concatenated with trailing words
  195. command.tail = ' '
  196. prev = command
  197. else:
  198. if prev is None:
  199. sim_para.text += word + ' '
  200. else:
  201. prev.tail += word + ' '
  202. # remove trailing whitespaces at the end of the description
  203. if prev is None:
  204. sim_para.text = sim_para.text.rstrip()
  205. else:
  206. prev.tail = prev.tail.rstrip()
  207. else:
  208. # the item is a branch node: call myself for child nodes
  209. convert_list(item['child'], tree, full_item_name + '.')
  210. return
  211. if need_generate(builddir+os.sep+docfile,
  212. srcdir+os.sep+docfile+pre_suffix, def_mtime):
  213. with open(srcdir+os.sep+docfile+pre_suffix, 'r') as doc_pre:
  214. # split LOCALSTATEDIR to avoid substitution
  215. doc_pre_xml = doc_pre.read().replace('@@LOCAL'+'STATEDIR@@',
  216. localstatedir)
  217. variable_tree = ElementTree.Element('variablelist')
  218. convert_list(item_list, variable_tree)
  219. pretty_xml = ElementTree.tostring(variable_tree)
  220. if not isinstance(pretty_xml, str):
  221. pretty_xml = pretty_xml.decode('utf-8')
  222. # put newline around <variablelist> and <varlistentry> element
  223. pretty_xml = \
  224. re.sub(r'(</?var(?:iablelist|listentry)>)', r'\1\n', pretty_xml)
  225. # indent <term> and <listitem>
  226. pretty_xml = \
  227. re.sub(r'(<(?:term|listitem)>)', r' \1', pretty_xml)
  228. # put newline after </term> and </listitem>
  229. pretty_xml = \
  230. re.sub(r'(</(?:term|listitem)>)', r'\1\n', pretty_xml)
  231. with open(builddir+os.sep+docfile, 'w') as doc:
  232. doc.write(doc_pre_xml.replace(
  233. '<!-- ### STATISTICS DATA PLACEHOLDER ### -->',
  234. pretty_xml))
  235. else:
  236. print('skip generating ' + docfile)
  237. return
  238. def generate_cxx(itemsfile, ccfile, utfile, def_mtime):
  239. '''Generate some part of statistics code in itemsfile, ccfile, utfile from
  240. skeleton (itemsfile+'.pre', ccfile+'.pre', utfile+'.pre').
  241. If the file is newer than both skeleton and def_mtime, file generation
  242. will be skipped.
  243. This method reads the content of skeleton and replaces
  244. // ### STATISTICS ITEMS DEFINITION ### with statistics items definition in
  245. ccfile and utfile,
  246. // ### STATISTICS ITEMS DECLARATION ### with statistics items declaration
  247. in itemsfile.
  248. Returns nothing.
  249. '''
  250. msg_counter_types = ['enum MSGCounterType {']
  251. item_names = ['// using -1 as counter_id to state it is not a '
  252. + 'counter item']
  253. item_names += ['const int NOT_ITEM = -1;', '']
  254. def convert_list(items, item_head, msg_counter_types, item_names):
  255. '''Convert item tree to a set of C++ code fragment in given lists.
  256. This method recursively builds two lists:
  257. - msg_counter_types consists of strings for all leaf items, each
  258. defines one enum element with a comment, e.g.
  259. COUNTER_ITEM, ///< item's description
  260. - item_names consists of tuples of three elements, depending on
  261. whether it's a leaf element (no child from it) or not:
  262. (leaf) ( "item_name", NULL, COUNTER_ITEM )
  263. (branch) ( "item_name", CHILD_NAME, NOT_ITEM )
  264. Each single call to this function builds a C++ structure beginning
  265. with the given item_head, which is a string that reads like
  266. 'const struct CounterSpec some_counters[] = {'
  267. followed by the item_names tuples corresponding to that level.
  268. If some of the items of this level have a child, recursive calls
  269. to this function extends msg_counter_types and item_names.
  270. item_names for this level will be concatenated at the end end of
  271. the given item_names.
  272. '''
  273. item_names_current = [item_head]
  274. for item in items:
  275. item_spec = ' { "' + item['name'] + '", '
  276. if item['child'] is None:
  277. item_spec += 'NULL, ' + item['index']
  278. msg_counter_types.append(' ' + item['index'] + ', ' +
  279. '///< ' + item['description'])
  280. else:
  281. item_spec += item['index'] + ', NOT_ITEM'
  282. child_head = 'const struct CounterSpec ' + \
  283. item['index'] + '[] = {'
  284. convert_list(item['child'], child_head,
  285. msg_counter_types, item_names)
  286. item_names_current.append(item_spec + ' },')
  287. item_names_current.append(' { NULL, NULL, NOT_ITEM }\n};')
  288. item_names.extend(item_names_current)
  289. convert_list(item_list, 'const struct CounterSpec msg_counter_tree[] = {',
  290. msg_counter_types, item_names)
  291. msg_counter_types.extend([
  292. ' // End of counter types',
  293. ' MSG_COUNTER_TYPES ///< The number of defined counters',
  294. '};'])
  295. item_decls = '\n'.join(msg_counter_types)
  296. item_defs = '\n'.join(item_names)
  297. if need_generate(builddir+os.sep+itemsfile,
  298. srcdir+os.sep+itemsfile+pre_suffix, def_mtime):
  299. with open(srcdir+os.sep+itemsfile+pre_suffix, 'r') \
  300. as statistics_items_h_pre:
  301. items_pre = statistics_items_h_pre.read()
  302. with open(builddir+os.sep+itemsfile, 'w') as statistics_items_h:
  303. statistics_items_h.write(items_pre.replace(
  304. '// ### STATISTICS ITEMS DECLARATION ###', item_decls))
  305. else:
  306. print('skip generating ' + itemsfile)
  307. if need_generate(builddir+os.sep+ccfile,
  308. srcdir+os.sep+ccfile+pre_suffix, def_mtime):
  309. with open(srcdir+os.sep+ccfile+pre_suffix, 'r') as statistics_cc_pre:
  310. items_pre = statistics_cc_pre.read()
  311. with open(builddir+os.sep+ccfile, 'w') as statistics_cc:
  312. statistics_cc.write(items_pre.replace(
  313. '// ### STATISTICS ITEMS DEFINITION ###', item_defs))
  314. else:
  315. print('skip generating ' + ccfile)
  316. if need_generate(builddir+os.sep+utfile,
  317. srcdir+os.sep+utfile+pre_suffix, def_mtime):
  318. with open(srcdir+os.sep+utfile+pre_suffix, 'r') \
  319. as statistics_ut_cc_pre:
  320. items_pre = statistics_ut_cc_pre.read()
  321. with open(builddir+os.sep+utfile, 'w') as statistics_ut_cc:
  322. statistics_ut_cc.write(items_pre.replace(
  323. '// ### STATISTICS ITEMS DEFINITION ###', item_defs))
  324. else:
  325. print('skip generating ' + utfile)
  326. return
  327. if __name__ == "__main__":
  328. try:
  329. def_mtime = import_definitions()
  330. generate_specfile('auth.spec', def_mtime)
  331. generate_docfile('b10-auth.xml', def_mtime)
  332. generate_cxx('statistics_items.h',
  333. 'statistics.cc',
  334. 'tests'+os.sep+'statistics_unittest.cc',
  335. def_mtime)
  336. except:
  337. sys.stderr.write('File generation failed due to exception: %s\n' %
  338. sys.exc_info()[1])
  339. exit(1)