1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Merge pull request #3268 from digitalocean/power-utilization-tweaks

Cleaned up logic for calculating power draw
This commit is contained in:
Jeremy Stretch
2019-06-20 09:09:06 -04:00
committed by GitHub
7 changed files with 112 additions and 88 deletions

View File

@ -1962,48 +1962,48 @@ class PowerPort(CableTermination, ComponentModel):
"Connected endpoint must be a PowerOutlet or PowerFeed, not {}.".format(type(value))
)
def get_power_stats(self):
def get_power_draw(self):
"""
Return utilization statistics for this PowerPort.
Return the allocated and maximum power draw (in VA) and child PowerOutlet count for this PowerPort.
"""
feed = self._connected_powerfeed
if not feed or not self.poweroutlets.count():
return None
# Calculate aggregate draw of all child power outlets if no numbers have been defined manually
if self.allocated_draw is None and self.maximum_draw is None:
outlet_ids = PowerOutlet.objects.filter(power_port=self).values_list('pk', flat=True)
utilization = PowerPort.objects.filter(_connected_poweroutlet_id__in=outlet_ids).aggregate(
maximum_draw_total=Sum('maximum_draw'),
allocated_draw_total=Sum('allocated_draw'),
)
ret = {
'allocated': utilization['allocated_draw_total'] or 0,
'maximum': utilization['maximum_draw_total'] or 0,
'outlet_count': len(outlet_ids),
'legs': [],
}
stats = []
powerfeed_available = self._connected_powerfeed.available_power
# Calculate per-leg aggregates for three-phase feeds
if self._connected_powerfeed and self._connected_powerfeed.phase == POWERFEED_PHASE_3PHASE:
for leg, leg_name in POWERFEED_LEG_CHOICES:
outlet_ids = PowerOutlet.objects.filter(power_port=self, feed_leg=leg).values_list('pk', flat=True)
utilization = PowerPort.objects.filter(_connected_poweroutlet_id__in=outlet_ids).aggregate(
maximum_draw_total=Sum('maximum_draw'),
allocated_draw_total=Sum('allocated_draw'),
)
ret['legs'].append({
'name': leg_name,
'allocated': utilization['allocated_draw_total'] or 0,
'maximum': utilization['maximum_draw_total'] or 0,
'outlet_count': len(outlet_ids),
})
outlet_ids = PowerOutlet.objects.filter(power_port=self).values_list('pk', flat=True)
utilization = PowerPort.objects.filter(_connected_poweroutlet_id__in=outlet_ids).aggregate(
maximum_draw_total=Sum('maximum_draw'),
allocated_draw_total=Sum('allocated_draw'),
)
utilization['outlets'] = len(outlet_ids)
utilization['available_power'] = powerfeed_available
allocated_utilization = int(
float(utilization['allocated_draw_total'] or 0) / powerfeed_available * 100
)
utilization['allocated_utilization'] = allocated_utilization
stats.append(utilization)
return ret
# Per-leg stats for three-phase feeds
if feed.phase == POWERFEED_PHASE_3PHASE:
for leg, leg_name in POWERFEED_LEG_CHOICES:
outlet_ids = PowerOutlet.objects.filter(power_port=self, feed_leg=leg).values_list('pk', flat=True)
utilization = PowerPort.objects.filter(_connected_poweroutlet_id__in=outlet_ids).aggregate(
maximum_draw_total=Sum('maximum_draw'),
allocated_draw_total=Sum('allocated_draw'),
)
utilization['name'] = 'Leg {}'.format(leg_name)
utilization['outlets'] = len(outlet_ids)
utilization['available_power'] = round(powerfeed_available / 3)
allocated_utilization = int(
float(utilization['allocated_draw_total'] or 0) / powerfeed_available * 100
)
utilization['allocated_utilization'] = allocated_utilization
stats.append(utilization)
return stats
# Default to administratively defined values
return {
'allocated': self.allocated_draw or 0,
'maximum': self.maximum_draw or 0,
'outlet_count': PowerOutlet.objects.filter(power_port=self).count(),
'legs': [],
}
#
@ -3037,14 +3037,3 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
def get_status_class(self):
return STATUS_CLASSES[self.status]
def get_power_stats(self):
"""
Return power utilization statistics
"""
power_port = self.connected_endpoint
if not power_port:
# Nothing is connected to the feed so it is not being utilized
return None
return power_port.get_power_stats()

View File

@ -299,8 +299,16 @@ class RackDetailTable(RackTable):
template_code=RACK_DEVICE_COUNT,
verbose_name='Devices'
)
get_utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False, verbose_name='Utilization')
get_power_utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False, verbose_name='Power Utilization')
get_utilization = tables.TemplateColumn(
template_code=UTILIZATION_GRAPH,
orderable=False,
verbose_name='Space'
)
get_power_utilization = tables.TemplateColumn(
template_code=UTILIZATION_GRAPH,
orderable=False,
verbose_name='Power'
)
class Meta(RackTable.Meta):
fields = (

View File

@ -586,6 +586,7 @@ ul.nav-tabs, ul.nav-pills {
/* Fix progress bar margin inside table cells */
td .progress {
margin-bottom: 0;
min-width: 100px;
}
textarea {
font-family: Consolas, Lucida Console, monospace;

View File

@ -349,24 +349,36 @@
<tr>
<th>Input</th>
<th>Outlets</th>
<th>Allocated/Max (W)</th>
<th>Available (VA)</th>
<th>Utilization (Allocated)</th>
<th>Allocated</th>
<th>Available</th>
<th>Utilization</th>
</tr>
{% for pp in power_ports %}
{% for leg in pp.get_power_stats %}
{% with utilization=pp.get_power_draw powerfeed=pp.connected_endpoint %}
<tr>
{% if leg.name %}
<td style="padding-left: 20px">{{ leg.name }}</td>
<td>{{ pp }}</td>
<td>{{ utilization.outlet_count }}</td>
<td>{{ utilization.allocated }}VA</td>
{% if powerfeed %}
<td>{{ powerfeed.available_power }}VA</td>
<td>{% utilization_graph utilization.allocated|percentage:powerfeed.available_power %}</td>
{% else %}
<td>{{ pp }}</td>
<td class="text-muted">&mdash;</td>
<td class="text-muted">&mdash;</td>
{% endif %}
<td>{{ leg.outlets|placeholder }}</td>
<td>{{ leg.allocated_draw_total }} / {{ leg.maximum_draw_total }}</td>
<td>{{ leg.available_power }}</td>
<td>{% utilization_graph leg.allocated_utilization %}</td>
</tr>
{% endfor %}
{% for leg in utilization.legs %}
<tr>
<td style="padding-left: 20px">Leg {{ leg.name }}</td>
<td>{{ leg.outlet_count }}</td>
<td>{{ leg.allocated }}</td>
<td>{{ powerfeed.available_power|divide:3 }}VA</td>
{% with phase_available=powerfeed.available_power|divide:3 %}
<td>{% utilization_graph leg.allocated|percentage:phase_available %}</td>
{% endwith %}
</tr>
{% endfor %}
{% endwith %}
{% endfor %}
</table>
</div>

View File

@ -106,6 +106,19 @@
{% endif %}
</td>
</tr>
<tr>
<td>Utilization (Allocated)</td>
{% with utilization=powerfeed.connected_endpoint.get_power_draw %}
{% if utilization %}
<td>
{{ utilization.allocated }}VA / {{ powerfeed.available_power }}VA
{% utilization_graph utilization.allocated|percentage:powerfeed.available_power %}
</td>
{% else %}
<td class="text-muted">N/A</td>
{% endif %}
{% endwith %}
</tr>
</table>
</div>
</div>
@ -138,30 +151,5 @@
</table>
</div>
</div>
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Power Utilization</strong>
</div>
<table class="table table-hover panel-body">
<tr>
<th>Outlets</th>
<th>Allocated/Max (W)</th>
<th>Available (VA)</th>
<th>Utilization (Allocated)</th>
</tr>
{% for leg in powerfeed.get_power_stats %}
<tr>
<td>{{ leg.outlets|placeholder }}</td>
<td>{{ leg.allocated_draw_total }} / {{ leg.maximum_draw_total }}</td>
<td>{{ leg.available_power }}</td>
<td>{% utilization_graph leg.allocated_utilization %}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
<div class="col-md-5">
</div>
</div>
{% endblock %}

View File

@ -223,7 +223,13 @@
<td>
<span class="label label-{{ powerfeed.get_type_class }}">{{ powerfeed.get_type_display }}</span>
</td>
<td>{% utilization_graph powerfeed.get_power_stats.0.allocated_utilization %}</td>
{% with power_port=powerfeed.connected_endpoint %}
{% if power_port %}
<td>{% utilization_graph power_port.get_power_draw.allocated|percentage:powerfeed.available_power %}</td>
{% else %}
<td class="text-muted">N/A</td>
{% endif %}
{% endwith %}
</tr>
{% endfor %}
</table>

View File

@ -166,6 +166,26 @@ def fgcolor(value):
return '#{}'.format(foreground_color(value))
@register.filter()
def divide(x, y):
"""
Return x/y (rounded).
"""
if x is None or y is None:
return None
return round(x / y)
@register.filter()
def percentage(x, y):
"""
Return x/y as a percentage.
"""
if x is None or y is None:
return None
return round(x / y * 100)
#
# Tags
#