Bug 2680 - Améliorer le support des alimentation redondantes dans PowerDiagram

- après avoir tenté de représenter le powerdiagram en deux modes simultanés (normal et worst_case), j'ai finalement opté pour une option qui permet de choisir un seul mode à la fois. Le mode worst case scenario est actif par défaut parce qu'il est le plus important : il permet de vérifier que le cablage est bien dimensionné en cas de coupure edf ou d'une alim défectueuse. Bien que le mode de calcul soit différent, on retrouve bien la même conso sur edf dans les deux modes.
- amélioration : ajout de la conso en W de chaque appareil (pratique pour vérifier s'il n'y a pas d'incohérence)
This commit is contained in:
Guillaume Raffy 2019-06-25 14:23:15 +00:00
parent 3e0a8ff373
commit ac76f82b67
2 changed files with 82 additions and 32 deletions

View File

@ -63,11 +63,32 @@ class Machine(object):
outgoing_connections.append(conn) outgoing_connections.append(conn)
return outgoing_connections return outgoing_connections
def get_power_consumption(self): def get_powered_machines(self):
power_consumption = self.power_consumption powered_machines = {}
powered_machines[self.name] = self
for conn in self.get_outgoing_connections(): for conn in self.get_outgoing_connections():
power_consumption += conn.get_power_consumption() conn_powered_machines = conn.to_plug.machine.get_powered_machines()
# print("machine %s : power_consumption += %f" % (self.name, conn.get_power_consumption())) 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 return power_consumption
def is_power_provider(self): def is_power_provider(self):
@ -124,14 +145,6 @@ class Plug(object):
return capacity 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): class Connection(object):
""" """
a power cable connecting an input power plug to an output power plug 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]] input_plug = from_machine.input_plugs[input_plug_names[0]]
return input_plug.get_incoming_connection().get_power_provider() return input_plug.get_incoming_connection().get_power_provider()
def get_power_consumption(self): def get_power_consumption(self, worst_case_scenario=False):
# 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 Returns the number of watts going through this cable
if self.is_redundancy_cable():
return 0.0 # consider secondary power supplies to drag 0 power
else:
return self.to_plug.machine.get_power_consumption()
: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:
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): class PowerConfig(object):
""" """
@ -296,7 +323,7 @@ class PowerConfig(object):
class CableColorer(object): class CableColorer(object):
def get_cable_color(self, cable): def get_cable_color(self, cable, worst_case_scenario):
""" """
:param Connection cable: :param Connection cable:
""" """
@ -304,11 +331,11 @@ class CableColorer(object):
class SimpleColorer(CableColorer): class SimpleColorer(CableColorer):
def get_cable_color(self, cable): def get_cable_color(self, cable, worst_case_scenario):
""" """
:param Connection cable: :param Connection cable:
""" """
power_consumption = cable.get_power_consumption() power_consumption = cable.get_power_consumption(worst_case_scenario)
amperes = power_consumption / 220.0 amperes = power_consumption / 220.0
capacity = cable.get_max_amperes() capacity = cable.get_max_amperes()
saturation = amperes / capacity saturation = amperes / capacity
@ -332,11 +359,11 @@ class RampColorer(CableColorer):
clamped_hotness = max(min(hotness, 1.0), 0.0) clamped_hotness = max(min(hotness, 1.0), 0.0)
return "%f, 1.0, 0.8" % ((clamped_hotness) * 0.1 + 0.23) 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: :param Connection cable:
""" """
power_consumption = cable.get_power_consumption() power_consumption = cable.get_power_consumption(worst_case_scenario)
amperes = power_consumption / 220.0 amperes = power_consumption / 220.0
capacity = cable.get_max_amperes() capacity = cable.get_max_amperes()
saturation = amperes / capacity saturation = amperes / capacity
@ -357,14 +384,13 @@ class RampColorer(CableColorer):
color = '/svg/red' # draw overloaded cables in red color = '/svg/red' # draw overloaded cables in red
return color 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 creates a svg diagram representing the input power configuration
:param PowerConfig power_config: the input power config :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['overlap'] = 'false'
graph.graph_attr['splines'] = 'true' graph.graph_attr['splines'] = 'true'
graph.graph_attr['rankdir'] = 'LR' # to get hrizontal tree rather than vertical 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']: if machine.name not in rack['machines']:
rack['machines'].append(machine) 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'] = '<\
<table border="1" cellborder="0" cellspacing="0">\
<tr>\
<td color="red">%s</td>\
<td bgcolor="black"><font color="white">%s W</font></td>\
</tr>\
</table>>' % (machine.name, machine_total_power_consumption)
if False: if False:
x = 0.0 x = 0.0
for rack in racks.itervalues(): for rack in racks.itervalues():
y = 0.0 y = 0.0
for machine in rack['machines']: 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) # print(machine.name, x, y)
y += 1.0 y += 1.0
x += 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: for con in power_config.connections:
# print(con.from_plug.machine.name, con.to_plug.machine.name) # 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 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 amperes = power_consumption / 220.0
color = '/svg/green' color = '/svg/green'
capacity = con.get_max_amperes() 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 penwidth = 100.0 * penwidth_scaler # make the problem clearly visible
else: else:
max_amp = str(capacity) + 'A' 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 penwidth = capacity * penwidth_scaler
label = "%.1f/%s" % (amperes, max_amp) label = "%.1f/%s" % (amperes, max_amp)
# color='//%d' % int(9.0-amperes/capacity*8) # 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) 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: if True:
for rack_id, rack in racks.iteritems(): for rack_id, rack in racks.iteritems():
# sub = graph.add_subgraph(rack, name='cluster_%s' % rack_id, rank='same') # sub = graph.add_subgraph(rack, name='cluster_%s' % rack_id, rank='same')
machine_names = list(machine.name for machine in rack['machines']) 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['label'] = rack_id
# sub.graph_attr['rank']='same' # sub.graph_attr['rank']='same'
# assert False # assert False

View File

@ -57,7 +57,7 @@ class Inventory(object):
def machine_spec_id_to_power_consumption(self, machine_spec_id): def machine_spec_id_to_power_consumption(self, machine_spec_id):
try: 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 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 # 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 power_consumption = 0.0