فهرست منبع

Merge pull request #3268 from digitalocean/power-utilization-tweaks

Cleaned up logic for calculating power draw
Jeremy Stretch 6 سال پیش
والد
کامیت
21856c6f0c

+ 38 - 49
netbox/dcim/models.py

@@ -1962,48 +1962,48 @@ class PowerPort(CableTermination, ComponentModel):
                 "Connected endpoint must be a PowerOutlet or PowerFeed, not {}.".format(type(value))
             )
 
-    def get_power_stats(self):
+    def get_power_draw(self):
         """
-        Return utilization statistics for this PowerPort.
+        Return the allocated and maximum power draw (in VA) and child PowerOutlet count for this PowerPort.
         """
-        feed = self._connected_powerfeed
-        if not feed or not self.poweroutlets.count():
-            return None
-
-        stats = []
-        powerfeed_available = self._connected_powerfeed.available_power
+        # Calculate aggregate draw of all child power outlets if no numbers have been defined manually
+        if self.allocated_draw is None and self.maximum_draw is None:
+            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_total=Sum('maximum_draw'),
+                allocated_draw_total=Sum('allocated_draw'),
+            )
+            ret = {
+                'allocated': utilization['allocated_draw_total'] or 0,
+                'maximum': utilization['maximum_draw_total'] or 0,
+                'outlet_count': len(outlet_ids),
+                'legs': [],
+            }
+
+            # Calculate per-leg aggregates for three-phase feeds
+            if self._connected_powerfeed and self._connected_powerfeed.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_total=Sum('maximum_draw'),
+                        allocated_draw_total=Sum('allocated_draw'),
+                    )
+                    ret['legs'].append({
+                        'name': leg_name,
+                        'allocated': utilization['allocated_draw_total'] or 0,
+                        'maximum': utilization['maximum_draw_total'] or 0,
+                        'outlet_count': len(outlet_ids),
+                    })
 
-        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_total=Sum('maximum_draw'),
-            allocated_draw_total=Sum('allocated_draw'),
-        )
-        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
-        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_total=Sum('maximum_draw'),
-                    allocated_draw_total=Sum('allocated_draw'),
-                )
-                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 ret
 
-        return stats
+        # Default to administratively defined values
+        return {
+            'allocated': self.allocated_draw or 0,
+            'maximum': self.maximum_draw or 0,
+            'outlet_count': PowerOutlet.objects.filter(power_port=self).count(),
+            'legs': [],
+        }
 
 
 #
@@ -3037,14 +3037,3 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
 
     def get_status_class(self):
         return STATUS_CLASSES[self.status]
-
-    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()

+ 10 - 2
netbox/dcim/tables.py

@@ -299,8 +299,16 @@ class RackDetailTable(RackTable):
         template_code=RACK_DEVICE_COUNT,
         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')
+    get_utilization = tables.TemplateColumn(
+        template_code=UTILIZATION_GRAPH,
+        orderable=False,
+        verbose_name='Space'
+    )
+    get_power_utilization = tables.TemplateColumn(
+        template_code=UTILIZATION_GRAPH,
+        orderable=False,
+        verbose_name='Power'
+    )
 
     class Meta(RackTable.Meta):
         fields = (

+ 1 - 0
netbox/project-static/css/base.css

@@ -586,6 +586,7 @@ ul.nav-tabs, ul.nav-pills {
 /* Fix progress bar margin inside table cells */
 td .progress {
     margin-bottom: 0;
+    min-width: 100px;
 }
 textarea {
     font-family: Consolas, Lucida Console, monospace;

+ 24 - 12
netbox/templates/dcim/device.html

@@ -349,24 +349,36 @@
                         <tr>
                             <th>Input</th>
                             <th>Outlets</th>
-                            <th>Allocated/Max (W)</th>
-                            <th>Available (VA)</th>
-                            <th>Utilization (Allocated)</th>
+                            <th>Allocated</th>
+                            <th>Available</th>
+                            <th>Utilization</th>
                         </tr>
                         {% for pp in power_ports %}
-                            {% for leg in pp.get_power_stats %}
+                            {% with utilization=pp.get_power_draw powerfeed=pp.connected_endpoint %}
                                 <tr>
-                                    {% if leg.name %}
-                                        <td style="padding-left: 20px">{{ leg.name }}</td>
+                                    <td>{{ pp }}</td>
+                                    <td>{{ utilization.outlet_count }}</td>
+                                    <td>{{ utilization.allocated }}VA</td>
+                                    {% if powerfeed %}
+                                        <td>{{ powerfeed.available_power }}VA</td>
+                                        <td>{% utilization_graph utilization.allocated|percentage:powerfeed.available_power %}</td>
                                     {% else %}
-                                        <td>{{ pp }}</td>
+                                        <td class="text-muted">&mdash;</td>
+                                        <td class="text-muted">&mdash;</td>
                                     {% endif %}
-                                    <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 %}
+                                {% for leg in utilization.legs %}
+                                    <tr>
+                                        <td style="padding-left: 20px">Leg {{ leg.name }}</td>
+                                        <td>{{ leg.outlet_count }}</td>
+                                        <td>{{ leg.allocated }}</td>
+                                        <td>{{ powerfeed.available_power|divide:3 }}VA</td>
+                                        {% with phase_available=powerfeed.available_power|divide:3 %}
+                                            <td>{% utilization_graph leg.allocated|percentage:phase_available %}</td>
+                                        {% endwith %}
+                                    </tr>
+                                {% endfor %}
+                            {% endwith %}
                         {% endfor %}
                     </table>
                 </div>

+ 13 - 25
netbox/templates/dcim/powerfeed.html

@@ -106,6 +106,19 @@
                         {% endif %}
                     </td>
                 </tr>
+                <tr>
+                    <td>Utilization (Allocated)</td>
+                    {% with utilization=powerfeed.connected_endpoint.get_power_draw %}
+                        {% if utilization %}
+                            <td>
+                                {{ utilization.allocated }}VA / {{ powerfeed.available_power }}VA
+                                {% utilization_graph utilization.allocated|percentage:powerfeed.available_power %}
+                            </td>
+                        {% else %}
+                            <td class="text-muted">N/A</td>
+                        {% endif %}
+                    {% endwith %}
+                </tr>
             </table>
         </div>
     </div>
@@ -138,30 +151,5 @@
             </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>
 {% endblock %}

+ 7 - 1
netbox/templates/dcim/rack.html

@@ -223,7 +223,13 @@
                             <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>
+                            {% with power_port=powerfeed.connected_endpoint %}
+                                {% if power_port %}
+                                    <td>{% utilization_graph power_port.get_power_draw.allocated|percentage:powerfeed.available_power %}</td>
+                                {% else %}
+                                    <td class="text-muted">N/A</td>
+                                {% endif %}
+                            {% endwith %}
                         </tr>
                     {% endfor %}
                 </table>

+ 20 - 0
netbox/utilities/templatetags/helpers.py

@@ -166,6 +166,26 @@ def fgcolor(value):
     return '#{}'.format(foreground_color(value))
 
 
+@register.filter()
+def divide(x, y):
+    """
+    Return x/y (rounded).
+    """
+    if x is None or y is None:
+        return None
+    return round(x / y)
+
+
+@register.filter()
+def percentage(x, y):
+    """
+    Return x/y as a percentage.
+    """
+    if x is None or y is None:
+        return None
+    return round(x / y * 100)
+
+
 #
 # Tags
 #