#!/usr/bin/env python
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Symbolizes and prints live objects as recorded by tcmalloc's
HeapProfilerDumpLiveObjects.
"""

import os
import re
import subprocess
import sys
import tempfile

def usage():
  print """\
Usage:
  tools/tcmalloc/print-live-objects.py out/Debug/chrome leaks.dmp
"""

def LoadDump(dump_file):
  result = []
  leakfmt = re.compile(
      r"^\s*1:\s*(\d+)\s*\[\s*1:\s*\d+\]\s*@(0x[a-f0-9]+)((\s+0x[a-f0-9]+)*)$")
  line_no = 0
  with open(dump_file) as f:
    for line in f:
      line_no = line_no + 1
      matches = leakfmt.match(line)
      if not matches:
        print "%s: could not parse line %d, skipping" % (dump_file, line_no)
      else:
        trace = { "size": int(matches.group(1)),
                  "address": matches.group(2),
                  "frames": matches.group(3).strip().split(" ")}
        result.append(trace)
  return result


def Symbolize(binary, traces):
  addresses = set()
  for trace in traces:
    for frame in trace["frames"]:
      addresses.add(frame)
  addr_file, addr_filename = tempfile.mkstemp()
  for addr in addresses:
    os.write(addr_file, "%s\n" % addr)
  os.close(addr_file)
  syms = subprocess.Popen([
      "addr2line", "-f", "-C", "-e", binary, "@%s" % addr_filename],
      stdout=subprocess.PIPE).communicate()[0].strip().split("\n")
  table = {}
  cwd = os.getcwd()
  for address, symbol, location in zip(addresses, syms[::2], syms[1::2]):
    if location != "??:0":
      filename, line = location.split(":")
      filename = os.path.realpath(filename)[len(cwd)+1:]
      location = "%s:%s" % (filename, line)
    table[address] = { "name": symbol, "location": location }
  for trace in traces:
    frames = []
    for frame in trace["frames"]:
      frames.append(table[frame])
    trace["frames"] = frames


def Main(argv):
  if sys.platform != 'linux2':
    print 'print-live-objects.py requires addr2line only present on Linux.'
    sys.exit(1)

  if len(argv) != 3:
    usage()
    sys.exit(1)

  traces = LoadDump(argv[2])
  Symbolize(argv[1], traces)

  if not traces:
    print "No leaks found!"

  for trace in sorted(traces, key=lambda x: -x["size"]):
    print "Leak of %d bytes at address %s" % (trace["size"], trace["address"])
    for frame in trace["frames"]:
      print "  %s (%s)" % (frame["name"], frame["location"])
    print ""


if __name__ == '__main__':
  Main(sys.argv)
