56
56
import java .io .IOException ;
57
57
import java .text .MessageFormat ;
58
58
import java .util .Base64 ;
59
+ import java .util .concurrent .Callable ;
59
60
import java .util .concurrent .CancellationException ;
60
61
import java .util .concurrent .CompletableFuture ;
61
62
import java .util .concurrent .ExecutionException ;
62
63
import java .util .concurrent .ExecutorService ;
63
64
import java .util .concurrent .Executors ;
65
+ import java .util .concurrent .Future ;
64
66
import java .util .logging .Level ;
65
67
import java .util .stream .Collectors ;
66
68
import java .util .stream .Stream ;
104
106
import org .openjdk .jmc .flightrecorder .stacktrace .FrameSeparator .FrameCategorization ;
105
107
import org .openjdk .jmc .flightrecorder .stacktrace .StacktraceModel ;
106
108
import org .openjdk .jmc .flightrecorder .ui .FlightRecorderUI ;
109
+ import org .openjdk .jmc .flightrecorder .ui .ItemCollectionToolkit ;
107
110
import org .openjdk .jmc .flightrecorder .ui .common .ImageConstants ;
108
111
import org .openjdk .jmc .flightrecorder .ui .messages .internal .Messages ;
109
112
import org .openjdk .jmc .ui .CoreImages ;
@@ -157,14 +160,15 @@ public class FlameGraphView extends ViewPart implements ISelectionListener {
157
160
158
161
private Browser browser ;
159
162
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 ;
165
163
private GroupByAction [] groupByActions ;
166
164
private GroupByFlameviewAction [] groupByFlameviewActions ;
167
165
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 ;
168
172
169
173
private enum GroupActionType {
170
174
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
186
190
187
191
}
188
192
193
+ private enum ModelState {
194
+ INIT , CALCULATION , READY , ABORTED , NONE ;
195
+ }
196
+
189
197
private class GroupByAction extends Action {
190
198
private final GroupActionType actionType ;
191
199
@@ -202,7 +210,7 @@ public void run() {
202
210
boolean newValue = isChecked () == GroupActionType .THREAD_ROOT .equals (actionType );
203
211
if (newValue != threadRootAtTop ) {
204
212
threadRootAtTop = newValue ;
205
- rebuildModel (currentItems );
213
+ triggerRebuildTask (currentItems );
206
214
}
207
215
}
208
216
}
@@ -272,6 +280,38 @@ public void run() {
272
280
}
273
281
}
274
282
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
+
275
315
@ Override
276
316
public void init (IViewSite site , IMemento memento ) throws PartInitException {
277
317
super .init (site , memento );
@@ -331,59 +371,58 @@ public void saveState(IMemento memento) {
331
371
public void selectionChanged (IWorkbenchPart part , ISelection selection ) {
332
372
if (selection instanceof IStructuredSelection ) {
333
373
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
+ }
342
380
}
343
381
}
344
382
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 );
349
388
}
350
- currentModelCalculator = getModelPreparer (items , frameSeparator , true );
351
- currentModelCalculator .thenAcceptAsync (this ::setModel , DisplayToolkit .inDisplayThread ())
352
- .exceptionally (FlameGraphView ::handleModelBuildException );
353
- }
354
389
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 );
362
394
}
363
395
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 );
368
399
}
369
400
}
370
401
371
- private void setViewerInput (TraceNode root ) {
402
+ private void setViewerInput (String json ) {
372
403
Stream .of (exportActions ).forEach ((action ) -> action .setEnabled (false ));
373
404
browser .setText (HTML_PAGE );
374
405
browser .addListener (SWT .Resize , event -> {
375
406
browser .execute ("resizeFlameGraph();" );
376
407
});
377
408
378
409
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
+
379
419
@ Override
380
420
public void completed (ProgressEvent event ) {
381
- browser .removeProgressListener (this );
382
421
browser .execute (String .format ("configureTooltipText('%s', '%s', '%s', '%s', '%s');" , TABLE_COLUMN_COUNT ,
383
422
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 ));
386
424
Stream .of (exportActions ).forEach ((action ) -> action .setEnabled (true ));
425
+ loaded = true ;
387
426
}
388
427
});
389
428
}
@@ -444,33 +483,19 @@ public Object function(Object[] arguments) {
444
483
fos .write (bytes );
445
484
fos .close ();
446
485
} catch (CancellationException e ) {
447
- // noop
486
+ // noop : model calculation is canceled when is still running
448
487
} catch (InterruptedException | ExecutionException | IOException e ) {
449
488
FlightRecorderUI .getDefault ().getLogger ().log (Level .SEVERE , "Failed to save flame graph" , e ); //$NON-NLS-1$
450
489
}
451
490
}
452
491
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 ) {
468
493
StringBuilder builder = new StringBuilder ();
469
494
String rootNodeStart = createJsonRootTraceNode (root );
470
495
builder .append (rootNodeStart );
471
496
renderChildren (builder , root );
472
497
builder .append ("]}" );
473
- return builder . toString () ;
498
+ return builder ;
474
499
}
475
500
476
501
private static void render (StringBuilder builder , TraceNode node ) {
0 commit comments