File ceilometer-horizon.patch of Package openstack-dashboard

--- horizon/static/horizon/js/horizon.ceilometer.js
+++ horizon/static/horizon/js/horizon.ceilometer.js
@@ -0,0 +1,221 @@
+horizon.ceilometer = {
+  initVariables: function () {
+    var self = this;
+    self.margin = {
+      top: 40,
+      right: 100,
+      bottom: 80,
+      left: 200
+    };
+    self.width = 1100 - self.margin.left - self.margin.right;
+    self.height = 480 - self.margin.top - self.margin.bottom;
+    self.svg = undefined;
+    self.x = d3.time.scale().range([0, self.width]);
+    self.y = d3.scale.linear().range([self.height, 0]);
+    self.xAxis = d3.svg.axis().scale(self.x).orient("bottom");
+    self.yAxis = d3.svg.axis().scale(self.y).orient("left");
+    self.line = d3.svg.line()
+      .x(function (d) {
+        return self.x(d.date);
+      })
+      .y(function (d) {
+        return self.y(d.value);
+      });
+
+    self.$meter = $("#meter");
+    self.$resource = $("#resource");
+    self.$date_from = $("#date_from");
+    self.$date_to = $("#date_to");
+    self.$get_samples = $("#samples_url");
+    self.$chart_container = $("#chart_container");
+    self.parseDate = d3.time.format("%Y-%m-%dT%H:%M:%S").parse;
+  },
+  getResources: function (meters) {
+    // Get ``meters`` variable from django template.
+    var self = this;
+    if (meters) {
+      self.meters = meters;
+    }
+  },
+
+  updateResourceOptions: function () {
+    // Update resource select field on stats.html template.
+    var self = this,
+      meter_name = self.$meter.val(),
+      i,
+      option;
+    self.$resource.empty();
+    if (self.meters) {
+      for (i = 0; i < self.meters.length; i = i + 1) {
+        if (meter_name === self.meters[i].name) {
+          option = '<option value="' + self.meters[i].resource_id + '">';
+          option += self.meters[i].resource_id + '</option>';
+          self.$resource.append(option);
+        }
+      }
+    }
+  },
+
+  loadChartData: function () {
+    var self = this,
+      meter = self.$meter.val(),
+      resource = self.$resource.val(),
+      from = self.$date_from.val(),
+      to = self.$date_to.val(),
+      horizon_samples_url = self.$get_samples.attr("url");
+    d3.select("svg").remove();
+
+    self.svg = d3.select("#chart_container").append("svg")
+      .attr("width", self.width + self.margin.left + self.margin.right)
+      .attr("height", self.height + self.margin.top + self.margin.bottom)
+      .append("g")
+      .attr("transform", "translate(" + self.margin.left + "," + self.margin.top + ")");
+
+    if (meter && resource) {
+      d3.csv(horizon_samples_url + "?meter=" + meter + "&resource=" + resource + "&from=" + from + "&to=" + to,
+        function (error, data) {
+          var chart_title = self.$meter.val() + " ";
+          chart_title += gettext("for resource") + " " + self.$resource.val();
+          chart_title += " (" + gettext("From") + " " + self.$date_to.val() + " " + gettext("to") + " ";
+          chart_title += self.$date_to.val() + ")";
+
+          // read selected option
+          var option = self.$meter.find("option").filter(":selected");
+          var meter_text = gettext("Value");
+          if (option) {
+            var unit = option[0].getAttribute("data-unit");
+            meter_text = meter_text + " (" + unit + ")";
+          }
+
+          data.forEach(function (d) {
+            d.date = self.parseDate(d.date);
+            d.value = +d.value;
+          });
+
+          self.x.domain(d3.extent(data, function (d) {
+            return d.date;
+          }));
+
+          var min_value = d3.min(data, function(d){return d.value});
+          var max_value = d3.max(data, function(d){return d.value});
+          if (min_value === max_value) {
+            self.y.domain([0, min_value * 2]);
+          } else {
+            self.y.domain(d3.extent(data, function (d) {
+              return d.value;
+            }));
+
+          }
+
+          self.svg.append("g")
+            .attr("class", "x axis")
+            .attr("transform", "translate(0," + self.height + ")")
+            .call(self.xAxis);
+          self.svg.append("g")
+            .attr("class", "y axis")
+            .call(self.yAxis)
+            .append("text")
+            .attr("transform", "rotate(-90)")
+            .attr("y", 6)
+            .attr("dy", ".71em")
+            .style("text-anchor", "end")
+            .text(meter_text);
+
+          self.svg.append("path")
+            .datum(data)
+            .attr("class", "line")
+            .style("stroke", "steelblue")
+            .style("stroke-width", 1.5)
+            .style("fill", "none")
+            .attr("d", self.line);
+
+          self.svg.append("text")
+            .attr("x", (self.width / 2))
+            .attr("y", -(self.margin.top / 2))
+            .attr("text-anchor", "middle")
+            .style("font-size", "14px")
+            .style("text-decoration", "none")
+            .text(chart_title);
+
+          d3.selectAll("path.domain")
+            .style("fill", "none")
+            .style("stroke", "black")
+            .style("stroke-width", 1);
+
+          d3.selectAll(".axis path")
+            .style("fill", "none")
+            .style("stroke", "black")
+            .style("shape-rendering", "crispEdges");
+
+          d3.selectAll(".axis")
+            .style("font-size", "10px");
+
+          d3.selectAll(".axis line")
+            .style("fill", "none")
+            .style("stroke", "black")
+            .style("shape-rendering", "crispEdges");
+        });
+    }
+  },
+
+  refreshPickers: function (date_interval) {
+    var now;
+    var targetDate;
+    var self = this;
+    now = new Date();
+    self.$date_to.datepicker('setValue', now);
+    targetDate = new Date();
+    targetDate.setDate(now.getDate() - date_interval);
+    self.$date_from.datepicker('setValue', targetDate);
+  },
+
+  init: function () {
+    var self = this,
+      to,
+      from;
+    self.initVariables();
+    from = self.$date_from.datepicker({format: "yyyy-mm-dd"})
+      .on('changeDate', function (ev) {
+        if (ev.date.valueOf() > to.date.valueOf()) {
+          var newDate = new Date(ev.date);
+          newDate.setDate(newDate.getDate() + 1);
+          to.setValue(newDate);
+        }
+        from.hide();
+        self.$date_to[0].focus();
+      }).data('datepicker');
+    to = self.$date_to.datepicker({
+      format: "yyyy-mm-dd",
+      onRender: function (date) { return date.valueOf() <= from.date.valueOf() ? 'disabled' : ''; }
+    }).on('changeDate', function () {
+      to.hide();
+      self.loadChartData();
+    }).data('datepicker');
+
+    $(".action_display_chart").click(function () {
+      self.loadChartData();
+    });
+
+    self.$meter.change(function () {
+      self.updateResourceOptions();
+      self.loadChartData();
+    });
+    self.$resource.change(function () {
+      self.loadChartData();
+    });
+
+    $("#date_options").change(function () {
+      var current = $(this).val();
+      if (current) {
+        self.refreshPickers(current);
+        self.loadChartData();
+      }
+    });
+    self.refreshPickers(1);
+    self.loadChartData();
+  }
+};
+
+horizon.addInitFunction(function () {
+  horizon.ceilometer.init();
+});
--- openstack_dashboard/api/ceilometer.py
+++ openstack_dashboard/api/ceilometer.py
@@ -0,0 +1,303 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# 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 itertools
+import logging
+
+from ceilometerclient import client as ceilometer_client
+from django.conf import settings
+
+from .base import APIDictWrapper
+from .base import APIResourceWrapper
+from .base import url_for
+
+import keystone
+
+
+LOG = logging.getLogger(__name__)
+
+
+class Meter(APIResourceWrapper):
+    _attrs = ['name', 'type', 'unit', 'resource_id', 'user_id',
+              'project_id']
+
+
+class Resource(APIResourceWrapper):
+    _attrs = ['resource_id', "source", "user_id", "project_id", "metadata"]
+
+    @property
+    def name(self):
+        name = self.metadata.get("name", None)
+        display_name = self.metadata.get("display_name", None)
+        return name or display_name or ""
+
+
+class Sample(APIResourceWrapper):
+    _attrs = ['counter_name', 'user_id', 'resource_id', 'timestamp',
+              'resource_metadata', 'source', 'counter_unit', 'counter_volume',
+              'project_id', 'counter_type', 'resource_metadata']
+
+    @property
+    def instance(self):
+        if 'display_name' in self.resource_metadata:
+            return self.resource_metadata['display_name']
+        elif 'instance_id' in self.resource_metadata:
+            return self.resource_metadata['instance_id']
+        else:
+            return None
+
+    @property
+    def name(self):
+        name = self.resource_metadata.get("name", None)
+        display_name = self.resource_metadata.get("display_name", None)
+        return name or display_name or ""
+
+
+class GlobalObjectStoreUsage(APIDictWrapper):
+    _attrs = ["id", "tenant", "user", "resource", "storage_objects",
+              "storage_objects_size", "storage_objects_outgoing_bytes",
+              "storage_objects_incoming_bytes"]
+
+
+class GlobalDiskUsage(APIDictWrapper):
+    _attrs = ["id", "tenant", "user", "resource", "disk_read_bytes",
+              "disk_read_requests", "disk_write_bytes",
+              "disk_write_requests"]
+
+
+class GlobalNetworkTrafficUsage(APIDictWrapper):
+    _attrs = ["id", "tenant", "user", "resource", "network_incoming_bytes",
+              "network_incoming_packets", "network_outgoing_bytes",
+              "network_outgoing_packets"]
+
+
+class GlobalNetworkUsage(APIDictWrapper):
+    _attrs = ["id", "tenant", "user", "resource", "network", "network_create",
+              "subnet", "subnet_create", "port", "port_create", "router",
+              "router_create", "ip_floating", "ip_floating_create"]
+
+
+class Statistic(APIResourceWrapper):
+    _attrs = ['period', 'period_start', 'period_end',
+              'count', 'min', 'max', 'sum', 'avg',
+              'duration', 'duration_start', 'duration_end']
+
+
+def ceilometerclient(request):
+    endpoint = url_for(request, 'metering')
+    insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
+    LOG.debug('ceilometerclient connection created using token "%s" '
+              'and endpoint "%s"' % (request.user.token.id, endpoint))
+    return ceilometer_client.Client('2', endpoint, token=request.user.token.id,
+                                    insecure=insecure)
+
+
+def sample_list(request, meter_name, query=[]):
+    """List the samples for this meters."""
+    samples = ceilometerclient(request).samples.list(meter_name=meter_name,
+                                                     q=query)
+    return [Sample(s) for s in samples]
+
+
+def meter_list(request, query=[]):
+    """List the user's meters."""
+    meters = ceilometerclient(request).meters.list(query)
+    return [Meter(m) for m in meters]
+
+
+def resource_list(request, query=[]):
+    """List the resources."""
+    resources = ceilometerclient(request).\
+        resources.list(q=query)
+    return [Resource(r) for r in resources]
+
+
+def statistic_list(request, meter_name, query=[]):
+    statistics = ceilometerclient(request).\
+        statistics.list(meter_name=meter_name, q=query)
+    return [Statistic(s) for s in statistics]
+
+
+def global_object_store_usage(request):
+    result_list = global_usage_preload(request,
+                                       ["storage.objects",
+                                        "storage.objects.size",
+                                        "storage.objects.incoming.bytes",
+                                        "storage.objects.outgoing.bytes"])
+    return [GlobalObjectStoreUsage(u) for u in result_list]
+
+
+def global_object_store_usage_get(request, usage_id):
+    meter_names = ["storage.objects",
+                   "storage.objects.size",
+                   "storage.objects.incoming.bytes",
+                   "storage.objects.outgoing.bytes"]
+    usage = global_usage_get(request, meter_names, usage_id)
+    return GlobalObjectStoreUsage(usage)
+
+
+def global_disk_usage(request):
+    result_list = global_usage_preload(request, ["disk.read.bytes",
+                                                 "disk.read.requests",
+                                                 "disk.write.bytes",
+                                                 "disk.write.requests"])
+    return [GlobalDiskUsage(u) for u in result_list]
+
+
+def global_disk_usage_get(request, usage_id):
+    meter_names = ["disk.read.bytes",
+                   "disk.read.requests",
+                   "disk.write.bytes",
+                   "disk.write.requests"]
+    usage = global_usage_get(request, meter_names, usage_id)
+    return GlobalDiskUsage(usage)
+
+
+def global_network_traffic_usage(request):
+    result_list = global_usage_preload(request, ["network.incoming.bytes",
+                                                 "network.incoming.packets",
+                                                 "network.outgoing.bytes",
+                                                 "network.outgoing.packets"])
+    return [GlobalNetworkTrafficUsage(u) for u in result_list]
+
+
+def global_network_traffic_usage_get(request, usage_id):
+    meter_names = ["network.incoming.bytes",
+                   "network.incoming.packets",
+                   "network.outgoing.bytes",
+                   "network.outgoing.packets"]
+    usage = global_usage_get(request, meter_names, usage_id)
+    return GlobalNetworkTrafficUsage(usage)
+
+
+def global_network_usage(request):
+    result_list = global_usage_preload(request,
+                                       ["network", "network_create",
+                                        "subnet", "subnet_create",
+                                        "port", "port_create",
+                                        "router", "router_create",
+                                        "ip_floating", "ip_floating_create"])
+    return [GlobalNetworkUsage(u) for u in result_list]
+
+
+def global_network_usage_get(request, usage_id):
+    meter_names = ["network", "network_create",
+                   "subnet", "subnet_create",
+                   "port", "port_create",
+                   "router", "router_create",
+                   "ip_floating", "ip_floating_create"]
+    usage = global_usage_get(request, meter_names, usage_id)
+    return GlobalNetworkUsage(usage)
+
+
+def global_usage_get(request, meter_names, usage_id):
+    try:
+        tenant_id, user_id, resource_id = usage_id.split("__")
+    except ValueError:
+        return []
+
+    query = []
+    if user_id and user_id != 'None':
+        query.append({"field": "user", "op": "eq", "value": user_id})
+    if tenant_id and tenant_id != 'None':
+        query.append({"field": "project", "op": "eq", "value": tenant_id})
+    if resource_id and resource_id != 'None':
+        query.append({"field": "resource", "op": "eq", "value": resource_id})
+
+    usage_list = []
+    usage = dict(id=usage_id,
+                 tenant=CachedResources.get_tenant_name(request,
+                                                        tenant_id),
+                 user=CachedResources.get_user_name(request,
+                                                    user_id),
+                 resource=resource_id)
+    for meter in meter_names:
+        statistics = statistic_list(request, meter,
+                                    query=query)
+        meter = meter.replace(".", "_")
+        if statistics:
+            usage.setdefault(meter, statistics[0].max)
+        else:
+            usage.setdefault(meter, 0)
+
+        usage_list.append(usage)
+
+    usage_list = itertools.groupby(
+        usage_list,
+        lambda x: x["id"],
+    )
+    usage_list = map(lambda x: list(x[1]), usage_list)
+    usage_list = reduce(lambda x, y: x.update(y) or x, usage_list)
+    return usage_list[0]
+
+
+def global_usage_preload(request, fields):
+    """
+    Get "user", "tenant", "resource" for a horizon table datum
+    without actually data.
+    The data will be loaded asynchronously via ``global_usage_get``.
+    """
+    meters = CachedResources.get_meter_list(request)
+
+    filtered = filter(lambda m: m.name in fields, meters)
+
+    usage_list = []
+    for m in filtered:
+        usage_list.append({
+            "id": "%s__%s__%s" % (m.project_id, m.user_id, m.resource_id),
+            "tenant": CachedResources.get_tenant_name(request,
+                                                      m.project_id),
+            "user": CachedResources.get_user_name(request,
+                                                  m.user_id),
+            "resource": m.resource_id
+        })
+    # To remove redundent usage.
+    tupled_usage_list = [tuple(d.items()) for d in usage_list]
+    unique_usage_list = [dict(t) for t in set(tupled_usage_list)]
+    return unique_usage_list
+
+
+class CachedResources(object):
+    """
+    Cached users, tenants and meters.
+    """
+    _users = None
+    _tenants = None
+    _meters = None
+
+    @classmethod
+    def get_meter_list(cls, request, query=None):
+        if not cls._meters:
+            cls._meters = meter_list(request, query)
+        return cls._meters
+
+    @classmethod
+    def get_user_name(cls, request, user_id):
+        if not cls._users:
+            cls._users = keystone.user_list(request)
+        for u in cls._users:
+            if u.id == user_id:
+                return u.name
+        return None
+
+    @classmethod
+    def get_tenant_name(cls, request, tenant_id):
+        if not cls._tenants:
+            cls._tenants, more = keystone.tenant_list(request, admin=True)
+        for t in cls._tenants:
+            if t.id == tenant_id:
+                return t.name
+        return None
--- openstack_dashboard/api/__init__.py
+++ openstack_dashboard/api/__init__.py
@@ -34,6 +34,7 @@
 Keystone/Nova/Glance/Swift et. al.
 """
 from openstack_dashboard.api import base
+from openstack_dashboard.api import ceilometer
 from openstack_dashboard.api import cinder
 from openstack_dashboard.api import glance
 from openstack_dashboard.api import keystone
--- openstack_dashboard/dashboards/admin/dashboard.py
+++ openstack_dashboard/dashboards/admin/dashboard.py
@@ -22,7 +22,7 @@
 class SystemPanels(horizon.PanelGroup):
     slug = "admin"
     name = _("System Panel")
-    panels = ('overview', 'instances', 'volumes', 'flavors',
+    panels = ('overview', 'metering', 'instances', 'volumes', 'flavors',
               'images', 'projects', 'users', 'networks', 'routers', 'info')
 
 
--- openstack_dashboard/dashboards/admin/metering/__init__.py
+++ openstack_dashboard/dashboards/admin/metering/__init__.py
@@ -0,0 +1 @@
+# empty file
--- openstack_dashboard/dashboards/admin/metering/panel.py
+++ openstack_dashboard/dashboards/admin/metering/panel.py
@@ -0,0 +1,29 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.utils.translation import ugettext_lazy as _
+
+import horizon
+from openstack_dashboard.dashboards.admin import dashboard
+
+
+class Metering(horizon.Panel):
+    name = _("Resource Usage")
+    slug = 'metering'
+    permissions = ('openstack.services.metering', 'openstack.roles.admin', )
+
+
+dashboard.Admin.register(Metering)
--- openstack_dashboard/dashboards/admin/metering/tables.py
+++ openstack_dashboard/dashboards/admin/metering/tables.py
@@ -0,0 +1,257 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# 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 logging
+
+from django.template.defaultfilters import filesizeformat
+from django.utils.translation import ugettext_lazy as _
+
+from horizon import tables
+from openstack_dashboard import api
+
+
+LOG = logging.getLogger(__name__)
+
+
+class CommonFilterAction(tables.FilterAction):
+    def filter(self, table, resources, filter_string):
+        q = filter_string.lower()
+        return [resource for resource in resources
+                if q in resource.resource.lower() or
+                   q in resource.tenant.lower() or
+                   q in resource.user.lower()]
+
+
+def get_status(fields):
+    def transform(datum):
+        if any([datum.get(field) is 0 or datum.get(field)
+                for field in fields]):
+            return "up"
+        else:
+            return "none"
+    return transform
+
+
+class GlobalDiskUsageUpdateRow(tables.Row):
+    ajax = True
+
+    def get_data(self, request, object_id):
+        return api.ceilometer.global_disk_usage_get(request, object_id)
+
+
+class GlobalDiskUsageTable(tables.DataTable):
+    tenant = tables.Column("tenant", verbose_name=_("Tenant"), sortable=True)
+    user = tables.Column("user", verbose_name=_("User"), sortable=True)
+    status = tables.Column(get_status(["disk_read_bytes",
+                                       "disk_write_bytes",
+                                       "disk_read_requests",
+                                       "disk_write_requests"
+                                       ]),
+                           verbose_name=_("Status"),
+                           hidden=True)
+    instance = tables.Column("resource",
+                             verbose_name=_("Resource"),
+                             sortable=True)
+    disk_read_bytes = tables.Column("disk_read_bytes",
+                                    filters=(filesizeformat,),
+                                    verbose_name=_("Disk Read Bytes"),
+                                    sortable=True)
+    disk_read_requests = tables.Column("disk_read_requests",
+                                       verbose_name=_("Disk Read Requests"),
+                                       sortable=True)
+    disk_write_bytes = tables.Column("disk_write_bytes",
+                                     verbose_name=_("Disk Write Bytes"),
+                                     filters=(filesizeformat,),
+                                     sortable=True)
+    disk_write_requests = tables.Column("disk_write_requests",
+                                        verbose_name=_("Disk Write Requests"),
+                                        sortable=True)
+
+    class Meta:
+        name = "global_disk_usage"
+        verbose_name = _("Global Disk Usage")
+        status_columns = ["status"]
+        table_actions = (CommonFilterAction,)
+        row_class = GlobalDiskUsageUpdateRow
+        multi_select = False
+
+
+class GlobalNetworkTrafficUsageUpdateRow(tables.Row):
+    ajax = True
+
+    def get_data(self, request, object_id):
+        return api.ceilometer\
+                .global_network_traffic_usage_get(request,
+                                                  object_id)
+
+
+class GlobalNetworkTrafficUsageTable(tables.DataTable):
+    tenant = tables.Column("tenant", verbose_name=_("Tenant"))
+    user = tables.Column("user", verbose_name=_("User"), sortable=True)
+    status = tables.Column(get_status(["network_incoming_bytes",
+                                       "network_incoming_packets",
+                                       "network_outgoing_bytes",
+                                       "network_outgoing_packets"]),
+                           verbose_name=_("Status"),
+                           hidden=True)
+    instance = tables.Column("resource",
+                             verbose_name=_("Resource"),
+                             sortable=True)
+    network_incoming_bytes = tables\
+            .Column("network_incoming_bytes",
+                    verbose_name=_("Network incoming Bytes"),
+                    filters=(filesizeformat,),
+                    sortable=True)
+    network_incoming_packets = tables\
+            .Column("network_incoming_packets",
+                    verbose_name=_("Network incoming Packets"),
+                    sortable=True)
+    network_outgoing_bytes = tables\
+            .Column("network_outgoing_bytes",
+                    verbose_name=_("Network Outgoing Bytes"),
+                    filters=(filesizeformat,),
+                    sortable=True)
+    network_outgoing_packets = tables\
+            .Column("network_outgoing_packets",
+                    verbose_name=_("Network Outgoing Packets"),
+                    sortable=True)
+
+    class Meta:
+        name = "global_network_traffic_usage"
+        verbose_name = _("Global Network Traffic Usage")
+        table_actions = (CommonFilterAction,)
+        row_class = GlobalNetworkTrafficUsageUpdateRow
+        status_columns = ["status"]
+        multi_select = False
+
+
+class GlobalNetworkUsageUpdateRow(tables.Row):
+    ajax = True
+
+    def get_data(self, request, object_id):
+        return api.ceilometer.global_network_usage_get(request,
+                                                       object_id)
+
+
+class GlobalNetworkUsageTable(tables.DataTable):
+    tenant = tables.Column("tenant", verbose_name=_("Tenant"))
+    user = tables.Column("user", verbose_name=_("User"), sortable=True)
+    status = tables.Column(get_status(["network", "network_create",
+                                       "subnet", "subnet_create",
+                                       "port", "port_create",
+                                       "router", "router_create",
+                                       "ip_floating", "ip_floating_create"]),
+                           verbose_name=_("Status"),
+                           hidden=True)
+    instance = tables.Column("resource",
+                             verbose_name=_("Resource"),
+                             sortable=True)
+    network_duration = tables.Column("network",
+                                     verbose_name=_("Network Duration"),
+                                     sortable=True)
+    network_creation_requests = tables\
+            .Column("network_create",
+                    verbose_name=_("Network Creation Requests"),
+                    sortable=True)
+    subnet_duration = tables.Column("subnet",
+                                    verbose_name=_("Subnet Duration"),
+                                    sortable=True)
+    subnet_creation = tables.Column("subnet_create",
+                                    verbose_name=_("Subnet Creation Requests"),
+                                    sortable=True)
+    port_duration = tables.Column("port",
+                                  verbose_name=_("Port Duration"),
+                                  sortable=True)
+    port_creation = tables.Column("port_create",
+                                  verbose_name=_("Port Creation Requests"),
+                                  sortable=True)
+    router_duration = tables.Column("router",
+                                    verbose_name=_("Router Duration"),
+                                    sortable=True)
+    router_creation = tables.Column("router_create",
+                                    verbose_name=_("Router Creation Requests"),
+                                    sortable=True)
+    port_duration = tables.Column("port",
+                                  verbose_name=_("Port Duration"),
+                                  sortable=True)
+    port_creation = tables.Column("port_create",
+                                  verbose_name=_("Port Creation Requests"),
+                                  sortable=True)
+    ip_floating_duration = tables\
+            .Column("ip_floating",
+                    verbose_name=_("Floating IP Duration"),
+                    sortable=True)
+    ip_floating_creation = tables\
+            .Column("ip_floating_create",
+                    verbose_name=_("Floating IP Creation Requests"),
+                    sortable=True)
+
+    class Meta:
+        name = "global_network_usage"
+        verbose_name = _("Global Network Usage")
+        table_actions = (CommonFilterAction,)
+        row_class = GlobalNetworkUsageUpdateRow
+        status_columns = ["status"]
+        multi_select = False
+
+
+class GlobalObjectStoreUsageUpdateRow(tables.Row):
+    ajax = True
+
+    def get_data(self, request, object_id):
+        return api.ceilometer.global_object_store_usage_get(request,
+                                                            object_id)
+
+
+class GlobalObjectStoreUsageTable(tables.DataTable):
+    tenant = tables.Column("tenant", verbose_name=_("Tenant"))
+    user = tables.Column("user", verbose_name=_("User"), sortable=True)
+    status = tables.Column(get_status(["storage_objects",
+                                       "storage_objects_size",
+                                       "storage_objects_incoming_bytes",
+                                       "storage_objects_outgoing_bytes"]),
+                           verbose_name=_("Status"),
+                           hidden=True)
+    resource = tables.Column("resource",
+                             verbose_name=_("Resource"),
+                             sortable=True)
+    storage_incoming_bytes = tables.Column(
+        "storage_objects_incoming_bytes",
+        verbose_name=_("Object Storage Incoming Bytes"),
+        filters=(filesizeformat,),
+        sortable=True)
+    storage_outgoing_bytes = tables.Column(
+        "storage_objects_outgoing_bytes",
+        verbose_name=_("Object Storage Outgoing Bytes"),
+        filters=(filesizeformat,),
+        sortable=True)
+    storage_objects = tables.Column(
+        "storage_objects",
+        verbose_name=_("Total Number of Objects"),
+        sortable=True)
+    storage_objects_size = tables.Column(
+        "storage_objects_size",
+        filters=(filesizeformat,),
+        verbose_name=_("Total Size of Objects "),
+        sortable=True)
+
+    class Meta:
+        name = "global_object_store_usage"
+        verbose_name = _("Global Object Store Usage")
+        table_actions = (CommonFilterAction,)
+        row_class = GlobalObjectStoreUsageUpdateRow
+        status_columns = ["status"]
+        multi_select = False
--- openstack_dashboard/dashboards/admin/metering/tabs.py
+++ openstack_dashboard/dashboards/admin/metering/tabs.py
@@ -0,0 +1,100 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+from django.utils.translation import ugettext_lazy as _
+
+from horizon import tabs
+from openstack_dashboard.api import ceilometer
+
+from .tables import GlobalDiskUsageTable
+from .tables import GlobalNetworkTrafficUsageTable
+from .tables import GlobalNetworkUsageTable
+from .tables import GlobalObjectStoreUsageTable
+
+
+class GlobalDiskUsageTab(tabs.TableTab):
+    table_classes = (GlobalDiskUsageTable,)
+    name = _("Global Disk Usage")
+    slug = "global_disk_usage"
+    template_name = ("horizon/common/_detail_table.html")
+
+    def get_global_disk_usage_data(self):
+        request = self.tab_group.request
+        result = ceilometer.global_disk_usage(request)
+        return result
+
+
+class GlobalNetworkTrafficUsageTab(tabs.TableTab):
+    table_classes = (GlobalNetworkTrafficUsageTable,)
+    name = _("Global Network Traffic Usage")
+    slug = "global_network_traffic_usage"
+    template_name = ("horizon/common/_detail_table.html")
+
+    def get_global_network_traffic_usage_data(self):
+        request = self.tab_group.request
+        result = ceilometer.global_network_traffic_usage(request)
+        return result
+
+
+class GlobalNetworkUsageTab(tabs.TableTab):
+    table_classes = (GlobalNetworkUsageTable,)
+    name = _("Global Network Usage")
+    slug = "global_network_usage"
+    template_name = ("horizon/common/_detail_table.html")
+
+    def get_global_network_usage_data(self):
+        request = self.tab_group.request
+        result = ceilometer.global_network_usage(request)
+        return result
+
+    def allowed(self, request):
+        permissions = ("openstack.services.network",)
+        return request.user.has_perms(permissions)
+
+
+class GlobalObjectStoreUsageTab(tabs.TableTab):
+    table_classes = (GlobalObjectStoreUsageTable,)
+    name = _("Global Object Store Usage")
+    slug = "global_object_store_usage"
+    template_name = ("horizon/common/_detail_table.html")
+
+    def get_global_object_store_usage_data(self):
+        request = self.tab_group.request
+        result = ceilometer.global_object_store_usage(request)
+        return result
+
+    def allowed(self, request):
+        permissions = ("openstack.services.object-store",)
+        return request.user.has_perms(permissions)
+
+
+class GlobalStatsTab(tabs.Tab):
+    name = _("Stats")
+    slug = "stats"
+    template_name = ("admin/metering/stats.html")
+
+    def get_context_data(self, request):
+        meters = ceilometer.CachedResources.get_meter_list(request)
+        # Now only 'cpu_utl' data is useful for line chart.
+        meters = filter(lambda m: m.name == "cpu_util", meters)
+        context = {'meters': meters}
+        return context
+
+
+class CeilometerOverviewTabs(tabs.TabGroup):
+    slug = "ceilometer_overview"
+    tabs = (GlobalDiskUsageTab, GlobalNetworkTrafficUsageTab,
+            GlobalNetworkUsageTab, GlobalObjectStoreUsageTab, GlobalStatsTab,)
+    sticky = True
--- openstack_dashboard/dashboards/admin/metering/templates/metering/index.html
+++ openstack_dashboard/dashboards/admin/metering/templates/metering/index.html
@@ -0,0 +1,15 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Resources usage Overview" %}{% endblock %}
+
+{% block page_header %}
+  {% include "horizon/common/_page_header.html" with title=_("Resources usage Overview")%}
+{% endblock page_header %}
+
+{% block main %}
+<div class="row-fluid">
+  <div class="span12">
+    {{ tab_group.render }}
+  </div>
+</div>
+{% endblock %}
--- openstack_dashboard/dashboards/admin/metering/templates/metering/samples.csv
+++ openstack_dashboard/dashboards/admin/metering/templates/metering/samples.csv
@@ -0,0 +1,5 @@
+date,value
+{% spaceless %}
+{% for sample in samples %}{{ sample.0 }},{{ sample.1 }}
+{% endfor %}
+{% endspaceless %}
--- openstack_dashboard/dashboards/admin/metering/templates/metering/stats.html
+++ openstack_dashboard/dashboards/admin/metering/templates/metering/stats.html
@@ -0,0 +1,81 @@
+{% load i18n %}
+{% load url from future %}
+{% spaceless %}
+  <script type="text/javascript">
+    (function(){
+      var init_ceilometer = function(){
+        var meters = [];
+        {% for m in meters %}
+          meters.push({
+            "name":  "{{ m.name }}",
+            "resource_id": "{{ m.resource_id }}",
+            "user_id": "{{ m.user_id }}",
+            "project_id": "{{ m.project_id }}",
+            "unit": "{{ m.unit }}",
+            "type": "{{ m.type }}"
+          });
+        {% endfor %}
+        horizon.ceilometer.getResources(meters);
+        horizon.ceilometer.updateResourceOptions();
+        horizon.ceilometer.loadChartData();
+      };
+      if (typeof horizon.ceilometer !== "undefined"){
+        init_ceilometer();
+      } else {
+        addHorizonLoadEvent(init_ceilometer);
+      }
+    }());
+  </script>
+{% endspaceless %}
+<div id="samples_url" url="{% url "horizon:admin:metering:samples" %}"></div>
+<div id="ceilometer-stats">
+  <form class="form-horizontal">
+    <div class="control-group">
+      <label for="meter" class="control-label">{% trans "Metric" %}:&nbsp;</label>
+      <div class="controls">
+        <select name="meter" id="meter" class="span2 example">
+          {% for meter in meters %}
+          <option value="{{ meter.name }}" data-unit="{{ meter.unit }}">
+          {{ meter.name }}
+          </option>
+          {% endfor %}
+        </select>
+      </div>
+    </div>
+    <div class="control-group">
+      <label for="resource" class="control-label">{% trans "Resource" %}:&nbsp;</label>
+      <div class="controls">
+        <select name="resource" id="resource" class="span4 example">
+          <option value="">{% trans "Select a meter to load options." %}</option>
+        </select>
+      </div>
+    </div>
+    <div class="control-group">
+      <label for="date_options" class="control-label">{% trans "Period" %}:&nbsp;</label>
+      <div class="controls">
+        <select id="date_options" name="date_options" class="span2">
+          <option value="1" selected>{% trans "Last day" %}</option>
+          <option value="7">{% trans "Last week" %}</option>
+          <option value="15">{% trans "Last 15 days" %}</option>
+          <option value="30">{% trans "Last 30 days" %}</option>
+          <option value="365">{% trans "Last year" %}</option>
+        </select>
+      </div>
+    </div>
+    <div class="control-group">
+      <label for="date_from" class="control-label">{% trans "From" %}:&nbsp;</label>
+      <div class="controls">
+        <input type="text" id="date_from" name="date_from" class="span2 example"/>
+      </div>
+    </div>
+    <div class="control-group">
+      <label for="date_to" class="control-label">{% trans "To" %}:&nbsp;</label>
+      <div class="controls">
+        <input type="text" id="date_to" name="date_to" class="span2 example"/>
+      </div>
+    </div>
+    <button type="button" id="action_display_chart"
+      class="btn btn-small btn-search action_display_chart">{% trans "Refresh" %}</button>
+  </form>
+</div>
+<div id="chart_container"></div>
--- openstack_dashboard/dashboards/admin/metering/tests.py
+++ openstack_dashboard/dashboards/admin/metering/tests.py
@@ -0,0 +1,108 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.core.urlresolvers import reverse
+from django import http
+from mox import IsA
+
+from openstack_dashboard import api
+from openstack_dashboard.test import helpers as test
+
+from .views import reduce_metrics
+from .views import to_days
+from .views import to_hours
+
+
+INDEX_URL = reverse("horizon:admin:metering:index")
+
+
+class MeteringViewTests(test.BaseAdminViewTests):
+    @test.create_stubs({api.ceilometer: ("global_disk_usage",
+                                         "global_network_traffic_usage",
+                                         "global_network_usage",
+                                         "global_object_store_usage"),
+                        api.ceilometer.CachedResources: ("get_meter_list",)})
+    def test_index_view(self):
+        global_disk_usages = self.global_disk_usages.list()
+        global_network_usages = self.global_network_usages.list()
+        global_network_traffic_usages = self.global_network_traffic_usages\
+                                            .list()
+        global_object_store_usages = self.global_object_store_usages.list()
+        meters = self.meters.list()
+
+        api.ceilometer.global_disk_usage(IsA(http.HttpRequest))\
+            .AndReturn(global_disk_usages)
+        api.ceilometer.global_network_usage(IsA(http.HttpRequest))\
+            .AndReturn(global_network_usages)
+        api.ceilometer.global_network_traffic_usage(IsA(http.HttpRequest))\
+            .AndReturn(global_network_traffic_usages)
+        api.ceilometer.global_object_store_usage(IsA(http.HttpRequest))\
+            .AndReturn(global_object_store_usages)
+        api.ceilometer.CachedResources.get_meter_list(IsA(http.HttpRequest))\
+                .AndReturn(meters)
+        self.mox.ReplayAll()
+
+        res = self.client.get(INDEX_URL)
+        self.assertTemplateUsed(res, "admin/metering/index.html")
+
+    @test.create_stubs({api.ceilometer: ("sample_list",)})
+    def test_sample_views(self):
+        samples = self.samples.list()
+        api.ceilometer.sample_list(IsA(http.HttpRequest), IsA(basestring),
+                                   IsA(list)).AndReturn(samples)
+        self.mox.ReplayAll()
+
+        res = self.client.get(reverse("horizon:admin:metering:samples") +
+                        '?meter=meter&from=2012-10-10&'
+                        'to=2012-10-11&resource=resouce')
+        self.assertTemplateUsed(res, "admin/metering/samples.csv")
+
+    def test_sample_views_without_request_args(self):
+        res = self.client.get(reverse("horizon:admin:metering:samples"))
+        self.assertEqual(res.status_code, 404)
+
+    def test_sample_views_wrong_dates(self):
+        res = self.client.get(reverse("horizon:admin:metering:samples"),
+                              dict(meter="meter",
+                                   date_from="cannot be parsed",
+                                   date_to="cannot be parsed",
+                                   resource="resource")
+                              )
+        self.assertEqual(res.status_code, 404)
+
+    def test_to_days_to_hours(self):
+        test_data = (["2001-01-01T01:01:01", 123],
+                     ["1999-12-12T00:00:00", 321],
+                     ["9999-12-12T12:59:59", 0])
+        for test in test_data:
+            date, value = to_days(test)
+            self.assertEqual(date, test[0][:11] + "00:00:00")
+            self.assertEqual(value, test[1])
+        for test in test_data:
+            date, value = to_hours(test)
+            self.assertEqual(date, test[0][:14] + "00:00")
+            self.assertEqual(value, test[1])
+
+    def test_reduce_metrics(self):
+        test_data = [["2001-01-01T00:00:00", 123],
+                     ["2001-01-01T00:00:00", 321],
+                     ["2001-01-01T00:00:00", 0],
+                     ["2001-01-02T00:00:00", 2],
+                     ["2001-01-02T00:00:00", 2],
+                     ["2001-01-02T00:00:00", 2]]
+        result = reduce_metrics(test_data)
+        self.assertEqual(result, [["2001-01-01T00:00:00", 444 / 3],
+                                  ["2001-01-02T00:00:00", 6 / 3]])
--- openstack_dashboard/dashboards/admin/metering/urls.py
+++ openstack_dashboard/dashboards/admin/metering/urls.py
@@ -0,0 +1,26 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.conf.urls import patterns
+from django.conf.urls import url
+
+from .views import IndexView
+from .views import SamplesView
+
+
+urlpatterns = patterns('openstack_dashboard.dashboards.admin.metering.views',
+    url(r'^$', IndexView.as_view(), name='index'),
+    url(r'^samples$', SamplesView.as_view(), name='samples'))
--- openstack_dashboard/dashboards/admin/metering/views.py
+++ openstack_dashboard/dashboards/admin/metering/views.py
@@ -0,0 +1,128 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from datetime import datetime
+import itertools
+import logging
+import operator
+
+from django.views.generic import TemplateView
+
+from horizon import exceptions
+from horizon import tabs
+from openstack_dashboard.api import ceilometer
+
+from .tabs import CeilometerOverviewTabs
+
+
+LOG = logging.getLogger(__name__)
+
+
+class IndexView(tabs.TabbedTableView):
+    tab_group_class = CeilometerOverviewTabs
+    template_name = 'admin/metering/index.html'
+
+
+def to_hours(item):
+    """
+    Convert the time of a sample to hour level
+    to strip the minute and second information.
+    It's used to preprocess data before interpolation.
+    """
+    date_obj = datetime.strptime(item[0], '%Y-%m-%dT%H:%M:%S')
+    new_date_str = date_obj.strftime("%Y-%m-%dT%H:00:00")
+    return new_date_str, item[1]
+
+
+def to_days(item):
+    """
+    Convert the time of a sample to day level
+    to strip the hour, minute and second information.
+    It's used to preprocess data before interpolation.
+    """
+    date_obj = datetime.strptime(item[0], '%Y-%m-%dT%H:%M:%S')
+    new_date_str = date_obj.strftime("%Y-%m-%dT00:00:00")
+    return new_date_str, item[1]
+
+
+def reduce_metrics(samples):
+    """
+    Given a set of samples, group them by time and calculate the average
+    value for that time point.
+    """
+    new_samples = []
+    for key, items in itertools.groupby(samples, operator.itemgetter(0)):
+        items = list(items)
+        if len(items) > 0:
+            avg = sum(map(lambda x: x[1], items)) / len(items)
+        else:
+            avg = 0
+        new_samples.append([key, avg])
+    return new_samples
+
+
+class SamplesView(TemplateView):
+    template_name = "admin/metering/samples.csv"
+
+    def get(self, request, *args, **kwargs):
+        meter = request.GET.get('meter', None)
+        date_from = request.GET.get('from', None)
+        date_to = request.GET.get('to', None)
+        resource = request.GET.get('resource', None)
+
+        if not(meter and date_from and date_to and resource):
+            raise exceptions.NotFound
+        else:
+            date_from += "T00:00:00"
+            date_to += "T23:59:59"
+            try:
+                date_from_obj = datetime.strptime(date_from,
+                                        "%Y-%m-%dT%H:%M:%S")
+                date_to_obj = datetime.strptime(date_to, "%Y-%m-%dT%H:%M:%S")
+            except ValueError:
+                raise exceptions.NotFound
+
+            query = [{'field': 'timestamp',
+                      'op': 'ge',
+                      'value': date_from},
+                     {'field': 'timestamp',
+                      'op': 'le',
+                      'value': date_to},
+                     {'field': 'resource',
+                      'op': 'eq',
+                      'value': resource}]
+            sample_list = ceilometer.sample_list(self.request, meter, query)
+            samples = []
+            for sample_data in sample_list:
+                samples.append([sample_data.timestamp[:19],
+                                sample_data.counter_volume])
+
+            # if requested period is too long interpolate data
+            delta = (date_to_obj - date_from_obj).days
+
+            if delta >= 365:
+                samples = map(to_days, samples)
+                samples = reduce_metrics(samples)
+            elif delta >= 30:
+                # reduce metrics to hours
+                samples = map(to_hours, samples)
+                samples = reduce_metrics(samples)
+
+            context = dict(samples=samples)
+            response_kwargs = dict(mimetype='text/csv')
+            return self.render_to_response(
+                    context=context,
+                    **response_kwargs)
--- static/horizon/js/horizon.ceilometer.js
+++ static/horizon/js/horizon.ceilometer.js
@@ -0,0 +1,221 @@
+horizon.ceilometer = {
+  initVariables: function () {
+    var self = this;
+    self.margin = {
+      top: 40,
+      right: 100,
+      bottom: 80,
+      left: 200
+    };
+    self.width = 1100 - self.margin.left - self.margin.right;
+    self.height = 480 - self.margin.top - self.margin.bottom;
+    self.svg = undefined;
+    self.x = d3.time.scale().range([0, self.width]);
+    self.y = d3.scale.linear().range([self.height, 0]);
+    self.xAxis = d3.svg.axis().scale(self.x).orient("bottom");
+    self.yAxis = d3.svg.axis().scale(self.y).orient("left");
+    self.line = d3.svg.line()
+      .x(function (d) {
+        return self.x(d.date);
+      })
+      .y(function (d) {
+        return self.y(d.value);
+      });
+
+    self.$meter = $("#meter");
+    self.$resource = $("#resource");
+    self.$date_from = $("#date_from");
+    self.$date_to = $("#date_to");
+    self.$get_samples = $("#samples_url");
+    self.$chart_container = $("#chart_container");
+    self.parseDate = d3.time.format("%Y-%m-%dT%H:%M:%S").parse;
+  },
+  getResources: function (meters) {
+    // Get ``meters`` variable from django template.
+    var self = this;
+    if (meters) {
+      self.meters = meters;
+    }
+  },
+
+  updateResourceOptions: function () {
+    // Update resource select field on stats.html template.
+    var self = this,
+      meter_name = self.$meter.val(),
+      i,
+      option;
+    self.$resource.empty();
+    if (self.meters) {
+      for (i = 0; i < self.meters.length; i = i + 1) {
+        if (meter_name === self.meters[i].name) {
+          option = '<option value="' + self.meters[i].resource_id + '">';
+          option += self.meters[i].resource_id + '</option>';
+          self.$resource.append(option);
+        }
+      }
+    }
+  },
+
+  loadChartData: function () {
+    var self = this,
+      meter = self.$meter.val(),
+      resource = self.$resource.val(),
+      from = self.$date_from.val(),
+      to = self.$date_to.val(),
+      horizon_samples_url = self.$get_samples.attr("url");
+    d3.select("svg").remove();
+
+    self.svg = d3.select("#chart_container").append("svg")
+      .attr("width", self.width + self.margin.left + self.margin.right)
+      .attr("height", self.height + self.margin.top + self.margin.bottom)
+      .append("g")
+      .attr("transform", "translate(" + self.margin.left + "," + self.margin.top + ")");
+
+    if (meter && resource) {
+      d3.csv(horizon_samples_url + "?meter=" + meter + "&resource=" + resource + "&from=" + from + "&to=" + to,
+        function (error, data) {
+          var chart_title = self.$meter.val() + " ";
+          chart_title += gettext("for resource") + " " + self.$resource.val();
+          chart_title += " (" + gettext("From") + " " + self.$date_to.val() + " " + gettext("to") + " ";
+          chart_title += self.$date_to.val() + ")";
+
+          // read selected option
+          var option = self.$meter.find("option").filter(":selected");
+          var meter_text = gettext("Value");
+          if (option) {
+            var unit = option[0].getAttribute("data-unit");
+            meter_text = meter_text + " (" + unit + ")";
+          }
+
+          data.forEach(function (d) {
+            d.date = self.parseDate(d.date);
+            d.value = +d.value;
+          });
+
+          self.x.domain(d3.extent(data, function (d) {
+            return d.date;
+          }));
+
+          var min_value = d3.min(data, function(d){return d.value});
+          var max_value = d3.max(data, function(d){return d.value});
+          if (min_value === max_value) {
+            self.y.domain([0, min_value * 2]);
+          } else {
+            self.y.domain(d3.extent(data, function (d) {
+              return d.value;
+            }));
+
+          }
+
+          self.svg.append("g")
+            .attr("class", "x axis")
+            .attr("transform", "translate(0," + self.height + ")")
+            .call(self.xAxis);
+          self.svg.append("g")
+            .attr("class", "y axis")
+            .call(self.yAxis)
+            .append("text")
+            .attr("transform", "rotate(-90)")
+            .attr("y", 6)
+            .attr("dy", ".71em")
+            .style("text-anchor", "end")
+            .text(meter_text);
+
+          self.svg.append("path")
+            .datum(data)
+            .attr("class", "line")
+            .style("stroke", "steelblue")
+            .style("stroke-width", 1.5)
+            .style("fill", "none")
+            .attr("d", self.line);
+
+          self.svg.append("text")
+            .attr("x", (self.width / 2))
+            .attr("y", -(self.margin.top / 2))
+            .attr("text-anchor", "middle")
+            .style("font-size", "14px")
+            .style("text-decoration", "none")
+            .text(chart_title);
+
+          d3.selectAll("path.domain")
+            .style("fill", "none")
+            .style("stroke", "black")
+            .style("stroke-width", 1);
+
+          d3.selectAll(".axis path")
+            .style("fill", "none")
+            .style("stroke", "black")
+            .style("shape-rendering", "crispEdges");
+
+          d3.selectAll(".axis")
+            .style("font-size", "10px");
+
+          d3.selectAll(".axis line")
+            .style("fill", "none")
+            .style("stroke", "black")
+            .style("shape-rendering", "crispEdges");
+        });
+    }
+  },
+
+  refreshPickers: function (date_interval) {
+    var now;
+    var targetDate;
+    var self = this;
+    now = new Date();
+    self.$date_to.datepicker('setValue', now);
+    targetDate = new Date();
+    targetDate.setDate(now.getDate() - date_interval);
+    self.$date_from.datepicker('setValue', targetDate);
+  },
+
+  init: function () {
+    var self = this,
+      to,
+      from;
+    self.initVariables();
+    from = self.$date_from.datepicker({format: "yyyy-mm-dd"})
+      .on('changeDate', function (ev) {
+        if (ev.date.valueOf() > to.date.valueOf()) {
+          var newDate = new Date(ev.date);
+          newDate.setDate(newDate.getDate() + 1);
+          to.setValue(newDate);
+        }
+        from.hide();
+        self.$date_to[0].focus();
+      }).data('datepicker');
+    to = self.$date_to.datepicker({
+      format: "yyyy-mm-dd",
+      onRender: function (date) { return date.valueOf() <= from.date.valueOf() ? 'disabled' : ''; }
+    }).on('changeDate', function () {
+      to.hide();
+      self.loadChartData();
+    }).data('datepicker');
+
+    $(".action_display_chart").click(function () {
+      self.loadChartData();
+    });
+
+    self.$meter.change(function () {
+      self.updateResourceOptions();
+      self.loadChartData();
+    });
+    self.$resource.change(function () {
+      self.loadChartData();
+    });
+
+    $("#date_options").change(function () {
+      var current = $(this).val();
+      if (current) {
+        self.refreshPickers(current);
+        self.loadChartData();
+      }
+    });
+    self.refreshPickers(1);
+    self.loadChartData();
+  }
+};
+
+horizon.addInitFunction(function () {
+  horizon.ceilometer.init();
+});
openSUSE Build Service is sponsored by