#524 - Added power utilization graphs to power feeds, devices, and racks

This commit is contained in:
John Anderson 2019-06-16 06:11:32 -04:00
parent 8b3ec625f6
commit 2eeffce924
7 changed files with 102 additions and 8 deletions

View File

@ -12,6 +12,10 @@ v2.6.0 (FUTURE)
* [#3204](https://github.com/digitalocean/netbox/issues/3204) - Fix interface filtering when connecting cables
* [#3207](https://github.com/digitalocean/netbox/issues/3207) - Fix link for connecting interface to rear port
## Enhancements (From Beta)
* [#524](https://github.com/digitalocean/netbox/issues/524) - Added power utilization graphs to power feeds, devices, and racks
---
v2.6-beta1 (2019-04-29)

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.1 on 2019-06-16 07:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0073_interface_form_factor_to_type'),
]
operations = [
migrations.AddField(
model_name='powerfeed',
name='available_power',
field=models.PositiveSmallIntegerField(default=0),
),
]

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, Sum
from django.db.models import Case, Count, Q, Sum, When, F, Subquery, OuterRef
from django.urls import reverse
from mptt.models import MPTTModel, TreeForeignKey
from taggit.managers import TaggableManager
@ -734,6 +734,25 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
u_available = len(self.get_available_units())
return int(float(self.u_height - u_available) / self.u_height * 100)
def get_power_utilization(self):
"""
Determine the utilization rate of power in the rack and return it as a percentage.
"""
power_stats = PowerFeed.objects.filter(
rack=self
).annotate(
allocated_draw_total=Sum('connected_endpoint__poweroutlets__connected_endpoint__allocated_draw'),
).values(
'allocated_draw_total',
'available_power'
)
if power_stats:
allocated_draw_total = sum(x['allocated_draw_total'] for x in power_stats)
available_power_total = sum(x['available_power'] for x in power_stats)
return int(allocated_draw_total / available_power_total * 100) or 0
return 0
class RackReservation(ChangeLoggedModel):
"""
@ -1961,6 +1980,10 @@ class PowerPort(CableTermination, ComponentModel):
)
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)
# Per-leg stats for three-phase feeds
@ -1974,6 +1997,10 @@ class PowerPort(CableTermination, ComponentModel):
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
@ -2936,6 +2963,9 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
validators=[MinValueValidator(1)],
default=20
)
available_power = models.PositiveSmallIntegerField(
default=0
)
power_factor = models.PositiveSmallIntegerField(
validators=[MinValueValidator(1), MaxValueValidator(100)],
default=80,
@ -2990,15 +3020,29 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
self.rack, self.rack.site, self.power_panel, self.power_panel.site
))
def save(self, *args, **kwargs):
# Cache the available_power property on the instance
kva = self.voltage * self.amperage * (self.power_factor / 100)
if self.phase == POWERFEED_PHASE_3PHASE:
self.available_power = round(kva * 1.732)
self.available_power = round(kva)
super().save(*args, **kwargs)
def get_type_class(self):
return STATUS_CLASSES[self.type]
def get_status_class(self):
return STATUS_CLASSES[self.status]
@property
def available_power(self):
kva = self.voltage * self.amperage * (self.power_factor / 100)
if self.phase == POWERFEED_PHASE_3PHASE:
return round(kva * 1.732)
return round(kva)
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

@ -300,11 +300,12 @@ class RackDetailTable(RackTable):
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')
class Meta(RackTable.Meta):
fields = (
'pk', 'name', 'site', 'group', 'status', 'facility_id', 'tenant', 'role', 'u_height', 'device_count',
'get_utilization',
'get_utilization', 'get_power_utilization',
)

View File

@ -351,6 +351,7 @@
<th>Outlets</th>
<th>Allocated/Max (W)</th>
<th>Available (VA)</th>
<th>Utilization (Allocated)</th>
</tr>
{% for pp in power_ports %}
{% for leg in pp.get_power_stats %}
@ -363,6 +364,7 @@
<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 %}
{% endfor %}

View File

@ -138,6 +138,29 @@
</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>

View File

@ -207,6 +207,7 @@
<th>Feed</th>
<th>Status</th>
<th>Type</th>
<th>Utilization</th>
</tr>
{% for powerfeed in power_feeds %}
<tr>
@ -222,6 +223,7 @@
<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>
</tr>
{% endfor %}
</table>