/* * Copyright (c) 2010-2012 Research In Motion Limited. All rights reserved. * * This program and the accompanying materials are made available * under the terms of the Eclipse Public License, Version 1.0, * which accompanies this distribution and is available at * * http://www.eclipse.org/legal/epl-v10.html * */ package net.rim.ejde.internal.ui.views.profiler; import java.awt.Desktop; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.Comparator; import java.util.Enumeration; import net.rim.ejde.internal.core.ContextManager; import net.rim.ejde.internal.core.RimIDEUtil; import net.rim.ejde.internal.ui.preferences.PreferenceConstants; import net.rim.ejde.internal.ui.views.BasicDebugView; import net.rim.ejde.internal.util.DebugUtils; import net.rim.ejde.internal.util.Messages; import net.rim.ejde.internal.util.ProjectUtils; import net.rim.ide.RIA; import net.rim.ide.RIA.ProfileType; import net.rim.ide.core.IDEError; import net.rim.ide.core.ProfileData; import net.rim.ide.core.ProfileData.SourceResolver; import net.rim.ide.core.ProfileItem; import net.rim.ide.core.ProfileItemSource; import net.rim.ide.core.ProfileLine; import net.rim.ide.core.ProfileMethod; import net.rim.ide.core.ProfileSourceLine; import net.rim.ide.core.Util; import net.rim.tools.compiler.debug.DebugMethod; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.debug.core.DebugEvent; import org.eclipse.debug.core.ILaunch; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.dialogs.ProgressMonitorDialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.TabFolder; import org.eclipse.ui.IViewSite; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.PlatformUI; /** * View to display profile data. */ public class ProfilerView extends BasicDebugView implements SourceResolver { public static final String PROFILER_VIEW_ID = "net.rim.ejde.ui.viewers.ProfilerView"; // Indexes of profile tabs final static public int INDEX_OF_TAB_SUMMARY = 0; final static public int INDEX_OF_TAB_METHOD = 1; final static public int INDEX_OF_TAB_SOURCE = 2; private static final Logger log = Logger.getLogger( ProfilerView.class ); private TabFolder _tabFolder; private int _whatToProfile = 0; private boolean _methodTimeType; private ProfileTab[] _profileTabs; private ProfileData _pd; boolean _isInitialized = false; private static final String BLACKBERRY_PROFILEVISDESKTOP_TMP_FILE_PREFIX = "bbprof.prefix."; private static final String BLACKBERRY_PROFILEVISDESKTOP_TMP_FILE_SUFFIX = ".bbprof"; /** * Constructs a ProfilerView instance. * * @throws CoreException * */ public ProfilerView() throws CoreException { super( FORWARD_BUTTON | BACKWARD_BUTTON | REFRESH_BUTTON | CLEAR_BUTTON | OPTIONS_BUTTON | SAVE_BUTTON | SAVE_TO_XML | SAVE_RAW_TO_XML ); RIA ria = RIA.getCurrentDebugger(); if( ria != null && !_isInitialized ) { initProfileParameters( ria ); } } /** * Initializes all parameters related to profile. * * @throws CoreException * */ private void initProfileParameters( RIA ria ) { if( ria != null ) { if( !DebugUtils.isRIMDebuggerRunning() ) { return; } if( !ria.getProfileEnabled() ) { ria.setProfileEnabled( true ); } cleanMessage(); try { // get profile options from preference store IPreferenceStore ps = ContextManager.PLUGIN.getPreferenceStore(); _whatToProfile = ps.getInt( PreferenceConstants.NET_RIM_EJDE_UI_VIEWS_WHATTOPROFILE ); ProfileType[] types; // MKS 2486071 String debugAttachedTo = ria.getDebugAttachTo(); if( debugAttachedTo == null || debugAttachedTo.isEmpty() ) { return; } else { types = ProfilingViewOptionsDialog.getProfileTypes( ria ); } if( !isValidProfileId( types, _whatToProfile ) ) { _whatToProfile = types[ 0 ].getId(); } _methodTimeType = ps.getBoolean( PreferenceConstants.NET_RIM_EJDE_UI_VIEWS_METHOD_TIME_TYPE ); log.debug( "Profile option - type:" + _whatToProfile + ", method: " + ( _methodTimeType ? "Cumulative" : "In method only" ) ); ria.profileSetType( _whatToProfile ); updateTypeColumeTitle(); _isInitialized = true; } catch( Exception e ) { log.error( "", e ); } } } protected void handleRIMDebugEvent( DebugEvent event ) { if( event.getKind() == DebugEvent.CREATE && !_isInitialized ) { initProfileParameters( RIA.getCurrentDebugger() ); } } public void createTableViewPart( Composite parent ) { GridLayout gridLayout = new GridLayout(); gridLayout.numColumns = 1; parent.setLayout( gridLayout ); // create a TabFolder component on the view _tabFolder = new TabFolder( parent, SWT.BOTTOM ); _tabFolder.setLayoutData( new GridData( GridData.FILL_BOTH ) ); _profileTabs = new ProfileTab[ 3 ]; _profileTabs[ INDEX_OF_TAB_SUMMARY ] = new SummaryProfileTab( this ); _profileTabs[ INDEX_OF_TAB_METHOD ] = new MethodProfileTab( this ); _profileTabs[ INDEX_OF_TAB_SOURCE ] = new SourceProfileTab( this ); // if( !DebugUtils.isRIMDebuggerRunning() ) { setMessage( Messages.ProcessView_NO_BB_DEBUG_SESSION_MSG, true ); } } /** * Set focus to a certain UI component. */ public void setFocus() { // nothing to do } /** * Gets the profile type that need to be displayed. * * @return Profile type. */ public int getWhatToProfile() { return _whatToProfile; } /** * Gets method time type. * * @return <code>true</code> method time will be cumulated, <code>false</code> otherwise. */ public boolean getMethodTimeType() { return _methodTimeType; } /** * Set the tab at the given zero-relative index in the tabfolder as selected. * * @param index * the index of the item to select. */ public void setActiveTab( int index ) { _tabFolder.setSelection( index ); } protected TabFolder getTabFolder() { return _tabFolder; } /** * Updates the title of the type column (what to profile) of each tab. */ void updateTypeColumeTitle() { Display.getDefault().syncExec( new Runnable() { @Override public void run() { // the _profileTabs could be null if this is called during the view creation if( _profileTabs == null ) { return; } for( int i = 0; i < _profileTabs.length; i++ ) { _profileTabs[ i ].updateTypeColumeTitle(); } } } ); } /** * Display profile data. */ public void displayProfileData( ProfileTab[] tabs ) { if( tabs == null || tabs.length == 0 ) return; if( _pd != null ) { for( int i = 0; i < tabs.length; i++ ) tabs[ i ].displayData( _pd ); } } /** * Display source code information of <code>pi</code>. * * @param pi * An instance of ProfileItem. */ protected void displaySourceData( ProfileItem pi ) { SourceProfileTab sourceTab = (SourceProfileTab) _profileTabs[ INDEX_OF_TAB_SOURCE ]; sourceTab.setTotal( _pd.getTotalExecutionTicks() ); sourceTab.setHistory( pi ); sourceTab.clearExpansion(); sourceTab.displayData( pi ); setActiveTab( INDEX_OF_TAB_SOURCE ); } /** * Clears the view (all tabs). * * @param clearPreferences * <code>true</code> record or the last operation on this tab will be cleaned; <code>false</code> record or the * last operation on this tab will not be cleaned. */ public void clearVeiwer( boolean clearPreferences ) { if( _pd != null ) // clear display on each tab for( int i = 0; i < _profileTabs.length; i++ ) _profileTabs[ i ].clearTab( clearPreferences ); } /** * Save profile data to a csv file. */ private void saveProfile() { if( _pd == null ) return; try { // save profile data to the file saveContents( RimIDEUtil.openCSVFileForSave( getSite().getShell() ) ); } catch( IDEError e ) { log.error( "", e ); } } /** * Writes the profile data to <code>file</code>. * * @param file * Destination file. * @throws IDEError */ private void saveContents( File file ) throws IDEError { if( file == null ) { return; } RIA ria = RIA.getCurrentDebugger(); if( ria == null ) { return; } String debugAttachedTo = ria.getDebugAttachTo(); if( debugAttachedTo == null || debugAttachedTo.isEmpty() ) { return; } PrintStream out = null; try { out = new PrintStream( new FileOutputStream( file ) ); out.print( RIA.getString( "ProfileCSVFileHeader1" ) ); //$NON-NLS-1$ out.print( ria.profileGetTypes()[ _whatToProfile ].getDescription() ); out.print( RIA.getString( "ProfileCSVFileHeader2" ) ); //$NON-NLS-1$ out.println(); ProfileItem[] modules = sortedElements( _pd, null ); for( int i = 0; i < modules.length; i++ ) { ProfileItem module = modules[ i ]; Object moduleName = module; ProfileItem[] methods = sortedElements( module, null ); for( int j = 0; j < methods.length; j++ ) { ProfileItem method = methods[ j ]; out.print( moduleName ); out.print( ", " ); //$NON-NLS-1$ String methodStr = method.toString(); Object handle = method.getMethodHandle(); if( handle != null && handle instanceof DebugMethod ) { methodStr = ( (DebugMethod) handle ).getFullName(); } out.print( Util.replace( methodStr, ",", "" ) ); //$NON-NLS-1$ //$NON-NLS-2$ out.print( ", " ); //$NON-NLS-1$ out.print( method.getTicks() ); out.print( ", " ); //$NON-NLS-1$ out.print( method.getCount() ); out.println(); } } out.close(); } catch( IOException e ) { log.error( "", e ); } } /** * Sorts children ProfileItems of <code>source</code>. * * @param source * ProfileItemSource instance. * @param comparator * Comparator instance used to sort children items of <code>source</code>. * @return Array of sorted children ProfileItems of <code>source</code>. */ protected static ProfileItem[] sortedElements( ProfileItemSource source, Comparator comparator ) { ProfileItem profileItems[] = getUnsortedElements( source ); if( profileItems.length == 0 ) return profileItems; if( ( source instanceof ProfileMethod ) || ( source instanceof ProfileLine ) ) // source lines are sorted use ProfileMethod's default comparator Arrays.sort( profileItems, source.getComparator() ); else Arrays.sort( profileItems, comparator == null ? source.getComparator() : comparator ); return profileItems; } /** * Gets unsorted children of given <code>source</code>. * * @param source * @return */ protected static ProfileItem[] getUnsortedElements( ProfileItemSource source ) { Enumeration enumeration = source.getChildrenKeys(); if( enumeration == null ) return new ProfileItem[ 0 ]; ProfileItem profileItems[] = new ProfileItem[ source.getChildCount() ]; int i = 0; while( enumeration.hasMoreElements() ) { ProfileItem pi = source.getChild( enumeration.nextElement() ); profileItems[ i++ ] = pi; } return profileItems; } private void refresh( boolean clearPreferences ) throws CoreException { RefreshProfilerViewJob job = new RefreshProfilerViewJob(); try { PlatformUI.getWorkbench().getProgressService().run( false, true, job ); } catch( InvocationTargetException e ) { log.error( "", e ); } catch( InterruptedException e ) { log.error( "", e ); } } class RefreshProfilerViewJob implements IRunnableWithProgress { public void run( IProgressMonitor monitor ) throws InvocationTargetException, InterruptedException { monitor.beginTask( Messages.ProfilerView_Refresh, 100 ); RIA ria = RIA.getCurrentDebugger(); if( ria == null ) { return; } clearVeiwer( true ); monitor.worked( 10 ); try { ria.profileRefreshData(); monitor.worked( 20 ); _pd = ria.profileGetData(); monitor.worked( 30 ); if( _pd == null ) return; ProfileItem.setTickMode( _methodTimeType ); monitor.worked( 40 ); displayProfileData( new ProfileTab[] { _profileTabs[ INDEX_OF_TAB_SUMMARY ], _profileTabs[ INDEX_OF_TAB_METHOD ], _profileTabs[ INDEX_OF_TAB_SOURCE ] } ); monitor.worked( 50 ); setHasData( true ); monitor.worked( 60 ); updateToolbar(); } catch( IDEError e ) { log.error( "", e ); } monitor.done(); } } // ------ Methods in BasicDebugView to be overridden ------ /** * RIM Debug session is terminated. * * @see BasicDebugView#RIMDebugTerminated(). */ public void RIMDebugTerminated( ILaunch[] launches ) { setMessage( Messages.ProcessView_NO_BB_DEBUG_SESSION_MSG, true ); _isInitialized = false; this.getSite().getShell().getDisplay().syncExec( new Runnable() { @Override public void run() { clear(); } } ); // this.getSite().getShell().getDisplay().asyncExec( new CloseViewJob( this ) ); } /** * Gets new profile data, erases the current profile data, and display the new profile data. * * @throws CoreException * @see BasicDebugView#refresh(). * */ public void refresh() throws CoreException { refresh( true ); } public void forward() { if( !( _tabFolder.getSelectionIndex() == INDEX_OF_TAB_SOURCE ) ) return; _profileTabs[ INDEX_OF_TAB_SOURCE ].forward(); } public void backward() { if( !( _tabFolder.getSelectionIndex() == INDEX_OF_TAB_SOURCE ) ) return; _profileTabs[ INDEX_OF_TAB_SOURCE ].backward(); } /** * (non-Javadoc) * * @see BasicDebugView#clear(). */ public void clear() { // clear the debugger RIA ria = RIA.getCurrentDebugger(); if( ria == null ) { return; } try { ria.profileClearData(); } catch( IDEError e ) { log.error( e ); return; } // clear the display data clearVeiwer( true ); _pd = null; setHasData( false ); updateToolbar(); updateTypeColumeTitle(); } /** * Gets the ProfileData instance represented by this view. * * @return a ProfileData instance. */ public ProfileData getProfileData() { return _pd; } /** * (non-Javadoc) * * @see BasicDebugView#save(). */ public void save() { saveProfile(); } public void dispose() { super.dispose(); if( RIA.getCurrentDebugger() != null ) { RIA.getCurrentDebugger().setProfileEnabled( false ); } _pd = null; _profileTabs = null; } private static boolean isValidProfileId( ProfileType[] types, int id ) { for( int i = 0; i < types.length; i++ ) { if( types[ i ].getId() == id ) { return true; } } return false; } /** * Opens an option dialog. Options related to profiler can be set on the dialog and will be saved as references when "OK" * button is pressed. * * @see BasicDebugView#setOptions(). */ public void setOptions() { // create an ImplicitBuildRuleEditDialog instance ProfilingViewOptionsDialog optionsDialog = new ProfilingViewOptionsDialog( getSite().getShell() ); // show the dialog optionsDialog.open(); if( optionsDialog.isOkButtonClicked() ) { try { IPreferenceStore ps = ContextManager.PLUGIN.getPreferenceStore(); int whatToProfile = ps.getInt( PreferenceConstants.NET_RIM_EJDE_UI_VIEWS_WHATTOPROFILE ); if( whatToProfile != _whatToProfile ) { initProfileParameters( RIA.getCurrentDebugger() ); clear(); enableActions( REFRESH_BUTTON, false ); } else { initProfileParameters( RIA.getCurrentDebugger() ); refresh( false ); } } catch( Exception e ) { log.error( "", e ); } } } /** * Save view content to a XML file. * <p> * <b>subclasses need to override this method.</b> */ public void saveXML() { File xmlFile = chooseDataFile(); if( xmlFile == null ) { return; } if( !xmlFile.exists() ) { xmlFile = ProjectUtils.createFile( xmlFile ); if( xmlFile == null || !xmlFile.exists() ) { return; } } ProgressMonitorDialog dialog = new ProgressMonitorDialog( ContextManager.getActiveWorkbenchShell() ); SaveDataRunnale runnable = new SaveDataRunnale( xmlFile, this ); try { dialog.run( false, true, runnable ); } catch( InvocationTargetException e ) { log.error( e ); MessageDialog .openError( ContextManager.getActiveWorkbenchShell(), e.getMessage(), Messages.ErrorHandler_DIALOG_TITLE ); } catch( InterruptedException e ) { log.error( e ); MessageDialog .openError( ContextManager.getActiveWorkbenchShell(), e.getMessage(), Messages.ErrorHandler_DIALOG_TITLE ); } } /** * Save raw data of the view content to a XML file. * <p> * <b>subclasses need to override this method.</b> */ public void saveRawToXML() { File xmlFile = chooseRawDataFile(); if( xmlFile == null ) { return; } if( !xmlFile.exists() ) { xmlFile = ProjectUtils.createFile( xmlFile ); if( xmlFile == null || !xmlFile.exists() ) { return; } } ProgressMonitorDialog dialog = new ProgressMonitorDialog( ContextManager.getActiveWorkbenchShell() ); SaveRawDataRunnale runnable = new SaveRawDataRunnale( xmlFile, this ); try { dialog.run( false, true, runnable ); } catch( InvocationTargetException e ) { log.error( e ); MessageDialog .openError( ContextManager.getActiveWorkbenchShell(), e.getMessage(), Messages.ErrorHandler_DIALOG_TITLE ); } catch( InterruptedException e ) { log.error( e ); MessageDialog .openError( ContextManager.getActiveWorkbenchShell(), e.getMessage(), Messages.ErrorHandler_DIALOG_TITLE ); } } private void saveRawData( File xmlFile ) { RIA ria = RIA.getCurrentDebugger(); if( ria == null ) { return; } if( xmlFile == null ) { return; } if( !xmlFile.exists() ) { xmlFile = ProjectUtils.createFile( xmlFile ); if( xmlFile == null || !xmlFile.exists() ) { return; } } PrintStream out = null; try { out = new PrintStream( new FileOutputStream( xmlFile ) ); ria.profileDumpRawXML( out ); } catch( Exception e ) { log.error( e ); MessageDialog .openError( ContextManager.getActiveWorkbenchShell(), Messages.ErrorHandler_DIALOG_TITLE, e.getMessage() ); } finally { if( out != null ) { out.close(); } } } private File chooseDataFile() { return chooseXMLFile( new String[] { "*.xml" }, new String[] { "XML File (*.xml)" } ); } private File chooseRawDataFile() { return chooseXMLFile( new String[] { "*.xml", "*.bbprof" }, new String[] { "XML File (*.xml)", "BlackBerry ProfileVisDesktop File (*.bbprof)" } ); } private File chooseXMLFile( String[] filterExtensions, String[] filterNamesExtensions ) { FileDialog dialog = new FileDialog( this.getSite().getShell(), SWT.SAVE ); dialog.setFilterExtensions( filterExtensions ); dialog.setFilterNames( filterNamesExtensions ); String xmlFile = dialog.open(); if( !StringUtils.isBlank( xmlFile ) ) { return new File( xmlFile ); } return null; } @Override public String resolveSourceLine( ProfileItem item ) { RIA ria = RIA.getCurrentDebugger(); if( ria == null ) { return Messages.SourceProfileTab_NO_SOURCE_MESSAGE; } ProfileSourceLine psl = item.getLineHandle(); if( psl == null ) return Messages.SourceProfileTab_NO_SOURCE_MESSAGE; Object line = psl.getLine(); if( line == null ) { return Messages.SourceProfileTab_NO_SOURCE_MESSAGE; } return line.toString(); } public void openProfileVis() { File tmpFile = getTmpFile(); ProgressMonitorDialog dialog = new ProgressMonitorDialog( ContextManager.getActiveWorkbenchShell() ); SaveRawDataRunnale runnable = new SaveRawDataRunnale( tmpFile, this ); try { dialog.run( false, true, runnable ); } catch( InvocationTargetException e ) { log.error( e ); MessageDialog .openError( ContextManager.getActiveWorkbenchShell(), e.getMessage(), Messages.ErrorHandler_DIALOG_TITLE ); return; } catch( InterruptedException e ) { log.error( e ); MessageDialog .openError( ContextManager.getActiveWorkbenchShell(), e.getMessage(), Messages.ErrorHandler_DIALOG_TITLE ); return; } try { Desktop.getDesktop().open( tmpFile ); } catch( IOException e ) { log.error( e ); MessageDialog.openError( ContextManager.getActiveWorkbenchShell(), Messages.ErrorHandler_DIALOG_TITLE, Messages.BBProfileVis_Not_Installed_ErrMsg ); } } private static synchronized File getTmpFile() { String tmpDir = System.getProperty( "java.io.tmpdir" ); if( tmpDir == null ) { // Shouldn't happen tmpDir = "."; } File file = null; for( int retry = 0; retry < 10; retry++ ) { file = new File( tmpDir, BLACKBERRY_PROFILEVISDESKTOP_TMP_FILE_PREFIX + System.currentTimeMillis() + BLACKBERRY_PROFILEVISDESKTOP_TMP_FILE_SUFFIX ); if( !file.exists() ) { return file; } } throw new RuntimeException( "unable to create temporary file" ); } // ----- Inner Classes ------ class CloseViewJob implements Runnable { ProfilerView _view; CloseViewJob( ProfilerView view ) { _view = view; } public void run() { IViewSite viewSite = _view.getViewSite(); if( viewSite == null ) { return; } IWorkbenchPage workbenchPage = viewSite.getPage(); if( workbenchPage == null ) { return; } workbenchPage.hideView( _view ); } } class SaveRawDataRunnale implements IRunnableWithProgress { File destFile; SourceResolver sourceResolver; public SaveRawDataRunnale( File destFile, SourceResolver sourceResolver ) { this.sourceResolver = sourceResolver; this.destFile = destFile; } @Override public void run( IProgressMonitor monitor ) throws InvocationTargetException, InterruptedException { try { monitor.beginTask( "Saving profiling raw data...", 10 ); log.trace( "Save raw to XML" ); monitor.worked( 1 ); saveRawData( destFile ); } finally { monitor.done(); } } } class SaveDataRunnale implements IRunnableWithProgress { File destFile; SourceResolver sourceResolver; public SaveDataRunnale( File destFile, SourceResolver sourceResolver ) { this.sourceResolver = sourceResolver; this.destFile = destFile; } @Override public void run( IProgressMonitor monitor ) throws InvocationTargetException, InterruptedException { try { monitor.beginTask( "Saving profiling data...", 10 ); log.trace( "Save XML" ); RIA ria = RIA.getCurrentDebugger(); if( ria == null ) { return; } monitor.worked( 1 ); ProfileData profileData = getProfileData(); if( profileData == null ) { return; } monitor.worked( 1 ); try { profileData.saveContentsInXml( destFile, ria.profileGetTypes()[ getWhatToProfile() ].getDescription(), this.sourceResolver ); } catch( IDEError e ) { log.error( e ); MessageDialog.openError( ContextManager.getActiveWorkbenchShell(), Messages.ErrorHandler_DIALOG_TITLE, e.getMessage() ); } } finally { monitor.done(); } } } }