First stab at power utilization tracking

This commit is contained in:
Jeremy Stretch 2019-04-10 16:32:13 -04:00
parent 3d5f85c0ca
commit ac3e899f5e
2 changed files with 86 additions and 1 deletions

View File

@ -9,7 +9,7 @@ from django.contrib.postgres.fields import ArrayField, JSONField
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.db.models import Count, Q
from django.db.models import Count, Q, Sum
from django.urls import reverse
from mptt.models import MPTTModel, TreeForeignKey
from taggit.managers import TaggableManager
@ -1938,6 +1938,41 @@ class PowerPort(CableTermination, ComponentModel):
"Connected endpoint must be a PowerOutlet or PowerFeed, not {}.".format(type(value))
)
def get_power_stats(self):
"""
Return power utilization statistics
"""
feed = self._connected_powerfeed
if not feed or not self.poweroutlets.count():
return None
stats = []
powerfeed_available = self._connected_powerfeed.available_power
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=Sum('maximum_draw'),
allocated_draw=Sum('allocated_draw'),
)
utilization['outlets'] = len(outlet_ids)
utilization['available_power'] = powerfeed_available
stats.append(utilization)
# 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=Sum('maximum_draw'),
allocated_draw=Sum('allocated_draw'),
)
utilization['name'] = 'Leg {}'.format(leg_name)
utilization['outlets'] = len(outlet_ids)
utilization['available_power'] = powerfeed_available / 3
stats.append(utilization)
return stats
#
# Power outlets
@ -2923,3 +2958,10 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
def get_status_class(self):
return STATUS_CLASSES[self.status]
@property
def available_power(self):
kva = self.voltage * self.amperage * self.max_utilization
if self.phase == POWERFEED_PHASE_3PHASE:
return kva * 1.732
return kva

View File

@ -332,6 +332,49 @@
{% endif %}
</div>
{% endif %}
{% if power_ports and poweroutlets %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Power Utilization</strong>
</div>
<table class="table table-hover panel-body">
<tr>
<th>Input</th>
<th>Outlets</th>
<th>Allocated/Max (W)</th>
<th>Available (VA)</th>
</tr>
{% for pp in power_ports %}
{% for leg in pp.get_power_stats %}
<tr>
{% if leg.name %}
<td style="padding-left: 20px">{{ leg.name }}</td>
{% else %}
<td>{{ pp }}</td>
{% endif %}
<td>{{ leg.outlets|placeholder }}</td>
<td>{{ leg.allocated_draw }} / {{ leg.maximum_draw }}</td>
<td>{{ leg.available_power }}</td>
</tr>
{% endfor %}
{% endfor %}
</table>
{% if perms.dcim.add_interface or perms.dcim.add_consoleport or perms.dcim.add_powerport %}
<div class="panel-footer text-right noprint">
{% if perms.dcim.add_consoleport %}
<a href="{% url 'dcim:consoleport_add' pk=device.pk %}" class="btn btn-xs btn-primary">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console port
</a>
{% endif %}
{% if perms.dcim.add_powerport %}
<a href="{% url 'dcim:powerport_add' pk=device.pk %}" class="btn btn-xs btn-primary">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add power port
</a>
{% endif %}
</div>
{% endif %}
</div>
{% endif %}
{% if request.user.is_authenticated %}
<div class="panel panel-default">
<div class="panel-heading">