def _GetSegmentStats(self, message_name): switches = self._network_config.GetSwitches() message_types = self._network_config.all_messages path_finder = network_util.PathFinder(switches, message_types) message = self._network_config.GetAioMessageType(message_name) graph = network_util.MessageGraph(path_finder, message) return network_util.GetSegmentStats(graph, message.all_senders)
def AddDependencies(task_mgr): """Add bootload dependencies given unicast routes in the network config.""" config = network_config.NetworkConfig() path_finder = network_util.PathFinder(config.GetSwitches(), config.all_messages) destinations = ['aio_nodes.' + t.GetTarget() for t in task_mgr.GetTasks()] paths = path_finder.GetHops(None, 'aio_nodes.operator', destinations, unicast=True) for path in paths: for ingress_index, _ in enumerate(path[:-1]): ingress_all = path_finder.GetAttachedNodes(path[ingress_index]) ingress_tms570 = [n for n in ingress_all if config.GetAioNode(n).tms570_node] for egress_index in range(ingress_index + 1, len(path)): egress_all = path_finder.GetAttachedNodes(path[egress_index]) egress_tms570 = [n for n in egress_all if config.GetAioNode(n).tms570_node] for egress in egress_tms570: for ingress in ingress_tms570: task_mgr.AddDependency(egress, ingress) # Configuration tasks depend on the corresponding application tasks. for target in [task.GetTarget() for task in task_mgr.GetTasks()]: task_mgr.AddInternalDependencies(target)
def testGetNodeBandwidthStatistics(self): switches = self._network_config.GetSwitches() message_types = self._network_config.all_messages path_finder = network_util.PathFinder(switches, message_types) node_stats = network_util.GetNodeBandwidthStatistics( path_finder, message_types) for aio_node in [ 'aio_nodes.controller_a', 'aio_nodes.controller_b', 'aio_nodes.controller_c' ]: self.assertEqual(node_stats[aio_node].send, {'ControllerCommand': 100}) self.assertEqual( node_stats[aio_node].receive, { 'FlightComputerSensor': 50, # 2 networks for all but the local Fc. 'MotorStatus': 800, # 4 motors * 2 networks. }) self.assertEqual(node_stats[aio_node].multicast_packet_rate['tx'], 100) self.assertEqual(node_stats[aio_node].multicast_packet_rate['rx'], 850) sensor_messages_sent = 0 for aio_node in ['aio_nodes.fc_a', 'aio_nodes.fc_b', 'aio_nodes.fc_c']: self.assertEqual(node_stats[aio_node].send, {'FlightComputerSensor': 10}) self.assertEqual(node_stats[aio_node].receive, {}) self.assertEqual(node_stats[aio_node].multicast_packet_rate['tx'], 10) self.assertEqual(node_stats[aio_node].multicast_packet_rate['rx'], 0) sensor_messages_sent += node_stats[aio_node].send[ 'FlightComputerSensor'] for aio_node in [ 'aio_nodes.motor_pbo', 'aio_nodes.motor_sbi', 'aio_nodes.motor_pbi', 'aio_nodes.motor_sbo' ]: self.assertEqual(node_stats[aio_node].send, { 'MotorStatus': 100, 'MotorStacking': 1000 }) # 2 networks * each other motor, no loopback packets. self.assertEqual(node_stats[aio_node].receive, { 'ControllerCommand': 1000, 'MotorStacking': 6000 }) self.assertEqual(node_stats[aio_node].multicast_packet_rate['tx'], 1100) self.assertEqual(node_stats[aio_node].multicast_packet_rate['rx'], 7000) # Multiply by two for the two networks, the by 2 for the two paths from the # wing to the ground. self.assertEqual( node_stats['aio_nodes.host'].receive['FlightComputerSensor'], sensor_messages_sent * 2 * 2)
def testCheckForUnintendedRecipients(self): config = self._network_config message_types = config.all_messages path_finder = network_util.PathFinder(config.GetSwitches(), message_types) for message in message_types: graph = network_util.MessageGraph(path_finder, message) network_util.CheckForUnintendedRecipients(graph)
def testCheckForLoopRoutes(self): config = self._network_config message_types = config.all_messages path_finder = network_util.PathFinder(config.GetSwitches(), message_types) for message in message_types: graph = network_util.MessageGraph(path_finder, message) visitor = network_util.MessageGraphVisitor() graph.VisitSenders(visitor, message.all_senders)
def GetFullBandwidthStatistics(config): """Gather the bandwidth statistics for each link, message, and source. Args: config: A NetworkConfig. Returns: stats: A dictionary of link statistics, indexed by a (sender, message, source, sink) tuple denoting sender and message type. """ Key = collections.namedtuple( # pylint: disable=invalid-name 'Key', ['sender', 'message', 'source', 'sink']) output = dict() message_types = config.all_messages switches = config.GetSwitches() locations = node_locations.GetNodeLocations(config) path_finder = network_util.PathFinder(switches, message_types) ignore_senders = FLAGS.ignore_node for location, senders in locations.iteritems(): if location in FLAGS.ignore_location: ignore_senders += [s.snake_name for s in senders] for message in message_types: if message.frequency_hz <= 0: continue senders = [ s for s in message.all_senders if s.snake_name not in ignore_senders ] graph = network_util.MessageGraph(path_finder, message) message_stats = network_util.GetSegmentStats(graph, senders) message_size = _GetMessageSize(message) for segment, senders in message_stats.iteritems(): for sender, stats in senders.iteritems(): packets_per_sec = stats['packets_per_sec'] bytes_per_sec = packets_per_sec * message_size packets_peak = stats['peak'] bytes_peak = packets_peak * message_size key = Key(sender=sender.snake_name, message=message.name, source=segment[0], sink=segment[1]) output[key] = Stat(bytes_per_sec=bytes_per_sec, packets_per_sec=packets_per_sec, bytes_peak=bytes_peak, packets_peak=packets_peak, utilization=bytes_per_sec * 8 / _GetBandwidth(switches, segment)) return output
def Initialize(self): config = network_config.NetworkConfig(settings.NETWORK_YAML) message_types = config.all_messages path_finder = network_util.PathFinder(config.GetSwitches(), message_types) self._node_stats = {} for tag, stats in network_util.GetNodeBandwidthStatistics( path_finder, message_types).iteritems(): self._node_stats[self._ParseNetworkConfigTag(tag)[1]] = stats self._link_stats = network_bandwidth.GetLinkBandwidthStatistics(config) self._freq_by_message_type = {} self._status_message_by_aio_node = {} for m in message_types: self._freq_by_message_type[m.name] = m.frequency_hz if m.name in ['CoreSwitchSlowStatus', 'SlowStatus']: for sender in m.all_senders: self._status_message_by_aio_node[ sender.camel_name] = m.name
def _WriteForwardingMapSource(autogen_root, output_dir, script_name, config): """Writes the implementation file for the forwarding map C module. Args: autogen_root: The MAKANI_HOME-equivalent top directory. output_dir: The directory in which to output the files. script_name: This script's filename. config: A NetworkConfig. """ file_basename = os.path.join(output_dir, 'routes') rel_path = os.path.relpath(file_basename, autogen_root) parts = [ textwrap.dedent(""" // Generated by {0}; do not edit. #include "{1}" """[1:]).format(script_name, rel_path + '.h') ] parts.append( textwrap.dedent("""\ #include <assert.h> #include <stddef.h> #include <stdint.h> #include "avionics/network/eop_message_type.h" #include "avionics/network/message_type.h" #include "avionics/network/winch_message_type.h" """)) for messages in config.messages_by_type.values(): path_finder = network_util.PathFinder(config.GetSwitches(), messages) forward = network_util.MakeForwardingMaps(messages, path_finder) parts.extend(_GetForwardingMap(forward, messages, config)) with open(file_basename + '.c', 'w') as f: f.write('\n'.join(parts))
def GetNodeLocations(network_config): """Identify node location given core switch location. Args: network_config: A NetworkConfig. Returns: A dict of sets of AIO node names, where the key name indicates the node location. Raises: ValueError: If a node appears in more than one location. """ nodes = { 'wing': set(), 'ground_station': set(), 'remote_command': set(), 'test_fixture': set() } core_switch_locations = { 'cs_a': 'wing', 'cs_b': 'wing', 'cs_dyno_a': 'test_fixture', 'cs_dyno_b': 'test_fixture', 'cs_gs_a': 'ground_station', 'cs_gs_b': 'ground_station', 'joystick_radio_ground': 'remote_command', 'remote_command': 'remote_command', } path_finder = network_util.PathFinder(network_config.GetSwitches(), network_config.aio_messages) destinations = ['aio_nodes.operator'] for aio_node in network_config.aio_nodes: source = network_util.AioNodeToTag(aio_node) try: switches = path_finder.GetFirstSwitch(None, source, destinations, core_switch_locations.keys()) except KeyError: switches = [] for switch in switches: nodes[core_switch_locations[switch]].add(aio_node) # Remove test fixtures. # TODO: Eliminate after we support multiple network configurations. test_fixtures = [network_config.GetAioNode(n) for n in [ 'simulated_joystick', 'simulator', 'torque_cell', 'visualizer']] for loc in nodes: if loc != 'test_fixture': nodes[loc] -= set(test_fixtures) nodes['test_fixture'] |= set(test_fixtures) # Check for potential duplicate listings. for loc_a in nodes: for loc_b in nodes: intersection = nodes[loc_a] & nodes[loc_b] if loc_a != loc_b and intersection: names = [node.snake_name for node in intersection] raise ValueError( 'Nodes appear both in {loc_a} and {loc_b}: {nodes}'.format( loc_a=loc_a, loc_b=loc_b, nodes=names)) return nodes
'Now try e.g. \'fdp ./reduced.dot -Tpng:gd > reduced.png && ' 'eog reduced.png\'') def main(argv): try: argv = FLAGS(argv) except gflags.FlagsError, e: sys.stderr.write('\nError: %s\n\nUsage: %s ARGS\n%s\n' % (e, argv[0], FLAGS)) sys.exit(1) config = network_config.NetworkConfig(FLAGS.network_file) message_types = config.all_messages path_finder = network_util.PathFinder(config.GetSwitches(), message_types) if FLAGS.print_routes: forwarding_maps = network_util.MakeForwardingMaps( message_types, path_finder) if FLAGS.print_routes: _PrintMulticastRoutes(forwarding_maps) if FLAGS.write_dot_files: _WriteDotFiles(path_finder) if __name__ == '__main__': main(sys.argv)
def _WriteSwitchInfo(autogen_root, output_dir, script_name, config): """Write switch_info.c. Args: autogen_root: The MAKANI_HOME-equivalent top directory. output_dir: The directory in which to output the files. script_name: This script's filename. config: A NetworkConfig. Raises: IpAddressException: On any IP address error. SwitchMismatchException: If the TMS570 port of a switch is invalid. TrunkException: If there is a problem with the trunk configuration. """ file_without_extension = os.path.join(output_dir, 'switch_info') rel_path = os.path.relpath(file_without_extension, autogen_root) parts = [ textwrap.dedent(""" // Generated by {name}; do not edit. #include "{header}" #include <assert.h> #include <stdbool.h> #include <stdint.h> #include <string.h> """.format(name=script_name, header=rel_path + '.h')) ] ip_addr = {node.snake_name: node.ip_octet for node in config.aio_nodes} switches = config.GetSwitches() switches_sorted = sorted(switches.iterkeys()) message_types = config.all_messages def _GetTms570Node(switch): if 'config' not in switch or 'tms570' not in switch['config']: return None port = switch['ports'][switch['config']['tms570']] if not port.startswith('aio_nodes.'): raise SwitchMismatchException( 'No AIO node connected to TMS570 port.') return port[len('aio_nodes.'):] # The VLAN numbering convention here uses the lowest of the last octet of the # IP addresses of the TMS570s on the two connected switches, concatenated # with the port number on the associated switch. Since the core switches have # 27 ports we need 5 bits to represent the port number. As a consequence # TMS570s with last octets greater than 127 will present a problem for this # scheme. Additionally the IP with last octet 0 is reserved for other VLANs. def _GetSegId(node, port, remote_node, remote_port): ip = ip_addr[node] remote_ip = ip_addr[remote_node] if remote_ip < ip: ip = remote_ip port = remote_port if ip <= 0 or ip > 127: raise IpAddressException('Invalid IP octet 0 >= IP or IP > 127.') return (ip << 5) | int(port) def _GenerateMask(port_list): return sum(1 << i for i in port_list) path_finder = network_util.PathFinder(switches, None, network_c=True) net_c_forward = network_util.MakeNetworkCForwardingMap(path_finder) for switch_name in switches_sorted: switch = switches[switch_name] # We use simple configs for virtual or managed switches. if 'config' not in switch or 'tms570' not in switch['config']: continue config = switch['config'] node = _GetTms570Node(switch) seg_ids = [0] * config['chip']['num_ports'] for port, remote in switch['ports'].iteritems(): if remote.startswith('switches.'): _, remote_switch_name, remote_port = remote.split('.') remote_switch = switches[remote_switch_name] remote_node = _GetTms570Node(remote_switch) if not remote_node or remote_node not in ip_addr: continue seg_ids[int(port)] = _GetSegId(node, port, remote_node, remote_port) info = {} info['node_camel'] = c_helpers.SnakeToCamel(node) info['switch_type'] = 'kSwitchType' + c_helpers.SnakeToCamel( config['chip']['type']) info['segment_vlans'] = 'segmentVlanIds' + info['node_camel'] info['num_ports'] = config['chip']['num_ports'] info['num_fiber_ports'] = config['chip']['num_fiber_ports'] parts.append( textwrap.fill('static const uint16_t {}[{}] = {{{}}};'.format( info['segment_vlans'], info['num_ports'], ', '.join(str(i) for i in seg_ids)), 80, subsequent_indent=' ')) # Trunk configuration. trunk_ports = set() select_ports = set() trunk_unicast_learning_ports = set() info['num_multicast_overrides'] = 0 info['multicast_overrides'] = 'NULL' if 'trunk' in config: trunk_ports = config['trunk']['ports'] if 'unicast_learning' in config['trunk']: trunk_unicast_learning_ports = config['trunk'][ 'unicast_learning'] if 'override_message_routes' in config['trunk']: mcast_overrides = [] override = config['trunk']['override_message_routes'] for override_type, override_ports in override.iteritems(): mask = _GenerateMask(override_ports) for m in message_types: if m.name == override_type: if m.eop_message: mac = network_config.EopMessageTypeToEthernetAddress( eop_message_type.__dict__['kEopMessageType' + override_type]) elif m.winch_message: mac = network_config.WinchMessageTypeToEthernetAddress( winch_message_type.__dict__[ 'kWinchMessageType' + override_type]) else: mac = network_config.AioMessageTypeToEthernetAddress( message_type.__dict__['kMessageType' + override_type]) break else: raise TrunkException( 'Override contains invalid message type %s.' % override_type) mcast_overrides.append(('{%s}' % ', '.join( '0x%02X' % x for x in [mac.a, mac.b, mac.c, mac.d, mac.e, mac.f]), mask)) info['multicast_overrides'] = 'multicastOverrides' + info[ 'node_camel'] info['num_multicast_overrides'] = len(mcast_overrides) parts.append( 'static const TrunkMulticastOverride {}[{}] = {{\n{}\n}};'. format( info['multicast_overrides'], info['num_multicast_overrides'], ',\n'.join(' {%s, 0x%08X}' % o for o in mcast_overrides))) if 'select_default_ports' in config['trunk']: select_ports = config['trunk']['select_default_ports'] else: select_ports = trunk_ports info['forward_mask_a'] = _GenerateMask(config['network_a']) info['forward_mask_b'] = _GenerateMask(config['network_b']) info['forward_mask_c'] = net_c_forward[switch_name] info['egress_mask_c'] = _GenerateMask(config.get('network_c', [])) if 'isolate' in config: if trunk_ports: raise TrunkException( 'Isolate and trunk definitions are currently not ' 'supported together.') else: isolate_ports = config['isolate'] else: isolate_ports = trunk_ports info['isolate_mask'] = _GenerateMask(isolate_ports) info['trunk_mask'] = _GenerateMask(trunk_ports) info['unicast_learning_mask'] = _GenerateMask( trunk_unicast_learning_ports) info['select_default_mask'] = _GenerateMask(select_ports) info['unicast_mask'] = _GenerateMask(config['unicast']) info['host_port'] = config['tms570'] info['mirror_port'] = config['mirror'] parts.append( 'static const SwitchInfo switchInfo{node_camel} = {{\n' ' {switch_type}, {num_ports}, {num_fiber_ports},\n' ' {host_port}, {mirror_port},\n' ' 0x{forward_mask_a:X}, 0x{forward_mask_b:X},\n' ' 0x{forward_mask_c:X}, 0x{egress_mask_c:X},\n' ' 0x{isolate_mask:X}, 0x{unicast_mask:X}, {segment_vlans},\n' ' {{0x{trunk_mask:X}, 0x{select_default_mask:X},' ' 0x{unicast_learning_mask:X},\n' ' {num_multicast_overrides}, {multicast_overrides}}}\n' '}};\n'.format(**info)) parts.append( textwrap.dedent(""" const SwitchInfo *GetSwitchInfo(AioNode node) { switch (node) {"""[1:])) unused_nodes = set(c_helpers.EnumHelper('AioNode', aio_node).Names()) for switch_name in switches_sorted: switch = switches[switch_name] if 'config' not in switch or 'tms570' not in switch['config']: continue node = _GetTms570Node(switch) node_camel = c_helpers.SnakeToCamel(node) node_name = 'kAioNode' + node_camel unused_nodes.remove(node_name) parts.append(' case %s:' % node_name) if 'config' not in switch: parts.append(' assert(false);') parts.append(' return NULL;') else: parts.append(' return &switchInfo%s;' % node_camel) parts.append(' // Fall-through intentional.') for node in sorted(unused_nodes): parts.append(' case %s:' % node) parts.append( textwrap.dedent(""" case kAioNodeForceSigned: case kNumAioNodes: default: assert(false); return NULL; } } """[1:])) with open(file_without_extension + '.c', 'w') as f: f.write('\n'.join(parts))