Saturday, April 4, 2009

Got a bug? Who ya gonna call?

No one!

Seriously, this is the problem we face all the time when users run into a problem with our software. They don't call, they figure out an inefficient work around, they get frustrated, or stop using the software. It's counter intuitive but I've seen it over and over again. The reasons and excuses are endless but it all boils down to the fact that few problems are ever going to make it back to you.

Have you read Joel Spolsky's The Joel Test: 12 Steps to Better Code? You should. I like it enough to add a thirteenth step, log all error messages. Logging errors to a file that no one reads doesn't count. You need to track the errors so that you can be proactive in finding solutions.

We took Manoel Marques's Plugging in a logging framework for Eclipse plug-ins and extended it so all errors are logged in a database, emailed to our user support group and appropriate software developers. We've even gone as far as having wiki pages that shows the latest trends in real time.

This doesn't mean we're being constantly interrupted during our work day. But it does give us the flexibility to monitor users and jump in to solve either a usability, training, or software bug issue.

Eclipse is robust. I've been amazed at how well it handles nasty NullPointerExceptions without crashing. Don't get me wrong, it is a good thing but it can hide problems from the user. In alot of cases our users don't know they are in trouble until we burst into their office like Murray, Aykroyd, and Ramis looking to bust up a few ghosts.

We found it a great help to extend Marques's code to allow us to customize the logging properties. We allow log properties in the following order.

Load from the users home account
We can drop a custom logging properties file in the users account, restart our application and we can monitor whats happening in greater detail.
Load from command line
Customize logging from the run configuration dialog. Developers use this to avoid inundating the group with errors.
Load from bundle
This is what is shipped to our clients. Thanks to Eclipse plugin fragments each client gets to setup their own settings.
Load from default
In case the log properties file is broken, we have a default setup. Paranoid you say. Yep!

We have a set of standard logging tags. When you playback the log file, think airplane black boxes, it makes it easier to figure out what the user was doing leading up to their problem.

public interface ILoggingTag {
    public static final String DELIMITER = ":";
    public static final String SPACE = " ";
    public static final String CREATE = "CREATE";
    public static final String OPEN = "OPEN";
    public static final String CLOSE = "CLOSE";
    public static final String START = "START";
    public static final String STOP = "STOP";
    public static final String FINISH = "FINISH";
    public static final String DELETE = "DELETE";
    public static final String CANCEL = "CANCEL";
    //......
    public static final String PLUGIN_START = PLUGIN + DELIMITER + START + SPACE;
    public static final String PLUGIN_STOP = PLUGIN + DELIMITER + STOP + SPACE;
    public static final String RESOURCE_OPEN = RESOURCE + DELIMITER + OPEN + SPACE;
    public static final String RESOURCE_CLOSE = RESOURCE + DELIMITER + CLOSE + SPACE;
}

Our code looks like this.

    @Override
    public void init(IEditorSite site, IEditorInput input) {
        //...
        logger.info(ILoggingTag.EDITOR_CREATE + input.getName());
    }

    @Override
    public void dispose() {
        log.debug(ILoggingTag.RESOURCE_CLOSE + getEditorInput().getName());
        //...
    }

We log messages to the users log file, to a PostgreSQL database, and send emails. Having errors stored in a database allows use to generate wiki reports showing the latest trends in usage, bugs, etc.

The payback; obviously fewer bugs but what was really important was a more relaxed user community willing to help use improve the software. In many cases we're not tracking down software bugs but working to improve software usability. Users want to use our software, I have more time to spend writing new code, and tracking down bugs takes less time.

Saturday, April 5, 2008

Dynamic Context Help

I admit that I've passed over this user assistance feature for the last few years. My reasons: it's fluff, the documentation is vague, and no one will use it. I couldn't have been more wrong.

We first wired up dynamic help to our job deck editor in January. Looking at the above image you will see that the *TFSUPPRESS tag NOISEDIP has been selected with the help view offered a link for more details. It is simple, it works, and it is not annoying.

The idea is to offer the user help on what has been selected in the active view or editor. The help appears in the Eclipse help view. I've put together an example plugin that shows how it works. It is a view listing three characters from the classic movie "The Good, the Bad and the Ugly". The view class is called DynamicHelpView.

Download the plugin source here from SourceForge.net. The code assumes you are using Eclipse 3.3 and at least Java 5.0. I'm assuming you have some experience in adding help to your plugin. If not, spend a few minutes looking at the plugin.xml, context.xml and reference/*.* files.

The Eclipse help view, if visible, will listen for view or editor activation and selection events. In our example, when it sees one of these events it calls DynamicHelpView.getAdapter(Class) with a request for an IContextProvider.

To get this to work, make a selection and request dynamic help by pressing the F1 key.

public Object getAdapter(Class adapter) {
  if (IContextProvider.class.equals(adapter)) {
    return new ContextProvider(Activator.PLUGIN_ID + ".context_dynamichelpview",
                               (IStructuredSelection) viewer.getSelection());
    }
  return super.getAdapter(adapter);
}

The ContextProvider retrieves the current table selection and returns a SelectionContext which provides the help view with help context resources to display. The help context resources can be related topics links or external links.

public class ContextProvider implements IContextProvider {
  public IContext getContext(Object target) {
    IContext context = HelpSystem.getContext(fContextId);
    if (!fSelected.isEmpty()) {
      context = new SelectionContext(context, fSelected);
    }
    return context;
  }

  public int getContextChangeMask() {
    return SELECTION;
  }
}

The SelectionContext creates a list of IHelpResources. The first is a a single external link to Wikipedia followed by some internal links to related static help. The static help was registered in plugin.xml.

public class SelectionContext implements IContext2 {
  private IHelpResource[] fHelpResources;
  private String fText;
  private String fTitle;

  public SelectionContext(IContext context, IStructuredSelection selection) {
    Assert.isNotNull(selection);
    if (context instanceof IContext2) {
      fTitle = ((IContext2) context).getTitle();
    }
    List helpResources = new ArrayList();
    String label = null;
    StringBuffer location = new StringBuffer("http://en.wikipedia.org/wiki/");
    if (selection.getFirstElement().equals("The Good")) {
      label = "Clint Eastwood " + selection.toString();
      location.append("Man_with_No_Name");
    } else if (selection.getFirstElement().equals("The Bad")) {
      label = "Lee Van Cleef " + selection.toString();
      location.append("Angel_Eyes_%28The_Good%2C_the_Bad_and_the_Ugly%29");
    } else if (selection.getFirstElement().equals("The Ugly")) {
      label = "Eli Wallach " + selection.toString();
      location.append("Tuco_%28The_Ugly%29");
    }
    helpResources.add(new SelectionHelpResource(label, location.toString()));
    // Add static help topics
    if (context != null) {
      IHelpResource[] resources = context.getRelatedTopics();
      if (resources != null) {
        for (int j = 0; j < resources.length; j++) {
          helpResources.add(resources[j]);
        }
      }
    }
    fHelpResources = helpResources.toArray(new IHelpResource[helpResources.size()]);
    if (context != null) {
      fText = context.getText();
    }
    if (fText == null) {
      fText = "";
    }
  }
}

Set a few break points in the plugin to learn how dynamic help works.

There is a small bug 173073 which requires you to hit a few keys to have the help view update its list of links. It has been fixed in Eclipse 3.4 (to be released in June 2008). Don't let this bug stop you from using the dynamic help.

Friday, February 15, 2008

My Favorite Breakpoints

I have been meaning to start listing the breakpoints I use to track down issues with Eclipse since the beginning of the year. Even when you know the problem is with your own code diving into the Eclipse code can greatly reduce your debug time. This is specific to the Eclipse 3.3 release. Please send me your favorite break points and I'll add them to the list.

User assistance
WorkbenchHelpListener.helpRequested(HelpEvent)
Prease F1 (or CTRL + F1 for GNOME) and you will end up here. A quick way to figure out which help context id is being requested.
WorkbenchHelpSystem.displayContext(IContext, int, int)
Most requests for help end up here.
StandaloneInfocenter.executeCommand(List)
Stand alone infocenter entry point.
Menu contribution (org.eclipse.ui.menus)

If you use commands and handlers with the new Eclipse 3.3 org.eclipse.ui.menus then these breakpoints are for you.

MenuPersistence.readAdditions()
See org.eclipse.ui.menus contributions being read from a plugin.xml file.
MenuAdditionCacheEntry.createContributionItems()
Most requests for help end up here.
PopupMenuExtender.menuAboutToShow(IMenuManager)
Called when MenuManager fires an about to show event

Friday, December 21, 2007

The Oil and Gas Industry using Eclipse

It has been too long since I last posted so I decided to show off some of what we've been working on at Kelman Technologies.

The screen shots show off the IPE (Interactive Processing Environment). It is our latest generation of seismic processing tools based on Eclipse RCP and other Eclipse projects. Before you get your mouse in a knot this isn't a sales pitch. The IPE is not for sale. We use open source tools to turn our intellectual property into tools for "in house" use.

The IPE is the front end to our geophysical processing system. We use it to help our clients find and enhance oil and gas reserves around the world. Developing software for this industry is a challenge because of the immense amount of data that needs to be managed and processed. We deal with extremely large computer clusters, terabyte sized disk farms, exabyte sized tape systems and insane visualization requirements. There are very few industries in the world that have the same high performance computing needs as the oil and gas industry.

Cross plots have been a big challenge. How do you visualize 20 million or more data points? We have tried a number of different scientific plotting libraries that have all failed to handle the large amount of data points. Early 2008 we are starting work on our own plotting library to replace SGT which replaced FreeHEP.

This is what the earth looks like to a geophysicist. A seismic plot can easily be 3 feet by 20 feet long on paper. Convert that to a usable image on the screen and you have a 12,000 by 23,000 pixel image. That is 276,000,000 pixels or about 828 megabytes of memory. Our users expect to have dozens of these images open at one time. Libraries like Java Advanced Imaging were never designed for this sort of work load.

You are seeing seismic data plotted as wiggles and as a variable density plot. Seismic data files can be as large at 350 gigabytes with the requirement that any seismic trace be retrieved for immediate viewing. SWT has been surprising good at keeping up with the performance demands.

We used BIRT for our spectral analysis editor. It made for a fast development cycle but the business oriented charting isn't up to plotting thousands of data points. The guys working on BIRT have been very responsive to our requests but it's not a high performance scientific charting API. Work on a replacement starts in 2008. We'll still use BIRT for our management related reports.

The seismic data keeps its own processing history. It's an audit trail of how the raw seismic field data was processed into a final client ready dataset. It can take hundreds of processing steps and months of CPU time to generate one seismic dataset. The history alone for a single file can reach 100 gigabytes. Being able to create our own custom IDocument saves us from loading the whole file into memory.

Quality control of the seismic data is another big challenge. The volume of data makes it impossible for a human to inspect all of the project data.

Managing the volume of files has been in nightmare in the past. I've been impressed with how well the resources plugin combined with the Common Navigator Framework has been able to keep up with our demands.

The Eclipse User Assistance API has been used heavily. We have offices all around the world so we have to provide easy access to good quality help documents. We are using Camtasia to create built in videos. I don't want to get a call from our users in Libya at two in the morning.

We've written a smart job deck editor that has all of the features of the Java editor but it works with our proprietary processing language called kismet. Our seismic processors are a bit like software programmers. They write job decks that tell our seismic processing system how to manipulate the seismic data.

Our clients send data in a bewildering array of data formats. Using our standard database editor has reduced the confusion experienced by our users. As new formats appear we create a new translator and plug it into the IPE. All of our plotting plugins can then take advantage of the new data.

Kismet jobs can report errors and warning. We have our own kismet builder/nature. We get the best of the Eclipse IDE for our users who have no programming experience. Editor user assistance, hover help, and other great built in features makes it the editor of choice.

We used iBatis to access our PostgreSQL job tracker server. It's an unorthodox use of iBatis (the IPE is not web based) but it makes for a fast development cycle and easy maintenance. Our users can monitor a job on any cluster computer from the comfort of their desk.

For those times when our client sends us data in a mystery format with no file extension. We can load any binary file into our binary editor. We even have an ascii table view to help with conversions from hex to octal to decimal to ascii. I wonder if we'll need a slider ruler view?

Another sore point I have with the available image libraries. The seismic industry image standard is TIFF on steroids. We can't load a typical TIFF image into an SWT Image object because it will use over 2 gigabytes of memory. We're still working out performance kinks on this one. I'll be looking at Batik and libTiff in 2008. The SWT TIFF support wasn't designed with us in mind.

Our front end to the Portable Batch System (PBS) developed by NASA back in the early 1990s.

We use PBS to manage our computer clusters. Users can submit hundreds or thousands of processing jobs with a simple click of a button. It's grid computing on steroids.

The processing requirements vary depending on the state of the seismic data. We still have to manually configure our computers for peek performance. That is why we have so many different processing queues. I know it sounds like we're in the age of ENIAC but we still haven't found computer hardware that is flexible enough to handle all our high performance processing needs. Heck, we're looking at moving our processing system onto GeForce graphics cards to squeeze out every last FLOP.

Job, jobs and more jobs. Managing thousands of jobs that could run for weeks is getting easier. Using an Eclipse view to filter and sort jobs has made life a little less stressful for our users. More advanced tools are in the works for 2008 and 2009 as we try to figure out better ways for our cluster computers to manage themselves.

Bless the wiki. We use it more and more to keep our internal users informed. The Eclipse platform allows us to search our internal doc, our wiki, and our Eclipse based info center with the click of one button. It's great.

We are pushing the Eclipse RCP to extremes that I know were never envisioned by the Eclipse team back in the 2001. Although we've found a few performance weaknesses we've never regretted our decision to adopt Eclipse RCP.

Thursday, September 20, 2007

IBM Lotus Expeditor and Symphony

Dreams of IBM Lotus Symphony components appearing inside our Eclipse RCP application have quickly faded when reality tapped me on the side of the head with a large SOA middleware wiffle bat.

IBM Lotus Symphony is built on top of Eclipse RCP and IBM Lotus Expeditor. That is a key point. Your existing Eclipse RCP applications need to be ported to the IBM Lotus Expeditor environment. For example, the three IBM Lotus Symphony applications run inside the Expeditor.

For those who are interested, start with the following articles. I'm assuming you are comfortable developing Eclipse RCP applications. In fact, only Eclipse 3.2 RCP applications. If you have made the jump to Eclipse 3.3 you will have to wait. Also, you don't need to download IBM Lotus Symphony.

The last article shows how to port our favorite Eclipse RCP Mail Application into the IBM Lotus Expeditor SOA Middleware universe.

Wednesday, September 12, 2007

Sigue Sigue SIGTERM

Our IT group wondered if our Eclipse RCP based application could handle a linux system shutdown. They kept catching hell for rebooting computers at night to keep the batch processing system running. Our users would lose unsaved work and get grumpy. Grumpy seismic processors are not a pretty sight.

The trick is to teach the Eclipse RCP application to listen for a SIGTERM by adding a shutdown hook.

Please note that this does not work on Windows. More about this in a future blog.

public class IPEApplication implements IApplication {
  public Object start(IApplicationContext context) throws Exception {
    final Display display = PlatformUI.createDisplay();
    Runtime.getRuntime().addShutdownHook(new ShutdownHook());  }
    // start workbench...
  }
}

The shutdown code must be run in the UI thread and should not be run if the workbench is being closed by other means. All dirty editors are automatically saved. This avoids prompting the user who is probably at home sleeping when their computer is shutdown. Finally the workbench is closed.

private class ShutdownHook extends Thread {
  @Override
  public void run() {
    try {
      final IWorkbench workbench = PlatformUI.getWorkbench();
      final Display display = PlatformUI.getWorkbench()
                                        .getDisplay();
      if (workbench != null && !workbench.isClosing()) {
        display.syncExec(new Runnable() {
          public void run() {
            IWorkbenchWindow [] workbenchWindows = 
                            workbench.getWorkbenchWindows();
            for(int i = 0;i < workbenchWindows.length;i++) {
              IWorkbenchWindow workbenchWindow =
                                        workbenchWindows[i];
              if (workbenchWindow == null) {
                // SIGTERM shutdown code must access
                // workbench using UI thread!!
              } else {
                IWorkbenchPage[] pages = workbenchWindow
                                           .getPages();
                for (int j = 0; j < pages.length; j++) {
                  IEditorPart[] dirtyEditors = pages[j]
                                           .getDirtyEditors();
                  for (int k = 0; k < dirtyEditors.length; k++) {
                    dirtyEditors[k]
                             .doSave(new NullProgressMonitor());
                  }
                }
              }
            }
          }
        });
        display.syncExec(new Runnable() {
          public void run() {
            workbench.close();
          }
        });
      }
    } catch (IllegalStateException e) {
      // ignore
    }
  }
}

Monday, September 3, 2007

Cheat Sheet Editor

Eclipse Europa (3.3) has a new Cheat Sheet Editor which is a vast improvement over the XML editor used in the Calipso (3.2) release.

  1. Create a cheat sheet, File->New->Other...->User Assistance->Cheat Sheets.
  2. Enter a filename and click Finish.

The new Cheat Sheet Editor will appear. Select the Item and you will see the definition and command sections appear to the right. Click the Browse... button to see the new Command Composer.

The Command Composer shows a list of available commands and their parameters. Gone are the days of hunting through eclipse plugin.xml files looking for command ids.

  1. In the Command Composer, select Window->Preferences.
  2. Use the Preference Page combo box to select General->Editors->File Associations.
  3. Click OK.

You now have a cheat sheet item which will display the File Association preference page.

If you want to see what happens inside Eclipse, display the ParameterizedCommand class and set a break point at executeWithChecks(Object, Object). Now run your application in debug mode, display your cheat sheet and press "Click to perform".

The command org.eclipse.ui.window.preferences is defined in org.eclipse.ui/plugin.xml. Its default handler is ShowPreferencePageHandler which is found in the org.eclipse.ui.workbench plugin.

Read up on the following extension points and use the ShowPreferencePageHandler as an template to build your own commands that can be accessed from a cheat sheet. See New menu contribution extension for readings on how to use a command in a menu, toolbar, or popup menu.

org.eclipse.ui.commands
A logical representation of a task. A handler does the work. See wiki and help guide.
org.eclipse.ui.handlers
A handler does what the command represents. See wiki and help guide.