Skip to content

Commit 877405e

Browse files
authored
Merge pull request #2389 from ControlSystemStudio/alarm_tree_startup
Alarm tree startup
2 parents 4077073 + 32a50d7 commit 877405e

File tree

4 files changed

+121
-20
lines changed

4 files changed

+121
-20
lines changed

app/alarm/model/src/main/java/org/phoebus/applications/alarm/AlarmSystem.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ public class AlarmSystem
9595
/** Limit for the number of context menu items */
9696
@Preference public static int alarm_menu_max_items;
9797

98+
/** Initial Alarm Tree UI update delay [ms] */
99+
@Preference public static int alarm_tree_startup_ms;
100+
98101
/** Alarm table columns */
99102
@Preference public static String[] alarm_table_columns;
100103

app/alarm/model/src/main/java/org/phoebus/applications/alarm/client/AlarmClient.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2018-2021 Oak Ridge National Laboratory.
2+
* Copyright (c) 2018-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
@@ -90,7 +90,7 @@ public class AlarmClient
9090
private long last_state_update = 0;
9191

9292
/** Timeout, not seen any messages from server? */
93-
private boolean has_timed_out = false;
93+
private volatile boolean has_timed_out = false;
9494

9595
/** @param server Kafka Server host:port
9696
* @param config_name Name of alarm tree root
@@ -571,6 +571,12 @@ public void acknowledge(final AlarmTreeItem<?> item, final boolean acknowledge)
571571
}
572572
}
573573

574+
/** @return <code>true</code> if connected to server, else updates have timed out */
575+
public boolean isServerAlive()
576+
{
577+
return !has_timed_out;
578+
}
579+
574580
/** Check if there have been any messages from server */
575581
private void checkServerState()
576582
{

app/alarm/model/src/main/resources/alarm_preferences.properties

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,20 @@ alarm_area_font_size=15
4545
# 'display' and 'command' menu entries.
4646
alarm_menu_max_items=10
4747

48+
# Initial Alarm Tree UI update delay [ms]
49+
#
50+
# The initial flurry of alarm tree updates can be slow
51+
# to render. By allowing the alarm client to accumulate
52+
# alarm tree information for a little time and then
53+
# performing an initial bulk representation, the overall
54+
# alarm tree startup can be faster, especially when
55+
# the UI is viewed via a remote desktop
56+
#
57+
# Set to 0 for original implementation where
58+
# all alarm tree items are added to the model
59+
# as they are received in initial flurry of updates.
60+
alarm_tree_startup_ms=2000
61+
4862
# Order of columns in alarm table
4963
# Allows re-ordering as well as omitting columns
5064
alarm_table_columns=Icon, PV, Description, Alarm Severity, Alarm Status, Alarm Time, Alarm Value, PV Severity, PV Status

app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeView.java

Lines changed: 96 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.logging.Level;
2323
import java.util.stream.Collectors;
2424

25+
import org.phoebus.applications.alarm.AlarmSystem;
2526
import org.phoebus.applications.alarm.client.AlarmClient;
2627
import org.phoebus.applications.alarm.client.AlarmClientLeaf;
2728
import org.phoebus.applications.alarm.client.AlarmClientListener;
@@ -85,8 +86,28 @@ public class AlarmTreeView extends BorderPane implements AlarmClientListener
8586
private final Label no_server = AlarmUI.createNoServerLabel();
8687
private final TreeView<AlarmTreeItem<?>> tree_view = new TreeView<>();
8788

89+
/** Model with current alarm tree, sends updates */
8890
private final AlarmClient model;
8991

92+
/** Latch for initially pausing model listeners
93+
*
94+
* Imagine a large alarm tree that changes.
95+
* The alarm table can periodically display the current
96+
* alarms, it does not need to display every change right away.
97+
* The tree on the other hand must reflect every added or removed item,
98+
* because updates cannot be applied once the tree structure gets out of sync.
99+
* When the model is first started, there is a flurry of additions and removals,
100+
* which arrive in the order in which the tree was generated, not necessarily
101+
* in the order they're laid out in the hierarchy.
102+
* These can be slow to render, especially if displaying via a remote desktop (ssh-X).
103+
* The alarm tree view thus starts in stages:
104+
* 1) Wait for model to receive the bulk of initial additions and removals
105+
* 2) Add listeners to model changes, but block them via this latch
106+
* 3) Represent the initial model
107+
* 4) Release this latch to handle changes (blocked and those that arrive from now on)
108+
*/
109+
private final CountDownLatch block_item_changes = new CountDownLatch(1);
110+
90111
/** Map from alarm tree path to view's TreeItem */
91112
private final ConcurrentHashMap<String, TreeItem<AlarmTreeItem<?>>> path2view = new ConcurrentHashMap<>();
92113

@@ -146,13 +167,49 @@ public AlarmTreeView(final AlarmClient model)
146167
setTop(createToolbar());
147168
setCenter(tree_view);
148169

149-
tree_view.setRoot(createViewItem(model.getRoot()));
150-
151-
model.addListener(this);
152-
153170
createContextMenu();
154171
addClickSupport();
155172
addDragSupport();
173+
174+
if (AlarmSystem.alarm_tree_startup_ms <= 0)
175+
{
176+
// Original implementation:
177+
// Create initial (empty) representation,
178+
// register listener, then model gets started
179+
block_item_changes.countDown();
180+
tree_view.setRoot(createViewItem(model.getRoot()));
181+
model.addListener(AlarmTreeView.this);
182+
}
183+
else
184+
UpdateThrottle.TIMER.schedule(this::startup, AlarmSystem.alarm_tree_startup_ms, TimeUnit.MILLISECONDS);
185+
186+
// Caller will start the model once we return from constructor
187+
}
188+
189+
private void startup()
190+
{
191+
// Waited for model to receive the bulk of initial additions and removals...
192+
Platform.runLater(() ->
193+
{
194+
if (! model.isRunning())
195+
{
196+
logger.log(Level.WARNING, model.getRoot().getName() + " was disposed while waiting for alarm tree startup");
197+
return;
198+
}
199+
// Listen to model changes, but they're blocked,
200+
// so this blocks model changes from now on
201+
model.addListener(AlarmTreeView.this);
202+
203+
// Represent model that should by now be fairly complete
204+
tree_view.setRoot(createViewItem(model.getRoot()));
205+
206+
// Set change indicator so that it clears when there are no more changes
207+
indicateChange();
208+
showServerState(model.isServerAlive());
209+
210+
// Un-block to handle changes from now on
211+
block_item_changes.countDown();
212+
});
156213
}
157214

158215
private ToolBar createToolbar()
@@ -170,7 +227,8 @@ private ToolBar createToolbar()
170227
ImageCache.getImageView(AlarmUI.class, "/icons/expand_alarms.png"));
171228
show_alarms.setTooltip(new Tooltip("Expand alarm tree to show active alarms"));
172229
show_alarms.setOnAction(event -> expandAlarms(tree_view.getRoot()));
173-
return new ToolBar(no_server, ToolbarHelper.createSpring(), collapse, show_alarms);
230+
231+
return new ToolBar(no_server, changing, ToolbarHelper.createSpring(), collapse, show_alarms);
174232
}
175233

176234
ToolBar getToolbar()
@@ -222,25 +280,29 @@ private void indicateChange()
222280
logger.log(Level.INFO, "Alarm tree changes start");
223281
setCursor(Cursor.WAIT);
224282
final ObservableList<Node> items = getToolbar().getItems();
225-
items.add(1, changing);
283+
if (! items.contains(changing))
284+
items.add(1, changing);
226285
}
227286
else
228287
previous.cancel(false);
229288
}
230289

290+
/** @param alive Have we seen server messages? */
291+
private void showServerState(final boolean alive)
292+
{
293+
final ObservableList<Node> items = getToolbar().getItems();
294+
items.remove(no_server);
295+
if (! alive)
296+
// Place left of spring, collapse, expand_alarms,
297+
// i.e. right of potential AlarmConfigSelector
298+
items.add(items.size()-3, no_server);
299+
}
300+
231301
// AlarmClientModelListener
232302
@Override
233303
public void serverStateChanged(final boolean alive)
234304
{
235-
Platform.runLater(() ->
236-
{
237-
final ObservableList<Node> items = getToolbar().getItems();
238-
items.remove(no_server);
239-
if (! alive)
240-
// Place left of spring, collapse, expand_alarms,
241-
// i.e. right of potential AlarmConfigSelector
242-
items.add(items.size()-3, no_server);
243-
});
305+
Platform.runLater(() -> showServerState(alive));
244306
}
245307

246308
// AlarmClientModelListener
@@ -257,11 +319,25 @@ public void serverDisableNotifyChanged(final boolean disable_notify)
257319
// NOP
258320
}
259321

322+
/** Block until changes to items should be shown */
323+
private void blockItemChanges()
324+
{
325+
try
326+
{
327+
block_item_changes.await();
328+
}
329+
catch (InterruptedException ex)
330+
{
331+
logger.log(Level.WARNING, "Blocker for item changes got interrupted", ex);
332+
}
333+
}
334+
260335
// AlarmClientModelListener
261336
@Override
262337
public void itemAdded(final AlarmTreeItem<?> item)
263338
{
264-
// System.out.println("Add " + item.getPathName());
339+
blockItemChanges();
340+
// System.out.println(Thread.currentThread() + " Add " + item.getPathName());
265341

266342
// Parent must already exist
267343
final AlarmTreeItem<BasicState> model_parent = item.getParent();
@@ -311,7 +387,8 @@ public void itemAdded(final AlarmTreeItem<?> item)
311387
@Override
312388
public void itemRemoved(final AlarmTreeItem<?> item)
313389
{
314-
// System.out.println("Removed " + item.getPathName());
390+
blockItemChanges();
391+
// System.out.println(Thread.currentThread() + " Removed " + item.getPathName());
315392

316393
// Remove item and all sub-items from model2ui
317394
final TreeItem<AlarmTreeItem<?>> view_item = removeViewItems(item);
@@ -365,7 +442,8 @@ private TreeItem<AlarmTreeItem<?>> removeViewItems(final AlarmTreeItem<?> item)
365442
@Override
366443
public void itemUpdated(final AlarmTreeItem<?> item)
367444
{
368-
// System.out.println("Updated " + item.getPathName());
445+
blockItemChanges();
446+
// System.out.println(Thread.currentThread() + " Updated " + item.getPathName());
369447
final TreeItem<AlarmTreeItem<?>> view_item = path2view.get(item.getPathName());
370448
if (view_item == null)
371449
{

0 commit comments

Comments
 (0)