diff --git a/PowerDiagram.py b/PowerDiagram.py index 89b5d12..d01e0b3 100644 --- a/PowerDiagram.py +++ b/PowerDiagram.py @@ -63,11 +63,32 @@ class Machine(object): outgoing_connections.append(conn) return outgoing_connections - def get_power_consumption(self): - power_consumption = self.power_consumption + def get_powered_machines(self): + powered_machines = {} + powered_machines[self.name] = self for conn in self.get_outgoing_connections(): - power_consumption += conn.get_power_consumption() - # print("machine %s : power_consumption += %f" % (self.name, conn.get_power_consumption())) + conn_powered_machines = conn.to_plug.machine.get_powered_machines() + for machine in conn_powered_machines.itervalues(): + if machine not in powered_machines.keys(): + powered_machines[machine.name] = machine + return powered_machines + + def get_power_consumption(self, worst_case_scenario=False): + """ + Returns the number of watts going through this 'machine' (which could just be a powerstrip) + + :param bool worst_case_scenario: if True, computes the number of watts going through this cable in the worst case scenario, in which this cable has to provide the power supply assumung all other backup powers are dead + """ + if worst_case_scenario: + # this machine has to provide the full power to all machine it powers, assuming all the power backup providers are dead + power_consumption = 0.0 + for machine in self.get_powered_machines().itervalues(): + power_consumption += machine.power_consumption + else: + power_consumption = self.power_consumption + for conn in self.get_outgoing_connections(): + power_consumption += conn.get_power_consumption(worst_case_scenario) + # print("machine %s : power_consumption += %f" % (self.name, conn.get_power_consumption())) return power_consumption def is_power_provider(self): @@ -124,14 +145,6 @@ class Plug(object): return capacity -# def get_power_consumption(self): -# power_consumption = 0.0 -# for conn in self.get_outgoing_connections(): -# power_consumption += conn.get_power_consumption() -# power_consumption += self.get_power_consumption() -# return self.from_plug.get_power_consumption() - - class Connection(object): """ a power cable connecting an input power plug to an output power plug @@ -181,14 +194,28 @@ class Connection(object): input_plug = from_machine.input_plugs[input_plug_names[0]] return input_plug.get_incoming_connection().get_power_provider() - def get_power_consumption(self): - # at the moment, this program doesn't handle redundant power supplies properly: - # the energy dragged by a power supply depends whether both power supplies are connnected to the same provider or not - if self.is_redundancy_cable(): - return 0.0 # consider secondary power supplies to drag 0 power + def get_power_consumption(self, worst_case_scenario=False): + """ + Returns the number of watts going through this cable + + :param bool worst_case_scenario: if True, computes the number of watts going through this cable in the worst case scenario, in which this cable has to provide the power supply assumung all other backup powers are dead + """ + to_machine = self.to_plug.machine + power_consumption = None + if worst_case_scenario: + if False: # self.is_redundancy_cable(): + power_consumption = 0.0 + else: + power_consumption = to_machine.get_power_consumption(worst_case_scenario) else: - return self.to_plug.machine.get_power_consumption() - + num_input_cables = 0 + for input_plug in to_machine.input_plugs.itervalues(): + input_cable = input_plug.get_incoming_connection() + assert input_cable + num_input_cables += 1 + assert num_input_cables > 0 + power_consumption = to_machine.get_power_consumption(worst_case_scenario) / num_input_cables + return power_consumption class PowerConfig(object): """ @@ -296,7 +323,7 @@ class PowerConfig(object): class CableColorer(object): - def get_cable_color(self, cable): + def get_cable_color(self, cable, worst_case_scenario): """ :param Connection cable: """ @@ -304,11 +331,11 @@ class CableColorer(object): class SimpleColorer(CableColorer): - def get_cable_color(self, cable): + def get_cable_color(self, cable, worst_case_scenario): """ :param Connection cable: """ - power_consumption = cable.get_power_consumption() + power_consumption = cable.get_power_consumption(worst_case_scenario) amperes = power_consumption / 220.0 capacity = cable.get_max_amperes() saturation = amperes / capacity @@ -332,11 +359,11 @@ class RampColorer(CableColorer): clamped_hotness = max(min(hotness, 1.0), 0.0) return "%f, 1.0, 0.8" % ((clamped_hotness) * 0.1 + 0.23) - def get_cable_color(self, cable): + def get_cable_color(self, cable, worst_case_scenario): """ :param Connection cable: """ - power_consumption = cable.get_power_consumption() + power_consumption = cable.get_power_consumption(worst_case_scenario) amperes = power_consumption / 220.0 capacity = cable.get_max_amperes() saturation = amperes / capacity @@ -357,14 +384,13 @@ class RampColorer(CableColorer): color = '/svg/red' # draw overloaded cables in red return color -def power_config_to_svg(power_config, svg_file_path): - +def power_config_to_svg(power_config, svg_file_path, worst_case_scenario=True): """ creates a svg diagram representing the input power configuration :param PowerConfig power_config: the input power config """ - graph = pygraphviz.AGraph() + graph = pygraphviz.AGraph(strict=False) # strict=False allows more than one connection between 2 nodes graph.graph_attr['overlap'] = 'false' graph.graph_attr['splines'] = 'true' graph.graph_attr['rankdir'] = 'LR' # to get hrizontal tree rather than vertical @@ -396,12 +422,30 @@ def power_config_to_svg(power_config, svg_file_path): if machine.name not in rack['machines']: rack['machines'].append(machine) + for machine in power_config.machines.itervalues(): + graph.add_node(machine.name) + node = graph.get_node(machine.name) + machine_total_power_consumption = int(machine.get_power_consumption(worst_case_scenario=worst_case_scenario)) + # node.attr['label'] = '%s (%d W)' % (machine.name, machine_total_power_consumption) + + node.attr['shape'] = 'plaintext' + + node.attr['label'] = '<\ + \ + \ + \ + \ + \ +
%s%s W
>' % (machine.name, machine_total_power_consumption) + + if False: x = 0.0 for rack in racks.itervalues(): y = 0.0 for machine in rack['machines']: - graph.add_node(machine.name, pos='%f,%f!' % (x, y)) # https://observablehq.com/@magjac/placing-graphviz-nodes-in-fixed-positions + node = graph.get_node(machine.name) + node.attr['pos'] = '%f,%f!' % (x, y) # https://observablehq.com/@magjac/placing-graphviz-nodes-in-fixed-positions # print(machine.name, x, y) y += 1.0 x += 1.0 @@ -411,7 +455,7 @@ def power_config_to_svg(power_config, svg_file_path): for con in power_config.connections: # print(con.from_plug.machine.name, con.to_plug.machine.name) if not con.is_redundancy_cable(): # don't display redundancy cables, as they might overlap and hide the main one - power_consumption = con.get_power_consumption() + power_consumption = con.get_power_consumption(worst_case_scenario=worst_case_scenario) amperes = power_consumption / 220.0 color = '/svg/green' capacity = con.get_max_amperes() @@ -422,18 +466,24 @@ def power_config_to_svg(power_config, svg_file_path): penwidth = 100.0 * penwidth_scaler # make the problem clearly visible else: max_amp = str(capacity) + 'A' - color = cable_colorer.get_cable_color(con) + color = cable_colorer.get_cable_color(con, worst_case_scenario=worst_case_scenario) penwidth = capacity * penwidth_scaler label = "%.1f/%s" % (amperes, max_amp) # color='//%d' % int(9.0-amperes/capacity*8) + # graph.add_edge(con.from_plug.machine.name, con.to_plug.machine.name, color="%s:%s" % (color, wsc_color), label=label, penwidth="%s:%s" % (penwidth, penwidth)) graph.add_edge(con.from_plug.machine.name, con.to_plug.machine.name, color=color, label=label, penwidth=penwidth) + if False: # len(con.from_plug.machine.input_plugs) == 0: + ultimate_power_provider_node = graph.get_node(con.from_plug.machine.name) + total_power_consumption = int(con.from_plug.machine.get_power_consumption(worst_case_scenario=worst_case_scenario)) + ultimate_power_provider_node.attr['label']='%s (%d W)' % (con.from_plug.machine.name, total_power_consumption) + if True: for rack_id, rack in racks.iteritems(): # sub = graph.add_subgraph(rack, name='cluster_%s' % rack_id, rank='same') machine_names = list(machine.name for machine in rack['machines']) - sub = graph.add_subgraph(machine_names, name='cluster_%s' % rack_id) + sub = graph.add_subgraph(machine_names, name='cluster_%s' % rack_id, style='rounded') sub.graph_attr['label'] = rack_id # sub.graph_attr['rank']='same' # assert False diff --git a/inventory.py b/inventory.py index a8c0913..2760ead 100644 --- a/inventory.py +++ b/inventory.py @@ -57,7 +57,7 @@ class Inventory(object): def machine_spec_id_to_power_consumption(self, machine_spec_id): try: - power_consumption = self._sql_reader.get_table_attr('machine_spec_to_power_consumption', 'machine_spec_id', machine_spec_id, 'power_consumption') + power_consumption = float(self._sql_reader.get_table_attr('machine_spec_to_power_consumption', 'machine_spec_id', machine_spec_id, 'power_consumption')) except SimpaDbUtil.TableAttrNotFound as e: # @UnusedVariable # some passive machines such as pdus are not detailed in the machine_spec_to_power_consumption because they don't consume power power_consumption = 0.0