/*
* Copyright 2016 Anyware Services
*
* 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.
*/
/**
* This tool displays monitoring data
*/
Ext.define('Ametys.plugins.admin.jvmstatus.MonitoringTool', {
extend: 'Ametys.tool.Tool',
statics: {
/**
* Downloads the given chart.
*/
download: function()
{
var tool = this._getTool();
if (tool != null)
{
tool.download();
}
},
/**
* Sets the zoom of all the charts at year level.
*/
setZoomYear: function()
{
this._setZoom(5);
},
/**
* Sets the zoom of all the charts at month level.
*/
setZoomMonth: function()
{
this._setZoom(12 * 5);
},
/**
* Sets the zoom of all the charts at week level.
*/
setZoomWeek: function()
{
this._setZoom(52 * 5);
},
/**
* Sets the zoom of all the charts at day level.
*/
setZoomDay: function()
{
this._setZoom(365 * 5);
},
/**
* Sets the zoom of all the charts at hour level.
*/
setZoomHour: function()
{
this._setZoom(365 * 24 * 5);
},
/**
* Moves the time axis to now.
*/
moveToNow: function()
{
var tool = this._getTool();
if (tool != null && !tool.getDrawMode())
{
tool.moveToNow.call(tool);
}
},
/**
* Enables / disables the draw mode, which enables to annotate the graphs.
*/
switchDrawMode: function()
{
var tool = this._getTool();
if (tool != null)
{
tool.setDrawMode.call(tool, !tool.getDrawMode());
}
},
/**
* Reloads the graphs data
*/
reloadGraphs: function()
{
var tool = this._getTool();
if (tool != null)
{
tool.reloadActiveGraph.call(tool);
}
},
/**
* @private
* Sets the given zoom on the charts.
* @param {Number} zoomValue The zoom value. Must be between 0 (excluded) and 1 (included)
*/
_setZoom: function(zoomValue)
{
var tool = this._getTool();
if (tool != null && !tool.getDrawMode())
{
tool.zoomOnTimeAxis.call(tool, [zoomValue]);
}
},
/**
* @private
* Gets the monitoring tool.
* @return {Ametys.plugins.admin.jvmstatus.MonitoringTool} The tool if opened, null otherwise.
*/
_getTool: function()
{
return Ametys.tool.ToolsManager.getTool("uitool-admin-monitoring");
}
},
/**
* @private
* @property {Ext.tab.Panel} _monitoringPanel The JVM status main panel
*/
/**
* @private
* @property {Boolean} _drawMode true if the tool is in draw mode, false otherwise.
*/
_drawMode: false,
/**
* Updates the time axis of the charts
* @param {Number} zoomValue The zoom value. Must greater or equals to 1.
*/
zoomOnTimeAxis: function(zoomValue)
{
var chart = this._monitoringPanel.getActiveTab();
if (chart != null)
{
var axis = chart.getAxis(0), // time axis
range = axis.getVisibleRange(),
oldEnd = range[1];
axis.setVisibleRange([oldEnd - 1 / zoomValue, oldEnd]);
chart.redraw();
}
},
/**
* Moves the end of the time axis to maximum
*/
moveToNow: function()
{
var chart = this._monitoringPanel.getActiveTab();
if (chart != null)
{
var axis = chart.getAxis(0), // time axis
range = axis.getVisibleRange(),
oldStart = range[0],
oldEnd = range[1],
length = oldEnd - oldStart;
axis.setVisibleRange([1-length, 1]);
chart.redraw();
}
},
/**
* Reloads the active graph data
*/
reloadActiveGraph: function()
{
var chart = this._monitoringPanel.getActiveTab(),
id = chart.getItemId(),
serverId = chart.serverId,
toDate = new Date(),
fromDate = new Date(toDate.getTime() - (1000*60*60*24*365*5)), // 5 year range
axis = chart.getAxis(0);
axis.setFromDate(fromDate);
axis.setToDate(toDate);
Ametys.data.ServerComm.send({
plugin: 'admin',
url: 'jvmstatus/monitoring/' + serverId + '.json',
responseType: "text",
priority: Ametys.data.ServerComm.PRIORITY_MAJOR,
callback: {
handler: this._populateGraphData,
arguments: [id],
scope: this
},
errorMessage: true
});
},
/**
* Sets the value of the draw mode, which enables to annotate the graphs.
*/
setDrawMode: function(drawMode)
{
var chart = this._monitoringPanel.getActiveTab();
if (chart != null)
{
chart.setDraw(drawMode);
this.enableInteractions(chart, !drawMode);
chart.getSurface('chart').removeAll(true);
chart.renderFrame();
}
this._drawMode = drawMode;
Ext.create("Ametys.message.Message", {
type: Ametys.message.Message.MODIFIED,
targets: {
id: Ametys.message.MessageTarget.TOOL,
parameters: { tools: [this] }
}
});
},
/**
* Gets the draw mode state of the tool.
* @return {Boolean} true if the tool is in draw mode, false otherwise.
*/
getDrawMode: function()
{
return this._drawMode;
},
/**
* Function to render values on a pretier way.
* @param {String} value The value to render.
* @return {String} The label to display.
*/
renderValue: function(value) {
return Ext.util.Format.number(value, '0,000.###'); // 3 digits max after decimal point, and will use the locale default separators for thousand and decimal
},
getMBSelectionInteraction: function()
{
return Ametys.tool.Tool.MB_TYPE_NOSELECTION;
},
createPanel: function ()
{
this._monitoringPanel = Ext.create('Ext.tab.Panel', {
tabPosition: 'left',
tabRotation: 0,
listeners: {
'tabchange': Ext.bind(function(tabPanel, newChart, oldChart, eOpts) {
if (oldChart != null)
{
newChart.getAxis(0).setVisibleRange(oldChart.getAxis(0).getVisibleRange());
}
this.setDrawMode(this.getDrawMode());
this.reloadActiveGraph();
}, this)
}
});
return this._monitoringPanel;
},
setParams: function (params)
{
this.callParent(arguments);
this.showOutOfDate();
},
/**
* Refreshes the tool
*/
refresh: function ()
{
this.showRefreshing();
Ametys.data.ServerComm.callMethod({
role: "org.ametys.runtime.plugins.admin.jvmstatus.JVMStatusHelper",
methodName: "getMonitoringData",
parameters: [],
callback: {
scope: this,
handler: this._refreshCb
},
errorMessage: {
category: this.self.getName(),
msg: "{{i18n PLUGINS_ADMIN_TOOL_MONITORING_SERVER_ERROR}}"
}
});
},
/**
* @private
* Callback for the refreshing process
* @param {Object} response the server's xml response
* @param {Object[]} args the callback arguments
* @param {Function} args.callback the callback
*/
_refreshCb: function (response, args)
{
if (!this.isNotDestroyed())
{
return;
}
this._monitoringPanel.removeAll();
// Construct the graphs
var sampleList = response['samples']['sampleList'];
Ext.Array.forEach(sampleList, function(sample) {
var serverId = sample.id,
id = serverId.replace(/\./g, '-'), // no '.' in ExtJS ids
label = sample.label,
description = sample.description,
thresholds = sample.thresholds,
toDate = new Date(),
fromDate = new Date(toDate.getTime() - (1000*60*60*24*365*5)), // 5 year range
chartCfg = this._getChartCfg(id, serverId, label, description, fromDate, toDate, thresholds);
this._monitoringPanel.add([chartCfg]);
}, this);
var first = this._monitoringPanel.items.first();
if (first != null)
{
this._monitoringPanel.setActiveTab(first);
this.statics().setZoomDay();
}
this.showRefreshed();
},
/**
* @private
* Callback after receiving data from the server to populate the graph.
* @param {Object} response the server response
* @param {Array} args The callback arguments
*/
_populateGraphData: function(response, args)
{
if (this._monitoringPanel == null)
{
return;
}
var id = args[0],
chart = this._monitoringPanel.getComponent(id),
store = chart.getStore();
if (chart.rendered)
{
// The server returned a string json, decode it into an array of object
var stringData = response.firstChild.textContent;
var data = Ext.decode(stringData, true);
// Compute the fields from data object
function getFields(data)
{
var fields = [];
Ext.Array.forEach(data, function(item) {
Ext.Object.each(item, function(key, value) {
if (!Ext.Array.contains(fields, key))
{
fields.push(key);
}
});
});
return fields;
};
var fields = getFields(data);
fields.sort().reverse(); //all MAX must be before AVERAGE for graphical purposes
// Create the series
var series = [];
Ext.Array.forEach(fields, function(field) {
if (field != 'time')
{
series.push(this._getLineSerieCfg(field));
}
}, this);
// Draw the series in the chart
chart.setSeries(series);
store.setFields(fields);
store.setData(data);
// depending on the series length, set the colors (AVERAGE will be same colors as MAX but a bit lighter)
var colorSet = ['#115fa6', '#ff8809', '#a61187', '#94ae0a', '#a61120', '#ffd13e', '#7c7474', '#a66111', '#24ad9a'],
offset = series.length / 2;
function shadeColor2(color, percent) { // see http://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors
var f=parseInt(color.slice(1),16),t=percent<0?0:255,p=percent<0?percent*-1:percent,R=f>>16,G=f>>8&0x00FF,B=f&0x0000FF;
return "#"+(0x1000000+(Math.round((t-R)*p)+R)*0x10000+(Math.round((t-G)*p)+G)*0x100+(Math.round((t-B)*p)+B)).toString(16).slice(1);
}
Ext.Array.erase(colorSet, offset, -1);
var newColors = colorSet;
Ext.Array.forEach(colorSet, function(color, index) {
newColors[index + offset] = shadeColor2(color, 0.5);
}, this);
chart.setColors(newColors);
}
},
/**
* @private
* Gets the object config for creating a graph
* @param {String} itemId The item id
* @param {String} serverId The id of server's sample manager
* @param {String} label The title of the graph panel
* @param {String} description The description of the graph panel
* @param {Date} fromDate The start of the time axis
* @param {Date} toDate the end of the time axis
* @param {Object} thresholds the thresholds for the limits to display. Can be null.
* @return {Object} The configuration object for creating a graph
*/
_getChartCfg: function(itemId, serverId, label, description, fromDate, toDate, thresholds)
{
return {
xtype: 'drawable-cartesian',
title: label,
border: false,
collapsible: true,
titleCollapse: true,
header: {
titlePosition: 1
},
itemId: itemId,
serverId: serverId,
style: {
marginBottom: '50px'
},
store: {
fields: ['time'],
data: []
},
interactions: [this._getPanzoomInteractionCfg()],
legend: {
type: 'monitoring-sprite',
docked: 'right',
description: description
},
shadow: false,
animation: true,
axes: [{
type: 'time',
position: 'bottom',
grid: true,
dateFormat: Ext.Date.patterns.FriendlyDateTime + " ",
fromDate: fromDate,
toDate: toDate,
maxZoom: 500000,
label: {
rotate: {
degrees: -90
}
}
}, {
type: 'numeric',
position: 'left',
grid: true,
minimum: 0,
limits: this._getLimitLineCfg(thresholds),
renderer: Ext.bind(function(axis, label, layoutContext) {
var value = layoutContext.renderer(label);
return this.renderValue(value);
}, this)
}],
series: []
};
},
/**
* @private
* Gets the object config for the panzoom interaction
* @return {Object} The configuration object for creating our custom panzoom interaction
*/
_getPanzoomInteractionCfg: function()
{
return {
xtype: 'timepanzoom'
};
},
/**
* @private
* Gets the object config for the limit line
* @param {Object} thresholds the thresholds for the limits to display. Can be null.
* @return {Object[]} The configuration object for creating limit lines
*/
_getLimitLineCfg: function(thresholds)
{
var limits = [];
thresholds = thresholds || {};
Ext.Object.each(thresholds, function(dsName, value) {
limits.push({
value: value,
line: {
title: {
text: Ext.util.Format.format('{{i18n PLUGINS_ADMIN_TOOL_MONITORING_CHART_THRESHOLD}}', dsName, value)
},
lineDash: [2,2]
}
});
}, this);
return limits;
},
/**
* @private
* Gets the object config for creating a serie.
* @param {String} field The name of the field for this serie
* @return {Object} The configuration object for the series
*/
_getLineSerieCfg: function(field)
{
return {
type: 'line',
xField: 'time',
yField: field,
axis: 'bottom',
fill: true,
nullStyle: 'connect',
style: {
fillOpacity: .6
},
tooltip: {
trackMouse: true,
dismissDelay: 0,
renderer: Ext.bind(function (tooltip, record, item) {
var date = Ext.Date.format(new Date(record.get('time')), Ext.Date.patterns.FriendlyDateTime);
var value = this.renderValue(record.get(field));
tooltip.setHtml(field + ' (' + date + '): ' + value);
}, this)
}
}
},
/**
* Downloads the given chart.
*/
download: function()
{
var chart = this._monitoringPanel.getActiveTab();
if (Ext.os.is.Desktop)
{
chart.download({
filename: chart.getTitle()
});
}
else
{
chart.preview();
}
},
/**
* Enables or disables the interactions of the given chart
* @param {Ext.chart.CartesianChart} chart The chart
* @param {Boolean} enable true to enable the interactions, false otherwise.
*/
enableInteractions: function(chart, enable)
{
Ext.Array.forEach(chart.getInteractions(), function (interaction) {
interaction.setEnabled(enable);
}, this);
}
});