/*
* RHQ Management Platform
* Copyright (C) 2005-2013 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.coregui.client.inventory.groups.detail.monitoring.metric;
import static org.rhq.core.domain.measurement.DataType.COMPLEX;
import static org.rhq.core.domain.measurement.DataType.MEASUREMENT;
import static org.rhq.coregui.client.inventory.resource.detail.monitoring.table.MetricsGridFieldName.ALERT_COUNT;
import static org.rhq.coregui.client.inventory.resource.detail.monitoring.table.MetricsGridFieldName.AVG_VALUE;
import static org.rhq.coregui.client.inventory.resource.detail.monitoring.table.MetricsGridFieldName.MAX_VALUE;
import static org.rhq.coregui.client.inventory.resource.detail.monitoring.table.MetricsGridFieldName.METRIC_DEF_ID;
import static org.rhq.coregui.client.inventory.resource.detail.monitoring.table.MetricsGridFieldName.METRIC_LABEL;
import static org.rhq.coregui.client.inventory.resource.detail.monitoring.table.MetricsGridFieldName.METRIC_NAME;
import static org.rhq.coregui.client.inventory.resource.detail.monitoring.table.MetricsGridFieldName.METRIC_SCHEDULE_ID;
import static org.rhq.coregui.client.inventory.resource.detail.monitoring.table.MetricsGridFieldName.METRIC_UNITS;
import static org.rhq.coregui.client.inventory.resource.detail.monitoring.table.MetricsGridFieldName.MIN_VALUE;
import static org.rhq.coregui.client.inventory.resource.detail.monitoring.table.MetricsGridFieldName.RESOURCE_GROUP_ID;
import static org.rhq.coregui.client.inventory.resource.detail.monitoring.table.MetricsGridFieldName.SPARKLINE;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.smartgwt.client.data.DSRequest;
import com.smartgwt.client.data.DSResponse;
import com.smartgwt.client.data.Record;
import com.smartgwt.client.widgets.grid.CellFormatter;
import com.smartgwt.client.widgets.grid.ListGridField;
import com.smartgwt.client.widgets.grid.ListGridRecord;
import org.rhq.core.domain.common.EntityContext;
import org.rhq.core.domain.criteria.Criteria;
import org.rhq.core.domain.measurement.MeasurementDefinition;
import org.rhq.core.domain.measurement.composite.MeasurementDataNumericHighLowComposite;
import org.rhq.core.domain.measurement.ui.MetricDisplaySummary;
import org.rhq.core.domain.measurement.ui.MetricDisplayValue;
import org.rhq.core.domain.resource.group.ResourceGroup;
import org.rhq.coregui.client.CoreGUI;
import org.rhq.coregui.client.gwt.GWTServiceLookup;
import org.rhq.coregui.client.inventory.common.graph.CustomDateRangeState;
import org.rhq.coregui.client.util.BrowserUtility;
import org.rhq.coregui.client.util.Log;
import org.rhq.coregui.client.util.MeasurementUtility;
import org.rhq.coregui.client.util.RPCDataSource;
import org.rhq.coregui.client.util.async.Command;
import org.rhq.coregui.client.util.async.CountDownLatch;
/**
* A simple data source to read in metric data summaries for a resource.
* This doesn't support paging - everything is returned in one query. Since
* the number of metrics per resource is relatively small (never more than tens of them),
* we just load them all in at once.
*
* @author John Mazzitelli
* @author Mike Thompson
*/
public class MetricsGroupViewDataSource extends RPCDataSource<MetricDisplaySummary, Criteria> {
private static final int NUMBER_OF_METRIC_POINTS = 60;
private final ResourceGroup resourceGroup;
private List<MetricDisplaySummary> metricDisplaySummaries;
private List<List<MeasurementDataNumericHighLowComposite>> metricsDataList;
private int[] definitionArrayIds;
public MetricsGroupViewDataSource(ResourceGroup resourceGroup) {
this.resourceGroup = resourceGroup;
}
/**
* The view that contains the list grid which will display this datasource's data will call this
* method to get the field information which is used to control the display of the data.
*
* @return list grid fields used to display the datasource data
*/
public ArrayList<ListGridField> getListGridFields() {
ArrayList<ListGridField> fields = new ArrayList<ListGridField>(7);
ListGridField sparklineField = new ListGridField(SPARKLINE.getValue(), MSG.chart_metrics_sparkline_header());
sparklineField.setCellFormatter(new CellFormatter() {
@Override
public String format(Object value, ListGridRecord record, int rowNum, int colNum) {
if (value == null) {
return "";
}
String contents = "<span id='sparkline_" + resourceGroup.getId() + "-"
+ record.getAttributeAsInt(METRIC_DEF_ID.getValue()) + "' class='dynamicsparkline' width='70' "
+ "values='" + record.getAttribute(SPARKLINE.getValue()) + "'></span>";
return contents;
}
});
sparklineField.setWidth(80);
fields.add(sparklineField);
ListGridField nameField = new ListGridField(METRIC_LABEL.getValue(), METRIC_LABEL.getLabel());
nameField.setWidth("30%");
fields.add(nameField);
ListGridField minField = new ListGridField(MIN_VALUE.getValue(), MIN_VALUE.getLabel());
minField.setWidth("15%");
fields.add(minField);
ListGridField maxField = new ListGridField(MAX_VALUE.getValue(), MAX_VALUE.getLabel());
maxField.setWidth("15%");
fields.add(maxField);
ListGridField avgField = new ListGridField(AVG_VALUE.getValue(), AVG_VALUE.getLabel());
avgField.setWidth("15%");
fields.add(avgField);
ListGridField alertsField = new ListGridField(ALERT_COUNT.getValue(), ALERT_COUNT.getLabel());
alertsField.setWidth("10%");
fields.add(alertsField);
return fields;
}
@Override
public MetricDisplaySummary copyValues(Record from) {
// we should never need this method - we only go in one direction
// if we ever need this, just have copyValues store an "object" attribute whose value is "from"
// which this method then just reads out. Since we don't need this now, save memory by not
// keeping the MetricDisplayValue around
return null;
}
@Override
public ListGridRecord copyValues(MetricDisplaySummary from) {
MeasurementUtility.formatSimpleMetrics(from);
ListGridRecord record = new ListGridRecord();
record.setAttribute(SPARKLINE.getValue(), getCsvMetricsForSparkline(from.getDefinitionId()));
record.setAttribute(METRIC_LABEL.getValue(), from.getLabel());
record.setAttribute(ALERT_COUNT.getValue(), String.valueOf(from.getAlertCount()));
record.setAttribute(MIN_VALUE.getValue(), getMetricStringValue(from.getMinMetric()));
record.setAttribute(MAX_VALUE.getValue(), getMetricStringValue(from.getMaxMetric()));
record.setAttribute(AVG_VALUE.getValue(), getMetricStringValue(from.getAvgMetric()));
record.setAttribute(METRIC_DEF_ID.getValue(), from.getDefinitionId());
record.setAttribute(METRIC_SCHEDULE_ID.getValue(), from.getScheduleId());
record.setAttribute(METRIC_UNITS.getValue(), from.getUnits());
record.setAttribute(METRIC_NAME.getValue(), from.getMetricName());
record.setAttribute(RESOURCE_GROUP_ID.getValue(), resourceGroup.getId());
return record;
}
private String getCsvMetricsForSparkline(int definitionId) {
StringBuilder sb = new StringBuilder();
List<MeasurementDataNumericHighLowComposite> selectedMetricsList = getMeasurementsForMeasurementDefId(definitionId);
for (MeasurementDataNumericHighLowComposite measurementData : selectedMetricsList) {
if (!Double.isNaN(measurementData.getValue())) {
sb.append((int) measurementData.getValue());
sb.append(",");
}
}
if (sb.toString().endsWith(",")) {
sb.setLength(sb.length() - 1);
}
// handle the case where we have just installed the server so not much history
// and our date range is set such that only one value returns which the
// sparkline graph will not plot anything, so we need at least 2 values
if (!sb.toString().contains(",")) {
// append another value just so we have 2 values and it will graph
return "0," + sb.toString();
}
return sb.toString();
}
private List<MeasurementDataNumericHighLowComposite> getMeasurementsForMeasurementDefId(int definitionId) {
int selectedIndex = 0;
// find the ordinal position as specified when querying the metrics
for (int i = 0; i < definitionArrayIds.length; i++) {
if (definitionArrayIds[i] == definitionId) {
selectedIndex = i;
break;
}
}
return metricsDataList.get(selectedIndex);
}
protected String getMetricStringValue(MetricDisplayValue value) {
return (value != null) ? value.toString() : "";
}
@Override
protected Criteria getFetchCriteria(DSRequest request) {
// NOTE: we don't use criterias for this datasource, just return null
return null;
}
@Override
protected void executeFetch(final DSRequest request, final DSResponse response, final Criteria unused) {
// This latch is the last thing that gets executed after we have executed the
// 1 query
final CountDownLatch countDownLatch = CountDownLatch.create(1, new Command() {
@Override
public void execute() {
// NOTE: this runs after the queryMetricDisplaySummaries is complete
queryGroupMetrics(resourceGroup, request, response);
}
});
organizeMeasurementDefinitionOrder(resourceGroup);
queryMetricDisplaySummaries(definitionArrayIds, CustomDateRangeState.getInstance().getStartTime(),
CustomDateRangeState.getInstance().getEndTime(), countDownLatch);
}
private void queryGroupMetrics(final ResourceGroup resourceGroup, final DSRequest request, final DSResponse response) {
GWTServiceLookup.getMeasurementDataService().findDataForCompatibleGroup(resourceGroup.getId(),
definitionArrayIds, CustomDateRangeState.getInstance().getStartTime(),
CustomDateRangeState.getInstance().getEndTime(), NUMBER_OF_METRIC_POINTS,
new AsyncCallback<List<List<MeasurementDataNumericHighLowComposite>>>() {
@Override
public void onFailure(Throwable caught) {
Log.warn("Error retrieving recent metrics charting data for resource [" + resourceGroup.getId()
+ "]:" + caught.getMessage());
}
@Override
public void onSuccess(List<List<MeasurementDataNumericHighLowComposite>> measurementDataList) {
if (null != measurementDataList && !measurementDataList.isEmpty()) {
metricsDataList = measurementDataList;
response.setData(buildRecords(metricDisplaySummaries));
new Timer() {
@Override
public void run() {
BrowserUtility.graphSparkLines();
}
}.schedule(150);
}
processResponse(request.getRequestId(), response);
}
});
}
private void organizeMeasurementDefinitionOrder(ResourceGroup resourceGroup) {
List<MeasurementDefinition> definitions = getMetricDefinitions(resourceGroup);
//bundle definition ids for asynch call.
definitionArrayIds = new int[definitions.size()];
//sort the charting data ex. Free Memory, Free Swap Space,..System Load
Collections.sort(definitions, new Comparator<MeasurementDefinition>() {
@Override
public int compare(MeasurementDefinition o1, MeasurementDefinition o2) {
return o1.getDisplayName().compareTo(o2.getDisplayName());
}
});
//organize definitionArrayIds for ordered request on server.
int index = 0;
for (MeasurementDefinition definitionToDisplay : definitions) {
definitionArrayIds[index++] = definitionToDisplay.getId();
}
}
private void queryMetricDisplaySummaries(int[] measurementDefIds, Long startTime, Long endTime,
final CountDownLatch countDownLatch) {
GWTServiceLookup.getMeasurementChartsService().getMetricDisplaySummariesForCompatibleGroup(
EntityContext.forGroup(resourceGroup), measurementDefIds, startTime, endTime, false,
new AsyncCallback<ArrayList<MetricDisplaySummary>>() {
@Override
public void onSuccess(ArrayList<MetricDisplaySummary> metricDisplaySummaries) {
setMetricDisplaySummaries(metricDisplaySummaries);
countDownLatch.countDown();
}
@Override
public void onFailure(Throwable caught) {
CoreGUI.getErrorHandler().handleError("Cannot load metrics", caught);
countDownLatch.countDown();
}
}
);
}
private void setMetricDisplaySummaries(List<MetricDisplaySummary> metricDisplaySummaries) {
this.metricDisplaySummaries = metricDisplaySummaries;
}
private List<MeasurementDefinition> getMetricDefinitions(ResourceGroup resourceGroup) {
List<MeasurementDefinition> definitions = new ArrayList<MeasurementDefinition>();
for (MeasurementDefinition measurementDefinition : resourceGroup.getResourceType().getMetricDefinitions()) {
if (measurementDefinition.getDataType() == MEASUREMENT || measurementDefinition.getDataType() == COMPLEX) {
definitions.add(measurementDefinition);
}
}
return definitions;
}
}