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,10 +63,31 @@ class Machine(object):
outgoing_connections.append(conn)
return outgoing_connections
def get_power_consumption(self):
def get_powered_machines(self):
powered_machines = {}
powered_machines[self.name] = self
for conn in self.get_outgoing_connections():
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()
power_consumption += conn.get_power_consumption(worst_case_scenario)
# print("machine %s : power_consumption += %f" % (self.name, conn.get_power_consumption()))
return power_consumption
@ -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
else:
return self.to_plug.machine.get_power_consumption()
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:
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'] = '<\
<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:
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

View File

@ -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