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:
parent
3e0a8ff373
commit
ac76f82b67
108
PowerDiagram.py
108
PowerDiagram.py
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue