chutney_manager.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. #!/usr/bin/env python3
  2. #
  3. import subprocess
  4. import logging
  5. import os
  6. import sys
  7. #
  8. def start_chutney_network(chutney_path, tor_path, network_file, controlling_pid=None, verification_rounds=None):
  9. args = [os.path.join(chutney_path, 'tools/test-network.sh'), '--chutney-path', chutney_path,
  10. '--tor-path', tor_path, '--stop-time', '-1', '--network', network_file]
  11. if controlling_pid is not None:
  12. args.extend(['--controlling-pid', str(controlling_pid)])
  13. #
  14. if verification_rounds is not None:
  15. args.extend(['--rounds', str(verification_rounds)])
  16. #
  17. try:
  18. return subprocess.check_output(args, stderr=subprocess.STDOUT).decode(sys.stdout.encoding)
  19. except subprocess.CalledProcessError as e:
  20. logging.error('Chutney error:\n' + e.output.decode(sys.stdout.encoding))
  21. raise
  22. #
  23. #
  24. def stop_chutney_network(chutney_path, network_file):
  25. args = [os.path.join(chutney_path, 'chutney'), 'stop', network_file]
  26. try:
  27. subprocess.check_output(args, stderr=subprocess.STDOUT)
  28. except subprocess.CalledProcessError as e:
  29. logging.error('Chutney error:\n' + e.output.decode(sys.stdout.encoding))
  30. raise
  31. #
  32. #
  33. class ChutneyNetwork:
  34. def __init__(self, chutney_path, tor_path, network_file, controlling_pid=None, verification_rounds=None):
  35. self.chutney_path = chutney_path
  36. self.network_file = network_file
  37. #
  38. try:
  39. self.startup_output = start_chutney_network(chutney_path, tor_path, network_file, controlling_pid=controlling_pid, verification_rounds=verification_rounds)
  40. except:
  41. self.stop()
  42. raise
  43. #
  44. #
  45. def stop(self):
  46. stop_chutney_network(self.chutney_path, self.network_file)
  47. #
  48. def __enter__(self):
  49. return self
  50. #
  51. def __exit__(self, exc_type, exc_val, exc_tb):
  52. self.stop()
  53. #
  54. #
  55. class Node:
  56. def __init__(self, **kwargs):
  57. self.options = kwargs
  58. #
  59. def guess_nickname(self, index):
  60. """
  61. This guesses the nickname based on the format Chutney uses. There is
  62. no good way to get the actual value.
  63. """
  64. #
  65. return '{:03}{}'.format(index, self.options['tag'])
  66. #
  67. def guess_control_port(self, index):
  68. """
  69. This guesses the control port based on the format Chutney uses. There is
  70. no good way to get the actual value.
  71. """
  72. #
  73. return 8000+index
  74. #
  75. def _value_formatter(self, value):
  76. if type(value) == str:
  77. return "'{}'".format(value)
  78. #
  79. return value
  80. #
  81. def __str__(self):
  82. arg_value_pairs = ['{}={}'.format(x, self._value_formatter(self.options[x])) for x in self.options]
  83. return 'Node({})'.format(', '.join(arg_value_pairs))
  84. #
  85. #
  86. def create_compact_chutney_config(nodes):
  87. if len(nodes) == 0:
  88. return None
  89. #
  90. config = ''
  91. for (name, count, options) in nodes:
  92. config += '{} = {}\n'.format(name, str(options))
  93. #
  94. config += '\n'
  95. config += 'NODES = {}\n'.format(' + '.join(['{}.getN({})'.format(name, count) for (name, count, options) in nodes]))
  96. config += '\n'
  97. config += 'ConfigureNodes(NODES)'
  98. #
  99. return config
  100. #
  101. def create_chutney_config(nodes):
  102. if len(nodes) == 0:
  103. return None
  104. #
  105. config = ''
  106. config += 'NODES = [{}]\n'.format(',\n'.join([str(node) for node in nodes]))
  107. config += '\n'
  108. config += 'ConfigureNodes(NODES)'
  109. #
  110. return config
  111. #
  112. def read_fingerprint(nickname, chutney_path):
  113. try:
  114. with open(os.path.join(chutney_path, 'net', 'nodes', nickname, 'fingerprint'), 'r') as f:
  115. return f.read().strip().split(' ')[1]
  116. #
  117. except IOError as e:
  118. return None
  119. #
  120. #
  121. def numa_scheduler(num_processors_needed, numa_nodes):
  122. """
  123. Finds the numa node with the most physical cores remaining and
  124. assigns physical cores (typically 2 virtual processors) until
  125. the process has enough processors.
  126. """
  127. #
  128. chosen_processors = []
  129. num_physical_cores = {x:len(numa_nodes[x]['physical_cores']) for x in numa_nodes}
  130. node_with_most_physical_cores = max(num_physical_cores, key=lambda x: (num_physical_cores.get(x), -x))
  131. while len(chosen_processors) < num_processors_needed:
  132. chosen_processors.extend(numa_nodes[node_with_most_physical_cores]['physical_cores'][0])
  133. # note: this may assign more processors than requested
  134. numa_nodes[node_with_most_physical_cores]['physical_cores'] = numa_nodes[node_with_most_physical_cores]['physical_cores'][1:]
  135. #
  136. return (node_with_most_physical_cores, chosen_processors)
  137. #
  138. if __name__ == '__main__':
  139. import time
  140. import tempfile
  141. import numa
  142. import useful
  143. #
  144. logging.basicConfig(level=logging.DEBUG)
  145. #
  146. chutney_path = '/home/sengler/code/measureme/chutney'
  147. tor_path = '/home/sengler/code/measureme/tor'
  148. #
  149. #nodes = [('authority', 2, Node(tag='a', relay=1, authority=1, torrc='authority.tmpl')),
  150. # ('other_relay', 14, Node(tag='r', relay=1, torrc='relay-non-exit.tmpl')),
  151. # ('exit_relay', 1, Node(tag='r', exit=1, torrc='relay.tmpl')),
  152. # ('client', 16, Node(tag='c', client=1, torrc='client.tmpl'))]
  153. #nodes = [('authority', 2, Node(tag='a', relay=1, num_cpus=2, authority=1, torrc='authority.tmpl')),
  154. # ('other_relay', 2, Node(tag='r', relay=1, num_cpus=2, torrc='relay-non-exit.tmpl')),
  155. # ('exit_relay', 1, Node(tag='r', exit=1, num_cpus=2, torrc='relay.tmpl')),
  156. # ('client', 2, Node(tag='c', client=1, num_cpus=1, torrc='client.tmpl'))]
  157. #
  158. nodes = [Node(tag='a', relay=1, num_cpus=2, authority=1, torrc='authority.tmpl') for _ in range(2)] + \
  159. [Node(tag='r', relay=1, num_cpus=2, torrc='relay-non-exit.tmpl') for _ in range(2)] + \
  160. [Node(tag='e', exit=1, num_cpus=2, torrc='relay.tmpl') for _ in range(1)] + \
  161. [Node(tag='c', client=1, num_cpus=1, torrc='client.tmpl') for _ in range(2)]
  162. #
  163. numa_remaining = numa.get_numa_overview()
  164. numa_sets = []
  165. for node in nodes:
  166. num_cpus = node.options['num_cpus']
  167. if num_cpus%2 != 0:
  168. num_cpus += 1
  169. #
  170. (numa_node, processors) = numa_scheduler(num_cpus, numa_remaining)
  171. node.options['numa_settings'] = (numa_node, processors)
  172. numa_sets.append((numa_node, processors))
  173. #
  174. print('Used processors: {}'.format(numa_sets))
  175. unused_processors = useful.generate_range_list([z for node in numa_remaining for y in numa_remaining[node]['physical_cores'] for z in y])
  176. print('Unused processors: {}'.format(unused_processors))
  177. #
  178. nicknames = [nodes[x].guess_nickname(x) for x in range(len(nodes))]
  179. print('Nicknames: {}'.format(nicknames))
  180. #
  181. (fd, tmp_network_file) = tempfile.mkstemp(prefix='chutney-network-')
  182. try:
  183. with os.fdopen(fd, mode='w') as f:
  184. #f.write(create_compact_chutney_config(nodes))
  185. f.write(create_chutney_config(nodes))
  186. #
  187. with ChutneyNetwork(chutney_path, tor_path, tmp_network_file) as net:
  188. # do stuff here
  189. fingerprints = []
  190. for nick in nicknames:
  191. fingerprints.append(read_fingerprint(nick, chutney_path))
  192. #
  193. print('Fingerprints: {}'.format(fingerprints))
  194. print('Press Ctrl-C to stop.')
  195. try:
  196. while True:
  197. time.sleep(60)
  198. #
  199. except KeyboardInterrupt:
  200. print()
  201. #
  202. #
  203. finally:
  204. os.remove(tmp_network_file)
  205. #
  206. #