/**
* 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.wsclient;
import java.net.SocketAddress;
import java.net.URI;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicLong;
import org.helios.apmrouter.metric.AgentIdentity;
import org.helios.apmrouter.metric.MetricURIBuilder;
import org.helios.apmrouter.metric.MetricURIBuilder.SubscriptionType;
import org.helios.apmrouter.subscription.EmptyMetricURISubscriptionEventListener;
import org.helios.apmrouter.subscription.MetricURIEvent;
import org.helios.apmrouter.subscription.MetricURISubscriptionEventListener;
import org.helios.apmrouter.trace.ITracer;
import org.helios.apmrouter.trace.TracerFactory;
import org.helios.apmrouter.util.SimpleLogger;
import org.helios.apmrouter.util.URLHelper;
import org.json.JSONObject;
/**
* <p>Title: WebSocketAgent</p>
* <p>Description: Agent implementation using websockets.</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.wsclient.WebSocketAgent</code></p>
*/
public class WebSocketAgent implements WebSocketEventListener {
/** The WebSocketClient this agent will use to comm with the server */
protected final WebSocketClient wsClient;
/** The web sock listeners registered by this agent */
protected final Set<WebSocketEventListener> webSockListeners = new CopyOnWriteArraySet<WebSocketEventListener>();
/** A set of subscribed global MetricURISubscriptionEventListeners */
protected final Set<MetricURISubscriptionEventListener> subListeners = new CopyOnWriteArraySet<MetricURISubscriptionEventListener>();
/** A map of arrays of MetricURISubscriptionEventListeners keyed by the request id */
protected final Map<Long, MetricURISubscriptionEventListener[]> reqListeners = new ConcurrentHashMap<Long, MetricURISubscriptionEventListener[]>();
/** A metricURI to request id mapping */
protected final Map<URI, Long> metricURIRequestIds = new ConcurrentHashMap<URI, Long>();
/** Empty listener array const */
protected static final MetricURISubscriptionEventListener[] EMPTY_LISTENER_ARR = {};
public static void main(String[] args) {
log("WebSocketAgent Test");
WebSocketAgent agent = null;
try {
agent = WebSocketAgent.newInstance("ws://localhost:8087/ws");
log("Connected to [" + agent.getWebSocketURI() + "]");
agent.wsClient.setSynchRequestTimeout(5000);
// int loop = 10000;
// int dot = loop/100;
// for(int i = 0; i < loop; i++) {
// agent.getMetrics("DefaultDomain/njw810/APMRouterServer/platform=JVM/category=cpu");
// System.out.print(".");
// if(i%dot==0) System.out.println("--[ " + i);
// }
// log("Warmup Complete");
// SystemClock.startTimer();
// for(int i = 0; i < loop; i++) {
// agent.getMetrics("DefaultDomain/njw810/APMRouterServer/platform=JVM/category=cpu");
// System.out.print(".");
// if(i%dot==0) System.out.println("--[ " + i);
// }
// ElapsedTime et = SystemClock.endTimer();
// log(et + " Avg " + et.avgMs(loop) + " ms. ") ;
String host = AgentIdentity.ID.getHost();
String agentName = AgentIdentity.ID.getAgentName();
log("AGENT: -----[ " + agentName + "@" + host + "]");
ITracer tracer = TracerFactory.getTracer();
URI metricURI = MetricURIBuilder.newBuilder("*", host, agentName).subType(SubscriptionType.NEW_METRIC).namespace("foo", "bar").build();
final Set<Object> results = new CopyOnWriteArraySet<Object>();
final AtomicLong st = new AtomicLong();
agent.subscribeMetricURISynch(metricURI, new EmptyMetricURISubscriptionEventListener(){
@Override
public void onNewMetric(Object newMetric) {
long elapsed = System.currentTimeMillis()-st.get();
log("Received New Metric after [" + elapsed + "] ms. :" + newMetric);
results.add(newMetric);
}
});
//Thread.sleep(3000);
log("Listener Registered");
for(int x = 0; x < 100; x++) {
st.set(System.currentTimeMillis());
String mname = "C" + x;
tracer.traceCounter(0, mname, "foo", "bar");
log("Traced [" + mname + "]");
Thread.sleep(5000);
}
log("Done");
} catch (Exception ex) {
ex.printStackTrace(System.err);
} finally {
if(agent!=null) {
try { agent.wsClient.shutdown(); } catch (Exception ex) {}
}
}
// agent.subscribeMetricURIAsynch(URLHelper.toURI("DefaultDomain/njw810/APMRouterServer/platform=JVM/category=cpu"), new EmptyMetricURISubscriptionEventListener(){
// final AtomicLong dataCount = new AtomicLong();
// @Override
// public void onMetricData(Object metricData) {
// long cnt = dataCount.incrementAndGet();
// if(cnt%5==0) {
// log("Metrics Received:" + cnt);
// }
// }});
// final Thread t = Thread.currentThread();
// agent.wsClient.channel.getCloseFuture().addListener(new ChannelFutureListener() {
// @Override
// public void operationComplete(ChannelFuture future) throws Exception {
// t.interrupt();
// }
// });
// try { Thread.currentThread().join(); } catch (Exception ex) {}
// log("Exiting.....");
// //agent.close();
}
public static void log(Object msg) {
System.out.println(msg);
}
/**
* Closes this agent's WebSocket connection
*/
public void close() {
wsClient.close();
}
/**
* Returns the agent's WebSocket URI
* @return the agent's WebSocket URI
*/
public URI getWebSocketURI() {
return wsClient.getWebSocketURI();
}
/**
* Registers a {@link MetricURISubscriptionEventListener}
* @param listener the listener to register
*/
public void registerMetricURISubscriptionEventListener(MetricURISubscriptionEventListener listener) {
if(listener!=null) {
subListeners.add(listener);
}
}
/**
* Unregisters a {@link MetricURISubscriptionEventListener}
* @param listener the listener to unregister
*/
public void unRegisterMetricURISubscriptionEventListener(MetricURISubscriptionEventListener listener) {
if(listener!=null) {
subListeners.remove(listener);
}
}
/**
* Returns a WebSocketAgent for the passed web socket URI
* @param wsUri the web socket URI to connect to
* @return a WebSocketAgent connected to the passed web socket URI
*/
public static WebSocketAgent newInstance(URI wsUri) {
return new WebSocketAgent(WebSocketClient.getInstance(wsUri));
}
/**
* Returns a WebSocketAgent for the passed web socket URI
* @param wsUri the web socket URI to connect to
* @return a WebSocketAgent connected to the passed web socket URI
*/
public static WebSocketAgent newInstance(CharSequence wsUri) {
return new WebSocketAgent(WebSocketClient.getInstance(URLHelper.toURI(wsUri)));
}
/**
* Adds a web socket event listener
* @param listener the listener to add
*/
public void addWebSocketEventListener(WebSocketEventListener listener) {
if(listener!=null) {
wsClient.addWebSocketEventListener(listener);
webSockListeners.add(listener);
}
}
/**
* Removes a web socket event listener
* @param listener the listener to remove
*/
public void removeWebSocketEventListener(WebSocketEventListener listener) {
if(listener!=null) {
wsClient.removeWebSocketEventListener(listener);
webSockListeners.remove(listener);
}
}
/**
* Removes all the web sock listeners registered from this agent
*/
protected void removeAllWebSocketEventListeners() {
for(WebSocketEventListener listener: webSockListeners) {
wsClient.removeWebSocketEventListener(listener);
}
webSockListeners.clear();
}
//212 748 4138
/**
* Creates a new WebSocketAgent
* @param wsClient The WebSocketClient this agent will use to comm with the server
*/
protected WebSocketAgent(WebSocketClient wsClient) {
super();
this.wsClient = wsClient;
this.wsClient.addWebSocketEventListener(this);
}
/**
* Returns the session id assigned to a web-sock connection by the server
* @return the session id assigned to a web-sock connection by the server
*/
public String getSessionId() {
return wsClient.getSessionId();
}
/**
* Sends a MetricURI subscription request
* @param asynch true for an asynchronous call, false for a synchronous call
* @param metricURI The MetricURI to subscribe to
* @param listeners The listeners that will be invoked on with incoming data for this subscription.
* If empty, the responses will be routed to the agent's global event listeners
* @return the subscription response if synch, otherwise, if asynch, returns null
*/
public JSONObject subscribeMetricURI(final boolean asynch, URI metricURI, MetricURISubscriptionEventListener...listeners) {
JsonRequest request = JsonRequestBuilder.newBuilder()
.put("svc", "catalog")
.put("op", "submetricuri")
.putMapPair("args", "uri", metricURI.toASCIIString())
.build();
if(asynch) {
registerURISub(request.getRequestId(), metricURI, listeners);
}
registerURISub(request.getRequestId(), metricURI, listeners);
JSONObject response = wsClient.sendRequest(asynch, request);
if(!asynch) {
registerURISub(request.getRequestId(), metricURI, listeners);
return response;
}
return null;
}
/**
* Cancels a MetricURI subscription
* @param asynch true for an asynchronous call, false for a synchronous call
* @param metricURI The MetricURI to subscribe to
*/
public void unsubscribeMetricURI(final boolean asynch, URI metricURI) {
JsonRequest request = JsonRequestBuilder.newBuilder()
.put("svc", "catalog")
.put("op", "unsubmetricuri")
.putMapPair("args", "uri", metricURI.toASCIIString())
.build();
if(asynch) {
registerURISub(request.getRequestId(), metricURI);
}
wsClient.sendRequest(asynch, request);
if(!asynch) {
registerURISub(request.getRequestId(), metricURI);
}
}
/**
* Asynchronously cancels a MetricURI subscription
* @param metricURI The MetricURI to subscribe to
*/
public void unsubscribeMetricURI(URI metricURI) {
unsubscribeMetricURI(true, metricURI);
}
/**
* Subscribes to the passed MetricURI asynchronously
* @param metricURI The MetricURI to subscribe to
* @param listeners The listeners that will be invoked on with incoming data for this subscription.
* If empty, the responses will be routed to the agent's global event listeners
*/
public void subscribeMetricURIAsynch(URI metricURI, MetricURISubscriptionEventListener...listeners) {
subscribeMetricURI(true, metricURI, listeners);
}
/**
* Returns the metrics for the passed URI
* @param metricURI The metric uri
* @return A json object containing the metrics
*/
public JSONObject getMetrics(CharSequence metricURI) {
if(metricURI==null) throw new IllegalArgumentException("The passed metricURI was null", new Throwable());
return getMetrics(URLHelper.toURI(metricURI));
}
/**
* Returns the metrics for the passed URI
* @param metricURI The metric uri
* @return A json object containing the metrics
*/
public JSONObject getMetrics(URI metricURI) {
if(metricURI==null) throw new IllegalArgumentException("The passed metricURI was null", new Throwable());
JsonRequest request = JsonRequestBuilder.newBuilder()
.put("svc", "catalog")
.put("op", "resolvemetricuri")
.putMapPair("args", "uri", metricURI.toASCIIString())
.build();
return wsClient.sendRequest(false, request);
}
/**
* Subscribes to the passed MetricURI synchronously
* @param metricURI The MetricURI to subscribe to
* @param listeners The listeners that will be invoked on with incoming data for this subscription.
* If empty, the responses will be routed to the agent's global event listeners
* @return the subscription response
*/
public JSONObject subscribeMetricURISynch(URI metricURI, MetricURISubscriptionEventListener...listeners) {
return subscribeMetricURI(false, metricURI, listeners);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.wsclient.WebSocketEventListener#onConnect(java.net.SocketAddress)
*/
@Override
public void onConnect(SocketAddress remoteAddress) {
SimpleLogger.info("Connected to [", remoteAddress, "]");
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.wsclient.WebSocketEventListener#onClose(java.net.SocketAddress)
*/
@Override
public void onClose(SocketAddress remoteAddress) {
SimpleLogger.info("Closed [", remoteAddress, "]");
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.wsclient.WebSocketEventListener#onError(java.net.SocketAddress, java.lang.Throwable)
*/
@Override
public void onError(SocketAddress remoteAddress, Throwable t) {
SimpleLogger.error("WS error from [", remoteAddress, "]", t);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.wsclient.WebSocketEventListener#onMessage(java.net.SocketAddress, org.json.JSONObject)
*/
@Override
public void onMessage(SocketAddress remoteAddress, JSONObject message) {
try {
//SimpleLogger.info(message.toString(2));
long rerid = message.getLong("rerid");
MetricURISubscriptionEventListener[] listeners = getListenersForRerid(rerid);
MetricURIEvent event = MetricURIEvent.forEvent(message.getString("t"));
switch(event) {
case DATA:
for(MetricURISubscriptionEventListener listener: listeners) {
listener.onMetricData(message);
}
break;
case NEW_METRIC:
for(MetricURISubscriptionEventListener listener: listeners) {
listener.onNewMetric(message);
}
break;
case STATE_CHANGE_ENTRY:
for(MetricURISubscriptionEventListener listener: listeners) {
listener.onMetricStateChangeEntry(message);
}
break;
case STATE_CHANGE_EXIT:
for(MetricURISubscriptionEventListener listener: listeners) {
listener.onMetricStateChangeExit(message);
}
break;
case STATE_CHANGE:
for(MetricURISubscriptionEventListener listener: listeners) {
listener.onMetricStateChange(message);
}
break;
default:
break;
}
} catch (Exception ex) {
SimpleLogger.error("Failed to process WebSocket JSON Response [", message, "]", ex);
}
}
/**
* Returns the listener array for the passed rerid
* @param rerid The in-reference-to request id
* @return an array of listeners which will be empty if the rerid was not found
*/
protected MetricURISubscriptionEventListener[] getListenersForRerid(long rerid) {
MetricURISubscriptionEventListener[] listeners = reqListeners.get(rerid);
return listeners==null ? subListeners.toArray(new MetricURISubscriptionEventListener[0]) : listeners;
}
/**
* Registers a metricURI subscription
* @param rid The request id
* @param metricUri The metric URI subscribed to
* @param listeners The listeners to be registered
*/
protected void registerURISub(long rid, URI metricUri, MetricURISubscriptionEventListener...listeners) {
metricURIRequestIds.put(metricUri, rid);
final MetricURISubscriptionEventListener[] _listeners = (listeners==null || listeners.length==0) ? EMPTY_LISTENER_ARR : listeners;
reqListeners.put(rid, _listeners);
}
/**
* Cleans up a terminated metric URI subscription
* @param metricUri The metric URI subscription to clean up
*/
protected void cleanupURISub(URI metricUri) {
Long rid = metricURIRequestIds.remove(metricUri);
if(rid!=null) {
reqListeners.remove(rid);
}
}
/*
*
* @JSONRequestHandler(name="submetricuri")
public void subscribeMetricURI(JsonRequest request, Channel channel) {
String muri = request.getArgument("uri");
try {
JSONObject request = new JSONObject();
int reqId = client.requestSerial.incrementAndGet();
request.put("rid", reqId);
request.put("t", "req");
request.put("svc", "sub");
request.put("op", "start");
JSONObject ags = new JSONObject();
ags.put("es", "jmx");
ags.put("esn", "service:jmx:local://DefaultDomain");
ags.put("f", "org.helios.apmrouter.session:service=SharedChannelGroup");
request.put("args", ags);
*/
}