5656import java .io .IOException ;
5757import java .text .MessageFormat ;
5858import java .util .Base64 ;
59+ import java .util .concurrent .Callable ;
5960import java .util .concurrent .CancellationException ;
6061import java .util .concurrent .CompletableFuture ;
6162import java .util .concurrent .ExecutionException ;
6263import java .util .concurrent .ExecutorService ;
6364import java .util .concurrent .Executors ;
65+ import java .util .concurrent .Future ;
6466import java .util .logging .Level ;
6567import java .util .stream .Collectors ;
6668import java .util .stream .Stream ;
104106import org .openjdk .jmc .flightrecorder .stacktrace .FrameSeparator .FrameCategorization ;
105107import org .openjdk .jmc .flightrecorder .stacktrace .StacktraceModel ;
106108import org .openjdk .jmc .flightrecorder .ui .FlightRecorderUI ;
109+ import org .openjdk .jmc .flightrecorder .ui .ItemCollectionToolkit ;
107110import org .openjdk .jmc .flightrecorder .ui .common .ImageConstants ;
108111import org .openjdk .jmc .flightrecorder .ui .messages .internal .Messages ;
109112import org .openjdk .jmc .ui .CoreImages ;
@@ -157,14 +160,15 @@ public class FlameGraphView extends ViewPart implements ISelectionListener {
157160
158161 private Browser browser ;
159162 private SashForm container ;
160- private TraceNode currentRoot ;
161- private CompletableFuture <TraceNode > currentModelCalculator ;
162- private boolean threadRootAtTop = true ;
163- private boolean icicleViewActive = true ;
164- private IItemCollection currentItems ;
165163 private GroupByAction [] groupByActions ;
166164 private GroupByFlameviewAction [] groupByFlameviewActions ;
167165 private ExportAction [] exportActions ;
166+ private boolean threadRootAtTop = true ;
167+ private boolean icicleViewActive = true ;
168+ private IItemCollection currentItems ;
169+ private ModelState modelState = ModelState .NONE ;
170+ private ModelRebuildCallable modelRebuildCallable ;
171+ private Future <Void > modelCalculationFuture ;
168172
169173 private enum GroupActionType {
170174 THREAD_ROOT (Messages .STACKTRACE_VIEW_THREAD_ROOT , IAction .AS_RADIO_BUTTON , CoreImages .THREAD ),
@@ -186,6 +190,10 @@ private GroupActionType(String message, int action, ImageDescriptor imageDescrip
186190
187191 }
188192
193+ private enum ModelState {
194+ INIT , CALCULATION , READY , ABORTED , NONE ;
195+ }
196+
189197 private class GroupByAction extends Action {
190198 private final GroupActionType actionType ;
191199
@@ -202,7 +210,7 @@ public void run() {
202210 boolean newValue = isChecked () == GroupActionType .THREAD_ROOT .equals (actionType );
203211 if (newValue != threadRootAtTop ) {
204212 threadRootAtTop = newValue ;
205- rebuildModel (currentItems );
213+ triggerRebuildTask (currentItems );
206214 }
207215 }
208216 }
@@ -272,6 +280,38 @@ public void run() {
272280 }
273281 }
274282
283+ private static class ModelRebuildCallable implements Callable <Void > {
284+
285+ private volatile boolean isInvalid ;
286+ private FlameGraphView view ;
287+ private IItemCollection items ;
288+
289+ private ModelRebuildCallable (FlameGraphView view , IItemCollection items ) {
290+ this .view = view ;
291+ this .items = items ;
292+ }
293+
294+ private void setInvalid () {
295+ this .isInvalid = true ;
296+ }
297+
298+ @ Override
299+ public Void call () throws Exception {
300+ view .modelState = ModelState .CALCULATION ;
301+ StacktraceModel model = new StacktraceModel (view .threadRootAtTop , view .frameSeparator , items );
302+ TraceNode root = TraceTreeUtils .createRootWithDescription (items , model .getRootFork ().getBranchCount ());
303+ TraceNode traceNode = TraceTreeUtils .createTree (root , model );
304+ String jsonModel = view .toJSonModel (traceNode ).toString ();
305+ if (isInvalid ) {
306+ view .modelState = ModelState .ABORTED ;
307+ return null ;
308+ }
309+ view .modelState = ModelState .READY ;
310+ DisplayToolkit .inDisplayThread ().execute (() -> view .setModel (items , jsonModel ));
311+ return null ;
312+ }
313+ }
314+
275315 @ Override
276316 public void init (IViewSite site , IMemento memento ) throws PartInitException {
277317 super .init (site , memento );
@@ -331,59 +371,58 @@ public void saveState(IMemento memento) {
331371 public void selectionChanged (IWorkbenchPart part , ISelection selection ) {
332372 if (selection instanceof IStructuredSelection ) {
333373 Object first = ((IStructuredSelection ) selection ).getFirstElement ();
334- setItems (AdapterUtil .getAdapter (first , IItemCollection .class ));
335- }
336- }
337-
338- private void setItems (IItemCollection items ) {
339- if (items != null ) {
340- currentItems = items ;
341- rebuildModel (items );
374+ IItemCollection items = AdapterUtil .getAdapter (first , IItemCollection .class );
375+ if (items == null ) {
376+ triggerRebuildTask (ItemCollectionToolkit .build (Stream .empty ()));
377+ } else if (!items .equals (currentItems )) {
378+ triggerRebuildTask (items );
379+ }
342380 }
343381 }
344382
345- private void rebuildModel (IItemCollection items ) {
346- // Release old model before building the new
347- if (currentModelCalculator != null ) {
348- currentModelCalculator .cancel (true );
383+ private void triggerRebuildTask (IItemCollection items ) {
384+ // Release old model calculation before building a new
385+ if (modelCalculationFuture != null ) {
386+ modelRebuildCallable .setInvalid ();
387+ modelCalculationFuture .cancel (true );
349388 }
350- currentModelCalculator = getModelPreparer (items , frameSeparator , true );
351- currentModelCalculator .thenAcceptAsync (this ::setModel , DisplayToolkit .inDisplayThread ())
352- .exceptionally (FlameGraphView ::handleModelBuildException );
353- }
354389
355- private CompletableFuture <TraceNode > getModelPreparer (
356- final IItemCollection items , final FrameSeparator separator , final boolean materializeSelectedBranches ) {
357- return CompletableFuture .supplyAsync (() -> {
358- StacktraceModel model = new StacktraceModel (threadRootAtTop , frameSeparator , items );
359- TraceNode root = TraceTreeUtils .createRootWithDescription (items , model .getRootFork ().getBranchCount ());
360- return TraceTreeUtils .createTree (root , model );
361- }, MODEL_EXECUTOR );
390+ modelState = ModelState .INIT ;
391+ currentItems = items ;
392+ modelRebuildCallable = new ModelRebuildCallable (this , items );
393+ modelCalculationFuture = MODEL_EXECUTOR .submit (modelRebuildCallable );
362394 }
363395
364- private void setModel (TraceNode root ) {
365- if (!browser .isDisposed () && !root .equals (currentRoot )) {
366- currentRoot = root ;
367- setViewerInput (root );
396+ private void setModel (final IItemCollection items , final String json ) {
397+ if (ModelState .READY .equals (modelState ) && items .equals (currentItems ) && !browser .isDisposed ()) {
398+ setViewerInput (json );
368399 }
369400 }
370401
371- private void setViewerInput (TraceNode root ) {
402+ private void setViewerInput (String json ) {
372403 Stream .of (exportActions ).forEach ((action ) -> action .setEnabled (false ));
373404 browser .setText (HTML_PAGE );
374405 browser .addListener (SWT .Resize , event -> {
375406 browser .execute ("resizeFlameGraph();" );
376407 });
377408
378409 browser .addProgressListener (new ProgressAdapter () {
410+ private boolean loaded = false ;
411+
412+ @ Override
413+ public void changed (ProgressEvent event ) {
414+ if (loaded ) {
415+ browser .removeProgressListener (this );
416+ }
417+ }
418+
379419 @ Override
380420 public void completed (ProgressEvent event ) {
381- browser .removeProgressListener (this );
382421 browser .execute (String .format ("configureTooltipText('%s', '%s', '%s', '%s', '%s');" , TABLE_COLUMN_COUNT ,
383422 TABLE_COLUMN_EVENT_TYPE , TOOLTIP_PACKAGE , TOOLTIP_SAMPLES , TOOLTIP_DESCRIPTION ));
384-
385- browser .execute (String .format ("processGraph(%s, %s);" , toJSon (root ), icicleViewActive ));
423+ browser .execute (String .format ("processGraph(%s, %s);" , json , icicleViewActive ));
386424 Stream .of (exportActions ).forEach ((action ) -> action .setEnabled (true ));
425+ loaded = true ;
387426 }
388427 });
389428 }
@@ -444,33 +483,19 @@ public Object function(Object[] arguments) {
444483 fos .write (bytes );
445484 fos .close ();
446485 } catch (CancellationException e ) {
447- // noop
486+ // noop : model calculation is canceled when is still running
448487 } catch (InterruptedException | ExecutionException | IOException e ) {
449488 FlightRecorderUI .getDefault ().getLogger ().log (Level .SEVERE , "Failed to save flame graph" , e ); //$NON-NLS-1$
450489 }
451490 }
452491
453- private static Void handleModelBuildException (Throwable ex ) {
454- if (!(ex .getCause () instanceof CancellationException )) {
455- FlightRecorderUI .getDefault ().getLogger ().log (Level .SEVERE , "Failed to build stacktrace view model" , ex ); //$NON-NLS-1$
456- }
457- return null ;
458- }
459-
460- private static String toJSon (TraceNode root ) {
461- if (root == null ) {
462- return "\" \" " ;
463- }
464- return render (root );
465- }
466-
467- private static String render (TraceNode root ) {
492+ private StringBuilder toJSonModel (TraceNode root ) {
468493 StringBuilder builder = new StringBuilder ();
469494 String rootNodeStart = createJsonRootTraceNode (root );
470495 builder .append (rootNodeStart );
471496 renderChildren (builder , root );
472497 builder .append ("]}" );
473- return builder . toString () ;
498+ return builder ;
474499 }
475500
476501 private static void render (StringBuilder builder , TraceNode node ) {
0 commit comments