001/* 002 * Copyright (c) 2015 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: crawl $' 006 * '$Date: 2015-11-02 20:02:37 +0000 (Mon, 02 Nov 2015) $' 007 * '$Revision: 34199 $' 008 * 009 * Permission is hereby granted, without written agreement and without 010 * license or royalty fees, to use, copy, modify, and distribute this 011 * software and its documentation for any purpose, provided that the above 012 * copyright notice and the following two paragraphs appear in all copies 013 * of this software. 014 * 015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 019 * SUCH DAMAGE. 020 * 021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 026 * ENHANCEMENTS, OR MODIFICATIONS. 027 * 028 */ 029package org.kepler.profiling.gui; 030 031import java.awt.Color; 032import java.awt.Component; 033import java.awt.Dimension; 034import java.awt.Font; 035import java.awt.GridBagConstraints; 036import java.awt.GridBagLayout; 037import java.awt.Insets; 038import java.awt.Point; 039import java.awt.event.ActionEvent; 040import java.awt.event.ActionListener; 041import java.awt.event.MouseAdapter; 042import java.awt.event.MouseEvent; 043import java.text.DateFormat; 044import java.text.SimpleDateFormat; 045import java.util.ArrayList; 046import java.util.Collections; 047import java.util.Date; 048import java.util.HashMap; 049import java.util.List; 050import java.util.Map; 051import java.util.concurrent.atomic.AtomicBoolean; 052 053import javax.swing.JButton; 054import javax.swing.JDialog; 055import javax.swing.JLabel; 056import javax.swing.JPopupMenu; 057import javax.swing.JScrollPane; 058import javax.swing.JTable; 059import javax.swing.JViewport; 060import javax.swing.SwingUtilities; 061import javax.swing.table.AbstractTableModel; 062import javax.swing.table.JTableHeader; 063import javax.swing.table.TableCellRenderer; 064 065import org.kepler.objectmanager.lsid.KeplerLSID; 066import org.kepler.provenance.FireState; 067import org.kepler.provenance.ProvenanceEnabledListener; 068import org.kepler.provenance.Recording; 069import org.kepler.provenance.RecordingException; 070import org.kepler.provenance.SimpleFiringRecording; 071 072import ptolemy.actor.Actor; 073import ptolemy.actor.CompositeActor; 074import ptolemy.actor.FiringEvent; 075import ptolemy.actor.IOPort; 076import ptolemy.actor.IOPortEvent; 077import ptolemy.actor.TypedIOPort; 078import ptolemy.actor.gui.PtolemyFrame; 079import ptolemy.domains.pn.kernel.PNDirector; 080import ptolemy.kernel.util.Nameable; 081import ptolemy.kernel.util.NamedObj; 082import ptolemy.util.MessageHandler; 083 084/** A panel that displays actor execution information. The execution 085 * information is driven by calling methods in the _executionMonitorRecording 086 * object. This class is abstract, and subclasses must instantiate 087 * _executionMonitorRecording and set its container to be the workflow 088 * to be monitored. 089 * 090 * @see Recording 091 * 092 * @author Daniel Crawl 093 * @version $Id: ExecutionMonitorPanel.java 34199 2015-11-02 20:02:37Z crawl $ 094 * 095 */ 096public abstract class ExecutionMonitorPanel extends ViewWatchingTabPane 097 implements ProvenanceEnabledListener { 098 099 /** Create a new ExecutionMonitorPanel with the specified name. */ 100 public ExecutionMonitorPanel(String name) { 101 super(new GridBagLayout()); 102 _tabName = name; 103 } 104 105 /** Initialize the tab. */ 106 @Override 107 public void initializeTab() throws Exception { 108 109 super.initializeTab(); 110 111 _workflow = (CompositeActor) ((PtolemyFrame)_frame).getModel(); 112 113 _executionTable = new JTable() { 114 115 @Override 116 public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { 117 Component component = super.prepareRenderer(renderer, row, column); 118 if(row == _selectedRow) { 119 component.setBackground(Color.BLUE); 120 component.setForeground(Color.WHITE); 121 } else { 122 component.setBackground(_actorsList.get(row).status.getColor()); 123 component.setForeground(Color.BLACK); 124 } 125 Font font = component.getFont(); 126 component.setFont(new Font(font.getName(), font.getStyle(), FONT_SIZE)); 127 return component; 128 } 129 }; 130 131 _executionTable.setPreferredScrollableViewportSize(new Dimension(500, 500)); 132 133 _executionTable.addMouseListener(new MouseAdapter() { 134 135 @Override 136 public void mousePressed(MouseEvent mouseEvent) { 137 Point point = mouseEvent.getPoint(); 138 _selectedRow = _executionTable.rowAtPoint(point); 139 140 // see if it's a double click in the error column 141 if(mouseEvent.getClickCount() == 2 && 142 _executionTable.columnAtPoint(point) == Column.Message.ordinal()) { 143 144 // display the error message in a dialog box if not empty 145 String message = (String) _executionTableModel.getValueAt(_selectedRow, Column.Message.ordinal()); 146 if(message != null && !message.trim().isEmpty()) { 147 MessageHandler.error(message); 148 } 149 } 150 } 151 152 @Override 153 public void mouseReleased(MouseEvent mouseEvent) { 154 Point point = mouseEvent.getPoint(); 155 _selectedRow = _executionTable.rowAtPoint(point); 156 } 157 }); 158 159 _executionTable.setRowHeight(20); 160 161 _executionTableModel = new ExecutionMonitorTableModel(); 162 _executionTable.setModel(_executionTableModel); 163 164 _executionTable.setAutoCreateRowSorter(true); 165 166 _initColumnSizes(); 167 168 _initContextMenu(); 169 170 JScrollPane scrollPane = new JScrollPane(_executionTable); 171 172 scrollPane.setColumnHeader(new JViewport() { 173 174 @Override 175 public Dimension getPreferredSize() { 176 Dimension dimension = super.getPreferredSize(); 177 dimension.height = 30; 178 return dimension; 179 } 180 181 }); 182 183 scrollPane.getViewport().setBackground(Color.WHITE); 184 185 JTableHeader header = _executionTable.getTableHeader(); 186 Font headerFont = header.getFont(); 187 header.setFont(new Font(headerFont.getName(), headerFont.getStyle(), FONT_SIZE)); 188 189 _replayButton = new JButton("Replay"); 190 _replayButton.addActionListener(new ActionListener() { 191 @Override 192 public void actionPerformed(ActionEvent e) { 193 _replay(); 194 } 195 }); 196 _replayButton.setEnabled(false); 197 198 JButton configureButton = new JButton("Configure"); 199 configureButton.addActionListener(new ActionListener() { 200 @Override 201 public void actionPerformed(ActionEvent e) { 202 JDialog dialog = 203 new ExecutionPanelConfigureDialog(ExecutionMonitorPanel.this, 204 _isRunning.get()); 205 dialog.pack(); 206 dialog.setVisible(true); 207 } 208 }); 209 210 _statusLabel = new JLabel(""); 211 212 GridBagConstraints c = new GridBagConstraints(); 213 c.anchor = GridBagConstraints.LINE_START; 214 215 c.weightx = 1; 216 c.weighty = 1; 217 c.gridx = 0; 218 c.gridy = 0; 219 c.gridwidth = 3; 220 c.fill = GridBagConstraints.BOTH; 221 add(scrollPane, c); 222 223 c.weightx = 0; 224 c.weighty = 0; 225 c.fill = GridBagConstraints.NONE; 226 227 c.gridy = 1; 228 c.gridwidth = 1; 229 add(_replayButton, c); 230 231 c.gridx = 1; 232 add(configureButton, c); 233 234 c.gridx = 2; 235 c.gridwidth = GridBagConstraints.REMAINDER; 236 c.insets = new Insets(0, 10, 0, 0); 237 add(_statusLabel, c); 238 239 setBackground(Color.WHITE); 240 } 241 242 /** Clean up resources since panel is being closed. */ 243 @Override 244 public void removeNotify() { 245 super.removeNotify(); 246 _actorsInfo.clear(); 247 _actorsList.clear(); 248 _executionMonitorRecording = null; 249 } 250 251 /** This method is called when provenance is enabled or disabled. 252 * @param enabled True if provenance is enabled. 253 */ 254 @Override 255 public void toggle(final boolean enabled) { 256 SwingUtilities.invokeLater(new Runnable() { 257 @Override 258 public void run() { 259 if(enabled) { 260 _statusLabel.setText(""); 261 } else { 262 _statusLabel.setText("Turn on provenance to monitor execution."); 263 } 264 } 265 }); 266 } 267 268 /////////////////////////////////////////////////////////////////// 269 //// protected methods //// 270 271 /** Get the ActorInfo for the selected row in the table. If no 272 * row is selected, returns null. 273 */ 274 protected ActorInfo _getSelectedRowInfo() { 275 if(_selectedRow > 0) { 276 return _actorsList.get(_selectedRow); 277 } 278 return null; 279 } 280 281 /** Initialize the pop up menu for the execution table. */ 282 protected void _initContextMenu() { 283 _executionTableContextMenu = new JPopupMenu(); 284 _executionTable.setComponentPopupMenu(_executionTableContextMenu); 285 } 286 287 /** Replay an execution. */ 288 abstract protected void _replay(); 289 290 /////////////////////////////////////////////////////////////////// 291 //// protected variables //// 292 293 /** The workflow object. */ 294 protected CompositeActor _workflow; 295 296 /** The recording class that updates the table. */ 297 protected ExecutionMonitorRecording _executionMonitorRecording; 298 299 /** Status label at the bottom of the table. */ 300 protected JLabel _statusLabel; 301 302 /** If true, the current time can be used to calculate the elapsed 303 * execution time of an actor. 304 */ 305 protected boolean _useCurrentTimeForElapsed = true; 306 307 /** Context menu for execution table. */ 308 protected JPopupMenu _executionTableContextMenu; 309 310 /////////////////////////////////////////////////////////////////// 311 //// package protected methods //// 312 313 /** Get if executions for an actor should be separate rows. */ 314 boolean getSeparateActorExecutions() { 315 return _separateActorExecutions; 316 } 317 318 /** Get if executions for composite actors should be shown. */ 319 boolean getShowCompositeActorExecutions() { 320 return _showCompositeActorExecutions; 321 } 322 323 /** Get the frequency to update the table. */ 324 int getUpdateDelay() { 325 return _updateFrequency; 326 } 327 328 /** Set if executions for an actor should be separate rows. */ 329 void setSeparateActorExecutions(boolean separate) { 330 _separateActorExecutions = separate; 331 } 332 333 /** Set if executions for composite actors should be shown. */ 334 void setShowCompositeActorExecutions(boolean show) { 335 _showCompositeActorExecutions = show; 336 } 337 338 /** Set the frequency to update the table. */ 339 void setUpdateDelay(int delay) { 340 _updateFrequency = delay; 341 } 342 343 /////////////////////////////////////////////////////////////////// 344 //// private methods //// 345 346 /** Set column width in the execution table. */ 347 private void _initColumnSizes() { 348 349 _executionTable.getColumnModel() 350 .getColumn(Column.Actor.ordinal()) 351 .setPreferredWidth(400); 352 353 _executionTable.getColumnModel() 354 .getColumn(Column.ElapsedTime.ordinal()) 355 .setPreferredWidth(100); 356 357 _executionTable.getColumnModel() 358 .getColumn(Column.ExecutionNumber.ordinal()) 359 .setMaxWidth(55); 360 361 _executionTable.getColumnModel() 362 .getColumn(Column.Start.ordinal()) 363 .setPreferredWidth(150); 364 365 _executionTable.getColumnModel() 366 .getColumn(Column.End.ordinal()) 367 .setPreferredWidth(150); 368 369 _executionTable.getColumnModel() 370 .getColumn(Column.Message.ordinal()) 371 .setPreferredWidth(350); 372 } 373 374 /////////////////////////////////////////////////////////////////// 375 //// inner classes //// 376 377 /** Table model for the execution table. */ 378 private class ExecutionMonitorTableModel extends AbstractTableModel { 379 380 /** Get the class of the value in the column. The class type 381 * is used to correctly sort the column. 382 */ 383 @Override 384 public Class<?> getColumnClass(int col) { 385 Class<?> clazz = Column.values()[col].getColumnClass(); 386 if(clazz != null) { 387 return clazz; 388 } 389 return Object.class; 390 } 391 392 /** Get the number of rows. */ 393 @Override 394 public int getRowCount() { 395 return _actorsList.size(); 396 } 397 398 /** Get the number of columns. */ 399 @Override 400 public int getColumnCount() { 401 return Column.values().length; 402 } 403 404 /** Get the column header name. */ 405 @Override 406 public String getColumnName(int col) { 407 return Column.values()[col].getName(); 408 } 409 410 /** Get the value at a row and column. */ 411 @Override 412 public Object getValueAt(int rowIndex, int columnIndex) { 413 ActorInfo info = _actorsList.get(rowIndex); 414 synchronized(info) { 415 return info.getColumn(columnIndex); 416 } 417 } 418 } 419 420 /** A class to monitor workflow execution by receiving provenance events. */ 421 protected class ExecutionMonitorRecording extends SimpleFiringRecording<Integer> { 422 423 public ExecutionMonitorRecording() throws RecordingException { 424 super(); 425 } 426 427 /** Record an actor firing. */ 428 @Override 429 public void actorFire(FiringEvent event, Date timestamp) throws RecordingException 430 { 431 Actor actor = event.getActor(); 432 433 //System.out.println(event); 434 435 // TODO if show composite actor executions turned on 436 // during workflow execution, actor info below is null and 437 // leads to NPE. 438 if(actor instanceof CompositeActor && 439 !_showCompositeActorExecutions) { 440 return; 441 } 442 443 FiringEvent.FiringEventType curEventType = event.getType(); 444 FireState<Integer> fireState = _fireStateTable.get(actor); 445 446 if(fireState == null) { 447 System.err.println("WARNING: received actor fire event" + 448 " for unregistered actor: " + actor.getFullName()); 449 return; 450 /* 451 throw new RecordingException( 452 "Received actor fire event for unregistered actor: " + 453 actor.getFullName()); 454 */ 455 } 456 457 String actorName = actor.getName(_recordingWorkflow); 458 ActorInfo info = _actorsInfo.get(actorName); 459 460 synchronized(fireState) { 461 // get the last type of firing start 462 FiringEvent.FiringEventType lastStartType = 463 fireState.getLastStartFireType(); 464 465 // see if current firing is new iteration: 466 // NOTE: PN does not report iterate firings so the iteration 467 // may begin with prefire if the last type of firing was not 468 // iterate. 469 if(curEventType == FiringEvent.BEFORE_ITERATE || 470 (curEventType == FiringEvent.BEFORE_PREFIRE && 471 lastStartType != FiringEvent.BEFORE_ITERATE)) { 472 473 int firing = fireState.getNumberOfFirings() + 1; 474 fireState.fireStart(curEventType, firing); 475 476 boolean newActorInfo = false; 477 if(info == null || 478 (_separateActorExecutions && firing > 0)) { 479 info = new ActorInfo(actorName); 480 info.useCurrentTimeForElapsed = _useCurrentTimeForElapsed; 481 newActorInfo = true; 482 } 483 484 synchronized(info) { 485 info.firing = firing; 486 info.status = ActorStatus.Running; 487 if(timestamp == null) { 488 info.start = new Date(); 489 } else { 490 info.start = timestamp; 491 } 492 493 if(newActorInfo) { 494 _actorsInfo.put(actorName, info); 495 _actorsList.add(info); 496 } 497 } 498 499 /* 500 SwingUtilities.invokeLater(new Runnable() { 501 @Override 502 public void run() { 503 _executionTableModel.fireTableDataChanged(); 504 } 505 }); 506 */ 507 } 508 // see if current firing is end of iteration: 509 else if(curEventType == FiringEvent.AFTER_ITERATE || 510 (curEventType == FiringEvent.AFTER_POSTFIRE && 511 lastStartType == FiringEvent.BEFORE_PREFIRE)) { 512 // NOTE: if the type is a AFTER_POSTFIRE and last start 513 // type is BEFORE_PREFIRE, we are running in PN. 514 // in this case, tell the fireState to stop firing using 515 // AFTER_PREFIRE instead of AFTER_POSTFIRE, since we never 516 // told the fireState about the BEFORE_POSTFIRE. 517 if(curEventType == FiringEvent.AFTER_POSTFIRE) { 518 fireState.fireStop(FiringEvent.AFTER_PREFIRE); 519 } else { 520 fireState.fireStop(curEventType); 521 } 522 523 synchronized(info) { 524 info.status = ActorStatus.Done; 525 if(timestamp == null) { 526 info.end = new Date(); 527 } else { 528 info.end = timestamp; 529 } 530 } 531 532 /* 533 SwingUtilities.invokeLater(new Runnable() { 534 @Override 535 public void run() { 536 _executionTableModel.fireTableDataChanged(); 537 } 538 }); 539 */ 540 } 541 } 542 } 543 544 /** The workflow had an error. 545 * 546 * @param source The source of the error. This may be null. 547 * @param throwable The error. 548 */ 549 @Override 550 public void executionError(Nameable source, Throwable throwable) 551 throws RecordingException 552 { 553 _isFinished.set(true); 554 555 if(source instanceof Actor) { 556 ActorInfo info; 557 if(source == _recordingWorkflow) { 558 info = _actorsInfo.get("Workflow"); 559 if(info == null) { 560 info = new ActorInfo("Workflow"); 561 info.useCurrentTimeForElapsed = _useCurrentTimeForElapsed; 562 info.status = ActorStatus.Running; 563 info.start = new Date(); 564 info.firing = 1; 565 566 _actorsInfo.put("Workflow", info); 567 _actorsList.add(info); 568 } 569 } else { 570 String actorName = ((Actor)source).getName(_recordingWorkflow); 571 info = _actorsInfo.get(actorName); 572 if(info == null) { 573 info = new ActorInfo(actorName); 574 info.useCurrentTimeForElapsed = _useCurrentTimeForElapsed; 575 _actorsInfo.put(actorName, info); 576 _actorsList.add(info); 577 } 578 } 579 580 synchronized(info) { 581 info.status = ActorStatus.Error; 582 info.message = throwable.getMessage(); 583 } 584 585 SwingUtilities.invokeLater(new Runnable() { 586 @Override 587 public void run() { 588 _executionTableModel.fireTableDataChanged(); 589 } 590 }); 591 } 592 } 593 594 /** Record the starting of workflow execution. */ 595 @Override 596 public void executionStart(KeplerLSID executionLSID, Date timestamp) throws RecordingException 597 { 598 _isFinished.set(false); 599 600 // create a row in the execution table for the workflow 601 ActorInfo info = new ActorInfo("Workflow"); 602 info.useCurrentTimeForElapsed = _useCurrentTimeForElapsed; 603 info.status = ActorStatus.Running; 604 if(timestamp == null) { 605 info.start = new Date(); 606 } else { 607 info.start = timestamp; 608 } 609 info.firing = 1; 610 611 _actorsInfo.put("Workflow", info); 612 _actorsList.add(info); 613 614 _isRunning.set(true); 615 616 SwingUtilities.invokeLater(new Runnable() { 617 @Override 618 public void run() { 619 _statusLabel.setText("Running."); 620 _executionTableModel.fireTableDataChanged(); 621 } 622 }); 623 624 // create a thread to periodically update the execution table 625 _updateThread = new Thread(new Runnable() { 626 @Override 627 public void run() { 628 while(!_isFinished.get()) { 629 synchronized(_updateLock) { 630 try { 631 _updateLock.wait(_updateFrequency); 632 } catch (InterruptedException e) { 633 MessageHandler.error("Interrupted while waiting for update lock.", e);; 634 } 635 SwingUtilities.invokeLater(new Runnable() { 636 @Override 637 public void run() { 638 _executionTableModel.fireTableDataChanged(); 639 } 640 }); 641 } 642 } 643 } 644 }); 645 _updateThread.start(); 646 } 647 648 /** Record the stopping of workflow execution. */ 649 @Override 650 public void executionStop(KeplerLSID executionLSID, Date timestamp) throws RecordingException 651 { 652 _isFinished.set(true); 653 654 // stop all the actors. 655 synchronized(_actorsInfo) { 656 for(ActorInfo info : _actorsInfo.values()) { 657 synchronized(info) { 658 if(info.status == ActorStatus.Running || 659 info.status == ActorStatus.Waiting) { 660 info.status = ActorStatus.Done; 661 if(info.status != ActorStatus.Waiting) { 662 if(timestamp == null) { 663 info.end = new Date(); 664 } else { 665 info.end = timestamp; 666 } 667 } 668 } 669 } 670 } 671 } 672 673 // wake up the updater thread and wait until it finishes. 674 synchronized(_updateLock) { 675 _updateLock.notify(); 676 } 677 678 try { 679 _updateThread.join(); 680 } catch (InterruptedException e) { 681 MessageHandler.error("Error joining update thread.", e); 682 } 683 684 _isRunning.set(false); 685 686 SwingUtilities.invokeLater(new Runnable() { 687 @Override 688 public void run() { 689 _statusLabel.setText("Done."); 690 _executionTableModel.fireTableDataChanged(); 691 } 692 }); 693 694 _blockingPorts.clear(); 695 696 SwingUtilities.invokeLater(new Runnable() { 697 @Override 698 public void run() { 699 _replayButton.setEnabled(true); 700 } 701 }); 702 703 } 704 705 /** Record a port event. */ 706 @Override 707 public void portEvent(IOPortEvent event, Date timestamp) throws RecordingException 708 { 709 String actorName = _blockingPorts.get(event.getPort()); 710 ActorInfo info; 711 712 // if actor name is not null, then reading the port blocks 713 // actor execution. 714 if(actorName != null) { 715 info = _actorsInfo.get(actorName); 716 717 // info is null if we are not showing composite 718 // actor executions and the port is contained by 719 // a composite actor. 720 if(info != null) { 721 722 switch(event.getEventType()) { 723 case IOPortEvent.GET_BEGIN: 724 synchronized(info) { 725 info.status = ActorStatus.Waiting; 726 } 727 //System.out.println("waiting " + info._name); 728 //System.out.flush(); 729 /* 730 SwingUtilities.invokeLater(new Runnable() { 731 @Override 732 public void run() { 733 _executionTableModel.fireTableDataChanged(); 734 } 735 }); 736 */ 737 break; 738 case IOPortEvent.GET_END: 739 synchronized(info) { 740 info.status = ActorStatus.Running; 741 } 742 //System.out.println("running " + info._name); 743 //System.out.flush(); 744 /* 745 SwingUtilities.invokeLater(new Runnable() { 746 @Override 747 public void run() { 748 _executionTableModel.fireTableDataChanged(); 749 } 750 }); 751 */ 752 break; 753 default: 754 // do nothing 755 break; 756 } 757 } 758 } 759 } 760 761 /** Register a port. */ 762 @Override 763 public boolean regPort(TypedIOPort port) { 764 765 // add the port to the set of blocking ports if the port 766 // is an input port and the executive director is PN. 767 // we check the executive director since if the actor is 768 // a composite actor, we want the outside director. 769 if(port.isInput() && 770 (port.getContainer() instanceof Actor) && 771 ((Actor)port.getContainer()).getExecutiveDirector() instanceof PNDirector) { 772 _blockingPorts.put(port, ((Actor)port.getContainer()).getName(_recordingWorkflow)); 773 //System.out.println("blocking ports added " + port.getFullName()); 774 } 775 return false; 776 } 777 778 /** Clear data structures before starting. */ 779 @Override 780 public void specificationStart() throws RecordingException { 781 super.specificationStart(); 782 _actorsInfo.clear(); 783 _actorsList.clear(); 784 _blockingPorts.clear(); 785 _recordingWorkflow = _recorderContainer.toplevel(); 786 SwingUtilities.invokeLater(new Runnable() { 787 @Override 788 public void run() { 789 _replayButton.setEnabled(false); 790 } 791 }); 792 } 793 794 /** True when the workflow finishes. */ 795 private final AtomicBoolean _isFinished = new AtomicBoolean(false); 796 797 /** A map of ports to actor names for input ports that block. */ 798 private final Map<IOPort, String> _blockingPorts = 799 Collections.synchronizedMap(new HashMap<IOPort, String>()); 800 801 /** A thread that periodically updates the execution table. */ 802 private Thread _updateThread; 803 804 /** A lock for the update thread. */ 805 private final Object _updateLock = new Object(); 806 807 /** The workflow. */ 808 private NamedObj _recordingWorkflow; 809 } 810 811 /** A utility class to hold information about a single 812 * execution of an actor. 813 */ 814 protected static class ActorInfo { 815 816 /** Create a new ActorInfo with an actor's name. */ 817 public ActorInfo(String name) { 818 this.name = name; 819 } 820 821 /** Get the object for a column in the execution table. */ 822 public Object getColumn(int index) { 823 switch(Column.values()[index]) { 824 case Actor: 825 //System.out.println("name = " + _name); 826 //System.out.flush(); 827 return name; 828 case ElapsedTime: 829 if(status == ActorStatus.Done || status == ActorStatus.Error) { 830 if(end == null || start == null) { 831 return "-"; 832 } 833 return String.format("%.2f", 834 Double.valueOf(end.getTime() - start.getTime()) / _MILLI_SEC_IN_SEC); 835 } else if(start != null && useCurrentTimeForElapsed) { 836 return String.format("%.2f", 837 Double.valueOf(new Date().getTime() - start.getTime()) / _MILLI_SEC_IN_SEC); 838 } else { 839 return "-"; 840 } 841 case End: 842 if(end == null) { 843 return "-"; 844 } 845 return _dateFormat.format(end); 846 case ExecutionNumber: 847 return firing; 848 case Message: 849 return message; 850 /* 851 case Status: 852 //System.out.println("status = " + status); 853 //System.out.flush(); 854 return status; 855 */ 856 case Start: 857 if(start == null) { 858 return "-"; 859 } 860 return _dateFormat.format(start); 861 default: 862 System.out.println("WARNING: ActorInfo.getColumn for unknown index: " + index); 863 return null; 864 } 865 } 866 867 @Override 868 public String toString() { 869 return name + " " + status.name(); 870 } 871 872 /** The time that execution started. */ 873 //public long startTime = -1; 874 875 /** The time that execution stopped. */ 876 //public long endTime = -1; 877 878 /** The timestamp that execution started. */ 879 public Date start; 880 881 /** The timestamp that execution end. */ 882 public Date end; 883 884 /** The current firing number of the actor. */ 885 public int firing = -1; 886 887 /** The execution status. */ 888 public ActorStatus status = ActorStatus.NotRun; 889 890 /** An optional execution message */ 891 public String message = ""; 892 893 /** If true, the current time can be used to calculate the elapsed 894 * execution time of an actor. 895 */ 896 public boolean useCurrentTimeForElapsed; 897 898 /** The fully-qualified name of the actor (with no leading period.) */ 899 public String name; 900 } 901 902 /** The columns in the execution table. */ 903 private enum Column { 904 Actor("Actor"), 905 ElapsedTime("Elapsed", Integer.class), 906 ExecutionNumber("Num", Integer.class), 907 Start("Start"), 908 End("End"), 909 //Status("Status"), 910 Message("Message"); 911 912 Column(String name) { 913 _name = name; 914 _class = String.class; 915 } 916 917 Column(String name, Class<?> clazz) { 918 _name = name; 919 _class = clazz; 920 } 921 922 /** Get the class of the value in the column. */ 923 public Class<?> getColumnClass() { 924 return _class; 925 } 926 927 /** Get the name of the column */ 928 public String getName() { 929 return _name; 930 } 931 932 /** The column name. */ 933 private String _name; 934 935 /** The column class type. */ 936 private Class<?> _class; 937 938 }; 939 940 /** The actor execution status. */ 941 private enum ActorStatus { 942 Done, 943 Error, 944 Running, 945 NotRun, 946 Waiting; 947 948 /** Get the color for the status. */ 949 public Color getColor() { 950 switch(this) { 951 case Done: 952 return Color.LIGHT_GRAY; 953 case Error: 954 return Color.RED; 955 case NotRun: 956 return Color.WHITE; 957 case Running: 958 return Color.GREEN; 959 case Waiting: 960 return Color.YELLOW; 961 default: 962 return Color.WHITE; 963 } 964 } 965 966 }; 967 968 /////////////////////////////////////////////////////////////////// 969 //// private variables //// 970 971 /** If true, provenance is turned on. */ 972 //private boolean _provenanceEnabled; 973 974 /** The execution table. */ 975 private JTable _executionTable; 976 977 /** The execution table model. */ 978 private AbstractTableModel _executionTableModel; 979 980 private final Map<String,ActorInfo> _actorsInfo = 981 Collections.synchronizedMap(new HashMap<String,ActorInfo>()); 982 983 private final List<ActorInfo> _actorsList = 984 Collections.synchronizedList(new ArrayList<ActorInfo>()); 985 986 /** If true, a new row is created for each actor firing in the execution table. */ 987 private boolean _separateActorExecutions = true; 988 989 /** If true, a new row is created in the execution table for 990 * each composite actor firing. 991 */ 992 private boolean _showCompositeActorExecutions = false; 993 994 /** Index of selected row in the execution table. */ 995 private int _selectedRow = -1; 996 997 /** How often (in milliseconds) to update the execution table. */ 998 private int _updateFrequency = 5000; 999 1000 /** Timestamp format for execution table. */ 1001 private final static DateFormat _dateFormat = new SimpleDateFormat("hh:mm:ss.S"); 1002 1003 /** Execution table font size for cell and column header. */ 1004 public final static int FONT_SIZE = 14; 1005 1006 /** Number of milliseconds in one second. */ 1007 private final static double _MILLI_SEC_IN_SEC = 1000.0; 1008 1009 /** Button for replay. */ 1010 private JButton _replayButton; 1011 1012 /** True when the workflow is executing. */ 1013 private AtomicBoolean _isRunning = new AtomicBoolean(false); 1014 1015}