mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 01:41:22 -06:00
#524 - Added power utilization graphs to power feeds, devices, and racks
This commit is contained in:
parent
8b3ec625f6
commit
2eeffce924
@ -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)
|
||||
|
@ -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),
|
||||
),
|
||||
]
|
@ -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()
|
||||
|
@ -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',
|
||||
)
|
||||
|
||||
|
||||
|
@ -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 %}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user