Skip to content

Commit 49a76d8

Browse files
authored
Logging improvements (#17)
Fixes #16
1 parent 8a13828 commit 49a76d8

File tree

5 files changed

+110
-34
lines changed

5 files changed

+110
-34
lines changed

src/main/java/org/jlab/epics2web/controller/WebConsole.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import javax.servlet.http.HttpServletResponse;
1111
import javax.websocket.Session;
1212
import org.jlab.epics2web.Application;
13+
import org.jlab.epics2web.epics.ChannelMonitor;
1314
import org.jlab.epics2web.websocket.WebSocketSessionManager;
1415
import org.jlab.epics2web.epics.ChannelManager;
1516

@@ -38,7 +39,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response)
3839
throws ServletException, IOException {
3940

4041

41-
Map<String, Integer> monitorMap = channelManager.getMonitorMap();
42+
Map<String, ChannelMonitor> monitorMap = channelManager.getMonitorMap();
4243
Map<Session, Set<String>> clientMap = sessionManager.getClientMap();
4344

4445
request.setAttribute("monitorMap", monitorMap);

src/main/java/org/jlab/epics2web/epics/ChannelManager.java

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.jlab.util.LockAcquisitionTimeoutException;
1010

1111
import java.io.IOException;
12+
import java.io.UnsupportedEncodingException;
1213
import java.util.ArrayList;
1314
import java.util.Collections;
1415
import java.util.HashMap;
@@ -68,6 +69,65 @@ public ChannelManager(CAJContext context, ScheduledExecutorService timeoutExecut
6869
this.callbackExecutor = callbackExecutor;
6970
}
7071

72+
public static String getDbrValueAsString(DBR dbr) {
73+
String strValue = null;
74+
if(dbr == null){
75+
strValue = "";
76+
} else if (dbr.isDOUBLE()) {
77+
double value = ((gov.aps.jca.dbr.DOUBLE) dbr).getDoubleValue()[0];
78+
if (Double.isFinite(value)) {
79+
strValue = String.valueOf(value);
80+
} else if (Double.isNaN(value)) {
81+
strValue = "NaN";
82+
} else {
83+
strValue = "Infinity";
84+
}
85+
} else if (dbr.isFLOAT()) {
86+
float value = ((gov.aps.jca.dbr.FLOAT) dbr).getFloatValue()[0];
87+
if (Float.isFinite(value)) {
88+
strValue = String.valueOf(value);
89+
} else if (Float.isNaN(value)) {
90+
strValue = "NaN";
91+
} else {
92+
strValue = "Infinity";
93+
}
94+
} else if (dbr.isINT()) {
95+
int value = ((gov.aps.jca.dbr.INT) dbr).getIntValue()[0];
96+
strValue = String.valueOf(value);
97+
} else if (dbr.isSHORT()) {
98+
short value = ((gov.aps.jca.dbr.SHORT) dbr).getShortValue()[0];
99+
strValue = String.valueOf(value);
100+
} else if (dbr.isENUM()) {
101+
short value = ((gov.aps.jca.dbr.ENUM) dbr).getEnumValue()[0];
102+
strValue = String.valueOf(value);
103+
} else if (dbr.isBYTE()) {
104+
byte[] value = ((gov.aps.jca.dbr.BYTE) dbr).getByteValue();
105+
int len = value.length;
106+
if (len > 1) {
107+
// epics2web generally doesn't handle arrays,
108+
// but for BYTE[] assume that data is really "long string".
109+
// Text ends at first '\0' or end of array
110+
for (int i=0; i<len; ++i)
111+
if (value[i] == 0) {
112+
len = i;
113+
break;
114+
}
115+
try {
116+
strValue = new String(value, 0, len, "UTF-8");
117+
} catch(UnsupportedEncodingException e) {
118+
throw new RuntimeException("JVM doesn't support UTF-8!");
119+
}
120+
}
121+
else
122+
strValue = String.valueOf(value[0]);
123+
} else {
124+
String value = ((gov.aps.jca.dbr.STRING) dbr).getStringValue()[0];
125+
strValue = value;
126+
}
127+
128+
return strValue;
129+
}
130+
71131
public void addValueToJSON(JsonObjectBuilder builder, DBR dbr) {
72132
try {
73133
if (dbr.isDOUBLE()) {
@@ -309,14 +369,9 @@ public Map<String, Exception> removeAll(PvListener listener) {
309369
*
310370
* @return The PV to monitor map
311371
*/
312-
public Map<String, Integer> getMonitorMap() {
313-
Map<String, Integer> map;
314-
Map<String, ChannelMonitor> copy = new HashMap<>(monitorMap); // First copy map so that concurrent changes won't bother us
315-
map = new HashMap<>();
316-
for (String pv : copy.keySet()) {
317-
map.put(pv, copy.get(pv).getListenerCount());
318-
}
319-
return map;
372+
public Map<String, ChannelMonitor> getMonitorMap() {
373+
// Really want readonly copy version here, but too lazy to make immutable ChannelMonitor
374+
return new HashMap<>(monitorMap);
320375
}
321376

322377
/**
@@ -326,8 +381,6 @@ public Map<String, Integer> getMonitorMap() {
326381
* @return The listener to PVs map
327382
*/
328383
public Map<PvListener, Set<String>> getClientMap() {
329-
Map<PvListener, Set<String>> map;
330-
map = Collections.unmodifiableMap(clientMap);
331-
return map;
384+
return Collections.unmodifiableMap(clientMap);
332385
}
333386
}

src/main/java/org/jlab/epics2web/epics/ChannelMonitor.java

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import gov.aps.jca.event.MonitorListener;
1616
import java.io.Closeable;
1717
import java.io.IOException;
18+
import java.util.Date;
1819
import java.util.Set;
1920
import java.util.concurrent.*;
2021
import java.util.concurrent.atomic.AtomicReference;
@@ -27,14 +28,19 @@
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

src/main/resources/logging.properties

Lines changed: 0 additions & 10 deletions
This file was deleted.

src/main/webapp/WEB-INF/views/console.jsp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,18 @@
6363
<thead>
6464
<tr>
6565
<th>PV</th>
66+
<th>State</th>
67+
<th>Last Update</th>
6668
<th>Clients</th>
6769
</tr>
6870
</thead>
6971
<tbody>
7072
<c:forEach items="${monitorMap}" var="monitor">
7173
<tr>
7274
<td><c:out value="${monitor.key}"/></td>
73-
<td><c:out value="${monitor.value}"/></td>
75+
<td><c:out value="${monitor.value.state}"/></td>
76+
<td><fmt:formatDate pattern="MMM dd yyy HH:mm:ss" value="${monitor.value.lastTimestamp}"/> - <c:out value="${monitor.value.lastValue}"/></td>
77+
<td><c:out value="${monitor.value.listenerCount}"/></td>
7478
</tr>
7579
</c:forEach>
7680
</tbody>

0 commit comments

Comments
 (0)