From e88f8a5bee687ebf5b8d32b2306f360895ac9a8e Mon Sep 17 00:00:00 2001 From: Ladislav Smola Date: Wed, 10 Sep 2014 11:12:13 +0200 Subject: [PATCH] Aggregated stats for roles Making aggregated stats for roles. Extracting more code to metering utils, to avoid duplication. Depends on Ceilometer patch: https://review.openstack.org/#/c/119803/ Change-Id: I6a9e16e8305ef2d6f14912081059ec93ea0606f1 --- tuskar_ui/infrastructure/nodes/views.py | 87 +------------ .../roles/templates/roles/detail.html | 116 +++++++++++++++-- tuskar_ui/infrastructure/roles/urls.py | 3 +- tuskar_ui/infrastructure/roles/views.py | 51 ++++++++ tuskar_ui/utils/metering.py | 119 ++++++++++++++++-- 5 files changed, 269 insertions(+), 107 deletions(-) diff --git a/tuskar_ui/infrastructure/nodes/views.py b/tuskar_ui/infrastructure/nodes/views.py index 267224c9c..8afae03a9 100644 --- a/tuskar_ui/infrastructure/nodes/views.py +++ b/tuskar_ui/infrastructure/nodes/views.py @@ -100,103 +100,26 @@ class DetailView(horizon_tabs.TabView): class PerformanceView(base.TemplateView): - LABELS = { - 'hardware.cpu.load.1min': _("CPU load 1 min average"), - 'hardware.system_stats.cpu.util': _("CPU utilization"), - 'hardware.system_stats.io.outgoing.blocks': _("IO raw sent"), - 'hardware.system_stats.io.incoming.blocks': _("IO raw received"), - 'hardware.network.ip.outgoing.datagrams': _("IP out requests"), - 'hardware.network.ip.incoming.datagrams': _("IP in requests"), - 'hardware.memory.swap.util': _("Swap utilization"), - } - - @staticmethod - def _series_for_meter(aggregates, - meter_id, - meter_name, - stats_name, - unit): - """Construct datapoint series for a meter from resource aggregates.""" - series = [] - for resource in aggregates: - if resource.get_meter(meter_name): - name = PerformanceView.LABELS.get(meter_id, meter_name) - point = {'unit': unit, - 'name': unicode(name), - 'meter': meter_id, - 'data': []} - for statistic in resource.get_meter(meter_name): - date = statistic.duration_end[:19] - value = float(getattr(statistic, stats_name)) - point['data'].append({'x': date, 'y': value}) - series.append(point) - return series - def get(self, request, *args, **kwargs): meter = request.GET.get('meter') date_options = request.GET.get('date_options') date_from = request.GET.get('date_from') date_to = request.GET.get('date_to') stats_attr = request.GET.get('stats_attr', 'avg') - group_by = request.GET.get('group_by') barchart = bool(request.GET.get('barchart')) node_uuid = kwargs.get('node_uuid') node = api.node.Node.get(request, node_uuid) - unit = '' - series = [] - try: instance_uuid = node.instance_uuid except AttributeError: - pass + json_output = None else: - query = [{'field': 'resource_id', - 'op': 'eq', - 'value': instance_uuid}] - - # Disk and Network I/O: data from 2 meters in one chart - if meter == 'disk-io': - meters = metering_utils.get_meters([ - 'hardware.system_stats.io.outgoing.blocks', - 'hardware.system_stats.io.incoming.blocks' - ]) - elif meter == 'network-io': - meters = metering_utils.get_meters([ - 'hardware.network.ip.outgoing.datagrams', - 'hardware.network.ip.incoming.datagrams' - ]) - else: - meters = metering_utils.get_meters([meter]) - - date_from, date_to = metering_utils._calc_date_args( - date_from, - date_to, - date_options) - - for meter_id, meter_name in meters: - resources, unit = metering_utils.query_data( - request=request, - date_from=date_from, - date_to=date_to, - group_by=group_by, - meter=meter_id, - query=query) - serie = self._series_for_meter( - resources, - meter_id, - meter_name, - stats_attr, - unit) - series += serie - - json_output = metering_utils.create_json_output( - series, - barchart, - unit, - date_from, - date_to) + json_output = metering_utils.get_nodes_stats( + request, instance_uuid, meter, date_options=date_options, + date_from=date_from, date_to=date_to, stats_attr=stats_attr, + barchart=barchart) return http.HttpResponse(json.dumps(json_output), content_type='application/json') diff --git a/tuskar_ui/infrastructure/roles/templates/roles/detail.html b/tuskar_ui/infrastructure/roles/templates/roles/detail.html index be4039894..546eef64b 100644 --- a/tuskar_ui/infrastructure/roles/templates/roles/detail.html +++ b/tuskar_ui/infrastructure/roles/templates/roles/detail.html @@ -8,24 +8,114 @@ {% endblock page_header %} {% block main %} +
-
+

{% blocktrans count counter=nodes|length %}{{ counter }} instance{% plural %}{{ counter }} instances{% endblocktrans %}

-
{% trans 'Flavor' %}
- {% if flavor %} -
{{ flavor.name }} {{ flavor.get_keys.cpu_arch }} | {{ flavor.vcpus }} {% trans "CPU" %} | {{ flavor.ram }} {% trans "MB RAM" %} | {{ flavor.disk }} {% trans "GB HDD" %}
- {% else %} -
{% trans 'No flavor associated' %}
- {% endif %} -
{% trans 'Image' %}
- {% if image %} -
{{ image.name }}
- {% else %} -
{% trans 'No image associated' %}
- {% endif %} +
{% trans 'Flavor' %}
+ {% if flavor %} +
{{ flavor.name }} {{ flavor.get_keys.cpu_arch }} | {{ flavor.vcpus }} {% trans "CPU" %} | {{ flavor.ram }} {% trans "MB RAM" %} | {{ flavor.disk }} {% trans "GB HDD" %}
+ {% else %} +
{% trans 'No flavor associated' %}
+ {% endif %} +
{% trans 'Image' %}
+ {% if image %} +
{{ image.name }}
+ {% else %} +
{% trans 'No image associated' %}
+ {% endif %}
+
+
+ {% if meter_conf %} + {% url 'horizon:infrastructure:roles:performance' role.uuid as role_perf_url %} +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+ {% for meter_label, url_part, y_max in meter_conf %} +
+ {% include "infrastructure/_performance_chart.html" with label=meter_label y_max=y_max url=role_perf_url|add:"?"|add:url_part only %} +
+ {% endfor %} +
+ {% else %} +

{% trans 'Metering service is not enabled.' %}

+ {% endif %} +
+
+
+
{{ table.render }}
diff --git a/tuskar_ui/infrastructure/roles/urls.py b/tuskar_ui/infrastructure/roles/urls.py index 67940c65d..9dfdb7787 100644 --- a/tuskar_ui/infrastructure/roles/urls.py +++ b/tuskar_ui/infrastructure/roles/urls.py @@ -24,5 +24,6 @@ urlpatterns = urls.patterns( name='detail'), urls.url(r'^(?P[^/]+)/edit$', views.UpdateView.as_view(), name='update'), - + urls.url(r'^(?P[^/]+)/performance/$', + views.PerformanceView.as_view(), name='performance'), ) diff --git a/tuskar_ui/infrastructure/roles/views.py b/tuskar_ui/infrastructure/roles/views.py index 72edff433..85a1400ae 100644 --- a/tuskar_ui/infrastructure/roles/views.py +++ b/tuskar_ui/infrastructure/roles/views.py @@ -11,10 +11,13 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +import json from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse_lazy +from django import http from django.utils.translation import ugettext_lazy as _ +from django.views.generic import base from glanceclient import exc as glance_exc from horizon import exceptions as horizon_exceptions @@ -22,9 +25,12 @@ from horizon import tables as horizon_tables from horizon import utils from horizon import workflows +from openstack_dashboard.api import base as api_base + from tuskar_ui import api from tuskar_ui.infrastructure.roles import tables from tuskar_ui.infrastructure.roles import workflows as role_workflows +from tuskar_ui.utils import metering as metering_utils INDEX_URL = 'horizon:infrastructure:roles:index' @@ -123,6 +129,23 @@ class DetailView(horizon_tables.DataTableView, RoleMixin, StackMixin): # won't work right now context['image'] = role.image(plan) + if stack: + if api_base.is_service_enabled(self.request, 'metering'): + # Meter configuration in the following format: + # (meter label, url part, barchart (True/False)) + context['meter_conf'] = ( + (_('System Load'), + metering_utils.url_part('hardware.cpu.load.1min', False), + None), + (_('CPU Utilization'), + metering_utils.url_part('hardware.system_stats.cpu.util', + True), + '100'), + (_('Swap Utilization'), + metering_utils.url_part('hardware.memory.swap.util', + True), + '100'), + ) return context @@ -157,3 +180,31 @@ class UpdateView(workflows.WorkflowView): 'flavor': role_flavor, 'image': role_image, } + + +class PerformanceView(base.TemplateView, RoleMixin, StackMixin): + def get(self, request, *args, **kwargs): + meter = request.GET.get('meter') + date_options = request.GET.get('date_options') + date_from = request.GET.get('date_from') + date_to = request.GET.get('date_to') + stats_attr = request.GET.get('stats_attr', 'avg') + barchart = bool(request.GET.get('barchart')) + + plan = api.tuskar.Plan.get_the_plan(self.request) + role = self.get_role(None) + role.image(plan) + + try: + image = role.image(plan) + image_uuid = image.id + except AttributeError: + json_output = None + else: + json_output = metering_utils.get_nodes_stats( + request, image_uuid, meter, date_options=date_options, + date_from=date_from, date_to=date_to, stats_attr=stats_attr, + barchart=barchart, group_by='image_id') + + return http.HttpResponse(json.dumps(json_output), + mimetype='application/json') diff --git a/tuskar_ui/utils/metering.py b/tuskar_ui/utils/metering.py index 4ddc7f16e..5a8319221 100644 --- a/tuskar_ui/utils/metering.py +++ b/tuskar_ui/utils/metering.py @@ -57,6 +57,16 @@ SETTINGS = { } } +LABELS = { + 'hardware.cpu.load.1min': _("CPU load 1 min average"), + 'hardware.system_stats.cpu.util': _("CPU utilization"), + 'hardware.system_stats.io.outgoing.blocks': _("IO raw sent"), + 'hardware.system_stats.io.incoming.blocks': _("IO raw received"), + 'hardware.network.ip.outgoing.datagrams': _("IP out requests"), + 'hardware.network.ip.incoming.datagrams': _("IP in requests"), + 'hardware.memory.swap.util': _("Swap utilization"), +} + #TODO(lsmola) this should probably live in Horizon common def query_data(request, @@ -89,20 +99,21 @@ def query_data(request, unit = meter_list[0].unit except Exception: unit = "" - if group_by == "resources": - # TODO(lsmola) need to implement group_by groups of resources - resources = [] - unit = "" - else: - ceilometer_usage = ceilometer.CeilometerUsage(request) - try: + + ceilometer_usage = ceilometer.CeilometerUsage(request) + try: + if group_by: + resources = ceilometer_usage.resource_aggregates_with_statistics( + query, [meter], period=period, stats_attr=None, + additional_query=additional_query) + else: resources = ceilometer_usage.resources_with_statistics( query, [meter], period=period, stats_attr=None, additional_query=additional_query) - except Exception: - resources = [] - exceptions.handle(request, - _('Unable to retrieve statistics.')) + except Exception: + resources = [] + exceptions.handle(request, + _('Unable to retrieve statistics.')) return resources, unit @@ -217,3 +228,89 @@ def create_json_output(series, barchart, unit, date_from, date_to): json_output = {'series': series} json_output = dict(json_output.items() + settings.items()) return json_output + + +def _series_for_meter(aggregates, + meter_id, + meter_name, + stats_name, + unit): + """Construct datapoint series for a meter from resource aggregates.""" + series = [] + for resource in aggregates: + if resource.get_meter(meter_name): + name = LABELS.get(meter_id, meter_name) + point = {'unit': unit, + 'name': unicode(name), + 'meter': meter_id, + 'data': []} + for statistic in resource.get_meter(meter_name): + date = statistic.duration_end[:19] + value = float(getattr(statistic, stats_name)) + point['data'].append({'x': date, 'y': value}) + series.append(point) + return series + + +def get_nodes_stats(request, uuid, meter, date_options=None, date_from=None, + date_to=None, stats_attr=None, barchart=None, + group_by=None): + + unit = '' + series = [] + + if uuid: + if group_by == "image_id": + query = {} + image_query = [{"field": "metadata.%s" % group_by, + "op": "eq", + "value": uuid}] + query[uuid] = image_query + else: + query = [{'field': 'resource_id', + 'op': 'eq', + 'value': uuid}] + + # Disk and Network I/O: data from 2 meters in one chart + if meter == 'disk-io': + meters = get_meters([ + 'hardware.system_stats.io.outgoing.blocks', + 'hardware.system_stats.io.incoming.blocks' + ]) + elif meter == 'network-io': + meters = get_meters([ + 'hardware.network.ip.outgoing.datagrams', + 'hardware.network.ip.incoming.datagrams' + ]) + else: + meters = get_meters([meter]) + + date_from, date_to = _calc_date_args( + date_from, + date_to, + date_options) + + for meter_id, meter_name in meters: + resources, unit = query_data( + request=request, + date_from=date_from, + date_to=date_to, + group_by=group_by, + meter=meter_id, + query=query) + serie = _series_for_meter( + resources, + meter_id, + meter_name, + stats_attr, + unit) + series += serie + + json_output = create_json_output( + series, + barchart, + unit, + date_from, + date_to) + + return json_output