Patchwork [google,gcc-4_7] offline profile merge tool (issue8508048)

login
register
mail settings
Submitter Rong Xu
Date April 8, 2013, 9:57 p.m.
Message ID <20130408215702.57CBF1007C7@rong.mtv.corp.google.com>
Download mbox | patch
Permalink /patch/234922/
State New
Headers show

Comments

Rong Xu - April 8, 2013, 9:57 p.m.
Hi,

This is a offline profile merge program.

Usage: profile_merge.py [options] arg1 arg2 ...

Options:
  -h, --help            show this help message and exit
  -w MULTIPLIERS, --multipliers=MULTIPLIERS
                        Comma separated list of multipliers to be applied for
                        each corresponding profile.
  -o OUTPUT_PROFILE, --output=OUTPUT_PROFILE
                        Output directory or zip file to dump the merged
                        profile. Default output is profile-merged.zip.

  Arguments:
    Comma separated list of input directories or zip files that contain
    profile data to merge.

Histogram is recomputed (i.e. preicise). Module grouping information
in LIPO is approximation.

Thanks,

-Rong

2013-04-08  Rong Xu  <xur@google.com>

	* contrib/profile_merge.py: An offline profile merge tool.


Property changes on: contrib/profile_merge.py
Xinliang David Li - April 8, 2013, 10:13 p.m.
The copyright header is wrong. Please use the standard one for GCC.

David

On Mon, Apr 8, 2013 at 2:57 PM, Rong Xu <xur@google.com> wrote:
> Hi,
>
> This is a offline profile merge program.
>
> Usage: profile_merge.py [options] arg1 arg2 ...
>
> Options:
>   -h, --help            show this help message and exit
>   -w MULTIPLIERS, --multipliers=MULTIPLIERS
>                         Comma separated list of multipliers to be applied for
>                         each corresponding profile.
>   -o OUTPUT_PROFILE, --output=OUTPUT_PROFILE
>                         Output directory or zip file to dump the merged
>                         profile. Default output is profile-merged.zip.
>
>   Arguments:
>     Comma separated list of input directories or zip files that contain
>     profile data to merge.
>
> Histogram is recomputed (i.e. preicise). Module grouping information
> in LIPO is approximation.
>
> Thanks,
>
> -Rong
>
> 2013-04-08  Rong Xu  <xur@google.com>
>
>         * contrib/profile_merge.py: An offline profile merge tool.
>
> Index: contrib/profile_merge.py
> ===================================================================
> --- contrib/profile_merge.py    (revision 0)
> +++ contrib/profile_merge.py    (revision 0)
> @@ -0,0 +1,1301 @@
> +#!/usr/bin/python2.7
> +#
> +# Copyright 2013 Google Inc. All Rights Reserved.
> +
> +"""Merge two or more gcda profile.
> +"""
> +
> +__author__ = 'Seongbae Park, Rong Xu'
> +__author_email__ = 'spark@google.com, xur@google.com'
> +
> +import array
> +from optparse import OptionGroup
> +from optparse import OptionParser
> +import os
> +import struct
> +import zipfile
> +
> +new_histogram = None
> +
> +
> +class Error(Exception):
> +  """Exception class for profile module."""
> +
> +
> +def ReadAllAndClose(path):
> +  """Return the entire byte content of the specified file.
> +
> +  Args:
> +    path: The path to the file to be opened and read.
> +
> +  Returns:
> +    The byte sequence of the content of the file.
> +  """
> +  data_file = open(path, 'rb')
> +  data = data_file.read()
> +  data_file.close()
> +  return data
> +
> +
> +def MergeCounters(objs, index, multipliers):
> +  """Accumulate the counter at "index" from all counters objs."""
> +  val = 0
> +  for j in xrange(len(objs)):
> +    val += multipliers[j] * objs[j].counters[index]
> +  return val
> +
> +
> +class DataObject(object):
> +  """Base class for various datum in GCDA/GCNO file."""
> +
> +  def __init__(self, tag):
> +    self.tag = tag
> +
> +
> +class Function(DataObject):
> +  """Function and its counters.
> +
> +  Attributes:
> +    length: Length of the data on the disk
> +    ident: Ident field
> +    line_checksum: Checksum of the line number
> +    cfg_checksum: Checksum of the control flow graph
> +    counters: All counters associated with the function
> +    file: The name of the file the function is defined in. Optional.
> +    line: The line number the function is defined at. Optional.
> +
> +  Function object contains other counter objects and block/arc/line objects.
> +  """
> +
> +  def __init__(self, reader, tag, n_words):
> +    """Read function record information from a gcda/gcno file.
> +
> +    Args:
> +      reader: gcda/gcno file.
> +      tag: funtion tag.
> +      n_words: length of function record in unit of 4-byte.
> +    """
> +    DataObject.__init__(self, tag)
> +    self.length = n_words
> +    self.counters = []
> +
> +    if reader:
> +      pos = reader.pos
> +      self.ident = reader.ReadWord()
> +      self.line_checksum = reader.ReadWord()
> +      self.cfg_checksum = reader.ReadWord()
> +
> +      # Function name string is in gcno files, but not
> +      # in gcda files. Here we make string reading optional.
> +      if (reader.pos - pos) < n_words:
> +        reader.ReadStr()
> +
> +      if (reader.pos - pos) < n_words:
> +        self.file = reader.ReadStr()
> +        self.line_number = reader.ReadWord()
> +      else:
> +        self.file = ''
> +        self.line_number = 0
> +    else:
> +      self.ident = 0
> +      self.line_checksum = 0
> +      self.cfg_checksum = 0
> +      self.file = None
> +      self.line_number = 0
> +
> +  def Write(self, writer):
> +    """Write out the function."""
> +
> +    writer.WriteWord(self.tag)
> +    writer.WriteWord(self.length)
> +    writer.WriteWord(self.ident)
> +    writer.WriteWord(self.line_checksum)
> +    writer.WriteWord(self.cfg_checksum)
> +    for c in self.counters:
> +      c.Write(writer)
> +
> +  def EntryCount(self):
> +    """Return the number of times the function called."""
> +    return self.ArcCounters().counters[0]
> +
> +  def Merge(self, others, multipliers):
> +    """Merge all functions in "others" into self.
> +
> +    Args:
> +      others: A sequence of Function objects
> +      multipliers: A sequence of integers to be multiplied during merging.
> +    """
> +    for o in others:
> +      assert self.ident == o.ident
> +      assert self.line_checksum == o.line_checksum
> +      assert self.cfg_checksum == o.cfg_checksum
> +
> +    for i in xrange(len(self.counters)):
> +      self.counters[i].Merge([o.counters[i] for o in others], multipliers)
> +
> +  def Print(self):
> +    """Print all the attributes in full detail."""
> +    print 'function: ident %d length %d line_chksum %x cfg_chksum %x' % (
> +        self.ident, self.length,
> +        self.line_checksum, self.cfg_checksum)
> +    if self.file:
> +      print 'file:     %s' % self.file
> +      print 'line_number:   %d' % self.line_number
> +    for c in self.counters:
> +      c.Print()
> +
> +  def ArcCounters(self):
> +    """Return the counter object containing Arcs counts."""
> +    for c in self.counters:
> +      if c.tag == DataObjectFactory.TAG_COUNTER_ARCS:
> +        return c
> +    return None
> +
> +
> +class Blocks(DataObject):
> +  """Block information for a function."""
> +
> +  def __init__(self, reader, tag, n_words):
> +    DataObject.__init__(self, tag)
> +    self.length = n_words
> +    self.__blocks = reader.ReadWords(n_words)
> +
> +  def Print(self):
> +    """Print the list of block IDs."""
> +    print 'blocks:  ', ' '.join(self.__blocks)
> +
> +
> +class Arcs(DataObject):
> +  """List of outgoing control flow edges for a single basic block."""
> +
> +  def __init__(self, reader, tag, n_words):
> +    DataObject.__init__(self, tag)
> +
> +    self.length = (n_words - 1) / 2
> +    self.block_id = reader.ReadWord()
> +    self.__arcs = reader.ReadWords(2 * self.length)
> +
> +  def Print(self):
> +    """Print all edge information in full detail."""
> +    print 'arcs: block', self.block_id
> +    print 'arcs: ',
> +    for i in xrange(0, len(self.__arcs), 2):
> +      print '(%d:%x)' % (self.__arcs[i], self.__arcs[i+1]),
> +      if self.__arcs[i+1] & 0x01: print 'on_tree'
> +      if self.__arcs[i+1] & 0x02: print 'fake'
> +      if self.__arcs[i+1] & 0x04: print 'fallthrough'
> +    print
> +
> +
> +class Lines(DataObject):
> +  """Line number information for a block."""
> +
> +  def __init__(self, reader, tag, n_words):
> +    DataObject.__init__(self, tag)
> +    self.length = n_words
> +    self.block_id = reader.ReadWord()
> +    self.line_numbers = []
> +    line_number = reader.ReadWord()
> +    src_files = reader.ReadStr()
> +    while src_files:
> +      line_number = reader.ReadWord()
> +      src_lines = [src_files]
> +      while line_number:
> +        src_lines.append(line_number)
> +        line_number = reader.ReadWord()
> +      self.line_numbers.append(src_lines)
> +      src_files = reader.ReadStr()
> +
> +  def Print(self):
> +    """Print all line numbers in full detail."""
> +    for l in self.line_numbers:
> +      print 'line_number: block %d' % self.block_id, ' '.join(l)
> +
> +
> +class Counters(DataObject):
> +  """List of counter values.
> +
> +  Attributes:
> +    counters: sequence of counter values.
> +  """
> +
> +  def __init__(self, reader, tag, n_words):
> +    DataObject.__init__(self, tag)
> +    self.counters = reader.ReadCounters(n_words / 2)
> +
> +  def Write(self, writer):
> +    """Write."""
> +    writer.WriteWord(self.tag)
> +    writer.WriteWord(len(self.counters) * 2)
> +    writer.WriteCounters(self.counters)
> +
> +  def IsComparable(self, other):
> +    """Returns true if two counters are comparable."""
> +    return (self.tag == other.tag and
> +            len(self.counters) == len(other.counters))
> +
> +  def Merge(self, others, multipliers):
> +    """Merge all counter values from others into self.
> +
> +    Args:
> +      others: other counters to merge.
> +      multipliers: multiplier to apply to each of the other counters.
> +
> +    The value in self.counters is overwritten and is not included in merging.
> +    """
> +    for i in xrange(len(self.counters)):
> +      self.counters[i] = MergeCounters(others, i, multipliers)
> +
> +  def Print(self):
> +    """Print the counter values."""
> +    if self.counters and reduce(lambda x, y: x or y, self.counters):
> +      print '%10s: ' % data_factory.GetTagName(self.tag), self.counters
> +
> +
> +def FindMaxKeyValuePair(table):
> +  """Return (key, value) pair of a dictionary that has maximum value."""
> +  maxkey = 0
> +  maxval = 0
> +  for k, v in table.iteritems():
> +    if v > maxval:
> +      maxval = v
> +      maxkey = k
> +  return maxkey, maxval
> +
> +
> +class SingleValueCounters(Counters):
> +  """Single-value counter.
> +
> +  Each profiled single value is encoded in 3 counters:
> +  counters[3 * i + 0]: the most frequent value
> +  counters[3 * i + 1]: the count of the most frequent value
> +  counters[3 * i + 2]: the total number of the evaluation of the value
> +  """
> +
> +  def Merge(self, others, multipliers):
> +    """Merge single value counters."""
> +    for i in xrange(0, len(self.counters), 3):
> +      table = {}
> +      for j in xrange(len(others)):
> +        o = others[j]
> +        key = o.counters[i]
> +        if key in table:
> +          table[key] += multipliers[j] * o.counters[i + 1]
> +        else:
> +          table[o.counters[i]] = multipliers[j] * o.counters[i + 1]
> +
> +      (maxkey, maxval) = FindMaxKeyValuePair(table)
> +
> +      self.counters[i] = maxkey
> +      self.counters[i + 1] = maxval
> +
> +      # Accumulate the overal count
> +      self.counters[i + 2] = MergeCounters(others, i + 2, multipliers)
> +
> +
> +class DeltaValueCounters(Counters):
> +  """Delta counter.
> +
> +  Each profiled delta value is encoded in four counters:
> +  counters[4 * i + 0]: the last measured value
> +  counters[4 * i + 1]: the most common difference
> +  counters[4 * i + 2]: the count of the most common difference
> +  counters[4 * i + 3]: the total number of the evaluation of the value
> +  Merging is similar to SingleValueCounters.
> +  """
> +
> +  def Merge(self, others, multipliers):
> +    """Merge DeltaValue counters."""
> +    for i in xrange(0, len(self.counters), 4):
> +      table = {}
> +      for j in xrange(len(others)):
> +        o = others[j]
> +        key = o.counters[i + 1]
> +        if key in table:
> +          table[key] += multipliers[j] * o.counters[i + 2]
> +        else:
> +          table[key] = multipliers[j] * o.counters[i + 2]
> +
> +      maxkey, maxval = FindMaxKeyValuePair(table)
> +
> +      self.counters[i + 1] = maxkey
> +      self.counters[i + 2] = maxval
> +
> +      # Accumulate the overal count
> +      self.counters[i + 3] = MergeCounters(others, i + 3, multipliers)
> +
> +
> +class IorCounters(Counters):
> +  """Bitwise-IOR counters."""
> +
> +  def Merge(self, others, _):
> +    """Merge IOR counter."""
> +    for i in xrange(len(self.counters)):
> +      self.counters[i] = 0
> +      for o in others:
> +        self.counters[i] |= o.counters[i]
> +
> +
> +class ICallTopNCounters(Counters):
> +  """Indirect call top-N counter.
> +
> +  Each profiled indirect call top-N is encoded in nine counters:
> +  counters[9 * i + 0]: number_of_evictions
> +  counters[9 * i + 1]: callee global id
> +  counters[9 * i + 2]: call_count
> +  counters[9 * i + 3]: callee global id
> +  counters[9 * i + 4]: call_count
> +  counters[9 * i + 5]: callee global id
> +  counters[9 * i + 6]: call_count
> +  counters[9 * i + 7]: callee global id
> +  counters[9 * i + 8]: call_count
> +  The 4 pairs of counters record the 4 most frequent indirect call targets.
> +  """
> +
> +  def Merge(self, others, multipliers):
> +    """Merge ICallTopN counters."""
> +    for i in xrange(0, len(self.counters), 9):
> +      table = {}
> +      for j, o in enumerate(others):
> +        multiplier = multipliers[j]
> +        for k in xrange(0, 4):
> +          key = o.counters[i+2*k+1]
> +          value = o.counters[i+2*k+2]
> +          if key in table:
> +            table[key] += multiplier * value
> +          else:
> +            table[key] = multiplier * value
> +      for j in xrange(0, 4):
> +        (maxkey, maxval) = FindMaxKeyValuePair(table)
> +        self.counters[i+2*j+1] = maxkey
> +        self.counters[i+2*j+2] = maxval
> +        if maxkey:
> +          del table[maxkey]
> +
> +
> +def IsGidInsane(gid):
> +  """Return if the given global id looks insane."""
> +  module_id = gid >> 32
> +  function_id = gid & 0xFFFFFFFF
> +  return (module_id == 0) or (function_id == 0)
> +
> +
> +class DCallCounters(Counters):
> +  """Direct call counter.
> +
> +  Each profiled direct call is encoded in two counters:
> +  counters[2 * i + 0]: callee global id
> +  counters[2 * i + 1]: call count
> +  """
> +
> +  def Merge(self, others, multipliers):
> +    """Merge DCall counters."""
> +    for i in xrange(0, len(self.counters), 2):
> +      self.counters[i+1] *= multipliers[0]
> +      for j, other in enumerate(others[1:]):
> +        global_id = other.counters[i]
> +        call_count = multipliers[j] * other.counters[i+1]
> +        if self.counters[i] != 0 and global_id != 0:
> +          if IsGidInsane(self.counters[i]):
> +            self.counters[i] = global_id
> +          elif IsGidInsane(global_id):
> +            global_id = self.counters[i]
> +          assert self.counters[i] == global_id
> +        elif global_id != 0:
> +          self.counters[i] = global_id
> +        self.counters[i+1] += call_count
> +        if IsGidInsane(self.counters[i]):
> +          self.counters[i] = 0
> +          self.counters[i+1] = 0
> +        if self.counters[i] == 0:
> +          assert self.counters[i+1] == 0
> +        if self.counters[i+1] == 0:
> +          assert self.counters[i] == 0
> +
> +
> +def WeightedMean2(v1, c1, v2, c2):
> +  """Weighted arithmetic mean of two values."""
> +  if c1 + c2 == 0:
> +    return 0
> +  return (v1*c1 + v2*c2) / (c1+c2)
> +
> +
> +class ReuseDistCounters(Counters):
> +  """ReuseDist counters.
> +
> +  We merge the counters one by one, which may render earlier counters
> +  contribute less to the final result due to the truncations. We are doing
> +  this to match the computation in libgcov, to make the
> +  result consistent in these two merges.
> +  """
> +
> +  def Merge(self, others, multipliers):
> +    """Merge ReuseDist counters."""
> +    for i in xrange(0, len(self.counters), 4):
> +      a_mean_dist = 0
> +      a_mean_size = 0
> +      a_count = 0
> +      a_dist_x_size = 0
> +      for j, other in enumerate(others):
> +        mul = multipliers[j]
> +        f_mean_dist = other.counters[i]
> +        f_mean_size = other.counters[i+1]
> +        f_count = other.counters[i+2]
> +        f_dist_x_size = other.counters[i+3]
> +        a_mean_dist = WeightedMean2(a_mean_dist, a_count,
> +                                    f_mean_dist, f_count*mul)
> +        a_mean_size = WeightedMean2(a_mean_size, a_count,
> +                                    f_mean_size, f_count*mul)
> +        a_count += f_count*mul
> +        a_dist_x_size += f_dist_x_size*mul
> +      self.counters[i] = a_mean_dist
> +      self.counters[i+1] = a_mean_size
> +      self.counters[i+2] = a_count
> +      self.counters[i+3] = a_dist_x_size
> +
> +
> +class Summary(DataObject):
> +  """Program level summary information."""
> +
> +  class Summable(object):
> +    """One instance of summable information in the profile."""
> +
> +    def __init__(self, num, runs, sum_all, run_max, sum_max):
> +      self.num = num
> +      self.runs = runs
> +      self.sum_all = sum_all
> +      self.run_max = run_max
> +      self.sum_max = sum_max
> +
> +    def Write(self, writer):
> +      """Serialize to the byte stream."""
> +
> +      writer.WriteWord(self.num)
> +      writer.WriteWord(self.runs)
> +      writer.WriteCounter(self.sum_all)
> +      writer.WriteCounter(self.run_max)
> +      writer.WriteCounter(self.sum_max)
> +
> +    def Merge(self, others, multipliers):
> +      """Merge the summary."""
> +      sum_all = 0
> +      run_max = 0
> +      sum_max = 0
> +      runs = 0
> +      for i in xrange(len(others)):
> +        sum_all += others[i].sum_all * multipliers[i]
> +        sum_max += others[i].sum_max * multipliers[i]
> +        run_max = max(run_max, others[i].run_max * multipliers[i])
> +        runs += others[i].runs
> +      self.sum_all = sum_all
> +      self.run_max = run_max
> +      self.sum_max = sum_max
> +      self.runs = runs
> +
> +    def Print(self):
> +      """Print the program summary value."""
> +      print '%10d %10d %15d %15d %15d' % (
> +          self.num, self.runs, self.sum_all, self.run_max, self.sum_max)
> +
> +  class HistogramBucket(object):
> +    def __init__(self, num_counters, min_value, cum_value):
> +      self.num_counters = num_counters
> +      self.min_value = min_value
> +      self.cum_value = cum_value
> +
> +    def Print(self, ix):
> +      if self.num_counters != 0:
> +        print 'ix=%d num_count=%d min_count=%d cum_count=%d' % (
> +            ix, self.num_counters, self.min_value, self.cum_value)
> +
> +  class Histogram(object):
> +    """Program level histogram information."""
> +
> +    def __init__(self):
> +      self.size = 252
> +      self.bitvector_size = (self.size + 31) / 32
> +      self.histogram = [[None]] * self.size
> +      self.bitvector = [0] * self.bitvector_size
> +
> +    def ComputeCntandBitvector(self):
> +      h_cnt = 0
> +      for h_ix in range(0, self.size):
> +        if self.histogram[h_ix] != [None]:
> +          if self.histogram[h_ix].num_counters:
> +            self.bitvector[h_ix/32] |= (1 << (h_ix %32))
> +            h_cnt += 1
> +      self.h_cnt = h_cnt
> +
> +    def Index(self, value):
> +      """Return the bucket index of a histogram value."""
> +      r = 1
> +      prev2bits = 0
> +
> +      if value <= 3:
> +        return value
> +      v = value
> +      while v > 3:
> +        r += 1
> +        v >>= 1
> +      v = value
> +      prev2bits = (v >> (r - 2)) & 0x3
> +      return (r - 1) * 4 + prev2bits
> +
> +    def Insert(self, value):
> +      """Add a count value to histogram."""
> +      i = self.Index(value)
> +      if self.histogram[i] != [None]:
> +        self.histogram[i].num_counters += 1
> +        self.histogram[i].cum_value += value
> +        if value < self.histogram[i].min_value:
> +          self.histogram[i].min_value = value
> +      else:
> +        self.histogram[i] = Summary.HistogramBucket(1, value, value)
> +
> +    def Print(self):
> +      """Print a histogram."""
> +      print 'Histogram:'
> +      for i in range(self.size):
> +        if self.histogram[i] != [None]:
> +          self.histogram[i].Print(i)
> +
> +    def Write(self, writer):
> +      for bv_ix in range(0, self.bitvector_size):
> +        writer.WriteWord(self.bitvector[bv_ix])
> +      for h_ix in range(0, self.size):
> +        if self.histogram[h_ix] != [None]:
> +          writer.WriteWord(self.histogram[h_ix].num_counters)
> +          writer.WriteCounter(self.histogram[h_ix].min_value)
> +          writer.WriteCounter(self.histogram[h_ix].cum_value)
> +
> +  def SummaryLength(self, h_cnt):
> +    """Return the of of summary for a given histogram count."""
> +    return 1 + (10 + 3 * 2) + h_cnt * 5
> +
> +  def __init__(self, reader, tag, n_words):
> +    DataObject.__init__(self, tag)
> +    self.length = n_words
> +    self.checksum = reader.ReadWord()
> +    self.sum_counter = []
> +    self.histograms = []
> +
> +    for _ in xrange(DataObjectFactory.N_SUMMABLE):
> +      num = reader.ReadWord()
> +      runs = reader.ReadWord()
> +      sum_all = reader.ReadCounter()
> +      run_max = reader.ReadCounter()
> +      sum_max = reader.ReadCounter()
> +
> +      histogram = self.Histogram()
> +      histo_bitvector = [[None]] * histogram.bitvector_size
> +      h_cnt = 0
> +
> +      for bv_ix in xrange(histogram.bitvector_size):
> +        val = reader.ReadWord()
> +        histo_bitvector[bv_ix] = val
> +        while val != 0:
> +          h_cnt += 1
> +          val &= (val-1)
> +      bv_ix = 0
> +      h_ix = 0
> +      cur_bitvector = 0
> +      for _ in xrange(h_cnt):
> +        while cur_bitvector == 0:
> +          h_ix = bv_ix * 32
> +          cur_bitvector = histo_bitvector[bv_ix]
> +          bv_ix += 1
> +        assert bv_ix <= histogram.bitvector_size
> +        while (cur_bitvector & 0x1) == 0:
> +          h_ix += 1
> +          cur_bitvector >>= 1
> +          assert h_ix < histogram.size
> +        n_counters = reader.ReadWord()
> +        minv = reader.ReadCounter()
> +        maxv = reader.ReadCounter()
> +        histogram.histogram[h_ix] = self.HistogramBucket(n_counters,
> +                                                         minv, maxv)
> +        cur_bitvector >>= 1
> +        h_ix += 1
> +
> +      self.histograms.append(histogram)
> +      self.sum_counter.append(self.Summable(
> +          num, runs, sum_all, run_max, sum_max))
> +
> +  def Write(self, writer):
> +    """Serialize to byte stream."""
> +    writer.WriteWord(self.tag)
> +    assert new_histogram
> +    self.length = self.SummaryLength(new_histogram[0].h_cnt)
> +    writer.WriteWord(self.length)
> +    writer.WriteWord(self.checksum)
> +    for i, s in enumerate(self.sum_counter):
> +      s.Write(writer)
> +      new_histogram[i].Write(writer)
> +
> +  def Merge(self, others, multipliers):
> +    """Merge with the other counter. Histogram will be recomputed afterwards."""
> +    for i in xrange(len(self.sum_counter)):
> +      self.sum_counter[i].Merge([o.sum_counter[i] for o in others], multipliers)
> +
> +  def Print(self):
> +    """Print all the summary info for a given module/object summary."""
> +    print '%s: checksum %X' % (
> +        data_factory.GetTagName(self.tag), self.checksum)
> +    print '%10s %10s %15s %15s %15s' % (
> +        'num', 'runs', 'sum_all', 'run_max', 'sum_max')
> +    for i in xrange(DataObjectFactory.N_SUMMABLE):
> +      self.sum_counter[i].Print()
> +      self.histograms[i].Print()
> +
> +
> +class ModuleInfo(DataObject):
> +  """Module information."""
> +
> +  def __init__(self, reader, tag, n_words):
> +    DataObject.__init__(self, tag)
> +    self.length = n_words
> +    self.module_id = reader.ReadWord()
> +    self.is_primary = reader.ReadWord()
> +    self.flags = reader.ReadWord()
> +    self.language = reader.ReadWord()
> +    self.num_quote_paths = reader.ReadWord()
> +    self.num_bracket_paths = reader.ReadWord()
> +    self.num_cpp_defines = reader.ReadWord()
> +    self.num_cpp_includes = reader.ReadWord()
> +    self.num_cl_args = reader.ReadWord()
> +    self.filename_len = reader.ReadWord()
> +    self.filename = []
> +    for _ in xrange(self.filename_len):
> +      self.filename.append(reader.ReadWord())
> +    self.src_filename_len = reader.ReadWord()
> +    self.src_filename = []
> +    for _ in xrange(self.src_filename_len):
> +      self.src_filename.append(reader.ReadWord())
> +    self.string_lens = []
> +    self.strings = []
> +    for _ in xrange(self.num_quote_paths + self.num_bracket_paths +
> +                    self.num_cpp_defines + self.num_cpp_includes +
> +                    self.num_cl_args):
> +      string_len = reader.ReadWord()
> +      string = []
> +      self.string_lens.append(string_len)
> +      for _ in xrange(string_len):
> +        string.append(reader.ReadWord())
> +      self.strings.append(string)
> +
> +  def Write(self, writer):
> +    """Serialize to byte stream."""
> +    writer.WriteWord(self.tag)
> +    writer.WriteWord(self.length)
> +    writer.WriteWord(self.module_id)
> +    writer.WriteWord(self.is_primary)
> +    writer.WriteWord(self.flags)
> +    writer.WriteWord(self.language)
> +    writer.WriteWord(self.num_quote_paths)
> +    writer.WriteWord(self.num_bracket_paths)
> +    writer.WriteWord(self.num_cpp_defines)
> +    writer.WriteWord(self.num_cpp_includes)
> +    writer.WriteWord(self.num_cl_args)
> +    writer.WriteWord(self.filename_len)
> +    for i in xrange(self.filename_len):
> +      writer.WriteWord(self.filename[i])
> +    writer.WriteWord(self.src_filename_len)
> +    for i in xrange(self.src_filename_len):
> +      writer.WriteWord(self.src_filename[i])
> +    for i in xrange(len(self.string_lens)):
> +      writer.WriteWord(self.string_lens[i])
> +      string = self.strings[i]
> +      for j in xrange(self.string_lens[i]):
> +        writer.WriteWord(string[j])
> +
> +  def Print(self):
> +    """Print the module info."""
> +    fn = ''
> +    for fn4 in self.src_filename:
> +      fn += chr((fn4) & 0xFF)
> +      fn += chr((fn4 >> 8) & 0xFF)
> +      fn += chr((fn4 >> 16) & 0xFF)
> +      fn += chr((fn4 >> 24) & 0xFF)
> +    print ('%s: %s [%s, %s, %s]'
> +           % (data_factory.GetTagName(self.tag),
> +              fn,
> +              ('primary', 'auxiliary')[self.is_primary == 0],
> +              ('exported', 'not-exported')[(self.flags & 0x1) == 0],
> +              ('include_all', '')[(self.flags & 0x2) == 0]))
> +
> +
> +class DataObjectFactory(object):
> +  """A factory of profile data objects."""
> +
> +  TAG_FUNCTION = 0x01000000
> +  TAG_BLOCK = 0x01410000
> +  TAG_ARCS = 0x01430000
> +  TAG_LINES = 0x01450000
> +  TAG_COUNTER_ARCS = 0x01a10000 + (0 << 17)
> +  TAG_COUNTER_INTERVAL = TAG_COUNTER_ARCS + (1 << 17)
> +  TAG_COUNTER_POW2 = TAG_COUNTER_ARCS + (2 << 17)
> +  TAG_COUNTER_SINGLE = TAG_COUNTER_ARCS + (3 << 17)
> +  TAG_COUNTER_DELTA = TAG_COUNTER_ARCS + (4 << 17)
> +  TAG_COUNTER_INDIRECT_CALL = TAG_COUNTER_ARCS + (5 << 17)
> +  TAG_COUNTER_AVERAGE = TAG_COUNTER_ARCS + (6 << 17)
> +  TAG_COUNTER_IOR = TAG_COUNTER_ARCS + (7 << 17)
> +  TAG_COUNTER_ICALL_TOPN = TAG_COUNTER_ARCS + (8 << 17)
> +  TAG_COUNTER_DCALL = TAG_COUNTER_ARCS + (9 << 17)
> +  TAG_COUNTER_REUSE_DIST = TAG_COUNTER_ARCS + (10 << 17)
> +
> +  TAG_PROGRAM_SUMMARY = 0x0a3000000L
> +  TAG_MODULE_INFO = 0x0ab000000L
> +
> +  N_SUMMABLE = 1
> +
> +  DATA_MAGIC = 0x67636461
> +  NOTE_MAGIC = 0x67636e6f
> +
> +  def __init__(self):
> +    self.__tagname = {}
> +    self.__tagname[self.TAG_FUNCTION] = ('function', Function)
> +    self.__tagname[self.TAG_BLOCK] = ('blocks', Blocks)
> +    self.__tagname[self.TAG_ARCS] = ('cfg_arcs', Arcs)
> +    self.__tagname[self.TAG_LINES] = ('lines', Lines)
> +    self.__tagname[self.TAG_PROGRAM_SUMMARY] = ('program_summary', Summary)
> +    self.__tagname[self.TAG_MODULE_INFO] = ('module_info', ModuleInfo)
> +    self.__tagname[self.TAG_COUNTER_ARCS] = ('arcs', Counters)
> +    self.__tagname[self.TAG_COUNTER_INTERVAL] = ('interval', Counters)
> +    self.__tagname[self.TAG_COUNTER_POW2] = ('pow2', Counters)
> +    self.__tagname[self.TAG_COUNTER_SINGLE] = ('single', SingleValueCounters)
> +    self.__tagname[self.TAG_COUNTER_DELTA] = ('delta', DeltaValueCounters)
> +    self.__tagname[self.TAG_COUNTER_INDIRECT_CALL] = (
> +        'icall', SingleValueCounters)
> +    self.__tagname[self.TAG_COUNTER_AVERAGE] = ('average', Counters)
> +    self.__tagname[self.TAG_COUNTER_IOR] = ('ior', IorCounters)
> +    self.__tagname[self.TAG_COUNTER_ICALL_TOPN] = ('icall_topn',
> +                                                   ICallTopNCounters)
> +    self.__tagname[self.TAG_COUNTER_DCALL] = ('dcall', DCallCounters)
> +    self.__tagname[self.TAG_COUNTER_REUSE_DIST] = ('reuse_dist',
> +                                                   ReuseDistCounters)
> +
> +  def GetTagName(self, tag):
> +    """Return the name for a given tag."""
> +    return self.__tagname[tag][0]
> +
> +  def Create(self, reader, tag, n_words):
> +    """Read the raw data from reader and return the data object."""
> +    if tag not in self.__tagname:
> +      print tag
> +
> +    assert tag in self.__tagname
> +    return self.__tagname[tag][1](reader, tag, n_words)
> +
> +
> +# Singleton factory object.
> +data_factory = DataObjectFactory()
> +
> +
> +class ProfileDataFile(object):
> +  """Structured representation of a gcda/gcno file.
> +
> +  Attributes:
> +    buffer: The binary representation of the file.
> +    pos: The current position in the buffer.
> +    magic: File type magic number.
> +    version: Compiler version.
> +    stamp: Time stamp.
> +    functions: A sequence of all Function objects.
> +        The order is preserved from the binary representation.
> +
> +  One profile data file (gcda or gcno file) is a collection
> +  of Function data objects and object/program summaries.
> +  """
> +
> +  def __init__(self, buf=None):
> +    """If buf is None, create a skeleton. Otherwise, read from buf."""
> +    self.pos = 0
> +    self.functions = []
> +    self.program_summaries = []
> +    self.module_infos = []
> +
> +    if buf:
> +      self.buffer = buf
> +      # Convert the entire buffer to ints as store in an array.  This
> +      # is a bit more convenient and faster.
> +      self.int_array = array.array('I', self.buffer)
> +      self.n_ints = len(self.int_array)
> +      self.magic = self.ReadWord()
> +      self.version = self.ReadWord()
> +      self.stamp = self.ReadWord()
> +      if (self.magic == data_factory.DATA_MAGIC or
> +          self.magic == data_factory.NOTE_MAGIC):
> +        self.ReadObjects()
> +      else:
> +        print 'error: %X is not a known gcov magic' % self.magic
> +    else:
> +      self.buffer = None
> +      self.magic = 0
> +      self.version = 0
> +      self.stamp = 0
> +
> +  def WriteToBuffer(self):
> +    """Return a string that contains the binary representation of the file."""
> +    self.pos = 0
> +    # When writing, accumulate written values in a list, then flatten
> +    # into a string.  This is _much_ faster than accumulating within a
> +    # string.
> +    self.buffer = []
> +    self.WriteWord(self.magic)
> +    self.WriteWord(self.version)
> +    self.WriteWord(self.stamp)
> +    for s in self.program_summaries:
> +      s.Write(self)
> +    for f in self.functions:
> +      f.Write(self)
> +    for m in self.module_infos:
> +      m.Write(self)
> +    self.WriteWord(0)  # EOF marker
> +    # Flatten buffer into a string.
> +    self.buffer = ''.join(self.buffer)
> +    return self.buffer
> +
> +  def WriteWord(self, word):
> +    """Write one word - 32-bit integer to buffer."""
> +    self.buffer.append(struct.pack('I', word & 0xFFFFFFFF))
> +
> +  def WriteWords(self, words):
> +    """Write a sequence of words to buffer."""
> +    for w in words:
> +      self.WriteWord(w)
> +
> +  def WriteCounter(self, c):
> +    """Write one counter to buffer."""
> +    self.WriteWords((int(c), int(c >> 32)))
> +
> +  def WriteCounters(self, counters):
> +    """Write a sequence of Counters to buffer."""
> +    for c in counters:
> +      self.WriteCounter(c)
> +
> +  def WriteStr(self, s):
> +    """Write a string to buffer."""
> +    l = len(s)
> +    self.WriteWord((l + 4) / 4)  # Write length
> +    self.buffer.append(s)
> +    for _ in xrange(4 * ((l + 4) / 4) - l):
> +      self.buffer.append('\x00'[0])
> +
> +  def ReadWord(self):
> +    """Read a word from buffer."""
> +    self.pos += 1
> +    return self.int_array[self.pos - 1]
> +
> +  def ReadWords(self, n_words):
> +    """Read the specified number of words (n_words) from buffer."""
> +    self.pos += n_words
> +    return self.int_array[self.pos - n_words:self.pos]
> +
> +  def ReadCounter(self):
> +    """Read a counter value from buffer."""
> +    v = self.ReadWord()
> +    return v | (self.ReadWord() << 32)
> +
> +  def ReadCounters(self, n_counters):
> +    """Read the specified number of counter values from buffer."""
> +    words = self.ReadWords(2 * n_counters)
> +    return [words[2 * i] | (words[2 * i + 1] << 32) for i in xrange(n_counters)]
> +
> +  def ReadStr(self):
> +    """Read a string from buffer."""
> +    length = self.ReadWord()
> +    if not length:
> +      return None
> +    # Read from the original string buffer to avoid having to convert
> +    # from int back to string.  The position counter is a count of
> +    # ints, so we need to multiply it by 4.
> +    ret = self.buffer[4 * self.pos: 4 * self.pos + 4 * length]
> +    self.pos += length
> +    return ret.rstrip('\x00')
> +
> +  def ReadObjects(self):
> +    """Read and process all data objects from buffer."""
> +    function = None
> +    while self.pos < self.n_ints:
> +      obj = None
> +      tag = self.ReadWord()
> +      if not tag and self.program_summaries:
> +        break
> +
> +      length = self.ReadWord()
> +      obj = data_factory.Create(self, tag, length)
> +      if obj:
> +        if tag == data_factory.TAG_FUNCTION:
> +          function = obj
> +          self.functions.append(function)
> +        elif tag == data_factory.TAG_PROGRAM_SUMMARY:
> +          self.program_summaries.append(obj)
> +        elif tag == data_factory.TAG_MODULE_INFO:
> +          self.module_infos.append(obj)
> +        else:
> +          # By default, all objects belong to the preceding function,
> +          # except for program summary or new function.
> +          function.counters.append(obj)
> +      else:
> +        print 'WARNING: unknown tag - 0x%X' % tag
> +
> +  def PrintBrief(self):
> +    """Print the list of functions in the file."""
> +    print 'magic:   0x%X' % self.magic
> +    print 'version: 0x%X' % self.version
> +    print 'stamp:   0x%X' % self.stamp
> +    for function in self.functions:
> +      print '%d' % function.EntryCount()
> +
> +  def Print(self):
> +    """Print the content of the file in full detail."""
> +    for function in self.functions:
> +      function.Print()
> +    for s in self.program_summaries:
> +      s.Print()
> +    for m in self.module_infos:
> +      m.Print()
> +
> +  def MergeFiles(self, files, multipliers):
> +    """Merge ProfileDataFiles and return a merged file."""
> +    for f in files:
> +      assert self.version == f.version
> +      assert len(self.functions) == len(f.functions)
> +
> +    for i in range(len(self.functions)):
> +      self.functions[i].Merge([f.functions[i] for f in files], multipliers)
> +
> +    for i in range(len(self.program_summaries)):
> +      self.program_summaries[i].Merge([f.program_summaries[i] for f in files],
> +                                      multipliers)
> +
> +    if self.module_infos:
> +      primary_module_id = self.module_infos[0].module_id
> +      module_group_ids = set(m.module_id for m in self.module_infos)
> +      for f in files:
> +        assert f.module_infos
> +        assert primary_module_id == f.module_infos[0].module_id
> +        assert ((f.module_infos[0].flags & 0x2) ==
> +                (self.module_infos[0].flags & 0x2))
> +        f.module_infos[0].flags |= self.module_infos[0].flags
> +        for m in f.module_infos:
> +          if m.module_id not in module_group_ids:
> +            module_group_ids.add(m.module_id)
> +            self.module_infos.append(m)
> +
> +
> +class OneImport(object):
> +  """Representation of one import for a primary module."""
> +
> +  def __init__(self, src, gcda):
> +    self.src = src
> +    self.gcda = gcda
> +    assert self.gcda.endswith('.gcda\n')
> +
> +  def GetLines(self):
> +    """Returns the text lines for the import."""
> +    lines = [self.src, self.gcda]
> +    return lines
> +
> +
> +class ImportsFile(object):
> +  """Representation of one .gcda.imports file."""
> +
> +  def __init__(self, profile_archive, import_file):
> +    self.filename = import_file
> +    if profile_archive.dir:
> +      f = open(os.path.join(profile_archive.dir, import_file), 'rb')
> +      lines = f.readlines()
> +      f.close()
> +    else:
> +      assert profile_archive.zip
> +      buf = profile_archive.zip.read(import_file)
> +      lines = []
> +      if buf:
> +        lines = buf.rstrip('\n').split('\n')
> +      for i in xrange(len(lines)):
> +        lines[i] += '\n'
> +
> +    self.imports = []
> +    for i in xrange(0, len(lines), 2):
> +      src = lines[i]
> +      gcda = lines[i+1]
> +      self.imports.append(OneImport(src, gcda))
> +
> +  def MergeFiles(self, files):
> +    """Merge ImportsFiles and return a merged file."""
> +    table = dict((imp.src, 1) for imp in self.imports)
> +
> +    for o in files:
> +      for imp in o.imports:
> +        if not imp.src in table:
> +          self.imports.append(imp)
> +          table[imp.src] = 1
> +
> +  def Write(self, datafile):
> +    """Write out to datafile as text lines."""
> +    lines = []
> +    for imp in self.imports:
> +      lines.extend(imp.GetLines())
> +    datafile.writelines(lines)
> +
> +  def WriteToBuffer(self):
> +    """Return a string that contains the binary representation of the file."""
> +    self.pos = 0
> +    self.buffer = ''
> +
> +    for imp in self.imports:
> +      for line in imp.GetLines():
> +        self.buffer += line
> +
> +    return self.buffer
> +
> +  def Print(self):
> +    """Print method."""
> +    print 'Imports for %s\n' % (self.filename)
> +    for imp in self.imports:
> +      for line in imp.GetLines():
> +        print line
> +
> +
> +class ProfileArchive(object):
> +  """A container for all gcda/gcno files under a directory (recursively).
> +
> +  Attributes:
> +    gcda: A dictionary with the gcda file path as key.
> +          If the value is 0, it means the file exists in the archive
> +          but not yet read.
> +    gcno: A dictionary with the gcno file path as key.
> +    dir: A path to the directory containing the gcda/gcno.
> +         If set, the archive is a directory.
> +    zip: A ZipFile instance. If set, the archive is a zip file.
> +
> +  ProfileArchive can be either a directory containing a directory tree
> +  containing gcda/gcno files, or a single zip file that contains
> +  the similar directory hierarchy.
> +  """
> +
> +  def __init__(self, path):
> +    self.gcda = {}
> +    self.gcno = {}
> +    self.imports = {}
> +    if os.path.isdir(path):
> +      self.dir = path
> +      self.zip = None
> +      self.ScanDir(path)
> +    elif path.endswith('.zip'):
> +      self.zip = zipfile.ZipFile(path)
> +      self.dir = None
> +      self.ScanZip()
> +
> +  def ReadFile(self, path):
> +    """Read the content of the file and return it.
> +
> +    Args:
> +      path: a relative path of the file inside the archive.
> +
> +    Returns:
> +      Sequence of bytes containing the content of the file.
> +
> +    Raises:
> +      Error: If file is not found.
> +    """
> +    if self.dir:
> +      return ReadAllAndClose(os.path.join(self.dir, path))
> +    elif self.zip:
> +      return self.zip.read(path)
> +    raise Error('File not found - "%s"' % path)
> +
> +  def ScanZip(self):
> +    """Find all .gcda/.gcno/.imports files in the zip."""
> +    for f in self.zip.namelist():
> +      if f.endswith('.gcda'):
> +        self.gcda[f] = 0
> +      elif f.endswith('.gcno'):
> +        self.gcno[f] = 0
> +      elif f.endswith('.imports'):
> +        self.imports[f] = 0
> +
> +  def ScanDir(self, direc):
> +    """Recursively visit all subdirs and find all .gcda/.gcno/.imports files."""
> +
> +    def ScanFile(_, dirpath, namelist):
> +      """Record gcda/gcno files."""
> +      for f in namelist:
> +        path = os.path.join(dirpath, f)
> +        if f.endswith('.gcda'):
> +          self.gcda[path] = 0
> +        elif f.endswith('.gcno'):
> +          self.gcno[path] = 0
> +        elif f.endswith('.imports'):
> +          self.imports[path] = 0
> +
> +    cwd = os.getcwd()
> +    os.chdir(direc)
> +    os.path.walk('.', ScanFile, None)
> +    os.chdir(cwd)
> +
> +  def ReadAll(self):
> +    """Read all gcda/gcno/imports files found inside the archive."""
> +    for f in self.gcda.iterkeys():
> +      self.gcda[f] = ProfileDataFile(self.ReadFile(f))
> +    for f in self.gcno.iterkeys():
> +      self.gcno[f] = ProfileDataFile(self.ReadFile(f))
> +    for f in self.imports.iterkeys():
> +      self.imports[f] = ImportsFile(self, f)
> +
> +  def Print(self):
> +    """Print all files in full detail - including all counter values."""
> +    for f in self.gcda.itervalues():
> +      f.Print()
> +    for f in self.gcno.itervalues():
> +      f.Print()
> +    for f in self.imports.itervalues():
> +      f.Print()
> +
> +  def PrintBrief(self):
> +    """Print only the summary information without the counter values."""
> +    for f in self.gcda.itervalues():
> +      f.PrintBrief()
> +    for f in self.gcno.itervalues():
> +      f.PrintBrief()
> +    for f in self.imports.itervalues():
> +      f.PrintBrief()
> +
> +  def Write(self, output_path):
> +    """Write the archive to disk."""
> +
> +    if output_path.endswith('.zip'):
> +      zip_out = zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED)
> +      for f in self.gcda.iterkeys():
> +        zip_out.writestr(f, self.gcda[f].WriteToBuffer())
> +      for f in self.imports.iterkeys():
> +        zip_out.writestr(f, self.imports[f].WriteToBuffer())
> +      zip_out.close()
> +
> +    else:
> +      if not os.path.exists(output_path):
> +        os.makedirs(output_path)
> +      for f in self.gcda.iterkeys():
> +        outfile_path = output_path + '/' + f
> +        if not os.path.exists(os.path.dirname(outfile_path)):
> +          os.makedirs(os.path.dirname(outfile_path))
> +        data_file = open(outfile_path, 'wb')
> +        data_file.write(self.gcda[f].WriteToBuffer())
> +        data_file.close()
> +      for f in self.imports.iterkeys():
> +        outfile_path = output_path + '/' + f
> +        if not os.path.exists(os.path.dirname(outfile_path)):
> +          os.makedirs(os.path.dirname(outfile_path))
> +        data_file = open(outfile_path, 'wb')
> +        self.imports[f].Write(data_file)
> +        data_file.close()
> +
> +  def Merge(self, archives, multipliers):
> +    """Merge one file at a time."""
> +
> +    # Read
> +    for a in archives:
> +      a.ReadAll()
> +    if not self in archives:
> +      self.ReadAll()
> +
> +    # First create set of all gcda files
> +    all_gcda_files = set()
> +    for a in [self] + archives:
> +      all_gcda_files = all_gcda_files.union(a.gcda.iterkeys())
> +
> +    # Iterate over all gcda files and create a merged object
> +    # containing all profile data which exists for this file
> +    # among self and archives.
> +    for gcda_file in all_gcda_files:
> +      files = []
> +      mults = []
> +      for i, a in enumerate(archives):
> +        if gcda_file in a.gcda:
> +          files.append(a.gcda[gcda_file])
> +          mults.append(multipliers[i])
> +      if gcda_file not in self.gcda:
> +        self.gcda[gcda_file] = files[0]
> +      self.gcda[gcda_file].MergeFiles(files, mults)
> +
> +    # Same process for imports files
> +    all_imports_files = set()
> +    for a in [self] + archives:
> +      all_imports_files = all_imports_files.union(a.imports.iterkeys())
> +
> +    for imports_file in all_imports_files:
> +      files = []
> +      for i, a in enumerate(archives):
> +        if imports_file in a.imports:
> +          files.append(a.imports[imports_file])
> +      if imports_file not in self.imports:
> +        self.imports[imports_file] = files[0]
> +      self.imports[imports_file].MergeFiles(files)
> +
> +  def ComputeHistogram(self):
> +    """Compute and return the histogram."""
> +
> +    histogram = [[None]] * DataObjectFactory.N_SUMMABLE
> +    for n in xrange(DataObjectFactory.N_SUMMABLE):
> +      histogram[n] = Summary.Histogram()
> +
> +    for o in self.gcda:
> +      for f in self.gcda[o].functions:
> +        for n in xrange(len(f.counters)):
> +          if n < DataObjectFactory.N_SUMMABLE:
> +            for c in xrange(len(f.counters[n].counters)):
> +              histogram[n].Insert(f.counters[n].counters[c])
> +    for n in xrange(DataObjectFactory.N_SUMMABLE):
> +      histogram[n].ComputeCntandBitvector()
> +    return histogram
> +
> +
> +def main():
> +  """Merge multiple profile data."""
> +
> +  global new_histogram
> +
> +  usage = 'usage: %prog [options] arg1 arg2 ...'
> +  parser = OptionParser(usage)
> +  parser.add_option('-w', '--multipliers',
> +                    dest='multipliers',
> +                    help='Comma separated list of multipliers to be applied '
> +                    'for each corresponding profile.')
> +  parser.add_option('-o', '--output',
> +                    dest='output_profile',
> +                    help='Output directory or zip file to dump the '
> +                    'merged profile. Default output is profile-merged.zip.')
> +  group = OptionGroup(parser, 'Arguments',
> +                      'Comma separated list of input directories or zip files '
> +                      'that contain profile data to merge.')
> +  parser.add_option_group(group)
> +
> +  (options, args) = parser.parse_args()
> +
> +  if len(args) < 2:
> +    parser.error('Please provide at least 2 input profiles.')
> +
> +  input_profiles = [ProfileArchive(path) for path in args]
> +
> +  if options.multipliers:
> +    profile_multipliers = [long(i) for i in options.multipliers.split(',')]
> +    if len(profile_multipliers) != len(input_profiles):
> +      parser.error('--multipliers has different number of elements from '
> +                   '--inputs.')
> +  else:
> +    profile_multipliers = [1 for i in range(len(input_profiles))]
> +
> +  if options.output_profile:
> +    output_profile = options.output_profile
> +  else:
> +    output_profile = 'profile-merged.zip'
> +
> +  input_profiles[0].Merge(input_profiles, profile_multipliers)
> +
> +  new_histogram = input_profiles[0].ComputeHistogram()
> +
> +  input_profiles[0].Write(output_profile)
> +
> +if __name__ == '__main__':
> +  main()
>
> Property changes on: contrib/profile_merge.py
> ___________________________________________________________________
> Added: svn:executable
>    + *
>
>
> --
> This patch is available for review at http://codereview.appspot.com/8508048

Patch

Index: contrib/profile_merge.py
===================================================================
--- contrib/profile_merge.py	(revision 0)
+++ contrib/profile_merge.py	(revision 0)
@@ -0,0 +1,1301 @@ 
+#!/usr/bin/python2.7
+#
+# Copyright 2013 Google Inc. All Rights Reserved.
+
+"""Merge two or more gcda profile.
+"""
+
+__author__ = 'Seongbae Park, Rong Xu'
+__author_email__ = 'spark@google.com, xur@google.com'
+
+import array
+from optparse import OptionGroup
+from optparse import OptionParser
+import os
+import struct
+import zipfile
+
+new_histogram = None
+
+
+class Error(Exception):
+  """Exception class for profile module."""
+
+
+def ReadAllAndClose(path):
+  """Return the entire byte content of the specified file.
+
+  Args:
+    path: The path to the file to be opened and read.
+
+  Returns:
+    The byte sequence of the content of the file.
+  """
+  data_file = open(path, 'rb')
+  data = data_file.read()
+  data_file.close()
+  return data
+
+
+def MergeCounters(objs, index, multipliers):
+  """Accumulate the counter at "index" from all counters objs."""
+  val = 0
+  for j in xrange(len(objs)):
+    val += multipliers[j] * objs[j].counters[index]
+  return val
+
+
+class DataObject(object):
+  """Base class for various datum in GCDA/GCNO file."""
+
+  def __init__(self, tag):
+    self.tag = tag
+
+
+class Function(DataObject):
+  """Function and its counters.
+
+  Attributes:
+    length: Length of the data on the disk
+    ident: Ident field
+    line_checksum: Checksum of the line number
+    cfg_checksum: Checksum of the control flow graph
+    counters: All counters associated with the function
+    file: The name of the file the function is defined in. Optional.
+    line: The line number the function is defined at. Optional.
+
+  Function object contains other counter objects and block/arc/line objects.
+  """
+
+  def __init__(self, reader, tag, n_words):
+    """Read function record information from a gcda/gcno file.
+
+    Args:
+      reader: gcda/gcno file.
+      tag: funtion tag.
+      n_words: length of function record in unit of 4-byte.
+    """
+    DataObject.__init__(self, tag)
+    self.length = n_words
+    self.counters = []
+
+    if reader:
+      pos = reader.pos
+      self.ident = reader.ReadWord()
+      self.line_checksum = reader.ReadWord()
+      self.cfg_checksum = reader.ReadWord()
+
+      # Function name string is in gcno files, but not
+      # in gcda files. Here we make string reading optional.
+      if (reader.pos - pos) < n_words:
+        reader.ReadStr()
+
+      if (reader.pos - pos) < n_words:
+        self.file = reader.ReadStr()
+        self.line_number = reader.ReadWord()
+      else:
+        self.file = ''
+        self.line_number = 0
+    else:
+      self.ident = 0
+      self.line_checksum = 0
+      self.cfg_checksum = 0
+      self.file = None
+      self.line_number = 0
+
+  def Write(self, writer):
+    """Write out the function."""
+
+    writer.WriteWord(self.tag)
+    writer.WriteWord(self.length)
+    writer.WriteWord(self.ident)
+    writer.WriteWord(self.line_checksum)
+    writer.WriteWord(self.cfg_checksum)
+    for c in self.counters:
+      c.Write(writer)
+
+  def EntryCount(self):
+    """Return the number of times the function called."""
+    return self.ArcCounters().counters[0]
+
+  def Merge(self, others, multipliers):
+    """Merge all functions in "others" into self.
+
+    Args:
+      others: A sequence of Function objects
+      multipliers: A sequence of integers to be multiplied during merging.
+    """
+    for o in others:
+      assert self.ident == o.ident
+      assert self.line_checksum == o.line_checksum
+      assert self.cfg_checksum == o.cfg_checksum
+
+    for i in xrange(len(self.counters)):
+      self.counters[i].Merge([o.counters[i] for o in others], multipliers)
+
+  def Print(self):
+    """Print all the attributes in full detail."""
+    print 'function: ident %d length %d line_chksum %x cfg_chksum %x' % (
+        self.ident, self.length,
+        self.line_checksum, self.cfg_checksum)
+    if self.file:
+      print 'file:     %s' % self.file
+      print 'line_number:   %d' % self.line_number
+    for c in self.counters:
+      c.Print()
+
+  def ArcCounters(self):
+    """Return the counter object containing Arcs counts."""
+    for c in self.counters:
+      if c.tag == DataObjectFactory.TAG_COUNTER_ARCS:
+        return c
+    return None
+
+
+class Blocks(DataObject):
+  """Block information for a function."""
+
+  def __init__(self, reader, tag, n_words):
+    DataObject.__init__(self, tag)
+    self.length = n_words
+    self.__blocks = reader.ReadWords(n_words)
+
+  def Print(self):
+    """Print the list of block IDs."""
+    print 'blocks:  ', ' '.join(self.__blocks)
+
+
+class Arcs(DataObject):
+  """List of outgoing control flow edges for a single basic block."""
+
+  def __init__(self, reader, tag, n_words):
+    DataObject.__init__(self, tag)
+
+    self.length = (n_words - 1) / 2
+    self.block_id = reader.ReadWord()
+    self.__arcs = reader.ReadWords(2 * self.length)
+
+  def Print(self):
+    """Print all edge information in full detail."""
+    print 'arcs: block', self.block_id
+    print 'arcs: ',
+    for i in xrange(0, len(self.__arcs), 2):
+      print '(%d:%x)' % (self.__arcs[i], self.__arcs[i+1]),
+      if self.__arcs[i+1] & 0x01: print 'on_tree'
+      if self.__arcs[i+1] & 0x02: print 'fake'
+      if self.__arcs[i+1] & 0x04: print 'fallthrough'
+    print
+
+
+class Lines(DataObject):
+  """Line number information for a block."""
+
+  def __init__(self, reader, tag, n_words):
+    DataObject.__init__(self, tag)
+    self.length = n_words
+    self.block_id = reader.ReadWord()
+    self.line_numbers = []
+    line_number = reader.ReadWord()
+    src_files = reader.ReadStr()
+    while src_files:
+      line_number = reader.ReadWord()
+      src_lines = [src_files]
+      while line_number:
+        src_lines.append(line_number)
+        line_number = reader.ReadWord()
+      self.line_numbers.append(src_lines)
+      src_files = reader.ReadStr()
+
+  def Print(self):
+    """Print all line numbers in full detail."""
+    for l in self.line_numbers:
+      print 'line_number: block %d' % self.block_id, ' '.join(l)
+
+
+class Counters(DataObject):
+  """List of counter values.
+
+  Attributes:
+    counters: sequence of counter values.
+  """
+
+  def __init__(self, reader, tag, n_words):
+    DataObject.__init__(self, tag)
+    self.counters = reader.ReadCounters(n_words / 2)
+
+  def Write(self, writer):
+    """Write."""
+    writer.WriteWord(self.tag)
+    writer.WriteWord(len(self.counters) * 2)
+    writer.WriteCounters(self.counters)
+
+  def IsComparable(self, other):
+    """Returns true if two counters are comparable."""
+    return (self.tag == other.tag and
+            len(self.counters) == len(other.counters))
+
+  def Merge(self, others, multipliers):
+    """Merge all counter values from others into self.
+
+    Args:
+      others: other counters to merge.
+      multipliers: multiplier to apply to each of the other counters.
+
+    The value in self.counters is overwritten and is not included in merging.
+    """
+    for i in xrange(len(self.counters)):
+      self.counters[i] = MergeCounters(others, i, multipliers)
+
+  def Print(self):
+    """Print the counter values."""
+    if self.counters and reduce(lambda x, y: x or y, self.counters):
+      print '%10s: ' % data_factory.GetTagName(self.tag), self.counters
+
+
+def FindMaxKeyValuePair(table):
+  """Return (key, value) pair of a dictionary that has maximum value."""
+  maxkey = 0
+  maxval = 0
+  for k, v in table.iteritems():
+    if v > maxval:
+      maxval = v
+      maxkey = k
+  return maxkey, maxval
+
+
+class SingleValueCounters(Counters):
+  """Single-value counter.
+
+  Each profiled single value is encoded in 3 counters:
+  counters[3 * i + 0]: the most frequent value
+  counters[3 * i + 1]: the count of the most frequent value
+  counters[3 * i + 2]: the total number of the evaluation of the value
+  """
+
+  def Merge(self, others, multipliers):
+    """Merge single value counters."""
+    for i in xrange(0, len(self.counters), 3):
+      table = {}
+      for j in xrange(len(others)):
+        o = others[j]
+        key = o.counters[i]
+        if key in table:
+          table[key] += multipliers[j] * o.counters[i + 1]
+        else:
+          table[o.counters[i]] = multipliers[j] * o.counters[i + 1]
+
+      (maxkey, maxval) = FindMaxKeyValuePair(table)
+
+      self.counters[i] = maxkey
+      self.counters[i + 1] = maxval
+
+      # Accumulate the overal count
+      self.counters[i + 2] = MergeCounters(others, i + 2, multipliers)
+
+
+class DeltaValueCounters(Counters):
+  """Delta counter.
+
+  Each profiled delta value is encoded in four counters:
+  counters[4 * i + 0]: the last measured value
+  counters[4 * i + 1]: the most common difference
+  counters[4 * i + 2]: the count of the most common difference
+  counters[4 * i + 3]: the total number of the evaluation of the value
+  Merging is similar to SingleValueCounters.
+  """
+
+  def Merge(self, others, multipliers):
+    """Merge DeltaValue counters."""
+    for i in xrange(0, len(self.counters), 4):
+      table = {}
+      for j in xrange(len(others)):
+        o = others[j]
+        key = o.counters[i + 1]
+        if key in table:
+          table[key] += multipliers[j] * o.counters[i + 2]
+        else:
+          table[key] = multipliers[j] * o.counters[i + 2]
+
+      maxkey, maxval = FindMaxKeyValuePair(table)
+
+      self.counters[i + 1] = maxkey
+      self.counters[i + 2] = maxval
+
+      # Accumulate the overal count
+      self.counters[i + 3] = MergeCounters(others, i + 3, multipliers)
+
+
+class IorCounters(Counters):
+  """Bitwise-IOR counters."""
+
+  def Merge(self, others, _):
+    """Merge IOR counter."""
+    for i in xrange(len(self.counters)):
+      self.counters[i] = 0
+      for o in others:
+        self.counters[i] |= o.counters[i]
+
+
+class ICallTopNCounters(Counters):
+  """Indirect call top-N counter.
+
+  Each profiled indirect call top-N is encoded in nine counters:
+  counters[9 * i + 0]: number_of_evictions
+  counters[9 * i + 1]: callee global id
+  counters[9 * i + 2]: call_count
+  counters[9 * i + 3]: callee global id
+  counters[9 * i + 4]: call_count
+  counters[9 * i + 5]: callee global id
+  counters[9 * i + 6]: call_count
+  counters[9 * i + 7]: callee global id
+  counters[9 * i + 8]: call_count
+  The 4 pairs of counters record the 4 most frequent indirect call targets.
+  """
+
+  def Merge(self, others, multipliers):
+    """Merge ICallTopN counters."""
+    for i in xrange(0, len(self.counters), 9):
+      table = {}
+      for j, o in enumerate(others):
+        multiplier = multipliers[j]
+        for k in xrange(0, 4):
+          key = o.counters[i+2*k+1]
+          value = o.counters[i+2*k+2]
+          if key in table:
+            table[key] += multiplier * value
+          else:
+            table[key] = multiplier * value
+      for j in xrange(0, 4):
+        (maxkey, maxval) = FindMaxKeyValuePair(table)
+        self.counters[i+2*j+1] = maxkey
+        self.counters[i+2*j+2] = maxval
+        if maxkey:
+          del table[maxkey]
+
+
+def IsGidInsane(gid):
+  """Return if the given global id looks insane."""
+  module_id = gid >> 32
+  function_id = gid & 0xFFFFFFFF
+  return (module_id == 0) or (function_id == 0)
+
+
+class DCallCounters(Counters):
+  """Direct call counter.
+
+  Each profiled direct call is encoded in two counters:
+  counters[2 * i + 0]: callee global id
+  counters[2 * i + 1]: call count
+  """
+
+  def Merge(self, others, multipliers):
+    """Merge DCall counters."""
+    for i in xrange(0, len(self.counters), 2):
+      self.counters[i+1] *= multipliers[0]
+      for j, other in enumerate(others[1:]):
+        global_id = other.counters[i]
+        call_count = multipliers[j] * other.counters[i+1]
+        if self.counters[i] != 0 and global_id != 0:
+          if IsGidInsane(self.counters[i]):
+            self.counters[i] = global_id
+          elif IsGidInsane(global_id):
+            global_id = self.counters[i]
+          assert self.counters[i] == global_id
+        elif global_id != 0:
+          self.counters[i] = global_id
+        self.counters[i+1] += call_count
+        if IsGidInsane(self.counters[i]):
+          self.counters[i] = 0
+          self.counters[i+1] = 0
+        if self.counters[i] == 0:
+          assert self.counters[i+1] == 0
+        if self.counters[i+1] == 0:
+          assert self.counters[i] == 0
+
+
+def WeightedMean2(v1, c1, v2, c2):
+  """Weighted arithmetic mean of two values."""
+  if c1 + c2 == 0:
+    return 0
+  return (v1*c1 + v2*c2) / (c1+c2)
+
+
+class ReuseDistCounters(Counters):
+  """ReuseDist counters.
+
+  We merge the counters one by one, which may render earlier counters
+  contribute less to the final result due to the truncations. We are doing
+  this to match the computation in libgcov, to make the
+  result consistent in these two merges.
+  """
+
+  def Merge(self, others, multipliers):
+    """Merge ReuseDist counters."""
+    for i in xrange(0, len(self.counters), 4):
+      a_mean_dist = 0
+      a_mean_size = 0
+      a_count = 0
+      a_dist_x_size = 0
+      for j, other in enumerate(others):
+        mul = multipliers[j]
+        f_mean_dist = other.counters[i]
+        f_mean_size = other.counters[i+1]
+        f_count = other.counters[i+2]
+        f_dist_x_size = other.counters[i+3]
+        a_mean_dist = WeightedMean2(a_mean_dist, a_count,
+                                    f_mean_dist, f_count*mul)
+        a_mean_size = WeightedMean2(a_mean_size, a_count,
+                                    f_mean_size, f_count*mul)
+        a_count += f_count*mul
+        a_dist_x_size += f_dist_x_size*mul
+      self.counters[i] = a_mean_dist
+      self.counters[i+1] = a_mean_size
+      self.counters[i+2] = a_count
+      self.counters[i+3] = a_dist_x_size
+
+
+class Summary(DataObject):
+  """Program level summary information."""
+
+  class Summable(object):
+    """One instance of summable information in the profile."""
+
+    def __init__(self, num, runs, sum_all, run_max, sum_max):
+      self.num = num
+      self.runs = runs
+      self.sum_all = sum_all
+      self.run_max = run_max
+      self.sum_max = sum_max
+
+    def Write(self, writer):
+      """Serialize to the byte stream."""
+
+      writer.WriteWord(self.num)
+      writer.WriteWord(self.runs)
+      writer.WriteCounter(self.sum_all)
+      writer.WriteCounter(self.run_max)
+      writer.WriteCounter(self.sum_max)
+
+    def Merge(self, others, multipliers):
+      """Merge the summary."""
+      sum_all = 0
+      run_max = 0
+      sum_max = 0
+      runs = 0
+      for i in xrange(len(others)):
+        sum_all += others[i].sum_all * multipliers[i]
+        sum_max += others[i].sum_max * multipliers[i]
+        run_max = max(run_max, others[i].run_max * multipliers[i])
+        runs += others[i].runs
+      self.sum_all = sum_all
+      self.run_max = run_max
+      self.sum_max = sum_max
+      self.runs = runs
+
+    def Print(self):
+      """Print the program summary value."""
+      print '%10d %10d %15d %15d %15d' % (
+          self.num, self.runs, self.sum_all, self.run_max, self.sum_max)
+
+  class HistogramBucket(object):
+    def __init__(self, num_counters, min_value, cum_value):
+      self.num_counters = num_counters
+      self.min_value = min_value
+      self.cum_value = cum_value
+
+    def Print(self, ix):
+      if self.num_counters != 0:
+        print 'ix=%d num_count=%d min_count=%d cum_count=%d' % (
+            ix, self.num_counters, self.min_value, self.cum_value)
+
+  class Histogram(object):
+    """Program level histogram information."""
+
+    def __init__(self):
+      self.size = 252
+      self.bitvector_size = (self.size + 31) / 32
+      self.histogram = [[None]] * self.size
+      self.bitvector = [0] * self.bitvector_size
+
+    def ComputeCntandBitvector(self):
+      h_cnt = 0
+      for h_ix in range(0, self.size):
+        if self.histogram[h_ix] != [None]:
+          if self.histogram[h_ix].num_counters:
+            self.bitvector[h_ix/32] |= (1 << (h_ix %32))
+            h_cnt += 1
+      self.h_cnt = h_cnt
+
+    def Index(self, value):
+      """Return the bucket index of a histogram value."""
+      r = 1
+      prev2bits = 0
+
+      if value <= 3:
+        return value
+      v = value
+      while v > 3:
+        r += 1
+        v >>= 1
+      v = value
+      prev2bits = (v >> (r - 2)) & 0x3
+      return (r - 1) * 4 + prev2bits
+
+    def Insert(self, value):
+      """Add a count value to histogram."""
+      i = self.Index(value)
+      if self.histogram[i] != [None]:
+        self.histogram[i].num_counters += 1
+        self.histogram[i].cum_value += value
+        if value < self.histogram[i].min_value:
+          self.histogram[i].min_value = value
+      else:
+        self.histogram[i] = Summary.HistogramBucket(1, value, value)
+
+    def Print(self):
+      """Print a histogram."""
+      print 'Histogram:'
+      for i in range(self.size):
+        if self.histogram[i] != [None]:
+          self.histogram[i].Print(i)
+
+    def Write(self, writer):
+      for bv_ix in range(0, self.bitvector_size):
+        writer.WriteWord(self.bitvector[bv_ix])
+      for h_ix in range(0, self.size):
+        if self.histogram[h_ix] != [None]:
+          writer.WriteWord(self.histogram[h_ix].num_counters)
+          writer.WriteCounter(self.histogram[h_ix].min_value)
+          writer.WriteCounter(self.histogram[h_ix].cum_value)
+
+  def SummaryLength(self, h_cnt):
+    """Return the of of summary for a given histogram count."""
+    return 1 + (10 + 3 * 2) + h_cnt * 5
+
+  def __init__(self, reader, tag, n_words):
+    DataObject.__init__(self, tag)
+    self.length = n_words
+    self.checksum = reader.ReadWord()
+    self.sum_counter = []
+    self.histograms = []
+
+    for _ in xrange(DataObjectFactory.N_SUMMABLE):
+      num = reader.ReadWord()
+      runs = reader.ReadWord()
+      sum_all = reader.ReadCounter()
+      run_max = reader.ReadCounter()
+      sum_max = reader.ReadCounter()
+
+      histogram = self.Histogram()
+      histo_bitvector = [[None]] * histogram.bitvector_size
+      h_cnt = 0
+
+      for bv_ix in xrange(histogram.bitvector_size):
+        val = reader.ReadWord()
+        histo_bitvector[bv_ix] = val
+        while val != 0:
+          h_cnt += 1
+          val &= (val-1)
+      bv_ix = 0
+      h_ix = 0
+      cur_bitvector = 0
+      for _ in xrange(h_cnt):
+        while cur_bitvector == 0:
+          h_ix = bv_ix * 32
+          cur_bitvector = histo_bitvector[bv_ix]
+          bv_ix += 1
+        assert bv_ix <= histogram.bitvector_size
+        while (cur_bitvector & 0x1) == 0:
+          h_ix += 1
+          cur_bitvector >>= 1
+          assert h_ix < histogram.size
+        n_counters = reader.ReadWord()
+        minv = reader.ReadCounter()
+        maxv = reader.ReadCounter()
+        histogram.histogram[h_ix] = self.HistogramBucket(n_counters,
+                                                         minv, maxv)
+        cur_bitvector >>= 1
+        h_ix += 1
+
+      self.histograms.append(histogram)
+      self.sum_counter.append(self.Summable(
+          num, runs, sum_all, run_max, sum_max))
+
+  def Write(self, writer):
+    """Serialize to byte stream."""
+    writer.WriteWord(self.tag)
+    assert new_histogram
+    self.length = self.SummaryLength(new_histogram[0].h_cnt)
+    writer.WriteWord(self.length)
+    writer.WriteWord(self.checksum)
+    for i, s in enumerate(self.sum_counter):
+      s.Write(writer)
+      new_histogram[i].Write(writer)
+
+  def Merge(self, others, multipliers):
+    """Merge with the other counter. Histogram will be recomputed afterwards."""
+    for i in xrange(len(self.sum_counter)):
+      self.sum_counter[i].Merge([o.sum_counter[i] for o in others], multipliers)
+
+  def Print(self):
+    """Print all the summary info for a given module/object summary."""
+    print '%s: checksum %X' % (
+        data_factory.GetTagName(self.tag), self.checksum)
+    print '%10s %10s %15s %15s %15s' % (
+        'num', 'runs', 'sum_all', 'run_max', 'sum_max')
+    for i in xrange(DataObjectFactory.N_SUMMABLE):
+      self.sum_counter[i].Print()
+      self.histograms[i].Print()
+
+
+class ModuleInfo(DataObject):
+  """Module information."""
+
+  def __init__(self, reader, tag, n_words):
+    DataObject.__init__(self, tag)
+    self.length = n_words
+    self.module_id = reader.ReadWord()
+    self.is_primary = reader.ReadWord()
+    self.flags = reader.ReadWord()
+    self.language = reader.ReadWord()
+    self.num_quote_paths = reader.ReadWord()
+    self.num_bracket_paths = reader.ReadWord()
+    self.num_cpp_defines = reader.ReadWord()
+    self.num_cpp_includes = reader.ReadWord()
+    self.num_cl_args = reader.ReadWord()
+    self.filename_len = reader.ReadWord()
+    self.filename = []
+    for _ in xrange(self.filename_len):
+      self.filename.append(reader.ReadWord())
+    self.src_filename_len = reader.ReadWord()
+    self.src_filename = []
+    for _ in xrange(self.src_filename_len):
+      self.src_filename.append(reader.ReadWord())
+    self.string_lens = []
+    self.strings = []
+    for _ in xrange(self.num_quote_paths + self.num_bracket_paths +
+                    self.num_cpp_defines + self.num_cpp_includes +
+                    self.num_cl_args):
+      string_len = reader.ReadWord()
+      string = []
+      self.string_lens.append(string_len)
+      for _ in xrange(string_len):
+        string.append(reader.ReadWord())
+      self.strings.append(string)
+
+  def Write(self, writer):
+    """Serialize to byte stream."""
+    writer.WriteWord(self.tag)
+    writer.WriteWord(self.length)
+    writer.WriteWord(self.module_id)
+    writer.WriteWord(self.is_primary)
+    writer.WriteWord(self.flags)
+    writer.WriteWord(self.language)
+    writer.WriteWord(self.num_quote_paths)
+    writer.WriteWord(self.num_bracket_paths)
+    writer.WriteWord(self.num_cpp_defines)
+    writer.WriteWord(self.num_cpp_includes)
+    writer.WriteWord(self.num_cl_args)
+    writer.WriteWord(self.filename_len)
+    for i in xrange(self.filename_len):
+      writer.WriteWord(self.filename[i])
+    writer.WriteWord(self.src_filename_len)
+    for i in xrange(self.src_filename_len):
+      writer.WriteWord(self.src_filename[i])
+    for i in xrange(len(self.string_lens)):
+      writer.WriteWord(self.string_lens[i])
+      string = self.strings[i]
+      for j in xrange(self.string_lens[i]):
+        writer.WriteWord(string[j])
+
+  def Print(self):
+    """Print the module info."""
+    fn = ''
+    for fn4 in self.src_filename:
+      fn += chr((fn4) & 0xFF)
+      fn += chr((fn4 >> 8) & 0xFF)
+      fn += chr((fn4 >> 16) & 0xFF)
+      fn += chr((fn4 >> 24) & 0xFF)
+    print ('%s: %s [%s, %s, %s]'
+           % (data_factory.GetTagName(self.tag),
+              fn,
+              ('primary', 'auxiliary')[self.is_primary == 0],
+              ('exported', 'not-exported')[(self.flags & 0x1) == 0],
+              ('include_all', '')[(self.flags & 0x2) == 0]))
+
+
+class DataObjectFactory(object):
+  """A factory of profile data objects."""
+
+  TAG_FUNCTION = 0x01000000
+  TAG_BLOCK = 0x01410000
+  TAG_ARCS = 0x01430000
+  TAG_LINES = 0x01450000
+  TAG_COUNTER_ARCS = 0x01a10000 + (0 << 17)
+  TAG_COUNTER_INTERVAL = TAG_COUNTER_ARCS + (1 << 17)
+  TAG_COUNTER_POW2 = TAG_COUNTER_ARCS + (2 << 17)
+  TAG_COUNTER_SINGLE = TAG_COUNTER_ARCS + (3 << 17)
+  TAG_COUNTER_DELTA = TAG_COUNTER_ARCS + (4 << 17)
+  TAG_COUNTER_INDIRECT_CALL = TAG_COUNTER_ARCS + (5 << 17)
+  TAG_COUNTER_AVERAGE = TAG_COUNTER_ARCS + (6 << 17)
+  TAG_COUNTER_IOR = TAG_COUNTER_ARCS + (7 << 17)
+  TAG_COUNTER_ICALL_TOPN = TAG_COUNTER_ARCS + (8 << 17)
+  TAG_COUNTER_DCALL = TAG_COUNTER_ARCS + (9 << 17)
+  TAG_COUNTER_REUSE_DIST = TAG_COUNTER_ARCS + (10 << 17)
+
+  TAG_PROGRAM_SUMMARY = 0x0a3000000L
+  TAG_MODULE_INFO = 0x0ab000000L
+
+  N_SUMMABLE = 1
+
+  DATA_MAGIC = 0x67636461
+  NOTE_MAGIC = 0x67636e6f
+
+  def __init__(self):
+    self.__tagname = {}
+    self.__tagname[self.TAG_FUNCTION] = ('function', Function)
+    self.__tagname[self.TAG_BLOCK] = ('blocks', Blocks)
+    self.__tagname[self.TAG_ARCS] = ('cfg_arcs', Arcs)
+    self.__tagname[self.TAG_LINES] = ('lines', Lines)
+    self.__tagname[self.TAG_PROGRAM_SUMMARY] = ('program_summary', Summary)
+    self.__tagname[self.TAG_MODULE_INFO] = ('module_info', ModuleInfo)
+    self.__tagname[self.TAG_COUNTER_ARCS] = ('arcs', Counters)
+    self.__tagname[self.TAG_COUNTER_INTERVAL] = ('interval', Counters)
+    self.__tagname[self.TAG_COUNTER_POW2] = ('pow2', Counters)
+    self.__tagname[self.TAG_COUNTER_SINGLE] = ('single', SingleValueCounters)
+    self.__tagname[self.TAG_COUNTER_DELTA] = ('delta', DeltaValueCounters)
+    self.__tagname[self.TAG_COUNTER_INDIRECT_CALL] = (
+        'icall', SingleValueCounters)
+    self.__tagname[self.TAG_COUNTER_AVERAGE] = ('average', Counters)
+    self.__tagname[self.TAG_COUNTER_IOR] = ('ior', IorCounters)
+    self.__tagname[self.TAG_COUNTER_ICALL_TOPN] = ('icall_topn',
+                                                   ICallTopNCounters)
+    self.__tagname[self.TAG_COUNTER_DCALL] = ('dcall', DCallCounters)
+    self.__tagname[self.TAG_COUNTER_REUSE_DIST] = ('reuse_dist',
+                                                   ReuseDistCounters)
+
+  def GetTagName(self, tag):
+    """Return the name for a given tag."""
+    return self.__tagname[tag][0]
+
+  def Create(self, reader, tag, n_words):
+    """Read the raw data from reader and return the data object."""
+    if tag not in self.__tagname:
+      print tag
+
+    assert tag in self.__tagname
+    return self.__tagname[tag][1](reader, tag, n_words)
+
+
+# Singleton factory object.
+data_factory = DataObjectFactory()
+
+
+class ProfileDataFile(object):
+  """Structured representation of a gcda/gcno file.
+
+  Attributes:
+    buffer: The binary representation of the file.
+    pos: The current position in the buffer.
+    magic: File type magic number.
+    version: Compiler version.
+    stamp: Time stamp.
+    functions: A sequence of all Function objects.
+        The order is preserved from the binary representation.
+
+  One profile data file (gcda or gcno file) is a collection
+  of Function data objects and object/program summaries.
+  """
+
+  def __init__(self, buf=None):
+    """If buf is None, create a skeleton. Otherwise, read from buf."""
+    self.pos = 0
+    self.functions = []
+    self.program_summaries = []
+    self.module_infos = []
+
+    if buf:
+      self.buffer = buf
+      # Convert the entire buffer to ints as store in an array.  This
+      # is a bit more convenient and faster.
+      self.int_array = array.array('I', self.buffer)
+      self.n_ints = len(self.int_array)
+      self.magic = self.ReadWord()
+      self.version = self.ReadWord()
+      self.stamp = self.ReadWord()
+      if (self.magic == data_factory.DATA_MAGIC or
+          self.magic == data_factory.NOTE_MAGIC):
+        self.ReadObjects()
+      else:
+        print 'error: %X is not a known gcov magic' % self.magic
+    else:
+      self.buffer = None
+      self.magic = 0
+      self.version = 0
+      self.stamp = 0
+
+  def WriteToBuffer(self):
+    """Return a string that contains the binary representation of the file."""
+    self.pos = 0
+    # When writing, accumulate written values in a list, then flatten
+    # into a string.  This is _much_ faster than accumulating within a
+    # string.
+    self.buffer = []
+    self.WriteWord(self.magic)
+    self.WriteWord(self.version)
+    self.WriteWord(self.stamp)
+    for s in self.program_summaries:
+      s.Write(self)
+    for f in self.functions:
+      f.Write(self)
+    for m in self.module_infos:
+      m.Write(self)
+    self.WriteWord(0)  # EOF marker
+    # Flatten buffer into a string.
+    self.buffer = ''.join(self.buffer)
+    return self.buffer
+
+  def WriteWord(self, word):
+    """Write one word - 32-bit integer to buffer."""
+    self.buffer.append(struct.pack('I', word & 0xFFFFFFFF))
+
+  def WriteWords(self, words):
+    """Write a sequence of words to buffer."""
+    for w in words:
+      self.WriteWord(w)
+
+  def WriteCounter(self, c):
+    """Write one counter to buffer."""
+    self.WriteWords((int(c), int(c >> 32)))
+
+  def WriteCounters(self, counters):
+    """Write a sequence of Counters to buffer."""
+    for c in counters:
+      self.WriteCounter(c)
+
+  def WriteStr(self, s):
+    """Write a string to buffer."""
+    l = len(s)
+    self.WriteWord((l + 4) / 4)  # Write length
+    self.buffer.append(s)
+    for _ in xrange(4 * ((l + 4) / 4) - l):
+      self.buffer.append('\x00'[0])
+
+  def ReadWord(self):
+    """Read a word from buffer."""
+    self.pos += 1
+    return self.int_array[self.pos - 1]
+
+  def ReadWords(self, n_words):
+    """Read the specified number of words (n_words) from buffer."""
+    self.pos += n_words
+    return self.int_array[self.pos - n_words:self.pos]
+
+  def ReadCounter(self):
+    """Read a counter value from buffer."""
+    v = self.ReadWord()
+    return v | (self.ReadWord() << 32)
+
+  def ReadCounters(self, n_counters):
+    """Read the specified number of counter values from buffer."""
+    words = self.ReadWords(2 * n_counters)
+    return [words[2 * i] | (words[2 * i + 1] << 32) for i in xrange(n_counters)]
+
+  def ReadStr(self):
+    """Read a string from buffer."""
+    length = self.ReadWord()
+    if not length:
+      return None
+    # Read from the original string buffer to avoid having to convert
+    # from int back to string.  The position counter is a count of
+    # ints, so we need to multiply it by 4.
+    ret = self.buffer[4 * self.pos: 4 * self.pos + 4 * length]
+    self.pos += length
+    return ret.rstrip('\x00')
+
+  def ReadObjects(self):
+    """Read and process all data objects from buffer."""
+    function = None
+    while self.pos < self.n_ints:
+      obj = None
+      tag = self.ReadWord()
+      if not tag and self.program_summaries:
+        break
+
+      length = self.ReadWord()
+      obj = data_factory.Create(self, tag, length)
+      if obj:
+        if tag == data_factory.TAG_FUNCTION:
+          function = obj
+          self.functions.append(function)
+        elif tag == data_factory.TAG_PROGRAM_SUMMARY:
+          self.program_summaries.append(obj)
+        elif tag == data_factory.TAG_MODULE_INFO:
+          self.module_infos.append(obj)
+        else:
+          # By default, all objects belong to the preceding function,
+          # except for program summary or new function.
+          function.counters.append(obj)
+      else:
+        print 'WARNING: unknown tag - 0x%X' % tag
+
+  def PrintBrief(self):
+    """Print the list of functions in the file."""
+    print 'magic:   0x%X' % self.magic
+    print 'version: 0x%X' % self.version
+    print 'stamp:   0x%X' % self.stamp
+    for function in self.functions:
+      print '%d' % function.EntryCount()
+
+  def Print(self):
+    """Print the content of the file in full detail."""
+    for function in self.functions:
+      function.Print()
+    for s in self.program_summaries:
+      s.Print()
+    for m in self.module_infos:
+      m.Print()
+
+  def MergeFiles(self, files, multipliers):
+    """Merge ProfileDataFiles and return a merged file."""
+    for f in files:
+      assert self.version == f.version
+      assert len(self.functions) == len(f.functions)
+
+    for i in range(len(self.functions)):
+      self.functions[i].Merge([f.functions[i] for f in files], multipliers)
+
+    for i in range(len(self.program_summaries)):
+      self.program_summaries[i].Merge([f.program_summaries[i] for f in files],
+                                      multipliers)
+
+    if self.module_infos:
+      primary_module_id = self.module_infos[0].module_id
+      module_group_ids = set(m.module_id for m in self.module_infos)
+      for f in files:
+        assert f.module_infos
+        assert primary_module_id == f.module_infos[0].module_id
+        assert ((f.module_infos[0].flags & 0x2) ==
+                (self.module_infos[0].flags & 0x2))
+        f.module_infos[0].flags |= self.module_infos[0].flags
+        for m in f.module_infos:
+          if m.module_id not in module_group_ids:
+            module_group_ids.add(m.module_id)
+            self.module_infos.append(m)
+
+
+class OneImport(object):
+  """Representation of one import for a primary module."""
+
+  def __init__(self, src, gcda):
+    self.src = src
+    self.gcda = gcda
+    assert self.gcda.endswith('.gcda\n')
+
+  def GetLines(self):
+    """Returns the text lines for the import."""
+    lines = [self.src, self.gcda]
+    return lines
+
+
+class ImportsFile(object):
+  """Representation of one .gcda.imports file."""
+
+  def __init__(self, profile_archive, import_file):
+    self.filename = import_file
+    if profile_archive.dir:
+      f = open(os.path.join(profile_archive.dir, import_file), 'rb')
+      lines = f.readlines()
+      f.close()
+    else:
+      assert profile_archive.zip
+      buf = profile_archive.zip.read(import_file)
+      lines = []
+      if buf:
+        lines = buf.rstrip('\n').split('\n')
+      for i in xrange(len(lines)):
+        lines[i] += '\n'
+
+    self.imports = []
+    for i in xrange(0, len(lines), 2):
+      src = lines[i]
+      gcda = lines[i+1]
+      self.imports.append(OneImport(src, gcda))
+
+  def MergeFiles(self, files):
+    """Merge ImportsFiles and return a merged file."""
+    table = dict((imp.src, 1) for imp in self.imports)
+
+    for o in files:
+      for imp in o.imports:
+        if not imp.src in table:
+          self.imports.append(imp)
+          table[imp.src] = 1
+
+  def Write(self, datafile):
+    """Write out to datafile as text lines."""
+    lines = []
+    for imp in self.imports:
+      lines.extend(imp.GetLines())
+    datafile.writelines(lines)
+
+  def WriteToBuffer(self):
+    """Return a string that contains the binary representation of the file."""
+    self.pos = 0
+    self.buffer = ''
+
+    for imp in self.imports:
+      for line in imp.GetLines():
+        self.buffer += line
+
+    return self.buffer
+
+  def Print(self):
+    """Print method."""
+    print 'Imports for %s\n' % (self.filename)
+    for imp in self.imports:
+      for line in imp.GetLines():
+        print line
+
+
+class ProfileArchive(object):
+  """A container for all gcda/gcno files under a directory (recursively).
+
+  Attributes:
+    gcda: A dictionary with the gcda file path as key.
+          If the value is 0, it means the file exists in the archive
+          but not yet read.
+    gcno: A dictionary with the gcno file path as key.
+    dir: A path to the directory containing the gcda/gcno.
+         If set, the archive is a directory.
+    zip: A ZipFile instance. If set, the archive is a zip file.
+
+  ProfileArchive can be either a directory containing a directory tree
+  containing gcda/gcno files, or a single zip file that contains
+  the similar directory hierarchy.
+  """
+
+  def __init__(self, path):
+    self.gcda = {}
+    self.gcno = {}
+    self.imports = {}
+    if os.path.isdir(path):
+      self.dir = path
+      self.zip = None
+      self.ScanDir(path)
+    elif path.endswith('.zip'):
+      self.zip = zipfile.ZipFile(path)
+      self.dir = None
+      self.ScanZip()
+
+  def ReadFile(self, path):
+    """Read the content of the file and return it.
+
+    Args:
+      path: a relative path of the file inside the archive.
+
+    Returns:
+      Sequence of bytes containing the content of the file.
+
+    Raises:
+      Error: If file is not found.
+    """
+    if self.dir:
+      return ReadAllAndClose(os.path.join(self.dir, path))
+    elif self.zip:
+      return self.zip.read(path)
+    raise Error('File not found - "%s"' % path)
+
+  def ScanZip(self):
+    """Find all .gcda/.gcno/.imports files in the zip."""
+    for f in self.zip.namelist():
+      if f.endswith('.gcda'):
+        self.gcda[f] = 0
+      elif f.endswith('.gcno'):
+        self.gcno[f] = 0
+      elif f.endswith('.imports'):
+        self.imports[f] = 0
+
+  def ScanDir(self, direc):
+    """Recursively visit all subdirs and find all .gcda/.gcno/.imports files."""
+
+    def ScanFile(_, dirpath, namelist):
+      """Record gcda/gcno files."""
+      for f in namelist:
+        path = os.path.join(dirpath, f)
+        if f.endswith('.gcda'):
+          self.gcda[path] = 0
+        elif f.endswith('.gcno'):
+          self.gcno[path] = 0
+        elif f.endswith('.imports'):
+          self.imports[path] = 0
+
+    cwd = os.getcwd()
+    os.chdir(direc)
+    os.path.walk('.', ScanFile, None)
+    os.chdir(cwd)
+
+  def ReadAll(self):
+    """Read all gcda/gcno/imports files found inside the archive."""
+    for f in self.gcda.iterkeys():
+      self.gcda[f] = ProfileDataFile(self.ReadFile(f))
+    for f in self.gcno.iterkeys():
+      self.gcno[f] = ProfileDataFile(self.ReadFile(f))
+    for f in self.imports.iterkeys():
+      self.imports[f] = ImportsFile(self, f)
+
+  def Print(self):
+    """Print all files in full detail - including all counter values."""
+    for f in self.gcda.itervalues():
+      f.Print()
+    for f in self.gcno.itervalues():
+      f.Print()
+    for f in self.imports.itervalues():
+      f.Print()
+
+  def PrintBrief(self):
+    """Print only the summary information without the counter values."""
+    for f in self.gcda.itervalues():
+      f.PrintBrief()
+    for f in self.gcno.itervalues():
+      f.PrintBrief()
+    for f in self.imports.itervalues():
+      f.PrintBrief()
+
+  def Write(self, output_path):
+    """Write the archive to disk."""
+
+    if output_path.endswith('.zip'):
+      zip_out = zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED)
+      for f in self.gcda.iterkeys():
+        zip_out.writestr(f, self.gcda[f].WriteToBuffer())
+      for f in self.imports.iterkeys():
+        zip_out.writestr(f, self.imports[f].WriteToBuffer())
+      zip_out.close()
+
+    else:
+      if not os.path.exists(output_path):
+        os.makedirs(output_path)
+      for f in self.gcda.iterkeys():
+        outfile_path = output_path + '/' + f
+        if not os.path.exists(os.path.dirname(outfile_path)):
+          os.makedirs(os.path.dirname(outfile_path))
+        data_file = open(outfile_path, 'wb')
+        data_file.write(self.gcda[f].WriteToBuffer())
+        data_file.close()
+      for f in self.imports.iterkeys():
+        outfile_path = output_path + '/' + f
+        if not os.path.exists(os.path.dirname(outfile_path)):
+          os.makedirs(os.path.dirname(outfile_path))
+        data_file = open(outfile_path, 'wb')
+        self.imports[f].Write(data_file)
+        data_file.close()
+
+  def Merge(self, archives, multipliers):
+    """Merge one file at a time."""
+
+    # Read
+    for a in archives:
+      a.ReadAll()
+    if not self in archives:
+      self.ReadAll()
+
+    # First create set of all gcda files
+    all_gcda_files = set()
+    for a in [self] + archives:
+      all_gcda_files = all_gcda_files.union(a.gcda.iterkeys())
+
+    # Iterate over all gcda files and create a merged object
+    # containing all profile data which exists for this file
+    # among self and archives.
+    for gcda_file in all_gcda_files:
+      files = []
+      mults = []
+      for i, a in enumerate(archives):
+        if gcda_file in a.gcda:
+          files.append(a.gcda[gcda_file])
+          mults.append(multipliers[i])
+      if gcda_file not in self.gcda:
+        self.gcda[gcda_file] = files[0]
+      self.gcda[gcda_file].MergeFiles(files, mults)
+
+    # Same process for imports files
+    all_imports_files = set()
+    for a in [self] + archives:
+      all_imports_files = all_imports_files.union(a.imports.iterkeys())
+
+    for imports_file in all_imports_files:
+      files = []
+      for i, a in enumerate(archives):
+        if imports_file in a.imports:
+          files.append(a.imports[imports_file])
+      if imports_file not in self.imports:
+        self.imports[imports_file] = files[0]
+      self.imports[imports_file].MergeFiles(files)
+
+  def ComputeHistogram(self):
+    """Compute and return the histogram."""
+
+    histogram = [[None]] * DataObjectFactory.N_SUMMABLE
+    for n in xrange(DataObjectFactory.N_SUMMABLE):
+      histogram[n] = Summary.Histogram()
+
+    for o in self.gcda:
+      for f in self.gcda[o].functions:
+        for n in xrange(len(f.counters)):
+          if n < DataObjectFactory.N_SUMMABLE:
+            for c in xrange(len(f.counters[n].counters)):
+              histogram[n].Insert(f.counters[n].counters[c])
+    for n in xrange(DataObjectFactory.N_SUMMABLE):
+      histogram[n].ComputeCntandBitvector()
+    return histogram
+
+
+def main():
+  """Merge multiple profile data."""
+
+  global new_histogram
+
+  usage = 'usage: %prog [options] arg1 arg2 ...'
+  parser = OptionParser(usage)
+  parser.add_option('-w', '--multipliers',
+                    dest='multipliers',
+                    help='Comma separated list of multipliers to be applied '
+                    'for each corresponding profile.')
+  parser.add_option('-o', '--output',
+                    dest='output_profile',
+                    help='Output directory or zip file to dump the '
+                    'merged profile. Default output is profile-merged.zip.')
+  group = OptionGroup(parser, 'Arguments',
+                      'Comma separated list of input directories or zip files '
+                      'that contain profile data to merge.')
+  parser.add_option_group(group)
+
+  (options, args) = parser.parse_args()
+
+  if len(args) < 2:
+    parser.error('Please provide at least 2 input profiles.')
+
+  input_profiles = [ProfileArchive(path) for path in args]
+
+  if options.multipliers:
+    profile_multipliers = [long(i) for i in options.multipliers.split(',')]
+    if len(profile_multipliers) != len(input_profiles):
+      parser.error('--multipliers has different number of elements from '
+                   '--inputs.')
+  else:
+    profile_multipliers = [1 for i in range(len(input_profiles))]
+
+  if options.output_profile:
+    output_profile = options.output_profile
+  else:
+    output_profile = 'profile-merged.zip'
+
+  input_profiles[0].Merge(input_profiles, profile_multipliers)
+
+  new_histogram = input_profiles[0].ComputeHistogram()
+
+  input_profiles[0].Write(output_profile)
+
+if __name__ == '__main__':
+  main()