Build Monitor: per-builder events and example view#6
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces per-builder build events (PRE_PROJECT_BUILD and POST_PROJECT_BUILD) to the Eclipse Resources framework, enabling more granular monitoring of the build process. The implementation includes updates to the event notification system, a new 'Build Monitor' example plugin, and comprehensive unit tests. Review feedback identified a potential violation of the IResourceChangeEvent contract regarding the timing of the POST_PROJECT_BUILD event relative to the workspace lock. Additionally, improvements were suggested for the example plugin to safely handle widget disposal during asynchronous UI refreshes and to ensure that exceptions from listeners are logged rather than silently ignored.
| try { | ||
| workspace.broadcastProjectBuildEvent(builderProject, builderName, | ||
| IResourceChangeEvent.POST_PROJECT_BUILD, trigger); | ||
| } finally { |
There was a problem hiding this comment.
The POST_PROJECT_BUILD event is currently fired before the workspace lock is re-acquired (via endUnprotected). This violates the general contract of IResourceChangeEvent which states that the workspace is closed for changes during notification. Since PRE_PROJECT_BUILD is fired with the lock held, POST_PROJECT_BUILD should ideally also be fired after endUnprotected to ensure consistency and that listeners see a locked workspace, unless the concurrent delivery while the lock is released is explicitly intended for performance reasons.
| private void refreshAsync() { | ||
| Display display = viewer.getControl().getDisplay(); | ||
| if (display.isDisposed()) { | ||
| return; | ||
| } | ||
| display.asyncExec(() -> { | ||
| if (!viewer.getControl().isDisposed()) { | ||
| viewer.refresh(); | ||
| } | ||
| }); | ||
| } |
There was a problem hiding this comment.
The refreshAsync method should safely handle cases where the view or its control might be disposed. Accessing viewer.getControl().getDisplay() on a disposed widget will throw an SWTException. Although the listener is removed in dispose(), events already in the queue or concurrent notifications could still trigger this.
private void refreshAsync() {
if (viewer == null || viewer.getControl() == null || viewer.getControl().isDisposed()) {
return;
}
Display display = viewer.getControl().getDisplay();
if (display.isDisposed()) {
return;
}
display.asyncExec(() -> {
if (viewer != null && viewer.getControl() != null && !viewer.getControl().isDisposed()) {
viewer.refresh();
}
});
}| } catch (RuntimeException ignored) { | ||
| // a broken listener must not poison the notification | ||
| } |
There was a problem hiding this comment.
There was a problem hiding this comment.
Code Review
This pull request introduces per-builder build events, specifically PRE_PROJECT_BUILD and POST_PROJECT_BUILD, to the Eclipse core resources framework. These events enable granular tracking of individual builders within a project build cycle, supported by updates to the event notification system and a new 'Build Monitor' example plugin. A potential NullPointerException was identified in BuildManager.java where builder.getCommand() might return null, and a defensive check was suggested to prevent build interruptions.
| IProject builderProject = builder.getProject(); | ||
| String builderName = builder.getCommand().getBuilderName(); | ||
| workspace.broadcastProjectBuildEvent(builderProject, builderName, | ||
| IResourceChangeEvent.PRE_PROJECT_BUILD, trigger); |
There was a problem hiding this comment.
Potential NullPointerException if builder.getCommand() returns null. According to the IncrementalProjectBuilder#getCommand() documentation, the returned value may be null if the builder was not created from a build command. A defensive check should be added before accessing the builder name to ensure the build process is not interrupted by an unexpected NPE.
| IProject builderProject = builder.getProject(); | |
| String builderName = builder.getCommand().getBuilderName(); | |
| workspace.broadcastProjectBuildEvent(builderProject, builderName, | |
| IResourceChangeEvent.PRE_PROJECT_BUILD, trigger); | |
| IProject builderProject = builder.getProject(); | |
| String builderName = builder.getCommand() != null ? builder.getCommand().getBuilderName() : null; | |
| workspace.broadcastProjectBuildEvent(builderProject, builderName, | |
| IResourceChangeEvent.PRE_PROJECT_BUILD, trigger); |
There was a problem hiding this comment.
Code Review
This pull request introduces per-builder build events (PRE_PROJECT_BUILD and POST_PROJECT_BUILD) to the Eclipse core resources framework, allowing listeners to track individual builder execution within a project build. The changes span the event notification system, workspace management, and include a new 'Build Monitor' example plugin along with unit tests. Feedback highlights a potential NullPointerException risk when accessing builder commands, suggests simplifying array reversal logic using standard library methods, and recommends using a fixed locale for formatting technical timing data to ensure consistency across environments.
| int depth = -1; | ||
| ISchedulingRule rule = null; | ||
| IProject builderProject = builder.getProject(); | ||
| String builderName = builder.getCommand().getBuilderName(); |
There was a problem hiding this comment.
| Object[] arr = sessions.toArray(); | ||
| for (int i = 0, j = arr.length - 1; i < j; i++, j--) { | ||
| Object tmp = arr[i]; | ||
| arr[i] = arr[j]; | ||
| arr[j] = tmp; | ||
| } |
There was a problem hiding this comment.
| private static String formatMillis(long nanos) { | ||
| if (nanos <= 0) { | ||
| return "—"; | ||
| } | ||
| double ms = nanos / 1_000_000.0; | ||
| if (ms < 1) { | ||
| return String.format("%.2f ms", ms); | ||
| } | ||
| if (ms < 1000) { | ||
| return String.format("%.0f ms", ms); | ||
| } | ||
| return String.format("%.2f s", ms / 1000.0); | ||
| } |
There was a problem hiding this comment.
ResourceChangeListenerList tracked per-event-type listener counts in six discrete volatile int fields (count1..count32), with adding(), removing(), hasListenerFor(), and clear() each duplicating a hard-coded switch over the same six bitmask values. Every new IResourceChangeEvent type required adding a field and four matching cases, and any mask bit that no one remembered to wire up was silently dropped by hasListenerFor. Collapse the bookkeeping into a single AtomicIntegerArray indexed by Integer.numberOfTrailingZeros(mask). adjust(mask, delta) walks the set bits of the mask once, replacing the two near-identical adding/removing helpers. hasListenerFor short-circuits non-single-bit and non-positive arguments, matching the previous switch's default-false behavior, and otherwise reads the counter for the single bit position. AtomicIntegerArray preserves the per-element volatile read semantics that hasListenerFor depends on outside the synchronized add/remove/clear paths. Drops the unreachable "listeners != null" guard in toString since the field is final and initialized at declaration, and inlines the ListenerEntry.toString StringBuilder into a concatenation expression for parity with the simplified outer toString.
09460c2 to
da3ec31
Compare
Introduce PRE_PROJECT_BUILD and POST_PROJECT_BUILD event types on IResourceChangeEvent, fired once per builder execution from BuildManager.basicBuild around the SafeRunner.run that invokes the builder. Listeners receive the IProject as source, the builder id (from the project's build spec) via the new getBuilderName() default method, and the build kind via getBuildKind(). No resource delta is attached; the surrounding workspace-level PRE_BUILD/POST_BUILD pair continues to carry the aggregated delta. Events fire above the needsBuild short-circuit so every builder that is considered reports a matched PRE/POST pair; builders whose project has no delta produce near-zero durations but still appear to listeners, giving tools complete visibility of the build sequence. Dispatch goes through a new NotificationManager.broadcastProjectBuildEvent path that skips delta computation and does not update lastPostBuildTree, so per-builder events remain pure timing/metadata signals nested inside the workspace build. ResourceChangeListenerList.hasListenerFor was a hard-coded switch over bitmasks 1..32 and silently dropped anything else; extend its counter bookkeeping to include 64 and 128 so listeners registered for the new event types actually receive them. Bumps org.eclipse.core.resources to 3.25.0 and promotes two pending @SInCE 3.24 tags on IFile to 3.25. Adds ProjectBuildEventTest covering FULL, CLEAN, INCREMENTAL-with-no-delta, nesting order against the enclosing workspace events, and the getBuilderName() contract. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduce a new example bundle org.eclipse.ui.examples.buildmonitor
that contributes a "Build Monitor" view. The view listens to the
per-builder PRE_PROJECT_BUILD / POST_PROJECT_BUILD events and
aggregates them into a TreeViewer: top-level rows are build sessions,
expanding reveals per-project timings, and expanding a project
reveals the individual builders that ran for it.
Two timing figures are shown per project:
* Pure build time: sum of each builder's (end - start). Reflects
the CPU work that was done on the project, independent of
scheduling.
* Time until ready: from the enclosing workspace PRE_BUILD to the
moment the project's last builder finished.
The recorder matches per-builder PRE/POST events using a thread-local
stack, which is race-free under parallel builds because a single
builder invocation runs synchronously on one thread.
Builders that BuildManager.needsBuild short-circuits still fire a
PRE/POST pair with near-zero duration; filter those out at a 100
microsecond threshold so projects that were only considered but did
not rebuild (e.g. every workspace project on an AUTO build triggered
by one file change) do not clutter the view. Skipped builders still
flow through the event API itself for tools that want to see them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
da3ec31 to
13e747d
Compare
CI verification PR. Adds
PRE_PROJECT_BUILD/POST_PROJECT_BUILDevents toIResourceChangeEvent, fired per builder fromBuildManager.basicBuild, and a neworg.eclipse.ui.examples.buildmonitorexample view that consumes them.This branch is stacked on top of upstream PR eclipse-platform#2664 (
Replace per-bit listener count fields with an AtomicIntegerArrayoneclipse-platform/eclipse.platform). The array-based bookkeeping makes the new 64/128 bitmasks work without any change toResourceChangeListenerList.Fallback if upstream PR eclipse-platform#2664 is rejected
If the
AtomicIntegerArrayrefactor is not merged, the per-builder events commit needs the following addition toResourceChangeListenerList.javaso thathasListenerFor(64)andhasListenerFor(128)actually returntruefor registered listeners (otherwise both new events are silently dropped):Recovery flow in that case:
git rebase --onto origin/master aa8786ac75to drop the refactor commit, then apply the diff above to the per-builder events commit viagit commit --amend.