diff --git a/PowerDiagram.py b/PowerDiagram.py index 0dd5ce0..89b5d12 100644 --- a/PowerDiagram.py +++ b/PowerDiagram.py @@ -70,6 +70,10 @@ class Machine(object): # print("machine %s : power_consumption += %f" % (self.name, conn.get_power_consumption())) return power_consumption + def is_power_provider(self): + return self.name == 'edf' or re.match('^ups[0-9]*$', self.name) + + class Plug(object): """ @@ -99,20 +103,24 @@ class Plug(object): def get_max_amperes(self): capacity = None + debug = False if self.is_input_plug(): in_con = self.get_incoming_connection() if in_con: # apply incoming connection amperes limitation capacity = add_capacity_constraints(capacity, in_con.get_max_amperes()) - # print(str(self)+ 'after incoming connection amperes limitation, capacity = ' + str(capacity)) + if debug: + print(str(self)+ 'after incoming connection amperes limitation, capacity = ' + str(capacity)) else: # apply the machine containing this plug's amperes limitation capacity = add_capacity_constraints(capacity, self.machine.get_max_amperes()) - # print(str(self)+'apply the machine containing this plug s amperes limitation, capacity = ' + str(capacity)) + if debug: + print(str(self)+'apply the machine containing this plug s amperes limitation, capacity = ' + str(capacity)) # apply this plug's amperes limitation capacity = add_capacity_constraints(capacity, self.current_capacity_constraint) - # print(str(self)+'after apply this plug s amperes limitation, capacity = ' + str(capacity)) + if debug: + print(str(self)+'after apply this plug s amperes limitation, capacity = ' + str(capacity), self.current_capacity_constraint) return capacity @@ -135,7 +143,7 @@ class Connection(object): self.current_capacity_constraint = None # the maximum amperes in this connection def __str__(self): - return str(self.from_plug) + ' -> ' + str(self.to_plug) + ' (' + str(self.from_plug.get_max_amperes()) + str(self.get_max_amperes()) + 'A)' + return str(self.from_plug) + ' -> ' + str(self.to_plug) + ' (' + str(self.from_plug.get_max_amperes()) + ' A, ' + str(self.get_max_amperes()) + ' A)' def get_max_amperes(self): # gLogger.debug('%s (%s A) -> %s (%s A): ' % (str(self.from_plug), str(self.from_plug.get_max_amperes()), str(self.to_plug), str(self.to_plug.current_capacity_constraint))) @@ -146,7 +154,32 @@ class Connection(object): return capacity def is_redundancy_cable(self): - return not (self.to_plug.name == 'i' or self.to_plug.name == 'i1') + to_machine = self.to_plug.machine + my_power_provider = self.get_power_provider() + # find the first sibling cable that has the same provider as self + first_cable_with_same_provider = None + for input_plug in to_machine.input_plugs.itervalues(): + sibling_cable = input_plug.get_incoming_connection() + if sibling_cable.get_power_provider() == my_power_provider: + first_cable_with_same_provider = sibling_cable + if first_cable_with_same_provider is None: + # no other connection with the same provider + return False + if first_cable_with_same_provider == self: + # for each provider, the 1st cable amongst the connectors using this provider is considered to be the original (not redundant) + return False + else: + # for each provider, all cable but the 1st one are considered as redundant + return True + + def get_power_provider(self): + from_machine = self.from_plug.machine + if from_machine.is_power_provider(): + return from_machine + input_plug_names = from_machine.input_plugs.keys() + assert len(input_plug_names) == 1, "from_machine is supposed to be a power strip (which is expected to only have one input)" + 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: @@ -263,12 +296,21 @@ class PowerConfig(object): class CableColorer(object): - def get_cable_color(self, amperes, capacity): + def get_cable_color(self, cable): + """ + :param Connection cable: + """ raise NotImplementedError class SimpleColorer(CableColorer): - def get_cable_color(self, amperes, capacity): + def get_cable_color(self, cable): + """ + :param Connection cable: + """ + power_consumption = cable.get_power_consumption() + amperes = power_consumption / 220.0 + capacity = cable.get_max_amperes() saturation = amperes / capacity if saturation > 1.0: color = '/svg/red' @@ -288,18 +330,31 @@ class RampColorer(CableColorer): :param float hotness: temperature of the wire ratio (0.0 : cold -> 1.0 : hot) """ clamped_hotness = max(min(hotness, 1.0), 0.0) - return "%f, 1.0, 0.8" % ((clamped_hotness) * 0.33 + 0.33) + return "%f, 1.0, 0.8" % ((clamped_hotness) * 0.1 + 0.23) - def get_cable_color(self, amperes, capacity): + def get_cable_color(self, cable): + """ + :param Connection cable: + """ + power_consumption = cable.get_power_consumption() + amperes = power_consumption / 220.0 + capacity = cable.get_max_amperes() saturation = amperes / capacity - # color = RampColorer.hotness_to_hsv_color(pow(saturation, 4.0)) - clamped_saturation = max(min(saturation, 1.0), 0.0) - color = '/ylgnbu9/%d' % (int(clamped_saturation * 5) + 4) + color = None + # print(cable.from_plug.machine.name, cable.to_plug.machine.name, cable.get_power_provider().name) + power_is_backed_up = cable.get_power_provider().name == 'ups3' + if power_is_backed_up: + # greenish colors + color = RampColorer.hotness_to_hsv_color(pow(saturation, 4.0)) + else: + # blueish colors using the pretty ylgnbu9 color palette + clamped_saturation = max(min(saturation, 1.0), 0.0) + color = '/ylgnbu9/%d' % (int(clamped_saturation * 5) + 4) if saturation < 0.001: color = '/svg/black' # probably an error elif saturation > 1.0: - color = '/svg/red' + color = '/svg/red' # draw overloaded cables in red return color def power_config_to_svg(power_config, svg_file_path): @@ -347,7 +402,7 @@ def power_config_to_svg(power_config, svg_file_path): 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 - print(machine.name, x, y) + # print(machine.name, x, y) y += 1.0 x += 1.0 @@ -358,16 +413,16 @@ def power_config_to_svg(power_config, svg_file_path): 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() amperes = power_consumption / 220.0 - color = 'green' + color = '/svg/green' capacity = con.get_max_amperes() penwidth_scaler = 0.25 if capacity is None: max_amp = '? A' - color = 'red' + color = '/svg/red' penwidth = 100.0 * penwidth_scaler # make the problem clearly visible else: max_amp = str(capacity) + 'A' - color = cable_colorer.get_cable_color(amperes, capacity) + color = cable_colorer.get_cable_color(con) penwidth = capacity * penwidth_scaler label = "%.1f/%s" % (amperes, max_amp) # color='//%d' % int(9.0-amperes/capacity*8)