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
|
* [#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
|
* [#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)
|
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.exceptions import ObjectDoesNotExist, ValidationError
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
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 django.urls import reverse
|
||||||
from mptt.models import MPTTModel, TreeForeignKey
|
from mptt.models import MPTTModel, TreeForeignKey
|
||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
@ -734,6 +734,25 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
|
|||||||
u_available = len(self.get_available_units())
|
u_available = len(self.get_available_units())
|
||||||
return int(float(self.u_height - u_available) / self.u_height * 100)
|
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):
|
class RackReservation(ChangeLoggedModel):
|
||||||
"""
|
"""
|
||||||
@ -1961,6 +1980,10 @@ class PowerPort(CableTermination, ComponentModel):
|
|||||||
)
|
)
|
||||||
utilization['outlets'] = len(outlet_ids)
|
utilization['outlets'] = len(outlet_ids)
|
||||||
utilization['available_power'] = powerfeed_available
|
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)
|
stats.append(utilization)
|
||||||
|
|
||||||
# Per-leg stats for three-phase feeds
|
# Per-leg stats for three-phase feeds
|
||||||
@ -1974,6 +1997,10 @@ class PowerPort(CableTermination, ComponentModel):
|
|||||||
utilization['name'] = 'Leg {}'.format(leg_name)
|
utilization['name'] = 'Leg {}'.format(leg_name)
|
||||||
utilization['outlets'] = len(outlet_ids)
|
utilization['outlets'] = len(outlet_ids)
|
||||||
utilization['available_power'] = round(powerfeed_available / 3)
|
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)
|
stats.append(utilization)
|
||||||
|
|
||||||
return stats
|
return stats
|
||||||
@ -2936,6 +2963,9 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
|
|||||||
validators=[MinValueValidator(1)],
|
validators=[MinValueValidator(1)],
|
||||||
default=20
|
default=20
|
||||||
)
|
)
|
||||||
|
available_power = models.PositiveSmallIntegerField(
|
||||||
|
default=0
|
||||||
|
)
|
||||||
power_factor = models.PositiveSmallIntegerField(
|
power_factor = models.PositiveSmallIntegerField(
|
||||||
validators=[MinValueValidator(1), MaxValueValidator(100)],
|
validators=[MinValueValidator(1), MaxValueValidator(100)],
|
||||||
default=80,
|
default=80,
|
||||||
@ -2990,15 +3020,29 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
|
|||||||
self.rack, self.rack.site, self.power_panel, self.power_panel.site
|
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):
|
def get_type_class(self):
|
||||||
return STATUS_CLASSES[self.type]
|
return STATUS_CLASSES[self.type]
|
||||||
|
|
||||||
def get_status_class(self):
|
def get_status_class(self):
|
||||||
return STATUS_CLASSES[self.status]
|
return STATUS_CLASSES[self.status]
|
||||||
|
|
||||||
@property
|
def get_power_stats(self):
|
||||||
def available_power(self):
|
"""
|
||||||
kva = self.voltage * self.amperage * (self.power_factor / 100)
|
Return power utilization statistics
|
||||||
if self.phase == POWERFEED_PHASE_3PHASE:
|
"""
|
||||||
return round(kva * 1.732)
|
power_port = self.connected_endpoint
|
||||||
return round(kva)
|
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'
|
verbose_name='Devices'
|
||||||
)
|
)
|
||||||
get_utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False, verbose_name='Utilization')
|
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):
|
class Meta(RackTable.Meta):
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'site', 'group', 'status', 'facility_id', 'tenant', 'role', 'u_height', 'device_count',
|
'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>Outlets</th>
|
||||||
<th>Allocated/Max (W)</th>
|
<th>Allocated/Max (W)</th>
|
||||||
<th>Available (VA)</th>
|
<th>Available (VA)</th>
|
||||||
|
<th>Utilization (Allocated)</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for pp in power_ports %}
|
{% for pp in power_ports %}
|
||||||
{% for leg in pp.get_power_stats %}
|
{% for leg in pp.get_power_stats %}
|
||||||
@ -363,6 +364,7 @@
|
|||||||
<td>{{ leg.outlets|placeholder }}</td>
|
<td>{{ leg.outlets|placeholder }}</td>
|
||||||
<td>{{ leg.allocated_draw_total }} / {{ leg.maximum_draw_total }}</td>
|
<td>{{ leg.allocated_draw_total }} / {{ leg.maximum_draw_total }}</td>
|
||||||
<td>{{ leg.available_power }}</td>
|
<td>{{ leg.available_power }}</td>
|
||||||
|
<td>{% utilization_graph leg.allocated_utilization %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -138,6 +138,29 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</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 class="col-md-5">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -207,6 +207,7 @@
|
|||||||
<th>Feed</th>
|
<th>Feed</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
|
<th>Utilization</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for powerfeed in power_feeds %}
|
{% for powerfeed in power_feeds %}
|
||||||
<tr>
|
<tr>
|
||||||
@ -222,6 +223,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<span class="label label-{{ powerfeed.get_type_class }}">{{ powerfeed.get_type_display }}</span>
|
<span class="label label-{{ powerfeed.get_type_class }}">{{ powerfeed.get_type_display }}</span>
|
||||||
</td>
|
</td>
|
||||||
|
<td>{% utilization_graph powerfeed.get_power_stats.0.allocated_utilization %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
Loading…
Reference in New Issue
Block a user