Updates for efficiency and consistency

This commit is contained in:
Josh VanDeraa 2021-03-13 00:53:36 +00:00
parent b88c4079bb
commit b6adfa1a17
10 changed files with 94 additions and 42 deletions

View File

@ -22,7 +22,7 @@ from utilities.choices import ColorChoices
from utilities.fields import ColorField, NaturalOrderingField from utilities.fields import ColorField, NaturalOrderingField
from utilities.querysets import RestrictedQuerySet from utilities.querysets import RestrictedQuerySet
from utilities.mptt import TreeManager from utilities.mptt import TreeManager
from utilities.utils import array_to_string, serialize_object from utilities.utils import array_to_string, serialize_object, UtilizationData
from .device_components import PowerOutlet, PowerPort from .device_components import PowerOutlet, PowerPort
from .devices import Device from .devices import Device
from .power import PowerFeed from .power import PowerFeed
@ -505,9 +505,10 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
return self.devices.filter(position=0) return self.devices.filter(position=0)
def get_utilization(self): def get_utilization(self):
""" """Gets utilization numerator and denominator for racks.
Determine the utilization rate of the rack and return it as a percentage. Occupied and reserved units both count
as utilized. Returns:
UtilizationData: (numerator=Occupied Unit Count, denominator=U Height of the rack)
""" """
# Determine unoccupied units # Determine unoccupied units
available_units = self.get_available_units() available_units = self.get_available_units()
@ -517,19 +518,19 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
if u in available_units: if u in available_units:
available_units.remove(u) available_units.remove(u)
occupied_unit_count = self.u_height - len(available_units)
# Return the numerator and denominator as percentage is to be calculated later where needed # Return the numerator and denominator as percentage is to be calculated later where needed
return (occupied_unit_count, self.u_height) return UtilizationData(numerator=self.u_height - len(available_units), denominator=self.u_height)
def get_power_utilization(self): def get_power_utilization(self):
""" """Determine the utilization numerator and denominator for power utilization on the rack.
Determine the utilization rate of power in the rack and return it as a percentage.
Returns:
UtilizationData: (numerator, denominator)
""" """
powerfeeds = PowerFeed.objects.filter(rack=self) powerfeeds = PowerFeed.objects.filter(rack=self)
available_power_total = sum(pf.available_power for pf in powerfeeds) available_power_total = sum(pf.available_power for pf in powerfeeds)
if not available_power_total: if not available_power_total:
return (0, 0) return UtilizationData(numerator=0, denominator=0)
pf_powerports = PowerPort.objects.filter( pf_powerports = PowerPort.objects.filter(
_cable_peer_type=ContentType.objects.get_for_model(PowerFeed), _cable_peer_type=ContentType.objects.get_for_model(PowerFeed),
@ -541,7 +542,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
_cable_peer_id__in=poweroutlets.values_list('id', flat=True) _cable_peer_id__in=poweroutlets.values_list('id', flat=True)
).aggregate(Sum('allocated_draw'))['allocated_draw__sum'] or 0 ).aggregate(Sum('allocated_draw'))['allocated_draw__sum'] or 0
return (allocated_draw_total, available_power_total) return UtilizationData(numerator=allocated_draw_total, denominator=available_power_total)
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') @extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')

View File

@ -82,9 +82,10 @@ RACKGROUP_ELEVATIONS = """
</a> </a>
""" """
# Value is a namedtuple that takes a numerator and denominator to pass in.
UTILIZATION_GRAPH = """ UTILIZATION_GRAPH = """
{% load helpers %} {% load helpers %}
{% utilization_graph value.0 value.1 %} {% utilization_graph value %}
""" """
# #

View File

@ -14,7 +14,7 @@ from dcim.models import Device, Interface
from extras.models import ChangeLoggedModel, CustomFieldModel, ObjectChange, TaggedItem from extras.models import ChangeLoggedModel, CustomFieldModel, ObjectChange, TaggedItem
from extras.utils import extras_features from extras.utils import extras_features
from utilities.querysets import RestrictedQuerySet from utilities.querysets import RestrictedQuerySet
from utilities.utils import array_to_string, serialize_object from utilities.utils import array_to_string, serialize_object, UtilizationData
from virtualization.models import VirtualMachine, VMInterface from virtualization.models import VirtualMachine, VMInterface
from .choices import * from .choices import *
from .constants import * from .constants import *
@ -308,13 +308,26 @@ class Aggregate(ChangeLoggedModel, CustomFieldModel):
return self.prefix.version return self.prefix.version
return None return None
def get_utilization(self):
def get_percent_utilized(self):
"""Gets the percentage utilized from the get_utilization method.
Returns
float: Percentage utilization
""" """
Determine the prefix utilization of the aggregate and return it as a percentage. utilization = self.get_utilization()
return int(utilization.numerator / float(utilization.denominator) * 100)
def get_utilization(self):
"""Gets the numerator and denominator for calculating utilization of an Aggregrate.
Returns:
UtilizationData: Aggregate utilization (numerator=size of child prefixes, denominator=prefix size)
""" """
queryset = Prefix.objects.filter(prefix__net_contained_or_equal=str(self.prefix)) queryset = Prefix.objects.filter(prefix__net_contained_or_equal=str(self.prefix))
child_prefixes = netaddr.IPSet([p.prefix for p in queryset]) child_prefixes = netaddr.IPSet([p.prefix for p in queryset])
return (child_prefixes.size, self.prefix.size)
return UtilizationData(numerator=child_prefixes.size, denominator=self.prefix.size)
class Role(ChangeLoggedModel): class Role(ChangeLoggedModel):
@ -595,24 +608,25 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
return '{}/{}'.format(next(available_ips.__iter__()), self.prefix.prefixlen) return '{}/{}'.format(next(available_ips.__iter__()), self.prefix.prefixlen)
def get_utilization(self): def get_utilization(self):
"""Get the child prefix size and parent size.
For Prefixes with a status of "container", get the number child prefixes. For all others, count child IP addresses.
Returns:
UtilizationData (namedtuple): (numerator, denominator)
""" """
Get the child prefix size and parent prefix size return them as a tuple. For Prefixes with a status of if self.status == Prefix.STATUS_CONTAINER:
"container", get the number child prefixes. For all others, count child IP addresses. queryset = Prefix.objects.filter(prefix__net_contained=str(self.prefix), vrf=self.vrf)
"""
if self.status == PrefixStatusChoices.STATUS_CONTAINER:
queryset = Prefix.objects.filter(
prefix__net_contained=str(self.prefix),
vrf=self.vrf
)
child_prefixes = netaddr.IPSet([p.prefix for p in queryset]) child_prefixes = netaddr.IPSet([p.prefix for p in queryset])
return (child_prefixes.size, self.prefix.size) return UtilizationData(numerator=child_prefixes.size, denominator=self.prefix.size)
else: else:
# Compile an IPSet to avoid counting duplicate IPs # Compile an IPSet to avoid counting duplicate IPs
child_count = netaddr.IPSet([ip.address.ip for ip in self.get_child_ips()]).size child_count = netaddr.IPSet([ip.address.ip for ip in self.get_child_ips()]).size
prefix_size = self.prefix.size prefix_size = self.prefix.size
if self.prefix.version == 4 and self.prefix.prefixlen < 31 and not self.is_pool: if self.prefix.version == 4 and self.prefix.prefixlen < 31 and not self.is_pool:
prefix_size -= 2 prefix_size -= 2
return (child_count, prefix_size) return UtilizationData(numerator=child_count, denominator=prefix_size)
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') @extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')

View File

@ -225,7 +225,7 @@
<td>{{ utilization.allocated }}VA</td> <td>{{ utilization.allocated }}VA</td>
{% if powerfeed.available_power %} {% if powerfeed.available_power %}
<td>{{ powerfeed.available_power }}VA</td> <td>{{ powerfeed.available_power }}VA</td>
<td>{% utilization_graph utilization.allocated powerfeed.available_power %}</td> <td>{% utilization_graph_raw_data utilization.allocated powerfeed.available_power %}</td>
{% else %} {% else %}
<td class="text-muted">&mdash;</td> <td class="text-muted">&mdash;</td>
<td class="text-muted">&mdash;</td> <td class="text-muted">&mdash;</td>
@ -238,7 +238,7 @@
<td>{{ leg.allocated }}</td> <td>{{ leg.allocated }}</td>
<td>{{ powerfeed.available_power|divide:3 }}VA</td> <td>{{ powerfeed.available_power|divide:3 }}VA</td>
{% with phase_available=powerfeed.available_power|divide:3 %} {% with phase_available=powerfeed.available_power|divide:3 %}
<td>{% utilization_graph leg.allocated phase_available %}</td> <td>{% utilization_graph_raw_data leg.allocated phase_available %}</td>
{% endwith %} {% endwith %}
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -113,7 +113,7 @@
<td> <td>
{{ utilization.allocated }}VA / {{ object.available_power }}VA {{ utilization.allocated }}VA / {{ object.available_power }}VA
{% if object.available_power > 0 %} {% if object.available_power > 0 %}
{% utilization_graph utilization.allocated object.available_power %} {% utilization_graph_raw_data utilization.allocated object.available_power %}
{% endif %} {% endif %}
</td> </td>
{% else %} {% else %}

View File

@ -242,7 +242,7 @@
</td> </td>
{% with power_port=powerfeed.connected_endpoint %} {% with power_port=powerfeed.connected_endpoint %}
{% if power_port %} {% if power_port %}
<td>{% utilization_graph power_port.get_power_draw.allocated powerfeed.available_power %}</td> <td>{% utilization_graph power_port.get_power_draw %}</td>
{% else %} {% else %}
<td class="text-muted">N/A</td> <td class="text-muted">N/A</td>
{% endif %} {% endif %}

View File

@ -77,7 +77,7 @@
<tr> <tr>
<td>Utilization</td> <td>Utilization</td>
<td> <td>
{{ object.get_utilization }}% {{ object.get_percent_utilized }}%
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@ -1,4 +1,4 @@
<div {% if utilization_count and total_count %}title="Used: {{ utilization_count }}&#013Total Count: {{ total_count }}" <div {% if utilization_count and total_count %}title="Used: {{ utilization_count }}&#13Total Count: {{ total_count }}"
{% endif %}class="progress text-center"> {% endif %}class="progress text-center">
{% if utilization < 30 %}<span style="font-size: 12px;">{{ utilization }}%</span>{% endif %} {% if utilization < 30 %}<span style="font-size: 12px;">{{ utilization }}%</span>{% endif %}
<div class="progress-bar progress-bar-{% if utilization >= danger_threshold %}danger{% elif utilization >= warning_threshold %}warning{% else %}success{% endif %}" <div class="progress-bar progress-bar-{% if utilization >= danger_threshold %}danger{% elif utilization >= warning_threshold %}warning{% else %}success{% endif %}"

View File

@ -243,22 +243,55 @@ def querystring(request, **kwargs):
@register.inclusion_tag('utilities/templatetags/utilization_graph.html') @register.inclusion_tag('utilities/templatetags/utilization_graph.html')
def utilization_graph(used_count, total, warning_threshold=75, danger_threshold=90): def utilization_graph(utilization_data, warning_threshold=75, danger_threshold=90):
"""Wrapper for a horizontal bar graph indicating a percentage of utilization from a tuple of data.
Takes the utilization_data that is a namedtuple with numerator and denominator field names and passes them into
the utilization_graph_raw_data to handle the generation graph data.
Args:
utilization_data (UtilizationData): Namedtuple with numerator and denominator keys
warning_threshold (int, optional): Warning Threshold Value. Defaults to 75.
danger_threshold (int, optional): Danger Threshold Value. Defaults to 90.
Returns:
dict: Dictionary with utilization, warning threshold, danger threshold, utilization count, and total count for
display
""" """
Display a horizontal bar graph indicating a percentage of utilization. return utilization_graph_raw_data(
numerator=utilization_data.numerator,
denominator=utilization_data.denominator,
warning_threshold=warning_threshold,
danger_threshold=danger_threshold,
)
@register.inclusion_tag("utilities/templatetags/utilization_graph_raw_data.html")
def utilization_graph_raw_data(numerator, denominator, warning_threshold=75, danger_threshold=90):
"""Display a horizontal bar graph indicating a percentage of utilization.
Args:
numerator (int): Numerator for creating a percentage
denominator (int): Denominator for creating a percentage
warning_threshold (int, optional): Warning Threshold Value. Defaults to 75.
danger_threshold (int, optional): Danger Threshold Value. Defaults to 90.
Returns:
dict: Dictionary with utilization, warning threshold, danger threshold, utilization count, and total count for
display
""" """
# Check for possible division by zero error # Check for possible division by zero error
if total == 0: if denominator == 0:
utilization = 0 utilization = 0
else: else:
utilization = int(float(used_count) / total * 100) utilization = int(float(numerator) / denominator * 100)
return { return {
'utilization': utilization, "utilization": utilization,
'warning_threshold': warning_threshold, "warning_threshold": warning_threshold,
'danger_threshold': danger_threshold, "danger_threshold": danger_threshold,
'utilization_count': used_count, "utilization_count": numerator,
'total_count': total, "total_count": denominator,
} }

View File

@ -1,6 +1,6 @@
import datetime import datetime
import json import json
from collections import OrderedDict from collections import OrderedDict, namedtuple
from itertools import count, groupby from itertools import count, groupby
from django.core.serializers import serialize from django.core.serializers import serialize
@ -326,3 +326,6 @@ def copy_safe_request(request):
'path': request.path, 'path': request.path,
'id': getattr(request, 'id', None), # UUID assigned by middleware 'id': getattr(request, 'id', None), # UUID assigned by middleware
}) })
# Setup UtilizationData named tuple for use by multiple methods
UtilizationData = namedtuple("UtilizationData", ["numerator", "denominator"])