1515import gov .aps .jca .event .MonitorListener ;
1616import java .io .Closeable ;
1717import java .io .IOException ;
18+ import java .util .Date ;
1819import java .util .Set ;
1920import java .util .concurrent .*;
2021import java .util .concurrent .atomic .AtomicReference ;
2728 *
2829 * @author slominskir
2930 */
30- class ChannelMonitor implements Closeable {
31+ public class ChannelMonitor implements Closeable {
3132
3233 private static final Logger LOGGER = Logger .getLogger (ChannelMonitor .class .getName ());
3334
34- public static final int PEND_IO_MILLIS = 3000 ;
3535 public static final long TIMEOUT_MILLIS = 3000 ;
3636
3737 private volatile DBR lastDbr = null ;
38+
39+ /**
40+ * We don't use TIME typed DBR, so we just track 'received' timestamp (which may differ from IOC 'generated' timestamp)
41+ */
42+ private volatile Date lastTimestamp = null ;
43+
3844 private final AtomicReference <MonitorState > state = new AtomicReference <>(MonitorState .CONNECTING ); // We don't use CAJChannel.getConnectionState() because we want to still be "connecting" during enum label fetch
3945 private final AtomicReference <String []> enumLabels = new AtomicReference <>(null ); // volatile arrays are unsafe due to individual indicies so use AtomicReference instead
4046 private Monitor monitor = null ; // Keep track of singleton monitor to avoid creating multiple on reconnect after disconnect
@@ -75,10 +81,12 @@ public ChannelMonitor(String pv, CAJContext context, ScheduledExecutorService ti
7581 this .timeoutExecutor = timeoutExecutor ;
7682 this .callbackExecutor = callbackExecutor ;
7783
78- //LOGGER.log(Level.FINEST, "Creating channel: {0}", pv );
84+ long start = System . currentTimeMillis ( );
7985 channel = (CAJChannel ) context .createChannel (pv , new TimedChannelConnectionListener ());
80-
8186 context .flushIO ();
87+ long stop = System .currentTimeMillis ();
88+ float elapsedSeconds = (stop - start ) / 1000.0f ;
89+ LOGGER .log (Level .FINEST , "Created channel {0} in {1} seconds" , new Object []{pv , elapsedSeconds });
8290 }
8391
8492 /**
@@ -125,6 +133,18 @@ public int getListenerCount() {
125133 return listeners .size ();
126134 }
127135
136+ public MonitorState getState () {
137+ return state .get ();
138+ }
139+
140+ public String getLastValue () {
141+ return ChannelManager .getDbrValueAsString (lastDbr );
142+ }
143+
144+ public Date getLastTimestamp () {
145+ return lastTimestamp ;
146+ }
147+
128148 /**
129149 * Close the ChannelMonitor.
130150 *
@@ -136,8 +156,12 @@ public void close() throws IOException {
136156 if (channel != null ) {
137157 try {
138158 // channel.destroy(); // method is unsafe (can deadlock)
139- // so use context method instead
159+ // so use context method instead
160+ long start = System .currentTimeMillis ();
140161 context .destroyChannel (channel , false ); // Don't force because ChannelManager.get() also uses same context!
162+ long stop = System .currentTimeMillis ();
163+ float elapsedSeconds = (stop - start ) / 1000.0f ;
164+ LOGGER .log (Level .FINEST , "Closed Channel {0} in {1} seconds" , new Object []{pv , elapsedSeconds });
141165 } catch (CAException e ) {
142166 throw new IOException ("Unable to close channel" , e );
143167 }
@@ -207,7 +231,7 @@ public Void call() throws Exception {
207231 boolean connected = (state .get () == MonitorState .CONNECTED );
208232
209233 if (!connected ) {
210- // LOGGER.log(Level.WARNING , "Unable to connect to channel (timeout)");
234+ LOGGER .log (Level .FINE , "Unable to connect to channel {0} (timeout)" , pv );
211235
212236 notifyPvInfoAll (false );
213237 }
@@ -228,7 +252,7 @@ public void connectionChanged(ConnectionEvent ce) {
228252 callbackExecutor .submit (new Runnable (){
229253 @ Override
230254 public void run () {
231- // LOGGER.log(Level.FINEST, "Connection Changed - Connected: {0 }", ce.isConnected());
255+ LOGGER .log (Level .FINEST , "Channel {0} Connection Changed - Connected: {1 }" , new Object []{ pv , ce .isConnected ()} );
232256
233257 try {
234258 future .cancel (false ); // only needed for initial connection, on reconnects this will result in "false" return value, which is ignored
@@ -242,8 +266,7 @@ public void run() {
242266 handleRegularConnectionOrReconnect ();
243267 }
244268 } else {
245- //CAJChannel c = (CAJChannel) ce.getSource();
246- //LOGGER.log(Level.WARNING, "Unable to connect to channel: {0}", c.getName());
269+ LOGGER .log (Level .FINE , "Notifying clients of disconnect from channel: {0}" , pv );
247270
248271 state .set (MonitorState .DISCONNECTED );
249272 notifyPvInfoAll (false );
@@ -265,9 +288,10 @@ public void run() {
265288 */
266289 private void handleRegularConnectionOrReconnect () throws
267290 IllegalStateException , CAException {
268- // Only create monitor on first connect, afterwards reconnect uses same old monitor
291+ // Only create monitor on first connect, afterward reconnect uses same old monitor
269292 synchronized (this ) {
270293 if (monitor == null ) {
294+ LOGGER .log (Level .FINEST , "Creating {0} Channel Monitor" , pv );
271295 // We generally don't handle arrays,
272296 // except for BYTE[], where we assume a "long string"
273297 int count = 1 ;
@@ -276,6 +300,8 @@ private void handleRegularConnectionOrReconnect() throws
276300 count = channel .getElementCount ();
277301 monitor = channel .addMonitor (channel .getFieldType (), count , Monitor .VALUE , new ChannelMonitorListener ());
278302 context .flushIO ();
303+ } else {
304+ LOGGER .log (Level .FINEST , "Reusing existing {0} Channel Monitor" , pv );
279305 }
280306 }
281307
@@ -291,13 +317,14 @@ private void handleRegularConnectionOrReconnect() throws
291317 * @throws CAException If unable to initialize
292318 */
293319 private void handleEnumConnection () throws IllegalStateException , CAException {
320+ LOGGER .log (Level .FINEST , "Fetching enum labels for {0}" , pv );
294321 channel .get (DBRType .LABELS_ENUM , 1 , new TimedChannelEnumGetListener ());
295322
296323 context .flushIO ();
297324 }
298325
299326 /**
300- * A private inner inner class to respond to an enum label caget.
327+ * A private inner class to respond to an enum label caget.
301328 */
302329 private class TimedChannelEnumGetListener implements GetListener {
303330
@@ -352,6 +379,7 @@ public void monitorChanged(MonitorEvent me) {
352379 DBR dbr = me .getDBR ();
353380
354381 lastDbr = dbr ;
382+ lastTimestamp = new Date ();
355383
356384 // Make sure handlers do not call back into CA lib on this callback thread.
357385 // We could call in separate thread, but that's costly and then you must
0 commit comments