Commit cfae5fec by Kálmán Viktor

dashboard: add disk usage breakdown pie chart

parent ab26a51e
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
"bootbox": "~4.3.0", "bootbox": "~4.3.0",
"intro.js": "0.9.0", "intro.js": "0.9.0",
"favico.js": "~0.3.5", "favico.js": "~0.3.5",
"datatables": "~1.10.4" "datatables": "~1.10.4",
"chart.js": "2.3.0"
} }
} }
...@@ -245,6 +245,12 @@ PIPELINE_JS = { ...@@ -245,6 +245,12 @@ PIPELINE_JS = {
), ),
"output_filename": "vm-detail.js", "output_filename": "vm-detail.js",
}, },
"datastore": {"source_filenames": (
"chart.js/dist/Chart.min.js",
"dashboard/datastore-details.js"
),
"output_filename": "datastore.js",
},
} }
......
...@@ -1563,3 +1563,28 @@ textarea[name="new_members"] { ...@@ -1563,3 +1563,28 @@ textarea[name="new_members"] {
margin: 15px 0 2px 0; margin: 15px 0 2px 0;
} }
} }
#datastore-chart-legend {
width: 350px;
margin-top: 100px;
margin-left: -120px;
/* Landscape phones and down */
@media (max-width: 992px) {
margin-left: -25px;
}
ul {
list-style: none;
}
li {
font-size: 18px;
margin-bottom: 2px;
span {
display: inline-block;
width: 30px;
height: 18px;
margin-right: 8px;
}
}
}
$(function() {
var data = JSON.parse($("#chart-data").data("data"));
var labels = [];
for(var i=0; i<data.labels.length; i++) {
labels.push(data.labels[i] + " (" + data.readable_data[i] + ")");
}
var pieChart = new Chart(document.getElementById("datastore-chart"), {
type: 'pie',
data: {
labels: labels,
datasets: [{
data: data['data'],
backgroundColor: [
"#57b257",
"#538ccc",
"#f0df24",
"#ff9a38",
"#7f7f7f",
]
}]
},
options: {
legend: {
display: false,
},
tooltips: {
callbacks: {
label: function(item, chartData) {
return data['labels'][item.index] + ": " + data['readable_data'][item.index];
}
}
},
}
});
$("#datastore-chart-legend").html(pieChart.generateLegend());
});
{% extends "dashboard/base.html" %} {% extends "dashboard/base.html" %}
{% load staticfiles %} {% load pipeline %}
{% load sizefieldtags %}
{% load i18n %} {% load i18n %}
{% load render_table from django_tables2 %} {% load render_table from django_tables2 %}
{% load crispy_forms_tags %} {% load crispy_forms_tags %}
...@@ -105,4 +106,58 @@ ...@@ -105,4 +106,58 @@
</div> </div>
</div> </div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="no-margin">
<i class="fa fa-pie-chart"></i>
{% trans "Disk usage breakdown" %}
</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-9">
<canvas id="datastore-chart"></canvas>
</div>
<div class="col-md-3">
<div id="datastore-chart-legend"></div>
</div>
</div>
<div id="chart-data" data-data='{
"data": [{{stats.template_actual_size}},
{{stats.vm_actual_size}},
{{stats.dumps}},
{{stats.iso_raw}},
{{stats.trash}}],
"readable_data": ["{{stats.template_actual_size|filesize}}",
"{{stats.vm_actual_size|filesize}}",
"{{stats.dumps|filesize}}",
"{{stats.dumps|filesize}}",
"{{stats.iso_raw|filesize}}",
"{{stats.trash|filesize}}"],
"labels": ["{% trans "Templates" %}",
"{% trans "Virtual machines" %}",
"{% trans "Memory dumps" %}",
"{% trans "ISO + Raw images" %}",
"{% trans "Trash" %}"]
}
'>
</div>
<div>
{% trans "Total virtual machine disk usage" %}:
<strong>{{ stats.vm_actual_size|filesize }}</strong>
<br />
{% trans "Actual virtual machine disk usage" %}:
<strong>{{ stats.vm_size|filesize}}</strong>
</div>
</div><!-- .panel-body -->
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
{% javascript "datastore" %}
{% endblock %} {% endblock %}
...@@ -91,6 +91,7 @@ class StorageDetail(SuperuserRequiredMixin, UpdateView): ...@@ -91,6 +91,7 @@ class StorageDetail(SuperuserRequiredMixin, UpdateView):
return qs return qs
def _get_stats(self): def _get_stats(self):
# datastore stats
stats = self.object.get_statistics() stats = self.object.get_statistics()
free_space = int(stats['free_space']) free_space = int(stats['free_space'])
free_percent = float(stats['free_percent']) free_percent = float(stats['free_percent'])
...@@ -98,11 +99,32 @@ class StorageDetail(SuperuserRequiredMixin, UpdateView): ...@@ -98,11 +99,32 @@ class StorageDetail(SuperuserRequiredMixin, UpdateView):
total_space = free_space / (free_percent/100.0) total_space = free_space / (free_percent/100.0)
used_space = total_space - free_space used_space = total_space - free_space
# file stats
data = self.get_object().get_file_statistics()
dumps_size = sum(d['size'] for d in data['dumps'])
trash = sum(d['size'] for d in data['trash'])
iso_raw = sum(d['size'] for d in data['disks']
if d['format'] in ("iso", "raw"))
vm_size = vm_actual_size = template_actual_size = 0
for d in data['disks']:
if d['format'] == "qcow2" and d['type'] == "normal":
template_actual_size += d['actual_size']
else:
vm_size += d['size']
vm_actual_size += d['actual_size']
return { return {
'used_percent': int(100 - free_percent), 'used_percent': int(100 - free_percent),
'free_space': filesizeformat(free_space), 'free_space': filesizeformat(free_space),
'used_space': filesizeformat(used_space), 'used_space': filesizeformat(used_space),
'total_space': filesizeformat(total_space), 'total_space': filesizeformat(total_space),
'dumps': dumps_size,
'trash': trash,
'iso_raw': iso_raw,
'vm_size': vm_size,
'vm_actual_size': vm_actual_size,
'template_actual_size': template_actual_size,
} }
def get_success_url(self): def get_success_url(self):
......
...@@ -110,6 +110,13 @@ class DataStore(Model): ...@@ -110,6 +110,13 @@ class DataStore(Model):
disks = Disk.objects.filter(destroyed__isnull=True, is_ready=True) disks = Disk.objects.filter(destroyed__isnull=True, is_ready=True)
return disks.exclude(filename__in=files) return disks.exclude(filename__in=files)
@method_cache(30)
def get_file_statistics(self, timeout=15):
queue_name = self.get_remote_queue_name('storage', "slow")
data = storage_tasks.get_file_statistics.apply_async(
args=[self.path], queue=queue_name).get(timeout=timeout)
return data
class Disk(TimeStampedModel): class Disk(TimeStampedModel):
......
...@@ -81,3 +81,8 @@ def recover_from_trash(datastore, disk_path): ...@@ -81,3 +81,8 @@ def recover_from_trash(datastore, disk_path):
@celery.task(name='storagedriver.get_storage_stat') @celery.task(name='storagedriver.get_storage_stat')
def get_storage_stat(path): def get_storage_stat(path):
pass pass
@celery.task(name='storagedriver.get_file_statistics')
def get_file_statistics(datastore):
pass
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment