chutney_manager.py 6.2 KB

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