Просмотр исходного кода

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

John Anderson 6 лет назад
Родитель
Сommit
2eeffce924

+ 4 - 0
CHANGELOG.md

@@ -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)

+ 18 - 0
netbox/dcim/migrations/0074_powerfeed_available_power_cache.py

@@ -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),
+        ),
+    ]

+ 51 - 7
netbox/dcim/models.py

@@ -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()

+ 2 - 1
netbox/dcim/tables.py

@@ -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',
         )
 
 

+ 2 - 0
netbox/templates/dcim/device.html

@@ -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 %}

+ 23 - 0
netbox/templates/dcim/powerfeed.html

@@ -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>

+ 2 - 0
netbox/templates/dcim/rack.html

@@ -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>