function runTool(platform, args) {
  var inputFile = args[0];
  if (!inputFile) {
    console.log('usage: webglgen.js file.wtf-trace');
    return -1;
  }

  var log = console.log;

  var argTable = buildArgTable_();

  var frameStart = 0;
  var frameNumber = 0;

  var resources = [];
  var nextResourceId = 0;

  var currentContext = 0;

  // ctxs = [gl, gl, gl, ...];
  // objs = { handle : object };
  // frame(number, startTime, fn, endTime);

  var traceListener = wtf.analysis.createTraceListener({
    'sourceAdded': function(timebase, contextInfo) {
      frameStart = timebase;
    },

    'wtf.timing#frameStart': function(e) {
      log('}, [' + resources.join(', ') + ']);');
      resources.length = 0;
      frameNumber = e.args['number'];
      log('frame(' +
          frameNumber + ', ' +
          '"' + util.formatTime(e.time) + '"' +
          ', function(ctxs, objs, resources) {');
      log('  var result;');
      log('  var gl = ctxs[' + currentContext + '];');
      frameStart = e.time;
    },
    'wtf.timing#frameEnd': function(e) {
      log('}, [' + resources.join(', ') + '], "' + util.formatTime(e.time) + '");');
      resources.length = 0;
      log('intraFrame(function(ctxs, objs, resources) {');
      log('  var result;');
      log('  var gl = ctxs[' + currentContext + '];');
    },

    'webglcapture.createContext': function(e) {
      log('  createContext(' +
          e.args['handle'] + ', ' + e.args['attributes'] + ')');
    },

    'webglcapture.setContext': function(e) {
      currentContext = e.args['handle'];
      log('  gl = ctxs[' + currentContext + '];');
      log('  reshapeContext(gl, ' + e.args['width'] + ', ' + e.args['height'] + ');');
    },

    'custom': function(e) {
      if (e instanceof wtf.analysis.ScopeEvent &&
          e.eventType.name.indexOf('WebGLRenderingContext#') == 0) {
        //util.logEvent(e, e.scope.getId(), e.args);
        var callName = e.eventType.name.substr(22);
        var offsetMs = util.pad(
            '+' + ((e.time - frameStart) | 0) + 'ms', -6);

        var argString = '';
        switch (callName) {
          case 'bufferData':
            var size = e.args['size'];
            var data = e.args['data'];
            if (data.length != size) {
              data = size;
            } else {
              data = 'new Uint8Array([' + data + '])';
            }
            argString = [
              e.args['target'],
              data,
              e.args['usage']
            ].join(', ');
            break;
          case 'texImage2D':
            switch (e.args['dataType']) {
              case 'pixels':
                argString = [
                  e.args['target'],
                  e.args['level'],
                  e.args['internalformat'],
                  e.args['width'],
                  e.args['height'],
                  e.args['border'],
                  e.args['format'],
                  e.args['type'],
                  'new Uint8Array([' + e.args['pixels'] + '])'
                ].join(', ');
                break;
              case 'null':
                argString = [
                  e.args['target'],
                  e.args['level'],
                  e.args['internalformat'],
                  e.args['width'],
                  e.args['height'],
                  e.args['border'],
                  e.args['format'],
                  e.args['type'],
                  'null'
                ].join(', ');
                break;
              default:
                argString = [
                  e.args['target'],
                  e.args['level'],
                  e.args['internalformat'],
                  e.args['format'],
                  e.args['type'],
                  'resources[' + nextResourceId + ']'
                ].join(', ');
                resources.push(
                    '{' +
                    '  id: ' + nextResourceId + ',' +
                    '  type: "image",' +
                    '  mimeType: "' + e.args['dataType'] + '",' +
                    '  data: new Uint8Array([' + e.args['pixels'] + '])' +
                    '}');
                nextResourceId++;
                break;
            }
            break;
          case 'texSubImage2D':
            switch (e.args['dataType']) {
              case 'pixels':
                argString = [
                  e.args['target'],
                  e.args['level'],
                  e.args['xoffset'],
                  e.args['yoffset'],
                  e.args['width'],
                  e.args['height'],
                  e.args['format'],
                  e.args['type'],
                  'new Uint8Array([' + e.args['pixels'] + '])'
                ].join(', ');
                break;
              case 'null':
                argString = [
                  e.args['target'],
                  e.args['level'],
                  e.args['xoffset'],
                  e.args['yoffset'],
                  e.args['width'],
                  e.args['height'],
                  e.args['format'],
                  e.args['type'],
                  'null'
                ].join(', ');
                break;
              default:
                argString = [
                  e.args['target'],
                  e.args['level'],
                  e.args['xoffset'],
                  e.args['yoffset'],
                  e.args['format'],
                  e.args['type'],
                  'resources[' + nextResourceId + ']'
                ].join(', ');
                resources.push(
                    '{' +
                    '  id: ' + nextResourceId + ',' +
                    '  type: "image",' +
                    '  mimeType: "' + e.args['dataType'] + '",' +
                    '  data: new Uint8Array([' + e.args['pixels'] + '])' +
                    '}');
                nextResourceId++;
                break;
            }
            break;
          default:
            var argTableEntry = argTable[callName];
            if (argTableEntry) {
              argString = argTableEntry(e.args);
            }
            break;
        }

        log('  /* ' + offsetMs + ' */ result = gl.' + callName + '(' +
            argString + ');');

        switch (callName) {
          case 'createBuffer':
          case 'createFramebuffer':
          case 'createProgram':
          case 'createRenderbuffer':
          case 'createShader':
          case 'createTexture':
            log('                ' +
                'objs[' + e.args['value'] + '] = result;');
            log('                ' +
                'assignObject(gl, result);');
            break;
          case 'getUniformLocation':
            log('                ' +
                'objs[' + e.args['value'] + '] = result;');
            break;
        }
      }
    }
  });

  // Start in an intra-frame block.
  log('intraFrame(function(ctxs, objs, resources) {');
  log('  var result;');
  log('  var gl = ctxs[' + currentContext + '];');

  if (!wtf.analysis.run(traceListener, inputFile)) {
    console.log('failed to start analysis!');
    return -1;
  }

  // Cleanup last intra-frame closure.
  log('}, [' + resources.join(', ') + ']);');
  resources.length = 0;

  return 0;
}
WebGL call-log code generator tool.
@param {!wtf.pal.IPlatform} platform Platform abstraction layer.
@param {!Array.<string>} args Command line arguments.
@return {number|!goog.async.Deferred} Return code or a deferred that is
    called back when the tool exits.