/**
* Helios, OpenSource Monitoring
* Brought to you by the Helios Development Group
*
* Copyright 2007, Helios Development Group and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
*/
package org.helios.apmrouter.dataservice.json.h2timeseries;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.ResultSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.sql.DataSource;
import org.helios.apmrouter.collections.ConcurrentLongSlidingWindow;
import org.helios.apmrouter.collections.ILongSlidingWindow;
import org.helios.apmrouter.dataservice.json.JSONRequestHandler;
import org.helios.apmrouter.dataservice.json.JsonRequest;
import org.helios.apmrouter.dataservice.json.marshalling.GSONJSONMarshaller;
import org.helios.apmrouter.destination.h2timeseries.H2TimeSeriesDestination;
import org.helios.apmrouter.server.ServerComponentBean;
import org.helios.apmrouter.util.SystemClock;
import org.helios.apmrouter.util.SystemClock.ElapsedTime;
import org.jboss.netty.channel.Channel;
import org.json.JSONArray;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jmx.export.annotation.ManagedMetric;
import org.springframework.jmx.support.MetricType;
/**
* <p>Title: H2TimeSeriesJSONDataService</p>
* <p>Description: JSON Data Service for the local H2 time-series data</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.dataservice.json.h2timeseries.H2TimeSeriesJSONDataService</code></p>
*/
@JSONRequestHandler(name="h2ts")
public class H2TimeSeriesJSONDataService extends ServerComponentBean {
/** The H2 data source */
protected DataSource dataSource = null;
/** The H2 destination for configs */
protected H2TimeSeriesDestination h2Dest = null;
/** The GSON marshaller */
protected GSONJSONMarshaller gsonMarshaller = null;
/** The time-series STEP in ms. */
protected long step = -1;
/** The time-series WIDTH */
protected long width= -1;
/** A map containing the step and width, keyed by json keys */
protected Map<String, Long> stepWidth = null;
/** The time-series WIDTH as an int */
protected int widthAsInt = -1;
/** The oldest allowed entries in live */
protected long oldestLive = -1;
/** A sliding window of liveData elapsed query time in ns */
protected final ILongSlidingWindow lastElapsedLiveData = new ConcurrentLongSlidingWindow(60);
/** The column ID for the agent */
public static final int AGENT_ID = 1;
/** The column ID for the type */
public static final int TYPE_ID = 2;
/** The column ID for the namespace */
public static final int NAMESPACE = 3;
/** The column ID for the array-ized namespace */
public static final int NARR = 4;
/** The column ID for the metric name */
public static final int NAME = 5;
/** The column ID for the metric id */
public static final int METRIC_ID = 6;
/** The column ID for the series timestamp */
public static final int TS = 7;
/** The column ID for the min value */
public static final int MIN = 8;
/** The column ID for the max value */
public static final int MAX = 9;
/** The column ID for the average value */
public static final int AVG = 10;
/** The column ID for the sample count */
public static final int CNT = 11;
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.ServerComponentBean#doStart()
*/
@Override
protected void doStart() throws Exception {
step = h2Dest.getTimeSeriesStep();
width = h2Dest.getTimeSeriesWidth();
oldestLive = width * step;
widthAsInt = width>Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)width;
MetricData.STEP = step;
MetricData.WIDTH = width;
Map<String, Long> m = new HashMap<String, Long>(2);
m.put("step", step);
m.put("width", width);
stepWidth = Collections.unmodifiableMap(m);
}
/**
* Returns live time-series data
* @param request The JSON request
* @param channel The channel to respond on
*/
@JSONRequestHandler(name="liveData")
public void liveData(JsonRequest request, Channel channel) {
SystemClock.startTimer();
JSONArray ids = request.getArgumentOrNull("IDS", JSONArray.class);
if(ids.length()==0) {
channel.write(request.response().setContent("NOOP"));
return;
}
Connection conn = null;
CallableStatement cs = null;
ResultSet rset = null;
try {
// StringBuilder sql = new StringBuilder("SELECT * FROM RICH_METRIC_DATA WHERE ID IN (");
// sql.append(ids.toString().replace("[", "").replace("]", "")).append(")");
// sql.append(" ORDER BY ID, TS");
StringBuilder sql = new StringBuilder("CALL CV(");
//StringBuilder sql = new StringBuilder("CALL MV(");
sql.append(SystemClock.time()-oldestLive).append(",");
sql.append(ids.toString().replace("[", "").replace("]", "")).append(")");
conn = dataSource.getConnection();
cs = conn.prepareCall(sql.toString());
//cs.registerOutParameter(1, Types.OTHER);
rset = cs.executeQuery();
Set<long[]> metricSet = null;
long currentId = -1;
while(rset.next()) {
final long id = rset.getLong(1);
if(id!=currentId) {
if(metricSet==null) {
metricSet = new LinkedHashSet<long[]>(widthAsInt);
metricSet.add(new long[]{
rset.getTimestamp(3).getTime(), rset.getLong(4), rset.getLong(5), rset.getLong(6), rset.getLong(7)
});
} else {
channel.write(request.response().setContent(new Object[]{id, stepWidth, metricSet})); // TODO: replace this with a pojo
metricSet = new LinkedHashSet<long[]>(widthAsInt);
metricSet.add(new long[]{
rset.getTimestamp(3).getTime(), rset.getLong(4), rset.getLong(5), rset.getLong(6), rset.getLong(7)
});
}
currentId = id;
} else {
metricSet.add(new long[]{
rset.getTimestamp(3).getTime(), rset.getLong(4), rset.getLong(5), rset.getLong(6), rset.getLong(7)
});
}
}
if(metricSet!=null && !metricSet.isEmpty()) {
channel.write(request.response().setContent(new Object[]{currentId, stepWidth, metricSet})); // TODO: replace this with a pojo
//gsonMarshaller.marshallToChannel(metricSet, channel);
}
// MetricData md = null;
// long currentId = -1;
// while(rset.next()) {
// long id = rset.getLong(H2TimeSeriesJSONDataService.METRIC_ID);
// if(id!=currentId) {
// if(md==null) {
// md = new MetricData(rset);
// response.add(md);
// } else {
// response.add(md);
// md = new MetricData(rset);
// }
// currentId = id;
// } else {
// md.extractData(rset);
// }
//
// }
// channel.write(request.response().setContent(response));
ElapsedTime et = SystemClock.endTimer();
lastElapsedLiveData.insert(et.elapsedNs);
} catch (Exception ex) {
error("Failed to execute livedata for ", ids, "]", ex);
channel.write(request.response().setContent(ex.toString()));
} finally {
if(rset!=null) try { rset.close(); } catch (Exception e) { /* No Op */ }
if(cs!=null) try { cs.close(); } catch (Exception e) { /* No Op */ }
if(conn!=null) try { conn.close(); } catch (Exception e) { /* No Op */ }
}
}
/**
* Returns the rolling average of the liveData query times in ms
* @return the rolling average of the liveData query times in ms
*/
@ManagedMetric(category="H2TimeSeriesJSONDataService", displayName="RollingLiveDataQueryTimeMs", metricType=MetricType.GAUGE, description="the rolling average of the liveData query times in ms")
public long getRollingLiveDataQueryTimeMs() {
return TimeUnit.MILLISECONDS.convert(getRollingLiveDataQueryTimeNs(), TimeUnit.NANOSECONDS);
}
/**
* Returns the rolling average of the liveData query times in ns
* @return the rolling average of the liveData query times in ns
*/
@ManagedMetric(category="H2TimeSeriesJSONDataService", displayName="RollingLiveDataQueryTimeNs", metricType=MetricType.GAUGE, description="the rolling average of the liveData query times in ns")
public long getRollingLiveDataQueryTimeNs() {
return lastElapsedLiveData.avg();
}
/**
* Returns the last liveData query time in ns
* @return the last liveData query time in ns, or -1 if there is no history
*/
@ManagedMetric(category="H2TimeSeriesJSONDataService", displayName="LastLiveDataQueryTimeNs", metricType=MetricType.GAUGE, description="the last liveData query time in ns")
public long getLastLiveDataQueryTimeNs() {
return lastElapsedLiveData.isEmpty() ? -1L : lastElapsedLiveData.get(0);
}
/**
* Returns the last liveData query time in ms
* @return the last liveData query time in ms, or -1 if there is no history
*/
@ManagedMetric(category="H2TimeSeriesJSONDataService", displayName="LastLiveDataQueryTimeMs", metricType=MetricType.GAUGE, description="the last liveData query time in ms")
public long getLastLiveDataQueryTimeMs() {
return TimeUnit.MILLISECONDS.convert(getLastLiveDataQueryTimeNs(), TimeUnit.NANOSECONDS);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.ServerComponent#resetMetrics()
*/
@Override
public void resetMetrics() {
super.resetMetrics();
lastElapsedLiveData.clear();
}
/**
* Sets the H2 datasource
* @param dataSource the dataSource to set
*/
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* Sets the H2Destination that provides the ts config
* @param h2Dest the H2Destination
*/
@Autowired(required=true)
public void setH2Dest(H2TimeSeriesDestination h2Dest) {
this.h2Dest = h2Dest;
}
/**
* Sets the GSON marshaller
* @param gsonMarshaller The GSON marshaller
*/
@Autowired(required=true)
public void setGsonMarshaller(GSONJSONMarshaller gsonMarshaller) {
this.gsonMarshaller = gsonMarshaller;
}
}