Files
xen-orchestra/app/modules/dashboard/health/index.js
T

322 lines
12 KiB
JavaScript

import angular from 'angular'
import Bluebird from 'bluebird'
import uiRouter from 'angular-ui-router'
import filter from 'lodash.filter'
import find from 'lodash.find'
import forEach from 'lodash.foreach'
import sortBy from 'lodash.sortby'
import xoApi from 'xo-api'
import xoServices from 'xo-services'
import view from './view'
export default angular.module('dashboard.health', [
uiRouter,
xoApi,
xoServices
])
.config(function ($stateProvider) {
$stateProvider.state('dashboard.health', {
controller: 'Health as bigController',
data: {
requireAdmin: true
},
url: '/health',
template: view
})
})
.filter('type', () => {
return function (objects, type) {
if (!type) {
return objects
}
return filter(objects, object => object.type === type)
}
})
.controller('Health', function () {})
.controller('HealthHeatmap', function (xoApi, xo, xoAggregate, notify, bytesToSizeFilter) {
this.charts = {
heatmap: null
}
this.objects = xoApi.all
this.prepareTypeFilter = function (selection) {
const object = selection[0]
this.typeFilter = object && object.type || undefined
}
this.selectAll = function (type) {
this.selected = filter(this.objects, object => object.type === type)
this.typeFilter = type
}
this.prepareMetrics = function (objects) {
this.chosen = objects && objects.slice()
this.metrics = undefined
this.selectedMetric = undefined
if (this.chosen && this.chosen.length) {
this.loadingMetrics = true
const statPromises = []
forEach(this.chosen, object => {
const apiType = (object.type === 'host' && 'host') || (object.type === 'VM' && 'vm') || undefined
if (!apiType) {
notify.error({
title: 'Unhandled object ' + (objects.name_label || ''),
message: 'There is no stats available for this type of objects'
})
object._ignored = true
} else {
delete object._ignored
statPromises.push(
xo[apiType].refreshStats(object.id, 2) // 2: week granularity (7 * 24 hours)
.then(result => ({object, result}))
.catch(error => {
error.object = object
object._ignored = true
throw error
})
)
}
})
Bluebird.settle(statPromises)
.then(stats => {
const averageMetrics = {}
let averageObjectLayers = {}
let averageCPULayers = 0
forEach(stats, statePromiseInspection => { // One object...
if (statePromiseInspection.isRejected()) {
notify.warning({
title: 'Error fetching stats',
message: 'Metrics do not include ' + statePromiseInspection.reason().object.name_label
})
} else if (statePromiseInspection.isFulfilled()) {
const {object, result} = statePromiseInspection.value()
const averageCPU = averageMetrics['All CPUs'] && averageMetrics['All CPUs'].values || []
forEach(result.cpus, (values, metricKey) => { // Every CPU metric of this object
metricKey = 'CPU ' + metricKey
averageObjectLayers[metricKey] !== undefined || (averageObjectLayers[metricKey] = 0)
averageObjectLayers[metricKey]++
averageCPULayers++
const mapValues = averageMetrics[metricKey] && averageMetrics[metricKey].values || [] // already fed or not
forEach(values, (value, key) => {
if (mapValues[key] === undefined) { // first value
mapValues.push({
value: +value,
date: +result.date[key] * 1000
})
} else { // average with previous
mapValues[key].value = ((mapValues[key].value || 0) * (averageObjectLayers[metricKey] - 1) + (+value)) / averageObjectLayers[metricKey]
}
if (averageCPU[key] === undefined) { // first overall value
averageCPU.push({
value: +value,
date: +result.date[key] * 1000
})
} else { // average with previous overall value
averageCPU[key].value = (averageCPU[key].value * (averageCPULayers - 1) + value) / averageCPULayers
}
})
averageMetrics[metricKey] = {
key: metricKey,
values: mapValues
}
})
averageMetrics['All CPUs'] = {
key: 'All CPUs',
values: averageCPU
}
forEach(result.vifs, (values, metricKey) => {
metricKey = 'Network ' + Math.floor(metricKey / 2) + ' ' + (metricKey % 2 ? 'out' : 'in')
averageObjectLayers[metricKey] !== undefined || (averageObjectLayers[metricKey] = 0)
averageObjectLayers[metricKey]++
const mapValues = averageMetrics[metricKey] && averageMetrics[metricKey].values || [] // already fed or not
forEach(values, (value, key) => {
if (mapValues[key] === undefined) { // first value
mapValues.push({
value: +value,
date: +result.date[key] * 1000
})
} else { // average with previous
mapValues[key].value = ((mapValues[key].value || 0) * (averageObjectLayers[metricKey] - 1) + (+value)) / averageObjectLayers[metricKey]
}
})
averageMetrics[metricKey] = {
key: metricKey,
values: mapValues,
filter: bytesToSizeFilter
}
})
forEach(result.pifs, (values, metricKey) => {
metricKey = 'NIC ' + Math.floor(metricKey / 2) + ' ' + (metricKey % 2 ? 'out' : 'in')
averageObjectLayers[metricKey] !== undefined || (averageObjectLayers[metricKey] = 0)
averageObjectLayers[metricKey]++
const mapValues = averageMetrics[metricKey] && averageMetrics[metricKey].values || [] // already fed or not
forEach(values, (value, key) => {
if (mapValues[key] === undefined) { // first value
mapValues.push({
value: +value,
date: +result.date[key] * 1000
})
} else { // average with previous
mapValues[key].value = ((mapValues[key].value || 0) * (averageObjectLayers[metricKey] - 1) + (+value)) / averageObjectLayers[metricKey]
}
})
averageMetrics[metricKey] = {
key: metricKey,
values: mapValues,
filter: bytesToSizeFilter
}
})
forEach(result.xvds, (values, metricKey) => {
metricKey = 'Disk ' + String.fromCharCode(Math.floor(metricKey / 2) + 65) + ' ' + (metricKey % 2 ? 'write' : 'read')
averageObjectLayers[metricKey] !== undefined || (averageObjectLayers[metricKey] = 0)
averageObjectLayers[metricKey]++
const mapValues = averageMetrics[metricKey] && averageMetrics[metricKey].values || [] // already fed or not
forEach(values, (value, key) => {
if (mapValues[key] === undefined) { // first value
mapValues.push({
value: +value,
date: +result.date[key] * 1000
})
} else { // average with previous
mapValues[key].value = ((mapValues[key].value || 0) * (averageObjectLayers[metricKey] - 1) + (+value)) / averageObjectLayers[metricKey]
}
})
averageMetrics[metricKey] = {
key: metricKey,
values: mapValues,
filter: bytesToSizeFilter
}
})
if (result.load) {
const metricKey = 'Load average'
averageObjectLayers[metricKey] !== undefined || (averageObjectLayers[metricKey] = 0)
averageObjectLayers[metricKey]++
const mapValues = averageMetrics[metricKey] && averageMetrics[metricKey].values || [] // already fed or not
forEach(result.load, (value, key) => {
if (mapValues[key] === undefined) { // first value
mapValues.push({
value: +value,
date: +result.date[key] * 1000
})
} else { // average with previous
mapValues[key].value = ((mapValues[key].value || 0) * (averageObjectLayers[metricKey] - 1) + (+value)) / averageObjectLayers[metricKey]
}
})
averageMetrics[metricKey] = {
key: metricKey,
values: mapValues
}
}
if (result.memoryUsed) {
const metricKey = 'RAM Used'
averageObjectLayers[metricKey] !== undefined || (averageObjectLayers[metricKey] = 0)
averageObjectLayers[metricKey]++
const mapValues = averageMetrics[metricKey] && averageMetrics[metricKey].values || [] // already fed or not
forEach(result.memoryUsed, (value, key) => {
if (mapValues[key] === undefined) { // first value
mapValues.push({
value: +value * (object.type === 'host' ? 1024 : 1),
date: +result.date[key] * 1000
})
} else { // average with previous
mapValues[key].value = ((mapValues[key].value || 0) * (averageObjectLayers[metricKey] - 1) + (+value)) / averageObjectLayers[metricKey]
}
})
averageMetrics[metricKey] = {
key: metricKey,
values: mapValues,
filter: bytesToSizeFilter
}
}
}
})
this.metrics = sortBy(averageMetrics, (_, key) => key)
this.loadingMetrics = false
})
}
}
})
.controller('HealthCubism', function ($scope, xoApi, xoAggregate, xo, $timeout) {
let ctrl, stats
ctrl = this
ctrl.synchronizescale = true
ctrl.objects = xoApi.all
ctrl.chosen = []
this.prepareTypeFilter = function (selection) {
const object = selection[0]
ctrl.typeFilter = object && object.type || undefined
}
this.selectAll = function (type) {
ctrl.selected = filter(ctrl.objects, object => object.type === type)
ctrl.typeFilter = type
}
this.prepareMetrics = function (objects) {
ctrl.chosen = objects
ctrl.selectedMetric = null
ctrl.loadingMetrics = true
xoAggregate
.refreshStats(ctrl.chosen, 2)
.then(function (result) {
stats = result
ctrl.metrics = stats.keys
ctrl.stats = {}
// $timeout(refreshStats, 1000)
ctrl.loadingMetrics = false
})
.catch(function (e) {
console.log(' ERROR ', e)
})
}
this.prepareStat = function () {
let min, max
max = 0
min = 0
ctrl.stats = {}
// compute a global extent => the chart will have the same scale
if (ctrl.synchronizescale) {
forEach(stats.details, function (stat, object_id) {
forEach(stat[ctrl.selectedMetric], function (val) {
if (!isNaN(val.value)) {
max = Math.max(val.value || 0, max)
}
})
})
ctrl.extents = [min, max]
} else {
ctrl.extents = null
}
forEach(stats.details, function (stat, object_id) {
const label = find(ctrl.chosen, {id: object_id})
ctrl.stats[label.name_label] = stat[ctrl.selectedMetric]
})
}
})
.name