chutney_manager.py 6.4 KB

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