Skip to content

Commit d321d45

Browse files
authored
Merge pull request #2282 from ControlSystemStudio/pva_beacons
PVA beacons
2 parents 5b9fc41 + 3ba5f36 commit d321d45

File tree

12 files changed

+582
-104
lines changed

12 files changed

+582
-104
lines changed

core/pva/pvasearchmonitor

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/sh
2+
3+
JAR=`echo target/core-pva*.jar`
4+
if [ -r "$JAR" ]
5+
then
6+
# Echo use jar file
7+
java -cp $JAR org.epics.pva.server.PVASearchMonitorMain "$@"
8+
else
9+
# Use build output
10+
java -cp target/classes org.epics.pva.server.PVASearchMonitorMain "$@"
11+
fi

core/pva/src/main/java/org/epics/pva/PVASettings.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2019-2021 Oak Ridge National Laboratory.
2+
* Copyright (c) 2019-2022 Oak Ridge National Laboratory.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -171,6 +171,33 @@ public class PVASettings
171171
/** Maximum number of array elements shown when printing data */
172172
public static int EPICS_PVA_MAX_ARRAY_FORMATTING = 256;
173173

174+
/** Range of beacon periods in seconds recognized as "fast, new" beacons
175+
* that re-start searches for disconnected channels.
176+
*
177+
* <p>The first beacon received from a server has a nominal period of 0.
178+
* Newly started servers send beacons every ~15 seconds.
179+
* After about 5 minutes they relax the beacon period to ~180 seconds.
180+
*
181+
* <p>The criteria for restarting searches is min &lt;= period &lt; max.
182+
* A min..max range of 0..30 recognizes the initial and ~15 second beacons
183+
* as new.
184+
* A range of 1..30 would ignore the initial beacon but re-start searches
185+
* on 15 sec beacons.
186+
* A min..max range of 0..0 would disable the re-start of searches.
187+
*/
188+
public static int EPICS_PVA_FAST_BEACON_MIN = 0,
189+
EPICS_PVA_FAST_BEACON_MAX = 30;
190+
191+
/** Maximum age of beacons in seconds
192+
*
193+
* <p>Beacon information older than this,
194+
* i.e. beacons that have not been received again
195+
* for this time are removed from the cache to preserve
196+
* memory. If they re-appear, they will be considered
197+
* new beacons
198+
*/
199+
public static int EPICS_PVA_MAX_BEACON_AGE = 300;
200+
174201
static
175202
{
176203
EPICS_PVA_ADDR_LIST = get("EPICS_PVA_ADDR_LIST", EPICS_PVA_ADDR_LIST);
@@ -183,6 +210,9 @@ public class PVASettings
183210
EPICS_PVA_CONN_TMO = get("EPICS_PVA_CONN_TMO", EPICS_PVA_CONN_TMO);
184211
EPICS_PVA_MAX_ARRAY_FORMATTING = get("EPICS_PVA_MAX_ARRAY_FORMATTING", EPICS_PVA_MAX_ARRAY_FORMATTING);
185212
EPICS_PVA_SEND_BUFFER_SIZE = get("EPICS_PVA_SEND_BUFFER_SIZE", EPICS_PVA_SEND_BUFFER_SIZE);
213+
EPICS_PVA_FAST_BEACON_MIN = get("EPICS_PVA_FAST_BEACON_MIN", EPICS_PVA_FAST_BEACON_MIN);
214+
EPICS_PVA_FAST_BEACON_MAX = get("EPICS_PVA_FAST_BEACON_MAX", EPICS_PVA_FAST_BEACON_MAX);
215+
EPICS_PVA_MAX_BEACON_AGE = get("EPICS_PVA_MAX_BEACON_AGE", EPICS_PVA_MAX_BEACON_AGE);
186216
}
187217

188218
/** Get setting from property, environment or default
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2022 Oak Ridge National Laboratory.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
******************************************************************************/
8+
9+
package org.epics.pva.client;
10+
11+
import java.net.InetSocketAddress;
12+
import java.time.Duration;
13+
import java.time.Instant;
14+
import java.util.Iterator;
15+
import java.util.Map.Entry;
16+
import java.util.concurrent.ConcurrentHashMap;
17+
import java.util.logging.Level;
18+
import java.util.logging.Logger;
19+
20+
import org.epics.pva.PVASettings;
21+
import org.epics.pva.server.Guid;
22+
23+
/** Track received beacons
24+
* @author Kay Kasemir
25+
*/
26+
@SuppressWarnings("nls")
27+
public class BeaconTracker
28+
{
29+
/** Logger for beacon info */
30+
public static final Logger logger = Logger.getLogger(BeaconTracker.class.getPackage().getName());
31+
32+
/** Info about one tracked beacon
33+
*
34+
* Server is identified by GUID
35+
*/
36+
private static class BeaconInfo
37+
{
38+
/** Server's unique ID */
39+
final Guid guid;
40+
41+
/** Address that sent beacon, likely server's UDP address */
42+
InetSocketAddress address;
43+
44+
/** Change increment reported by server */
45+
int changes;
46+
47+
/** Time of last beacon */
48+
Instant last = null;
49+
50+
/** Period of beacon before the last one */
51+
long previous_period = 0;
52+
53+
/** Period (seconds) of last beacon */
54+
long period = 0;
55+
56+
BeaconInfo(final Guid guid, final InetSocketAddress address, final int changes)
57+
{
58+
this.guid = guid;
59+
this.address = address;
60+
this.changes = changes;
61+
}
62+
63+
/** Update beacon period because we just received another one
64+
* @param now Time for which to update the beacon period
65+
* @return Does this indicate a "new" beacon?
66+
*/
67+
boolean updatePeriod(final Instant now)
68+
{
69+
if (last == null)
70+
period = 0;
71+
else
72+
period = Duration.between(last, now).getSeconds();
73+
last = now;
74+
75+
// Is this a server we see for the first time (period 0)
76+
// or one started recently (period ~15 sec)?
77+
final boolean is_new_server = period >= PVASettings.EPICS_PVA_FAST_BEACON_MIN &&
78+
period < PVASettings.EPICS_PVA_FAST_BEACON_MAX;
79+
// .. and we see it for the first time?
80+
final boolean was_new_server = previous_period > 0 &&
81+
previous_period < PVASettings.EPICS_PVA_FAST_BEACON_MAX;
82+
previous_period = period;
83+
// -> That would be a reason to re-start searches
84+
//
85+
// Servers we see for the first time (period 0) may in fact be
86+
// long running servers that only send beacons every 180 s.
87+
// They will be reported as "new", but this happens likely
88+
// when we just started searching for a channel,
89+
// so its search interval has not settled, yet, and will
90+
// thus not be 'boosted', avoiding an early burst of searches
91+
// as we see each server's beacons for the first time.
92+
return is_new_server && !was_new_server;
93+
}
94+
}
95+
96+
/** Map of server IDs to beacon info */
97+
private final ConcurrentHashMap<Guid, BeaconInfo> beacons = new ConcurrentHashMap<>();
98+
99+
/** Last time the 'beacons' were cleaned of orphaned entries */
100+
private Instant last_cleanup = Instant.now();
101+
102+
/** Check if a received beacon indicates a new server landscape
103+
* @param guid Globally unique ID of the server
104+
* @param server Server that sent a beacon
105+
* @param changes Change count, increments and rolls over as server adds new channels
106+
*
107+
* @return Should we restart searches for unresolved PVs?
108+
*/
109+
public boolean check(final Guid guid, final InetSocketAddress server, final int changes)
110+
{
111+
// Only assemble detail of new beacon if FINE logging is enabled
112+
String detail = logger.isLoggable(Level.FINE)
113+
? " *"
114+
: null;
115+
116+
final Instant now = Instant.now();
117+
118+
// Locate or create beacon info for that GUID
119+
final BeaconInfo info = beacons.computeIfAbsent(guid, s -> new BeaconInfo(guid, server, changes));
120+
121+
// Does period indicate a new server?
122+
boolean something_changed = info.updatePeriod(now);
123+
if (something_changed && detail != null)
124+
detail += info.period <= 0 ? " (new beacon)" : " (fast period)";
125+
// Does server report from new address?
126+
if (! server.equals(info.address))
127+
{
128+
if (detail != null)
129+
detail += " (new address)";
130+
info.address = server;
131+
something_changed = true;
132+
}
133+
// Does server report that it might have new channels?
134+
if (changes != info.changes)
135+
{
136+
if (detail != null)
137+
detail += " (new changes)";
138+
info.changes = changes;
139+
something_changed = true;
140+
}
141+
142+
// Periodically remove old beacon infos
143+
final long table_age = Duration.between(last_cleanup, now).getSeconds();
144+
if (table_age > PVASettings.EPICS_PVA_MAX_BEACON_AGE)
145+
{
146+
removeOldBeaconInfo(now);
147+
last_cleanup = now;
148+
}
149+
// Log detail, if available
150+
if (detail != null)
151+
logger.log(Level.FINE, "Beacon update\n" + getTable(now, info.guid, detail));
152+
153+
// Search or not?
154+
return something_changed;
155+
}
156+
157+
/** Delete old beacon info */
158+
private void removeOldBeaconInfo(final Instant now)
159+
{
160+
final Iterator<Entry<Guid, BeaconInfo>> infos = beacons.entrySet().iterator();
161+
while (infos.hasNext())
162+
{
163+
final BeaconInfo info = infos.next().getValue();
164+
final long age = Duration.between(info.last, now).getSeconds();
165+
if (age > 180) // TODO beacon_cleanup_period
166+
{
167+
logger.log(Level.FINER,
168+
() -> "Removing beacon info " + info.guid + " (" + info.address + "), last seen " + age + " seconds ago");
169+
infos.remove();
170+
}
171+
}
172+
}
173+
174+
/** @param now Current time
175+
* @param active ID of server that just sent a beacon or <code>null</code> if nothing new
176+
* @param detail Detail of what's new or <code>null</code> if nothing new
177+
* @return Tabular list of beacons, optionally highlighting the 'active' server with 'detail'
178+
*/
179+
private String getTable(final Instant now, final Guid active, final String detail)
180+
{
181+
final StringBuilder buf = new StringBuilder();
182+
buf.append("GUID IP Age Changes Period\n");
183+
for (BeaconInfo info : beacons.values())
184+
{
185+
final long age = Duration.between(info.last, now).getSeconds();
186+
buf.append(String.format("%s %-35s %3d s %3d %4d s",
187+
info.guid.asText(),
188+
info.address,
189+
age,
190+
info.changes,
191+
info.period));
192+
if (info.guid.equals(active))
193+
buf.append(" ")
194+
.append(detail);
195+
buf.append("\n");
196+
}
197+
return buf.toString();
198+
}
199+
200+
/** @return String representation */
201+
@Override
202+
public String toString()
203+
{
204+
return getTable(Instant.now(), null, null);
205+
}
206+
}

0 commit comments

Comments
 (0)