/**
* Monitor.java
*
* Copyright 2012 Niolex, Inc.
*
* Niolex licenses this file to you 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.
*/
package org.apache.niolex.commons.remote;
import static org.apache.niolex.commons.remote.ConnectionWorker.endl;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.apache.niolex.commons.codec.StringUtil;
import org.apache.niolex.commons.collection.CircularList;
import org.apache.niolex.commons.concurrent.ConcurrentUtil;
import org.apache.niolex.commons.concurrent.ThreadUtil;
import org.apache.niolex.commons.util.DateTimeUtil;
/**
* Monitor the system internal status.
*
* User application can add the internal status as KV pair into this monitor,
* and we will attach a time stamp with every value and store the latest M
* items in memory.
*
* @author <a href="mailto:xiejiyun@gmail.com">Xie, Jiyun</a>
* @version 1.0.5, $Date: 2012-11-22$
*/
public class Monitor implements Runnable {
/**
* Store all the monitor informations.
*/
private final ConcurrentHashMap<String, CircularList<MonItem>> dataMap =
new ConcurrentHashMap<String, CircularList<MonItem>>();
/**
* Store all the connections need real time update.
*/
private final ConcurrentHashMap<String, ConcurrentLinkedQueue<OutputStream>> realtimeMap =
new ConcurrentHashMap<String, ConcurrentLinkedQueue<OutputStream>>();
/**
* Store all the messages pending to send to real time client.
*/
private final LinkedBlockingQueue<QueItem> realtimeQueue = new LinkedBlockingQueue<QueItem>();
/**
* The max number of old items need to be stored.
*/
private final int maxOldItems;
/**
* The internal thread to send real time data.
*/
private final Thread thread;
/**
* The internal working status.
*/
private volatile boolean isWorking;
/**
* Constructs a new monitor with the specified max old items.
*
* @param maxOldItems the max number of old items need to be stored
*/
public Monitor(int maxOldItems) {
super();
this.maxOldItems = maxOldItems;
this.isWorking = true;
this.thread = new Thread(this);
this.thread.setDaemon(true);
this.thread.start();
}
/**
* Add a new monitor value into the internal map.
*
* @param key the monitor key
* @param value the current value
*/
public void addValue(String key, int value) {
CircularList<MonItem> list = dataMap.get(key);
if (list == null) {
list = ConcurrentUtil.initMap(dataMap, key, new CircularList<MonItem>(maxOldItems));
}
MonItem e = new MonItem(value);
list.add(e);
// Process all the real time connections.
ConcurrentLinkedQueue<OutputStream> que = realtimeMap.get(key);
if (que != null && !que.isEmpty()) {
realtimeQueue.add(new QueItem(key, e, que));
}
}
/**
* Process all the messages.
*
* Override super method
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
while (isWorking) {
// Wait on the queue for an item.
try {
QueItem q = realtimeQueue.take();
// Process all the real time connections.
if (!q.que.isEmpty()) {
StringBuilder sb = new StringBuilder();
sb.append(q.key).append('[').append(q.time).append("]=").append(q.value);
sb.append('\n');
byte[] arr = StringUtil.strToUtf8Byte(sb.toString());
Iterator<OutputStream> iter = q.que.iterator();
while (iter.hasNext()) {
try {
OutputStream o = iter.next();
o.write(arr);
} catch (Exception e) {
iter.remove();
}
}
}
} catch (Exception e) {}
}
}
/**
* A connection use this method to monitor the status of the specified key.
*
* @param out the output stream to write results
* @param key the key to monitor
* @param parameter the parameter, with the following options:<pre>
* Option Meaning
* Watch Watch the historical and real time statistics.
* Real Only need the real time statistics.
* History (Default)Only need the historical statistics.</pre>
* @throws IOException if I/O related error occurred
*/
public void doMonitor(OutputStream out, String key, String parameter) throws IOException {
switch (parameter.charAt(0)) {
case 'w':
case 'W':
// Watch the historical [and] real time statistics.
printHistorical(out, key);
case 'r':
case 'R':
// Only need the real time statistics.
attachReadTime(out, key);
break;
default:
// (Default)Only need the historical statistics.
printHistorical(out, key);
break;
}
}
/**
* Print the historical information into this output stream.
*
* @param out the output stream to write results
* @param key the key to monitor
*/
private void printHistorical(OutputStream out, String key) throws IOException {
CircularList<MonItem> set = dataMap.get(key);
if (set == null) {
out.write(StringUtil.strToUtf8Byte("The Key not found in Monitor." + endl()));
} else {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < set.size(); ++i) {
MonItem item = set.get(i);
sb.append(key).append('[').append(DateTimeUtil.formatDate2DateTimeStr(item.time));
sb.append(']').append('=').append(item.value).append(endl());
}
out.write(StringUtil.strToUtf8Byte(sb.toString()));
}
}
/**
* Attach the output stream for real time monitor status update.
*
* @param out the output stream to write results
* @param key the key to monitor
*/
private void attachReadTime(OutputStream out, String key) {
ConcurrentLinkedQueue<OutputStream> que = realtimeMap.get(key);
if (que == null) {
que = ConcurrentUtil.initMap(realtimeMap, key, new ConcurrentLinkedQueue<OutputStream>());
}
que.add(out);
}
/**
* Stop the internal thread.
*/
public void stop() {
this.isWorking = false;
this.thread.interrupt();
ThreadUtil.join(thread);
}
/**
* The internal monitor item structure.
*
* @author <a href="mailto:xiejiyun@gmail.com">Xie, Jiyun</a>
* @version 1.0.5, $Date: 2012-11-23$
*/
public static class MonItem {
final long time;
final int value;
public MonItem(int value) {
super();
this.time = System.currentTimeMillis();
this.value = value;
}
}
/**
* The internal queue item structure.
*
* @author <a href="mailto:xiejiyun@gmail.com">Xie, Jiyun</a>
* @version 1.0.5, $Date: 2012-12-3$
*/
public static class QueItem {
final String key;
final long time;
final int value;
final ConcurrentLinkedQueue<OutputStream> que;
public QueItem(String key, MonItem e, ConcurrentLinkedQueue<OutputStream> que) {
super();
this.key = key;
this.time = e.time;
this.value = e.value;
this.que = que;
}
}
}