package org.rhq.coregui.client.inventory.groups.detail.monitoring.table;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
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.criteria.Criteria;
import org.rhq.core.domain.criteria.ResourceCriteria;
import org.rhq.core.domain.measurement.DataType;
import org.rhq.core.domain.measurement.MeasurementDefinition;
import org.rhq.core.domain.measurement.MeasurementSchedule;
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.Resource;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.core.domain.resource.composite.ResourceComposite;
import org.rhq.core.domain.util.PageList;
import org.rhq.coregui.client.CoreGUI;
import org.rhq.coregui.client.UserSessionManager;
import org.rhq.coregui.client.gwt.GWTServiceLookup;
import org.rhq.coregui.client.inventory.resource.type.ResourceTypeRepository;
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;
import org.rhq.coregui.client.util.preferences.MeasurementUserPreferences;
/**
* 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 MetricsTableDataSource extends RPCDataSource<MetricDisplaySummary, Criteria> {
public static final String FIELD_SPARKLINE = "sparkline";
public static final String FIELD_METRIC_LABEL = "label";
public static final String FIELD_ALERT_COUNT = "alertCount";
public static final String FIELD_MIN_VALUE = "min";
public static final String FIELD_MAX_VALUE = "max";
public static final String FIELD_AVG_VALUE = "avg";
public static final String FIELD_LAST_VALUE = "last";
public static final String FIELD_METRIC_DEF_ID = "defId";
public static final String FIELD_METRIC_SCHED_ID = "schedId";
public static final String FIELD_METRIC_UNITS = "units";
public static final String FIELD_METRIC_NAME = "name";
public static final String FIELD_RESOURCE_ID = "resourceId";
private int resourceId;
private List<MetricDisplaySummary> metricDisplaySummaries;
private List<List<MeasurementDataNumericHighLowComposite>> metricsDataList;
private MeasurementUserPreferences measurementUserPrefs;
public MetricsTableDataSource(int resourceId) {
this.resourceId = resourceId;
measurementUserPrefs = new MeasurementUserPreferences(UserSessionManager.getUserPreferences());
}
/**
* 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(FIELD_SPARKLINE, "chart");
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_" + resourceId + "-"
+ record.getAttributeAsInt(FIELD_METRIC_DEF_ID) + "' class='dynamicsparkline' width='70' "
+ "values='" + record.getAttribute(FIELD_SPARKLINE) + "'></span>";
return contents;
}
});
sparklineField.setWidth(80);
fields.add(sparklineField);
ListGridField nameField = new ListGridField(FIELD_METRIC_LABEL, MSG.common_title_name());
nameField.setWidth("30%");
fields.add(nameField);
ListGridField alertsField = new ListGridField(FIELD_ALERT_COUNT, MSG.common_title_alerts());
alertsField.setWidth("10%");
fields.add(alertsField);
ListGridField minField = new ListGridField(FIELD_MIN_VALUE, MSG.common_title_monitor_minimum());
minField.setWidth("15%");
fields.add(minField);
ListGridField maxField = new ListGridField(FIELD_MAX_VALUE, MSG.common_title_monitor_maximum());
maxField.setWidth("15%");
fields.add(maxField);
ListGridField avgField = new ListGridField(FIELD_AVG_VALUE, MSG.common_title_monitor_average());
avgField.setWidth("15%");
fields.add(avgField);
ListGridField lastField = new ListGridField(FIELD_LAST_VALUE, MSG.view_resource_monitor_table_live());
lastField.setWidth("15%");
fields.add(lastField);
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(FIELD_SPARKLINE, getCsvMetricsForSparkline());
record.setAttribute(FIELD_METRIC_LABEL, from.getLabel());
record.setAttribute(FIELD_ALERT_COUNT, String.valueOf(from.getAlertCount()));
record.setAttribute(FIELD_MIN_VALUE, getMetricStringValue(from.getMinMetric()));
record.setAttribute(FIELD_MAX_VALUE, getMetricStringValue(from.getMaxMetric()));
record.setAttribute(FIELD_AVG_VALUE, getMetricStringValue(from.getAvgMetric()));
record.setAttribute(FIELD_LAST_VALUE, getMetricStringValue(from.getLastMetric()));
record.setAttribute(FIELD_METRIC_DEF_ID, from.getDefinitionId());
record.setAttribute(FIELD_METRIC_SCHED_ID, from.getScheduleId());
record.setAttribute(FIELD_METRIC_UNITS, from.getUnits());
record.setAttribute(FIELD_METRIC_NAME, from.getMetricName());
record.setAttribute(FIELD_RESOURCE_ID, resourceId);
return record;
}
private String getCsvMetricsForSparkline() {
StringBuilder sb = new StringBuilder();
Log.debug("getCsvMetricsForSparkline.metricsDataList: " + metricsDataList.size());
for (List<MeasurementDataNumericHighLowComposite> measurementData : metricsDataList) {
for (int i = 0; i < measurementData.size(); i++) {
// take the last 20 values
if (i >= measurementData.size() - 20) {
if (!Double.isNaN(measurementData.get(i).getValue())) {
sb.append((int) measurementData.get(i).getValue());
sb.append(",");
}
}
}
if (sb.toString().endsWith(",")) {
sb.setLength(sb.length() - 1);
}
}
Log.debug("getCsvMetricsForSparkline: " + sb.toString());
return sb.toString();
}
protected String getMetricStringValue(MetricDisplayValue value) {
return (value != null) ? value.toString() : "";
}
@Override
protected Criteria getFetchCriteria(DSRequest request) {
// 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) {
GWTServiceLookup.getMeasurementScheduleService().findSchedulesForResourceAndType(resourceId,
DataType.MEASUREMENT, null, true, new AsyncCallback<ArrayList<MeasurementSchedule>>() {
@Override
public void onSuccess(ArrayList<MeasurementSchedule> measurementSchedules) {
int[] scheduleIds = new int[measurementSchedules.size()];
int i = 0;
for (MeasurementSchedule measurementSchedule : measurementSchedules) {
scheduleIds[i++] = measurementSchedule.getId();
}
final CountDownLatch countDownLatch = CountDownLatch.create(2, new Command() {
@Override
public void execute() {
response.setData(buildRecords(metricDisplaySummaries));
processResponse(request.getRequestId(), response);
new Timer() {
@Override
public void run() {
BrowserUtility.graphSparkLines();
}
}.schedule(150);
Log.debug("Finished CountdownLatch for metrics loaded: " + metricsDataList.size());
}
});
retrieveResourceMetrics(resourceId, countDownLatch);
GWTServiceLookup.getMeasurementChartsService().getMetricDisplaySummariesForResource(resourceId,
scheduleIds, measurementUserPrefs.getMetricRangePreferences().begin,
measurementUserPrefs.getMetricRangePreferences().end,
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();
}
}
);
}
@Override
public void onFailure(Throwable caught) {
CoreGUI.getErrorHandler().handleError("Cannot load schedules", caught);
}
});
}
void setMetricDisplaySummaries(List<MetricDisplaySummary> metricDisplaySummaries) {
this.metricDisplaySummaries = metricDisplaySummaries;
}
public void retrieveResourceMetrics(final Integer resourceId, final CountDownLatch countDownLatch) {
ResourceCriteria criteria = new ResourceCriteria();
criteria.addFilterId(resourceId);
//locate the resource
GWTServiceLookup.getResourceService().findResourceCompositesByCriteria(criteria,
new AsyncCallback<PageList<ResourceComposite>>() {
@Override
public void onFailure(Throwable caught) {
Log.debug("Error retrieving resource resource composite for resource [" + resourceId + "]:"
+ caught.getMessage());
}
@Override
public void onSuccess(PageList<ResourceComposite> resourceCompositePageList) {
if (!resourceCompositePageList.isEmpty()) {
final ResourceComposite resourceComposite = resourceCompositePageList.get(0);
final Resource resource = resourceComposite.getResource();
// Load the fully fetched ResourceType.
ResourceType resourceType = resource.getResourceType();
ResourceTypeRepository.Cache.getInstance().getResourceTypes(resourceType.getId(),
EnumSet.of(ResourceTypeRepository.MetadataType.measurements),
new ResourceTypeRepository.TypeLoadedCallback() {
public void onTypesLoaded(ResourceType type) {
resource.setResourceType(type);
//metric definitions
Set<MeasurementDefinition> definitions = type.getMetricDefinitions();
//build id mapping for measurementDefinition instances Ex. Free Memory -> MeasurementDefinition[100071]
final HashMap<String, MeasurementDefinition> measurementDefMap = new HashMap<String, MeasurementDefinition>();
for (MeasurementDefinition definition : definitions) {
measurementDefMap.put(definition.getDisplayName(), definition);
}
//bundle definition ids for asynch call.
int[] definitionArrayIds = new int[definitions.size()];
final String[] displayOrder = new String[definitions.size()];
measurementDefMap.keySet().toArray(displayOrder);
//sort the charting data ex. Free Memory, Free Swap Space,..System Load
Arrays.sort(displayOrder);
//organize definitionArrayIds for ordered request on server.
int index = 0;
for (String definitionToDisplay : displayOrder) {
definitionArrayIds[index++] = measurementDefMap.get(definitionToDisplay)
.getId();
}
GWTServiceLookup.getMeasurementDataService().findDataForResource(resourceId,
definitionArrayIds, measurementUserPrefs.getMetricRangePreferences().begin,
measurementUserPrefs.getMetricRangePreferences().end, 60,
new AsyncCallback<List<List<MeasurementDataNumericHighLowComposite>>>() {
@Override
public void onFailure(Throwable caught) {
Log.warn("Error retrieving recent metrics charting data for resource ["
+ resourceId + "]:" + caught.getMessage());
}
@Override
public void onSuccess(
List<List<MeasurementDataNumericHighLowComposite>> measurementDataList) {
if (!measurementDataList.isEmpty()) {
metricsDataList = measurementDataList;
Log.debug("*** Setting metricsDataList.size: "
+ metricsDataList.size());
countDownLatch.countDown();
}
}
});
}
});
}
}
});
}
}