This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: [google gcc-4_7] offline profile merge (patchset 2) (issue8508048)


Ok for google branches.

thanks,

David

On Mon, Apr 8, 2013 at 3:44 PM, Rong Xu <xur@google.com> wrote:
> Revised copyright info.
>
> -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,1320 @@
> +#!/usr/bin/python2.7
> +#
> +#    Copyright (C) 2013
> +#    Free Software Foundation, Inc.
> +#
> +# This file is part of GCC.
> +#
> +# GCC is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3, or (at your option)
> +# any later version.
> +#
> +# GCC is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with GCC; see the file COPYING3.  If not see
> +# <http://www.gnu.org/licenses/>.
> +#
> +
> +
> +"""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


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]