@gen.engine
def RunOnce(client, callback):
  # Find all crash files.
  merged_store = ObjectStore.GetInstance(logs_util.UserCrashLogsPaths.MERGED_LOGS_BUCKET)
  files = yield gen.Task(store_utils.ListAllKeys, merged_store, prefix='merged_user_crashes')

  crash_files = set()
  symbolicated_files = set()
  for f in files:
    if f.endswith('.crash') or f.endswith('.crash.gz'):
      crash_files.add(f)
    elif f.endswith('.crash.symbol') or f.endswith('.crash.gz.symbol'):
      # Strip '.symbol'.
      symbolicated_files.add(f[:-7])

  missing = crash_files.difference(symbolicated_files)

  """
  A sample list of files to test with. Should probably go away in favor of cmdline.
  missing = [
  'merged_user_crashes/8339/2013-07-25/dev-18194-01-17-14.918-2.0.1.61.crash',
  'merged_user_crashes/7872/2013-07-24/dev-17246-17-46-07.277-2.0.1.61.crash',
  'merged_user_crashes/8246/2013-07-25/dev-17947-00-07-59.215-2.0.1.61.crash',
  'merged_user_crashes/8246/2013-07-25/dev-17947-00-08-17.822-2.0.1.61.crash',
  'merged_user_crashes/8342/2013-07-25/dev-18136-00-58-09.061-2.0.1.61.crash',
  'merged_user_crashes/2286/2013-07-24/dev-17163-15-19-11.778-2.1.0.70.dev.jailbroken.crash',
  'merged_user_crashes/8768/2013-07-25/dev-18941-06-04-18.591-2.0.2.69.crash',
  'merged_user_crashes/8339/2013-07-25/dev-18194-01-18-17.613-2.0.1.61.crash',
  'merged_user_crashes/8074/2013-07-24/dev-17645-23-19-33.700-2.0.1.61.crash',
  'merged_user_crashes/8320/2013-07-25/dev-18096-01-31-49.906-2.0.1.61.crash',
  'merged_user_crashes/8246/2013-07-25/dev-17947-00-09-11.781-2.0.1.61.crash',
  'merged_user_crashes/8751/2013-07-25/dev-18910-05-54-43.572-2.0.2.69.crash',
  'merged_user_crashes/8246/2013-07-25/dev-17947-00-09-19.120-2.0.1.61.crash',
  'merged_user_crashes/8341/2013-07-25/dev-18134-00-46-50.904-2.0.1.61.crash',
  'merged_user_crashes/8316/2013-07-25/dev-18080-02-14-49.269-2.0.1.61.crash',
  'merged_user_crashes/8743/2013-07-25/dev-18898-05-40-50.450-2.0.1.61.crash',
  'merged_user_crashes/8074/2013-07-24/dev-17645-23-12-56.844-2.0.1.61.crash',
  'merged_user_crashes/2286/2013-07-24/dev-17163-15-19-20.884-2.1.0.70.dev.jailbroken.crash',
  'merged_user_crashes/8093/2013-07-24/dev-17650-23-29-34.198-2.0.1.61.crash',
  'merged_user_crashes/8339/2013-07-25/dev-18194-01-17-40.123-2.0.1.61.crash',
  'merged_user_crashes/8074/2013-07-24/dev-17645-23-19-41.996-2.0.1.61.crash'
  ]
  """

  logging.info('Found %d crash logs, %d missing: %r' % (len(crash_files), len(missing), missing))
  if not missing:
    callback()
    return

  missing_crashes = sorted(list(missing))
  processed_crashes = {}
  failures = {}
  for f in missing_crashes:
    try:
      # We could encounter any number of failures in get, symbolicate, and put.
      contents = yield gen.Task(merged_store.Get, f)
      # Is there a single command to do this?
      lines = [l + '\n' for l in contents.split('\n')]
      sym = Symbolicator()
      sym.process_one_file(lines)

      newfile = f + '.symbol'
      if not options.options.dry_run:
        yield gen.Task(merged_store.Put, newfile, sym.FullOutput())
        logging.info('Wrote %d bytes to %s' % (len(sym.FullOutput()), newfile))

      # We write the full symbolicated file to S3, but the summary email only includes
      # the summary: preamble + crashed thread backtrace.
      out_dict = sym.OutputDict()
      out_dict['filename'] = newfile
      processed_crashes[newfile] = out_dict
    except:
      msg = traceback.format_exc()
      logging.error('Failed to process %s: %s' % (f, msg))
      failures[f] = msg

  logging.info('Successfully processed crashes: %r' % processed_crashes.keys())
  logging.info('Symbolicate failures: %r' % failures)
  # Generate the email. Keys for 'processed_crashes' are the final filenames (with .symbol).
  # For 'failures', keys are the non-symbolicated filenames.
  title = '%d new client crashes' % len(missing_crashes)
  text = title + '\n'

  def _S3URL(filename):
    return merged_store.GenerateUrl(filename,
                                    expires_in=constants.SECONDS_PER_DAY * options.options.s3_url_expiration_days,
                                    content_type='text/plain')

  if failures:
    title += ' (%d failed during processing)' % len(failures)
    text += '\nProcessing failures: %d\n' % len(failures)
    for f, tb in failures.iteritems():
      text += '--------------------------\n'
      text += 'Non-symbolicated file: %s\n%s\n' % (_S3URL(f), tb)

  if processed_crashes:
    deduped_crashes = defaultdict(list)
    for v in processed_crashes.values():
      deduped_crashes[v['crashed_thread_backtrace']].append(v)
    text += '\n%d symbolicated crashes, %d after deduping\n\n' % \
            (len(processed_crashes), len(deduped_crashes))

    for crash_set in deduped_crashes.values():
      text += '--------------------------\n'
      text += '%d crashes after deduping. Full symbolicated files:\n' % len(crash_set)
      for c in crash_set:
        text += '%s\n' % _S3URL(c['filename'])
      text += '\nFirst crash:\n%s%s%s\n' % (c['preamble'], c['crashed_thread_title'],
                                            c['crashed_thread_backtrace'])


  yield gen.Task(SendEmail, title, text)
  callback()
dataset/ETHPy150Open viewfinderco/viewfinder/backend/logs/symbolicate_user_crashes.py/RunOnce/original