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:
@ -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()
|
||||
|
@ -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 = (
|
||||
|
@ -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;
|
||||
|
@ -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">—</td>
|
||||
<td class="text-muted">—</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>
|
||||
|
@ -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 %}
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
#
|
||||
|
Reference in New Issue
Block a user