(root)/
binutils-2.41/
gprofng/
src/
PathTree.cc
/* Copyright (C) 2021-2023 Free Software Foundation, Inc.
   Contributed by Oracle.

   This file is part of GNU Binutils.

   This program 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.

   This program 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 this program; if not, write to the Free Software
   Foundation, 51 Franklin Street - Fifth Floor, Boston,
   MA 02110-1301, USA.  */

#include "config.h"
#include <stdio.h>
#include <stdlib.h>

#include "util.h"
#include "DefaultMap.h"
#include "CacheMap.h"

#include "DbeSession.h"
#include "Application.h"
#include "CallStack.h"
#include "Emsg.h"
#include "Experiment.h"
#include "Expression.h"
#include "Function.h"
#include "Histable.h"
#include "IndexObject.h"
#include "MetricList.h"
#include "Module.h"
#include "DbeView.h"
#include "Metric.h"
#include "PathTree.h"
#include "LoadObject.h"
#include "Sample.h"
#include "StringBuilder.h"
#include "Table.h"

// Define counts, rate for error warnings for statistical profiles
#define MIN_PROF_CNT    100
#define MAX_PROF_RATE   1000.

#define NUM_DESCENDANTS(nd) ((nd)->descendants ? (nd)->descendants->size() : 0)
#define IS_LEAF(nd)         ((nd)->descendants == NULL)

#ifdef DEBUG
#define DBG(__func) __func
#else
#define DBG(__func)
#endif

void
PathTree::construct (DbeView *_dbev, int _indxtype, PathTreeType _pathTreeType)
{
  dbev = _dbev;
  indxtype = _indxtype;
  pathTreeType = _pathTreeType;
  status = 0;
  nchunks = 0;
  chunks = NULL;
  nodes = 1; // don't use node 0
  nslots = 0;
  slots = NULL;
  root_idx = 0;
  root = NULL;
  depth = 1;
  dnodes = 0;
  phaseIdx = -1;
  nexps = 0;
  total_obj = NULL;
  indx_expr = NULL;
  statsq = NULL;
  warningq = NULL;
  cancel_ok = 1;
  ptree_internal = NULL;
  ftree_internal = NULL;
  ftree_needs_update = false;
  depth_map = NULL;
  init ();
}

PathTree::~PathTree ()
{
  fini ();
  for (long i = 0; i < nchunks; i++)
    delete[] chunks[i];
  delete[] chunks;
}

void
PathTree::init ()
{
  fn_map = new DefaultMap<Function*, NodeIdx>;
  stack_prop = PROP_NONE;
  desc_htable_size = 511;
  desc_htable_nelem = 0;
  descHT = new hash_node_t*[desc_htable_size];
  for (int i = 0; i < desc_htable_size; i++)
    descHT[i] = NULL;
  pathMap = new CacheMap<uint64_t, NodeIdx>;
  statsq = new Emsgqueue (NTXT ("statsq"));
  warningq = new Emsgqueue (NTXT ("warningq"));
  if (indxtype < 0)
    {
      Function *ftotal = dbeSession->get_Total_Function ();
      if (pathTreeType == PATHTREE_INTERNAL_FUNCTREE)
	total_obj = ftotal;
      else
	total_obj = ftotal->find_dbeinstr (0, 0);
      VMode view_mode = dbev->get_view_mode ();
      if (view_mode == VMODE_MACHINE)
	stack_prop = PROP_MSTACK;
      else if (view_mode == VMODE_EXPERT)
	stack_prop = PROP_XSTACK;
      else if (view_mode == VMODE_USER)
	{
	  stack_prop = PROP_USTACK;
	  if (dbeSession->is_omp_available ()
	      && pathTreeType == PATHTREE_INTERNAL_OMP)
	    stack_prop = PROP_XSTACK;
	}
    }
  else
    {
      total_obj = new IndexObject (indxtype, (uint64_t) - 2);
      total_obj->set_name (dbe_strdup (NTXT ("<Total>")));
      char *idxname = dbeSession->getIndexSpaceName (indxtype);
      if (streq (idxname, NTXT ("OMP_preg")))
	stack_prop = PROP_CPRID;
      else if (streq (idxname, NTXT ("OMP_task")))
	stack_prop = PROP_TSKID;
      else
	indx_expr = dbeSession->getIndexSpaceExpr (indxtype);
    }
  root_idx = new_Node (0, total_obj, false);
  root = NODE_IDX (root_idx);
}

void
PathTree::fini ()
{
  // For each node free its descendants vector
  // and reset the node list of its function
  for (long i = 1; i < nodes; i++)
    {
      Node *node = NODE_IDX (i);
      if (node->descendants)
	delete node->descendants;
    }
  nodes = 1; // don't use node 0

  for (int i = 0; i < nslots; i++)
    {
      int **tmp = slots[i].mvals;
      for (long j = 0; j < nchunks; j++)
	delete[] tmp[j];
      delete[] tmp;
    }
  delete[] slots;
  slots = NULL;
  nslots = 0;
  delete fn_map;
  fn_map = NULL;
  delete pathMap;
  pathMap = NULL;
  destroy (depth_map);
  depth_map = NULL;
  if (indxtype >= 0)
    delete total_obj;

  for (int i = 0; i < desc_htable_size; i++)
    {
      hash_node_t *p = descHT[i];
      while (p)
	{
	  hash_node_t *p1 = p;
	  p = p->next;
	  delete p1;
	}
    }
  delete[] descHT;
  delete statsq;
  delete warningq;
  depth = 1;
  dnodes = 0;
  phaseIdx = -1;
  nexps = 0;
  status = 0;
}

PtreePhaseStatus
PathTree::reset ()
{
  if (pathTreeType == PATHTREE_INTERNAL_FUNCTREE)
    return NORMAL; // never process reset for ftree_internal.

  if (dbeSession->is_omp_available () && dbev->get_view_mode () == VMODE_USER
      && pathTreeType == PATHTREE_MAIN && ptree_internal == NULL)
    ptree_internal = new PathTree (dbev, indxtype, PATHTREE_INTERNAL_OMP);

  if (phaseIdx != dbev->getPhaseIdx ())
    {
      fini ();
      init ();
      phaseIdx = dbev->getPhaseIdx ();
      ftree_needs_update = true;
    }
  for (; nexps < dbeSession->nexps (); nexps++)
    {
      ftree_needs_update = true;
      if (add_experiment (nexps) == CANCELED)
	return CANCELED;
    }

  // LIBRARY_VISIBILITY
  if (dbev->isNewViewMode ())
    dbev->resetNewViewMode ();
  if (dbev->isShowHideChanged ())
    dbev->resetShowHideChanged ();
  return NORMAL;
}

int
PathTree::allocate_slot (int id, ValueTag vtype)
{

  int i;
  int slot_idx = find_slot (id);
  if (slot_idx >= 0)
    {
      DBG (assert (slots[slot_idx].vtype == vtype));
      return slot_idx;
    }
  slot_idx = nslots++;

  Slot *old_slots = slots;
  slots = new Slot[nslots];
  for (i = 0; i < slot_idx; i++)
    slots[i] = old_slots[i];
  delete[] old_slots;

  slots[slot_idx].id = id;
  slots[slot_idx].vtype = vtype;
  int **ip = new int*[nchunks];
  for (i = 0; i < nchunks; i++)
    ip[i] = NULL;
  slots[slot_idx].mvals = ip;

  return slot_idx;
}

void
PathTree::allocate_slots (Slot *new_slots, int new_nslots)
{
  // duplicates new_slots

  // if previously had more slots than currently requested, delete the data from those slots.
  for (int i = new_nslots; i < nslots; i++)
    {
      int **tmp = slots[i].mvals;
      for (long j = 0; j < nchunks; j++)
	delete tmp[j];
      delete tmp;
    }
  if (new_nslots == 0)
    {
      nslots = new_nslots;
      delete[] slots;
      slots = NULL;
      return;
    }

  Slot *old_slots = slots;
  slots = new Slot[new_nslots];
  for (int i = 0; i < new_nslots; i++)
    {
      slots[i] = new_slots[i]; // pick up id and vtype
      if (i < nslots)
	slots[i].mvals = old_slots[i].mvals;
      else
	{
	  if (nchunks == 0)
	    slots[i].mvals = NULL;
	  else
	    {
	      int **ip = new int*[nchunks];
	      for (long j = 0; j < nchunks; j++)
		ip[j] = NULL;
	      slots[i].mvals = ip;
	    }
	}
    }
  nslots = new_nslots;
  delete old_slots;
}

int
PathTree::find_slot (int id)
{
  for (int i = 0; i < nslots; i++)
    if (slots[i].id == id)
      return i;
  return -1;
}

PathTree::NodeIdx
PathTree::new_Node (NodeIdx anc, Histable *instr, bool leaf)
{
  if (nodes >= nchunks * CHUNKSZ)
    {
      long idx = nchunks++;

      // Reallocate Node chunk array
      Node **old_chunks = chunks;
      chunks = new Node*[nchunks];
      for (long k = 0; k < idx; k++)
	chunks[k] = old_chunks[k];
      delete[] old_chunks;

      // Reallocate metric value chunk arrays.
      for (int i = 0; i < nslots; i++)
	{
	  int **mvals = new int*[nchunks];
	  for (long k = 0; k < idx; k++)
	    {
	      mvals[k] = slots[i].mvals[k];
	    }
	  delete[] slots[i].mvals;
	  slots[i].mvals = mvals;
	  slots[i].mvals[idx] = NULL;
	}

      // Allocate new chunk for nodes.
      // Note that we don't need to allocate new chunks
      // for metric values at this point as we rely on
      // lazy allocation.
      //
      allocate_chunk (chunks, idx);
    }
  NodeIdx node_idx = nodes++;
  Node *node = NODE_IDX (node_idx);
  node->ancestor = anc;
  node->descendants = leaf ? (Vector<NodeIdx>*)NULL : new Vector<NodeIdx>(2);
  node->instr = instr;
  Function *func = (Function*) (instr->convertto (Histable::FUNCTION));
  node->funclist = fn_map->get (func);
  fn_map->put (func, node_idx);
  return node_idx;
}

PathTree::NodeIdx
PathTree::find_path (Experiment *exp, DataView *dview, long recIdx)
{
  if (indx_expr != NULL)
    {
      Expression::Context ctx (dbev, exp, dview, recIdx);
      uint64_t idx = indx_expr->eval (&ctx);
      Histable *cur_obj = dbeSession->createIndexObject (indxtype, idx);
      cur_obj->set_name_from_context (&ctx);
      NodeIdx dsc_idx = find_in_desc_htable (root_idx, cur_obj, true);
      depth = 2;
      return dsc_idx;
    }

  bool showAll = dbev->isShowAll ();
  int t_stack_prop = stack_prop;
  void *stackId = dview->getObjValue (t_stack_prop, recIdx);
  NodeIdx node_idx;
  if (stackId != NULL)
    {
      // pathMap does not work with NULL key
      node_idx = pathMap->get ((uint64_t) stackId);
      if (node_idx != 0)
	return node_idx;
    }
  Vector<Histable*> *stack = (Vector<Histable*>*)CallStack::getStackPCs (stackId, !showAll);
  int stack_size = stack->size ();
  if (stack_size == 0)
    return root_idx;

  node_idx = root_idx;
  int thisdepth = 1;

  for (int i = stack_size - 1; i >= 0; i--)
    {
      bool leaf = (i == 0);
      Histable *cur_addr = stack->fetch (i);

      // bail out of loop if load object API-only is set
      // and this is not the top frame
      // This is now done in HSTACK if hide is set

      Function *func = (Function*) cur_addr->convertto (Histable::FUNCTION);
      if (func != NULL)
	{
	  Module *mod = func->module;
	  LoadObject *lo = mod->loadobject;
	  int segx = lo->seg_idx;
	  if (showAll && dbev->get_lo_expand (segx) == LIBEX_API
	      && i != stack_size - 1)
	    leaf = true;
	}

      NodeIdx dsc_idx = find_desc_node (node_idx, cur_addr, leaf);
      thisdepth++;
      node_idx = dsc_idx;

      // LIBEX_API processing might have set leaf to true
      if (leaf)
	break;
    }
  if (thisdepth > depth)
    depth = thisdepth;
  delete stack;
  pathMap->put ((uint64_t) stackId, node_idx);
  return node_idx;
}

static int
desc_node_comp (const void *s1, const void *s2, const void *ptree)
{
  PathTree::NodeIdx t1, t2;
  t1 = *(PathTree::NodeIdx *)s1;
  t2 = *(PathTree::NodeIdx *)s2;
  PathTree* Ptree = (PathTree *) ptree;
  PathTree::Node *n1 = Ptree->NODE_IDX (t1);
  PathTree::Node *n2 = Ptree->NODE_IDX (t2);
  Histable *d1 = n1->instr;
  Histable *d2 = n2->instr;
  if (d1->id < d2->id)
    return -1;
  else if (d1->id > d2->id)
    return +1;
  else
    return 0;
}

PathTree::NodeIdx
PathTree::find_in_desc_htable (NodeIdx node_idx, Histable *instr, bool leaf)
{
  unsigned int hash_code = (unsigned int) instr->id % desc_htable_size;
  Node *node = NODE_IDX (node_idx);
  hash_node_t *p = NULL;
  for (p = descHT[hash_code]; p; p = p->next)
    {
      Node *dsc = NODE_IDX (p->nd);
      Histable *dinstr = dsc->instr;
      if (dinstr->id == instr->id && leaf == IS_LEAF (dsc))
	return p->nd;
    }
  // Not found
  NodeIdx dsc_idx = new_Node (node_idx, instr, leaf);
  node->descendants->append (dsc_idx);
  p = new hash_node_t ();
  p->nd = dsc_idx;
  p->next = descHT[hash_code];
  descHT[hash_code] = p;
  desc_htable_nelem++;

  // time to resize
  if (desc_htable_nelem == desc_htable_size)
    {
      int old_htable_size = desc_htable_size;
      desc_htable_size = old_htable_size * 2 + 1;
      hash_node_t **old_htable = descHT;
      descHT = new hash_node_t*[desc_htable_size];
      for (int i = 0; i < desc_htable_size; i++)
	descHT[i] = NULL;

      for (int i = 0; i < old_htable_size; i++)
	if (old_htable[i] != NULL)
	  {
	    hash_node *old_p;
	    hash_node_t *hash_p = old_htable[i];
	    while (hash_p != NULL)
	      {
		hash_node_t *new_p = new hash_node_t ();
		new_p->nd = hash_p->nd;
		Node *dnode = NODE_IDX (hash_p->nd);
		Histable *dnode_instr = dnode->instr;
		hash_code = (unsigned int) dnode_instr->id % desc_htable_size;
		new_p->next = descHT[hash_code];
		descHT[hash_code] = new_p;
		old_p = hash_p;
		hash_p = hash_p->next;
		delete old_p;
	      }
	  }
      delete[] old_htable;
    }
  return dsc_idx;
}

PathTree::NodeIdx
PathTree::find_desc_node (NodeIdx node_idx, Histable *instr, bool leaf)
{
  // Binary search. All nodes are ordered by Histable::id.

  // We have a special case when two nodes with the same
  //	id value may co-exist: one representing a leaf node and
  //	another one representing a call site.
  Node *node = NODE_IDX (node_idx);
  int left = 0;
  int right = NUM_DESCENDANTS (node) - 1;
  while (left <= right)
    {
      int index = (left + right) / 2;
      NodeIdx dsc_idx = node->descendants->fetch (index);
      Node *dsc = NODE_IDX (dsc_idx);
      Histable *dinstr = dsc->instr;
      if (instr->id < dinstr->id)
	right = index - 1;
      else if (instr->id > dinstr->id)
	left = index + 1;
      else if (leaf == IS_LEAF (dsc))
	return dsc_idx;
      else if (leaf)
	right = index - 1;
      else
	left = index + 1;
    }

  // None was found. Create one.
  NodeIdx dsc_idx = new_Node (node_idx, instr, leaf);
  node->descendants->insert (left, dsc_idx);
  return dsc_idx;
}

PtreePhaseStatus
PathTree::process_packets (Experiment *exp, DataView *packets, int data_type)
{
  Expression::Context ctx (dbev, exp);
  char *progress_bar_msg = NULL;
  int progress_bar_percent = -1;

  Vector<BaseMetric*> *mlist = dbev->get_all_reg_metrics ();
  Vector<BaseMetric*> mlist2;
  StringBuilder stb;
  for (int midx = 0, mlist_sz = mlist->size (); midx < mlist_sz; ++midx)
    {
      BaseMetric *mtr = mlist->fetch (midx);
      if (mtr->get_packet_type () == data_type &&
	  (mtr->get_expr () == NULL || mtr->get_expr ()->passes (&ctx)))
	{
	  Hwcentry *hwc = mtr->get_hw_ctr ();
	  if (hwc)
	    {
	      stb.setLength (0);
	      // XXX this should be done at metric registration
	      Collection_params *col_params = exp->get_params ();
	      for (int i = 0; i < MAX_HWCOUNT; i++)
		{
		  // We may have duplicate counters in col_params,
		  // check for all (see 5081284).
		  if (dbe_strcmp (hwc->name, col_params->hw_aux_name[i]) == 0)
		    {
		      if (stb.length () != 0)
			stb.append (NTXT ("||"));
		      stb.append (NTXT ("HWCTAG=="));
		      stb.append (i);
		    }
		}
	      if (stb.length () == 0)
		continue;
	      stb.append (NTXT ("&& ((HWCINT & "));
	      stb.append ((long long) HWCVAL_ERR_FLAG);
	      stb.append (NTXT (")==0)"));
	      char *s = stb.toString ();
	      mtr->set_cond_spec (s);
	      free (s);
	    }
	  ValueTag vtype = mtr->get_vtype ();
	  switch (vtype)
	    {
	    case VT_INT:
	    case VT_ULLONG:
	    case VT_LLONG:
	      break; // nothing to do
	    default:
	      vtype = VT_ULLONG; // ym: not sure when this would happen
	      break;
	    }
	  allocate_slot (mtr->get_id (), vtype);
	  mlist2.append (mtr);
	}
    }

  Slot **mslots = new Slot*[mlist2.size ()];
  for (int midx = 0, mlist_sz = mlist2.size (); midx < mlist_sz; ++midx)
    {
      BaseMetric *mtr = mlist2.fetch (midx);
      int id = mtr->get_id ();
      int slot_ind = find_slot (id);
      mslots[midx] = SLOT_IDX (slot_ind);
    }

  for (long i = 0, packets_sz = packets->getSize (); i < packets_sz; ++i)
    {
      if (dbeSession->is_interactive ())
	{
	  if (NULL == progress_bar_msg)
	    progress_bar_msg = dbe_sprintf (GTXT ("Processing Experiment: %s"),
					  get_basename (exp->get_expt_name ()));
	  int val = (int) (100 * i / packets_sz);
	  if (val > progress_bar_percent)
	    {
	      progress_bar_percent += 10;
	      if (theApplication->set_progress (val, progress_bar_msg)
		  && cancel_ok)
		{
		  delete[] mslots;
		  return CANCELED;
		}
	    }
	}

      NodeIdx path_idx = 0;
      ctx.put (packets, i);

      for (int midx = 0, mlist_sz = mlist2.size (); midx < mlist_sz; ++midx)
	{
	  BaseMetric *mtr = mlist2.fetch (midx);
	  if (mtr->get_cond () != NULL && !mtr->get_cond ()->passes (&ctx))
	    continue;

	  int64_t mval = mtr->get_val ()->eval (&ctx);
	  if (mval == 0)
	    continue;
	  if (path_idx == 0)
	    path_idx = find_path (exp, packets, i);
	  NodeIdx node_idx = path_idx;
	  Slot *mslot = mslots[midx];
	  while (node_idx)
	    {
	      INCREMENT_METRIC (mslot, node_idx, mval);
	      node_idx = NODE_IDX (node_idx)->ancestor;
	    }
	}
    }
  if (dbeSession->is_interactive ())
    free (progress_bar_msg);
  delete[] mslots;
  if (indx_expr != NULL)
    root->descendants->sort ((CompareFunc) desc_node_comp, this);
  return NORMAL;
}

DataView *
PathTree::get_filtered_events (int exp_index, int data_type)
{
  if (indx_expr != NULL)
    {
      IndexObjType_t *indexObj = dbeSession->getIndexSpace (indxtype);
      if (indexObj->memObj && data_type != DATA_HWC)
	return NULL;
    }
  return dbev->get_filtered_events (exp_index, data_type);
}

PtreePhaseStatus
PathTree::add_experiment (int exp_index)
{
  StringBuilder sb;
  char *expt_name;
  char *base_name;
  Emsg *m;
  Experiment *experiment = dbeSession->get_exp (exp_index);
  if (experiment->broken != 0)
    return NORMAL;
  status = 0;
  expt_name = experiment->get_expt_name ();
  base_name = get_basename (expt_name);

  hrtime_t starttime = gethrtime ();
  hrtime_t startvtime = gethrvtime ();

  // Experiment::getEndTime was initially implemented as
  // returning exp->last_event. To preserve the semantics
  // new Experiment::getLastEvent() is used here.
  hrtime_t tot_time = experiment->getLastEvent () - experiment->getStartTime ();

  if (!dbev->isShowAll () && (dbev->isShowHideChanged ()
			      || dbev->isNewViewMode ()))
    experiment->resetShowHideStack ();

  // To report experiment index to the user,
  // start numeration from 1, not 0
  sb.sprintf (GTXT ("PathTree processing experiment %d (`%s'); duration %lld.%06lld"),
	      exp_index + 1, base_name,
	      tot_time / NANOSEC, (tot_time % NANOSEC / 1000));
  m = new Emsg (CMSG_COMMENT, sb);
  statsq->append (m);

  DataView *prof_packet = get_filtered_events (exp_index, DATA_CLOCK);
  if (prof_packet && prof_packet->getSize () > 0)
    {
      if (process_packets (experiment, prof_packet, DATA_CLOCK) == CANCELED)
	return CANCELED;
      long clock_cnt = prof_packet->getSize ();
      double clock_rate;
      if (tot_time != 0)
	clock_rate = (double) clock_cnt / (double) tot_time * (double) NANOSEC;
      else
	clock_rate = (double) 0.;
      if (experiment->timelineavail)
	sb.sprintf (GTXT ("  Processed %ld clock-profile events (%3.2f/sec.)"),
		    clock_cnt, clock_rate);
      else
	sb.sprintf (GTXT ("  Processed %ld clock-profile events"), clock_cnt);
      m = new Emsg (CMSG_COMMENT, sb);
      statsq->append (m);

      // check for statistical validity
      if ((experiment->timelineavail == true)
	   && !dbev->get_filter_active () && (clock_cnt < MIN_PROF_CNT))
	{
	  sb.sprintf (GTXT ("WARNING: too few clock-profile events (%ld) in experiment %d (`%s') for statistical validity"),
		      clock_cnt, exp_index + 1, base_name);
	  m = new Emsg (CMSG_COMMENT, sb);
	  statsq->append (m);
	}
    }

  DataView *sync_packet = get_filtered_events (exp_index, DATA_SYNCH);
  if (sync_packet && sync_packet->getSize () > 0)
    {
      if (process_packets (experiment, sync_packet, DATA_SYNCH) == CANCELED)
	return CANCELED;
      long sync_cnt = sync_packet->getSize ();
      sb.sprintf (GTXT ("  Processed %ld synctrace events"), sync_cnt);
      m = new Emsg (CMSG_COMMENT, sb);
      statsq->append (m);
    }

  DataView *iotrace_packet = get_filtered_events (exp_index, DATA_IOTRACE);
  if (iotrace_packet && iotrace_packet->getSize () > 0)
    {
      if (process_packets (experiment, iotrace_packet, DATA_IOTRACE) == CANCELED)
	return CANCELED;
      long iotrace_cnt = iotrace_packet->getSize ();
      sb.sprintf (GTXT ("  Processed %ld IO trace events"), iotrace_cnt);
      m = new Emsg (CMSG_COMMENT, sb);
      statsq->append (m);
    }

  DataView *hwc_packet = get_filtered_events (exp_index, DATA_HWC);
  if (hwc_packet && hwc_packet->getSize () > 0)
    {
      if (process_packets (experiment, hwc_packet, DATA_HWC) == CANCELED)
	return CANCELED;
      long hwc_cnt = hwc_packet->getSize ();
      double hwc_rate = (double) hwc_cnt / (double) tot_time * (double) NANOSEC;
      if (experiment->timelineavail)
	sb.sprintf (GTXT ("  Processed %ld hwc-profile events (%3.2f/sec.)"),
		    hwc_cnt, hwc_rate);
      else
	sb.sprintf (GTXT ("  Processed %ld hwc-profile events"), hwc_cnt);
      m = new Emsg (CMSG_COMMENT, sb);
      statsq->append (m);

      // check for statistical validity
      if (experiment->timelineavail && !dbev->get_filter_active () && (hwc_cnt < MIN_PROF_CNT))
	{
	  sb.sprintf (GTXT ("WARNING: too few HW counter profile events (%ld) in experiment %d (`%s') for statistical validity"),
		      hwc_cnt, exp_index + 1, base_name);
	  m = new Emsg (CMSG_COMMENT, sb);
	  statsq->append (m);
	}
    }

  DataView *heap_packet = get_filtered_events (exp_index, DATA_HEAP);
  if (heap_packet && heap_packet->getSize () > 0)
    {
      if (process_packets (experiment, heap_packet, DATA_HEAP) == CANCELED)
	return CANCELED;
      long heap_cnt = heap_packet->getSize ();
      sb.sprintf (GTXT ("  Processed %ld heaptrace events"), heap_cnt);
      m = new Emsg (CMSG_COMMENT, sb);
      statsq->append (m);
    }

  DataView *race_packet = get_filtered_events (exp_index, DATA_RACE);
  if (race_packet && race_packet->getSize () > 0)
    {
      if (process_packets (experiment, race_packet, DATA_RACE) == CANCELED)
	return CANCELED;
      long race_cnt = race_packet->getSize ();
      sb.sprintf (GTXT ("  Processed %ld race access events"), race_cnt);
      m = new Emsg (CMSG_COMMENT, sb);
      statsq->append (m);
    }

  DataView *deadlock_packet = get_filtered_events (exp_index, DATA_DLCK);
  if (deadlock_packet && deadlock_packet->getSize () > 0)
    {
      if (process_packets (experiment, deadlock_packet, DATA_DLCK) == CANCELED)
	return CANCELED;
      long race_cnt = deadlock_packet->getSize ();
      sb.sprintf (GTXT ("  Processed %ld race access events"), race_cnt);
      m = new Emsg (CMSG_COMMENT, sb);
      statsq->append (m);
    }

  hrtime_t pathtime = gethrtime () - starttime;
  hrtime_t pathvtime = gethrvtime () - startvtime;
  sb.sprintf (GTXT ("PathTree time = %lld.%06lld CPU-time %lld.%06lld\n"),
	      pathtime / NANOSEC, (pathtime % NANOSEC) / 1000,
	      pathvtime / NANOSEC, (pathvtime % NANOSEC) / 1000);
  m = new Emsg (CMSG_COMMENT, sb);
  statsq->append (m);
  return NORMAL;
}

Hist_data *
PathTree::compute_metrics (MetricList *mlist, Histable::Type type,
			   Hist_data::Mode mode, Vector<Histable*> *objs,
			   Histable *context, Vector<Histable*> *sel_objs,
			   PtreeComputeOption computeOpt)
{
  VMode view_mode = dbev->get_view_mode ();

  // For displaying disassembly correctly in user mode with openmp
  if (ptree_internal != NULL &&
      (view_mode == VMODE_EXPERT ||
       (view_mode == VMODE_USER && (type == Histable::INSTR
				    || (dbev->isOmpDisMode ()
					&& type == Histable::FUNCTION
					&& mode == Hist_data::CALLEES
					&& computeOpt == COMPUTEOPT_OMP_CALLEE))
				    )))
    return ptree_internal->compute_metrics (mlist, type, mode, objs, context,
					    sel_objs);

  PtreePhaseStatus resetStatus = reset ();

  hist_data = new Hist_data (mlist, type, mode);
  int nmetrics = mlist->get_items ()->size ();
  int sort_ind = -1;
  Hist_data::HistItem *hi;
  int index;

  if (status != 0 || resetStatus == CANCELED)
    return hist_data;

  hist_data->set_status (Hist_data::SUCCESS);
  if (dbeSession->is_interactive () && mode != Hist_data::CALLEES)
    theApplication->set_progress (0, GTXT ("Constructing Metrics"));

  xlate = new int[nmetrics];
  for (int mind = 0; mind < nmetrics; mind++)
    {
      Metric *mtr = mlist->get (mind);
      xlate[mind] = find_slot (mtr->get_id ());
    }

  // Compute dynamic metrics
  obj_list = new Histable*[depth];
  if ((type == Histable::LINE || type == Histable::INSTR)
      && mode == Hist_data::CALLERS)
    node_list = new Node*[depth];
  percent = 0;
  ndone = 0;
  if (mode == Hist_data::MODL)
    {
      Histable *obj = objs && objs->size () > 0 ? objs->fetch (0) : NULL;
      if (obj != NULL)
	{
	  switch (obj->get_type ())
	    {
	    case Histable::FUNCTION:
	      {
		Vector<Function*> *funclist = new Vector<Function*>;
		funclist->append ((Function*) obj);
		get_metrics (funclist, context);
		delete funclist;
		break;
	      }
	    case Histable::MODULE:
	      {
		Vector<Histable*> *comparableModules = obj->get_comparable_objs ();
		if (comparableModules != NULL)
		  {
		    Vector<Function*> *functions = new Vector<Function*>;
		    for (int i = 0; i < comparableModules->size (); i++)
		      {
			Module *mod = (Module*) comparableModules->fetch (i);
			if (mod)
			  {
			    bool found = false;
			    for (int i1 = 0; i1 < i; i1++)
			      {
				if (mod == comparableModules->fetch (i1))
				  {
				    found = true;
				    break;
				  }
			      }
			    if (!found)
			      functions->addAll (mod->functions);
			  }
		      }
		    get_metrics (functions, context);
		    delete functions;
		  }
		else
		  get_metrics (((Module*) obj)->functions, context);
		break;
	      }
	    case Histable::SOURCEFILE:
	      get_metrics (((SourceFile *) obj)->get_functions (), context);
	      break;
	    default:
	      DBG (assert (0));
	    }
	}
    }
  else if (mode == Hist_data::CALLERS)
    {
      if (objs && objs->size () > 0)
	get_clr_metrics (objs);
    }
  else if (mode == Hist_data::CALLEES)
    {
      if (objs && objs->size () > 0)
	get_cle_metrics (objs);
      else   // Special case: get root
	get_cle_metrics (NULL);
    }
  else if (mode == Hist_data::SELF)
    {
      if (objs->size () == 1)
	{
	  Histable *obj = objs->fetch (0);
	  if (obj != NULL)
	    {
	      if (obj->get_type () == Histable::LINE)
		{
		  Vector<Function*> *funclist = new Vector<Function*>;
		  for (DbeLine *dl = (DbeLine*) obj->convertto (Histable::LINE);
			  dl; dl = dl->dbeline_func_next)
		    if (dl->func)
		      funclist->append (dl->func);

		  get_self_metrics (obj, funclist, sel_objs);
		  delete funclist;
		}
	      else if (obj->get_type () == Histable::FUNCTION
		       || obj->get_type () == Histable::INSTR)
		{
		  // Use shortcut for functions and oth.
		  if (context)
		    {
		      Vector<Function*> *funclist = NULL;
		      if (context->get_type () == Histable::MODULE)
			funclist = ((Module*) context)->functions->copy ();
		      else
			{
			  funclist = new Vector<Function*>;
			  funclist->append ((Function*) context);
			}
		      get_self_metrics (obj, funclist, sel_objs);
		      delete funclist;
		    }
		  else
		    get_self_metrics (objs);
		}
	      else
		get_self_metrics (objs);
	    }
	}
      else
	get_self_metrics (objs);
    }
  else   // Hist_data::ALL
    get_metrics (root_idx, 0);

  delete[] obj_list;
  if ((type == Histable::LINE || type == Histable::INSTR)
      && mode == Hist_data::CALLERS)
    delete[] node_list;

  // Postprocess; find total
  for (long mind = 0, sz = mlist->get_items ()->size (); mind < sz; mind++)
    {
      Metric *mtr = mlist->get_items ()->get (mind);
      Metric::SubType subtype = mtr->get_subtype ();
      ValueTag vtype = mtr->get_vtype ();
      hist_data->total->value[mind].tag = vtype;

      switch (vtype)
	{
	  // ignoring the following cases (why?)
	case VT_SHORT:
	case VT_FLOAT:
	case VT_HRTIME:
	case VT_LABEL:
	case VT_ADDRESS:
	case VT_OFFSET:
	  break;

	case VT_INT:
	  // Calculate total as the sum of all values in hist_data for
	  // ATTRIBUTED metrics only. For all others, use root node values.
	  //
	  if ((mode == Hist_data::CALLERS || mode == Hist_data::CALLEES)
	      && subtype == Metric::ATTRIBUTED)
	    {
	      hist_data->total->value[mind].i = 0;
	      Vec_loop (Hist_data::HistItem*, hist_data->hist_items, index, hi)
	      {
		hist_data->total->value[mind].i += hi->value[mind].i;
	      }
	      if (mode == Hist_data::CALLEES)
		hist_data->total->value[mind].i += hist_data->gprof_item->value[mind].i;
	    }
	  else if (xlate[mind] != -1)
	    ASN_METRIC_VAL (hist_data->total->value[mind], slots[xlate[mind]],
			    root_idx);
	  break;

	case VT_LLONG:
	  Vec_loop (Hist_data::HistItem*, hist_data->hist_items, index, hi)
	  {
	    hi->value[mind].tag = vtype;
	  }

	  if ((mode == Hist_data::CALLERS || mode == Hist_data::CALLEES)
	      && subtype == Metric::ATTRIBUTED)
	    {
	      hist_data->total->value[mind].ll = 0;
	      Vec_loop (Hist_data::HistItem*, hist_data->hist_items, index, hi)
	      {
		hist_data->total->value[mind].ll += hi->value[mind].ll;
	      }
	      if (mode == Hist_data::CALLEES)
		hist_data->total->value[mind].ll += hist_data->gprof_item->value[mind].ll;
	    }
	  else if (xlate[mind] != -1)
	    ASN_METRIC_VAL (hist_data->total->value[mind], slots[xlate[mind]], root_idx);
	  break;

	case VT_ULLONG:
	  Vec_loop (Hist_data::HistItem*, hist_data->hist_items, index, hi)
	  {
	    hi->value[mind].tag = vtype;
	  }
	  if ((mode == Hist_data::CALLERS || mode == Hist_data::CALLEES)
	      && subtype == Metric::ATTRIBUTED)
	    {
	      hist_data->total->value[mind].ull = 0;
	      Vec_loop (Hist_data::HistItem*, hist_data->hist_items, index, hi)
	      {
		hist_data->total->value[mind].ull += hi->value[mind].ull;
	      }
	      if (mode == Hist_data::CALLEES)
		hist_data->total->value[mind].ull += hist_data->gprof_item->value[mind].ull;
	    }
	  else if (xlate[mind] != -1)
	    ASN_METRIC_VAL (hist_data->total->value[mind], slots[xlate[mind]], root_idx);
	  break;

	case VT_DOUBLE:
	  double prec = mtr->get_precision ();
	  ValueTag vt = (xlate[mind] != -1) ? slots[xlate[mind]].vtype : VT_INT;
	  Vec_loop (Hist_data::HistItem*, hist_data->hist_items, index, hi)
	  {
	    double val = (vt == VT_LLONG ? hi->value[mind].ll :
			  (vt == VT_ULLONG ? hi->value[mind].ull
			   : hi->value[mind].i));
	    hi->value[mind].tag = vtype;
	    hi->value[mind].d = val / prec;
	  }

	  if ((mode == Hist_data::CALLERS || mode == Hist_data::CALLEES)
	       && subtype == Metric::ATTRIBUTED)
	    {
	      hist_data->total->value[mind].d = 0.0;
	      Vec_loop (Hist_data::HistItem*, hist_data->hist_items, index, hi)
	      {
		hist_data->total->value[mind].d += hi->value[mind].d;
	      }
	      if (mode == Hist_data::CALLEES)
		hist_data->total->value[mind].d +=
		      (double) (vt == VT_LLONG ? hist_data->gprof_item->value[mind].ll :
				(vt == VT_ULLONG ? hist_data->gprof_item->value[mind].ull :
				 hist_data->gprof_item->value[mind].i)) / prec;
	    }
	  else if (xlate[mind] != -1)
	    {
	      TValue& total = hist_data->total->value[mind];
	      ASN_METRIC_VAL (total, slots[xlate[mind]], root_idx);
	      double val = (vt == VT_LLONG ? total.ll :
			    (vt == VT_ULLONG ? total.ll : total.i));
	      total.d = val / prec;
	    }
	  break;
	}
    }
  delete[] xlate;

  // Determine by which metric to sort if any
  bool rev_sort = mlist->get_sort_rev ();
  for (long mind = 0, sz = mlist->get_items ()->size (); mind < sz; mind++)
    {
      Metric *mtr = mlist->get_items ()->get (mind);
      if (mlist->get_sort_ref_index () == mind)
	sort_ind = mind;

      switch (mtr->get_type ())
	{
	case BaseMetric::SIZES:
	  Vec_loop (Hist_data::HistItem *, hist_data->hist_items, index, hi)
	  {
	    Histable *h = mtr->get_comparable_obj (hi->obj);
	    hi->value[mind].tag = VT_LLONG;
	    hi->value[mind].ll = h ? h->get_size () : 0;
	  }
	  break;
	case BaseMetric::ADDRESS:
	  Vec_loop (Hist_data::HistItem *, hist_data->hist_items, index, hi)
	  {
	    Histable *h = mtr->get_comparable_obj (hi->obj);
	    hi->value[mind].tag = VT_ADDRESS;
	    hi->value[mind].ll = h ? h->get_addr () : 0;
	  }
	  break;
	case BaseMetric::DERIVED:
	  {
	    Definition *def = mtr->get_definition ();
	    long *map = def->get_map ();
	    for (long i1 = 0, sz1 = hist_data->hist_items->size (); i1 < sz1; i1++)
	      {
		/* Hist_data::HistItem * */hi = hist_data->hist_items->get (i1);
		hi->value[mind].tag = VT_DOUBLE;
		hi->value[mind].d = def->eval (map, hi->value);
	      }
	    hist_data->total->value[mind].tag = VT_DOUBLE;
	    hist_data->total->value[mind].d = def->eval (map, hist_data->total->value);
	  }
	  break;
	default:
	  break;
	}
    }

  hist_data->sort (sort_ind, rev_sort);
  hist_data->compute_minmax ();
  if (dbeSession->is_interactive () && mode != Hist_data::CALLERS)
    theApplication->set_progress (0, GTXT (""));

#if DEBUG_FTREE
  if (ftree_hist_data)
    {
      bool matches = ftree_debug_match_hist_data (hist_data, ftree_hist_data);
      if (!matches)
	assert (false);
      delete hist_data;
      hist_data = ftree_hist_data; // return the debug version
    }
#endif
  return hist_data;
}

#if DEBUG_FTREE
bool
PathTree::ftree_debug_match_hist_data (Hist_data *data /* ref */,
				       Hist_data *data_tmp)
{
  if (data->get_status () != Hist_data::SUCCESS)
    {
      DBG (assert (false));
      return false;
    }
  if (data == NULL && data != data_tmp)
    {
      DBG (assert (false));
      return false;
    }

  MetricList *mlist;
  mlist = data->get_metric_list ();
  MetricList *mlist_tmp;
  mlist_tmp = data_tmp->get_metric_list ();
  if (mlist->size () != mlist_tmp->size ())
    {
      DBG (assert (false));
      return false;
    }

  // Get table size: count visible metrics
  int nitems = data->size ();
  if (data->size () != data_tmp->size ())
    {
      DBG (assert (false));
      return false;
    }

  for (int i = 0; i < nitems; ++i)
    {
      Hist_data::HistItem *item = data->fetch (i);
      Hist_data::HistItem *item_tmp = data_tmp->fetch (i);
      if (item->obj->id != item_tmp->obj->id)
	{
	  DBG (assert (false));
	  return false;
	}
    }

  for (long i = 0, sz = mlist->size (); i < sz; i++)
    {
      long met_ind = i;
      Metric *mitem = mlist->get (i);
      Metric *mitem_tmp = mlist_tmp->get (i);

      if (mitem->get_id () != mitem_tmp->get_id ())
	{
	  DBG (assert (false));
	  return false;
	}
      if (mitem->get_visbits () != mitem_tmp->get_visbits ())
	{
	  DBG (assert (false));
	  return false;
	}
      if (mitem->get_vtype () != mitem_tmp->get_vtype ())
	{
	  DBG (assert (false));
	  return false;
	}

      if (!mitem->is_visible () && !mitem->is_tvisible ()
	  && !mitem->is_pvisible ())
	continue;
      // table->append(dbeGetTableDataOneColumn(data, i));
      for (long row = 0, sz_row = data->size (); row < sz_row; row++)
	{
	  Metric *m = mitem;
	  TValue res;
	  TValue res_tmp;
	  TValue *v = data->get_value (&res, met_ind, row);
	  TValue *v_tmp = data_tmp->get_value (&res_tmp, met_ind, row);
	  if ((m->get_visbits () & VAL_RATIO) != 0)
	    {
	      if (v->tag != VT_LABEL)
		{
		  if (v->to_double () != v_tmp->to_double ())
		    {
		      DBG (assert (false));
		      return false;
		    }
		}
	      continue;
	    }
	  switch (m->get_vtype ())
	    {
	    case VT_DOUBLE:
	      {
		double diff = v->d - v_tmp->d;
		if (diff < 0) diff = -diff;
		if (diff > 0.0001)
		  {
		    DBG (assert (false));
		    return false;
		  }
		else
		  DBG (assert (true));
		break;
	      }
	    case VT_INT:
	      if (v->i != v_tmp->i)
		{
		  DBG (assert (false));
		  return false;
		}
	      break;
	    case VT_ULLONG:
	    case VT_LLONG:
	    case VT_ADDRESS:
	      if (v->ll != v_tmp->ll)
		{
		  DBG (assert (false));
		  return false;
		}
	      break;

	    case VT_LABEL:
	      if (dbe_strcmp (v->l, v_tmp->l))
		{
		  DBG (assert (false));
		  return false;
		}
	      break;
	    default:
	      DBG (assert (false));
	      return false;
	    }
	}
    }
  return true;
}
#endif

Histable *
PathTree::get_hist_func_obj (Node *node)
{
  LoadObject *lo;
  Function *func;
  func = (Function*) (node->instr->convertto (Histable::FUNCTION));
  // LIBRARY VISIBILITY
  lo = func->module->loadobject;
  if (dbev->get_lo_expand (lo->seg_idx) == LIBEX_HIDE)
    return lo->get_hide_function ();
  return get_compare_obj (func);
}

Histable *
PathTree::get_hist_obj (Node *node, Histable* context)
{
  LoadObject *lo;
  Function *func;
  switch (hist_data->type)
    {
    case Histable::INSTR:
      if (hist_data->mode == Hist_data::MODL)
	{
	  if (node->instr->get_type () != Histable::INSTR)
	    return NULL;
	}
      else
	{
	  // LIBRARY VISIBILITY
	  func = (Function*) (node->instr->convertto (Histable::FUNCTION));
	  lo = func->module->loadobject;
	  if (dbev->get_lo_expand (lo->seg_idx) == LIBEX_HIDE)
	    return lo->get_hide_function ();
	}
      return node->instr;

    case Histable::LINE:
      if (hist_data->mode != Hist_data::MODL)
	{
	  func = (Function*) (node->instr->convertto (Histable::FUNCTION));
	  lo = func->module->loadobject;
	  // LIBRARY VISIBILITY
	  if (dbev->get_lo_expand (lo->seg_idx) == LIBEX_HIDE)
	    return lo->get_hide_function ();
	}
      // For openmp user mode - the stack is already made with dbelines,
      // no need to convert it
      if (node->instr->get_type () == Histable::LINE)
	return node->instr;
      return node->instr->convertto (Histable::LINE, context);

    case Histable::FUNCTION:
      if (pathTreeType == PATHTREE_INTERNAL_FUNCTREE && node->ancestor != 0)
	func = (Function*) node->instr;
      else
	func = (Function*) (node->instr->convertto (Histable::FUNCTION));
      lo = func->module->loadobject;
      // LIBRARY VISIBILITY
      if (dbev->get_lo_expand (lo->seg_idx) == LIBEX_HIDE)
	return lo->get_hide_function ();
      return get_compare_obj (func);
    case Histable::MODULE:
      func = (Function*) (node->instr->convertto (Histable::FUNCTION));
      return func->module;
    case Histable::LOADOBJECT:
      func = (Function*) (node->instr->convertto (Histable::FUNCTION));
      return func->module->loadobject;
    case Histable::INDEXOBJ:
    case Histable::MEMOBJ:
      return node->instr;
    default:
      DBG (assert (0));
    }
  return NULL;
}

Histable *
PathTree::get_compare_obj (Histable *obj)
{
  if (obj && dbev->comparingExperiments ())
    obj = dbev->get_compare_obj (obj);
  return obj;
}

void
PathTree::get_metrics (NodeIdx node_idx, int dpth)
{
  Node *node = NODE_IDX (node_idx);
  Histable *cur_obj = get_hist_obj (node);
  obj_list[dpth] = cur_obj;

  // Check for recursion (inclusive metrics)
  int incl_ok = 1;
  for (int i = dpth - 1; i >= 0; i--)
    if (cur_obj == obj_list[i])
      {
	incl_ok = 0;
	break;
      }

  // Check for leaf nodes (exclusive metrics)
  int excl_ok = 0;
  if (IS_LEAF (node) || node == NODE_IDX (root_idx))
    excl_ok = 1;

  // We shouldn't eliminate empty subtrees here because
  // we create the list of hist items dynamically and want
  // one for each object in the tree.
  cur_obj = get_compare_obj (cur_obj);
  Hist_data::HistItem *hi = hist_data->append_hist_item (cur_obj);
  DBG (assert (hi != NULL));

  MetricList *mlist = hist_data->get_metric_list ();
  for (long ind = 0, sz = mlist->size (); ind < sz; ind++)
    {
      if (xlate[ind] == -1)
	continue;
      Metric *mtr = mlist->get (ind);
      Metric::SubType subtype = mtr->get_subtype ();
      if (IS_MVAL_ZERO (slots[xlate[ind]], node_idx))
	continue;

      switch (subtype)
	{
	case Metric::INCLUSIVE:
	  if (incl_ok && hi)
	    ADD_METRIC_VAL (hi->value[ind], slots[xlate[ind]], node_idx);
	  break;
	case Metric::EXCLUSIVE:
	  if (excl_ok && hi)
	    ADD_METRIC_VAL (hi->value[ind], slots[xlate[ind]], node_idx);
	  break;
	  // ignoring the following cases (why?)
	case Metric::STATIC:
	case Metric::ATTRIBUTED:
	  break;
	case Metric::DATASPACE:
	  if (hi)
	    ADD_METRIC_VAL (hi->value[ind], slots[xlate[ind]], node_idx);
	  break;
	}
    }

  if (dbeSession->is_interactive ())
    {
      ndone++;
      int new_percent = 95 * ndone / nodes;
      if (new_percent > percent)
	{
	  percent = new_percent;
	  theApplication->set_progress (percent, NULL);
	}
    }

  // Recursively process all descendants
  int index;
  int dsize = NUM_DESCENDANTS (node);
  for (index = 0; index < dsize; index++)
    get_metrics (node->descendants->fetch (index), dpth + 1);
}

void
PathTree::get_clr_metrics (Vector<Histable*> *objs, NodeIdx node_idx,
			   int pmatch, int dpth)
{
  Node *node = NODE_IDX (node_idx);
  Histable *cur_obj;
  if (hist_data->type == Histable::LINE || hist_data->type == Histable::INSTR)
    {
      cur_obj = get_hist_func_obj (node);
      node_list[dpth] = node;
    }
  else
    cur_obj = get_hist_obj (node);
  obj_list[dpth] = cur_obj;

  bool match = false;
  int nobj = objs->size ();
  if (dpth + 1 >= nobj)
    {
      match = true;
      for (int i = 0; i < nobj; ++i)
	{
	  if (objs->fetch (i) != obj_list[dpth - nobj + 1 + i])
	    {
	      match = false;
	      break;
	    }
	}
    }

  Hist_data::HistItem *hi = NULL;
  Hist_data::HistItem *hi_adj = NULL;
  if (match && dpth >= nobj)
    {
      if (hist_data->type == Histable::LINE
	  || hist_data->type == Histable::INSTR)
	hi = hist_data->append_hist_item (get_hist_obj (node_list[dpth - nobj]));
      else
	hi = hist_data->append_hist_item (obj_list[dpth - nobj]);

      if (pmatch >= 0 && pmatch >= nobj)
	{
	  if (hist_data->type == Histable::LINE
	      || hist_data->type == Histable::INSTR)
	    hi_adj = hist_data->append_hist_item (get_hist_obj (
						    node_list[pmatch - nobj]));
	  else
	    hi_adj = hist_data->append_hist_item (obj_list[pmatch - nobj]);
	}
    }

  if (hi != NULL)
    {
      MetricList *mlist = hist_data->get_metric_list ();
      for (long ind = 0, sz = mlist->size (); ind < sz; ind++)
	{
	  if (xlate[ind] == -1)
	    continue;
	  if (IS_MVAL_ZERO (slots[xlate[ind]], node_idx))
	    continue;
	  Metric *mtr = mlist->get (ind);
	  Metric::SubType subtype = mtr->get_subtype ();

	  switch (subtype)
	    {
	    case Metric::ATTRIBUTED:
	      if (hi)
		ADD_METRIC_VAL (hi->value[ind], slots[xlate[ind]], node_idx);
	      if (hi_adj)
		SUB_METRIC_VAL (hi_adj->value[ind], slots[xlate[ind]], node_idx);
	      break;
	    case Metric::STATIC:
	    case Metric::EXCLUSIVE:
	    case Metric::INCLUSIVE:
	    case Metric::DATASPACE:
	      break;
	    }
	}
    }

  // Recursively process all descendants
  int dsize = NUM_DESCENDANTS (node);
  for (int index = 0; index < dsize; index++)
    get_clr_metrics (objs, node->descendants->fetch (index),
		     match ? dpth : pmatch, dpth + 1);
}

void
PathTree::get_clr_metrics (Vector<Histable*> *objs)
{
  get_clr_metrics (objs, root_idx, -1, 0);
}

void
PathTree::get_cle_metrics (Vector<Histable*> *objs, NodeIdx node_idx, int pcle,
			   int pmatch, int dpth)
{
  Node *node = NODE_IDX (node_idx);
  Histable *cur_obj = get_hist_obj (node);
  obj_list[dpth] = cur_obj;

  bool match = false;
  int nobj = objs->size ();
  if (dpth + 1 >= nobj)
    {
      match = true;
      for (int i = 0; i < nobj; ++i)
	if (objs->fetch (i) != obj_list[dpth - nobj + 1 + i])
	  {
	    match = false;
	    break;
	  }
    }

  Hist_data::HistItem *hi = NULL;
  Hist_data::HistItem *hi_adj = NULL;
  if (pmatch >= 0 && dpth == pmatch + 1)
    hi = hist_data->append_hist_item (cur_obj);
  if (match && IS_LEAF (node))
    hi = hist_data->gprof_item;
  if (pcle >= 0)
    hi_adj = hist_data->append_hist_item (obj_list[pcle]);

  if (hi != NULL)
    {
      MetricList *mlist = hist_data->get_metric_list ();
      for (long ind = 0, sz = mlist->size (); ind < sz; ind++)
	{
	  if (xlate[ind] == -1)
	    continue;
	  if (IS_MVAL_ZERO (slots[xlate[ind]], node_idx))
	    continue;
	  Metric *mtr = mlist->get (ind);
	  Metric::SubType subtype = mtr->get_subtype ();
	  if (subtype == Metric::ATTRIBUTED)
	    {
	      ADD_METRIC_VAL (hi->value[ind], slots[xlate[ind]], node_idx);
	      if (hi_adj)
		SUB_METRIC_VAL (hi_adj->value[ind], slots[xlate[ind]], node_idx);
	    }
	}
    }

  // Recursively process all descendants
  int dsize = NUM_DESCENDANTS (node);
  for (int index = 0; index < dsize; index++)
    get_cle_metrics (objs, node->descendants->fetch (index),
		     pmatch >= 0 && dpth == pmatch + 1 ? dpth : pcle,
		     match ? dpth : pmatch, dpth + 1);
}

void
PathTree::get_cle_metrics (Vector<Histable*> *objs, NodeIdx node_idx, int dpth)
{
  Node *node = NODE_IDX (node_idx);
  Histable *cur_obj = get_hist_obj (node);
  Hist_data::HistItem *hi = NULL;
  if (NULL == objs)   // Special case: get root
    hi = hist_data->append_hist_item (cur_obj);
  else
    {
      if (dpth == objs->size ())
	hi = hist_data->append_hist_item (cur_obj);
      else if (cur_obj == objs->fetch (dpth))
	{
	  // Recursively process all descendants
	  int dsize = NUM_DESCENDANTS (node);
	  for (int index = 0; index < dsize; index++)
	    get_cle_metrics (objs, node->descendants->fetch (index), dpth + 1);
	  if (dpth == objs->size () - 1 && dsize == 0)
	    hi = hist_data->gprof_item;
	}
    }

  if (hi != NULL)
    {
      MetricList *mlist = hist_data->get_metric_list ();
      for (long ind = 0, sz = mlist->size (); ind < sz; ind++)
	{
	  if (xlate[ind] == -1)
	    continue;
	  if (IS_MVAL_ZERO (slots[xlate[ind]], node_idx))
	    continue;
	  Metric *mtr = mlist->get (ind);
	  Metric::SubType subtype = mtr->get_subtype ();
	  if (subtype == Metric::ATTRIBUTED)
	    ADD_METRIC_VAL (hi->value[ind], slots[xlate[ind]], node_idx);
	}
    }
}

void
PathTree::ftree_reset ()
{
  if (pathTreeType == PATHTREE_MAIN && indxtype < 0)
    {
      reset ();
      if (ftree_needs_update)
	{
	  if (ftree_internal == NULL)
	    {
	      ftree_internal = new PathTree (dbev, indxtype,
					     PATHTREE_INTERNAL_FUNCTREE);
	      if (ftree_internal == NULL)
		return;
	    }
	  ftree_internal->ftree_build (this);
	  ftree_needs_update = false;
	}
    }
}

void
PathTree::ftree_build (PathTree * mstr)
{
  fini ();
  init ();
  allocate_slots (mstr->slots, mstr->nslots);
  ftree_build (mstr, mstr->root_idx, root_idx);
  depth = mstr->depth;
  depth_map_build ();
}

#if DEBUG_FTREE // possibly TBR
void
PathTree::ftree_dump ()
{
  hrtime_t starttime, endtime;
  int nmetrics = 1;
  // int nmetrics = nslots;
  for (int kk = 0; kk < nmetrics; kk++)
    {
      int id = slots[kk].id;
      starttime = gethrtime ();
      long nodecnt = 0;
      for (int ii = 0; ii < depth; ii++)
	{
	  Vector<Vector<void*>*> *tmp = (Vector<Vector<void*>*>*)get_ftree_level
		  (id, ii);
	  if (tmp == NULL)
	    continue;
	  long sz = tmp->get (0)->size ();
	  nodecnt += sz;
#if 1
	  //   fprintf(stderr, "... finished (%ld nodes)\n", sz);
#else
	  Vector<NodeIdx> *nodeIdxList = (Vector<NodeIdx> *)tmp->get (0);
	  Vector<NodeIdx> *ancestorNodeIdxList = (Vector<NodeIdx> *)tmp->get (1);
	  Vector<uint64_t> *idList = (Vector<uint64_t> *)tmp->get (2);
	  Vector<uint64_t> *vals = (Vector<uint64_t> *)tmp->get (3);
	  for (int jj = 0; jj < sz; jj++)
	    fprintf (stderr, " ...%d:%d node=%ld, anc=%ld, id=%llu, val=%llu\n",
		     sz, jj, nodeIdxList->get (jj),
		     ancestorNodeIdxList->get (jj),
		     idList->get (jj), vals->get (jj));
#endif
	  destroy (tmp);
	}
      endtime = gethrtime ();
      fprintf (stderr, "====================== %ld nodes time=%llu\n",
	       nodecnt, (endtime - starttime) / 1000 / 1000);
    }
}
#endif

// ftree: translate mstr Histable::INSTR to Histable::FUNCTION
void
PathTree::ftree_build (PathTree *mstr, NodeIdx mstr_node_idx,
		       NodeIdx local_node_idx)
{
  // requires: slots, nslots
  Node *mstr_node = mstr->NODE_IDX (mstr_node_idx);
  int dsize = NUM_DESCENDANTS (mstr_node);

  // Add metrics
  for (int i = 0; i < nslots; i++)
    {
      if (i >= mstr->nslots)
	continue; //weird
      if (slots[i].vtype != mstr->slots[i].vtype)
	continue; //weird
      TValue val;
      val.ll = 0;
      mstr->ASN_METRIC_VAL (val, mstr->slots[i], mstr_node_idx);
      int64_t mval;
      switch (slots[i].vtype)
	{
	case VT_ULLONG:
	case VT_LLONG:
	  mval = val.ll;
	  break;
	case VT_INT:
	  mval = val.i;
	  break;
	default:
	  mval = 0;
	  break;
	}
      if (mval)
	{
	  Slot * mslot = SLOT_IDX (i);
	  if (mslot)
	    INCREMENT_METRIC (mslot, local_node_idx, mval);
	}
    }

  // Recursively process all descendants
  for (int index = 0; index < dsize; index++)
    {
      NodeIdx mstr_desc_node_idx = mstr_node->descendants->fetch (index);
      Node *mstr_desc_node = mstr->NODE_IDX (mstr_desc_node_idx);
      Function *func = (Function*) mstr_desc_node->instr->convertto (Histable::FUNCTION);
      int mstr_desc_dsize = NUM_DESCENDANTS (mstr_desc_node);
      bool leaf = (mstr_desc_dsize == 0);
      NodeIdx local_desc_node_idx = find_desc_node (local_node_idx, func, leaf);
      ftree_build (mstr, mstr_desc_node_idx, local_desc_node_idx);
    }
}

void
PathTree::depth_map_build ()
{
  destroy (depth_map);
  depth_map = new Vector<Vector<NodeIdx>*>(depth);
  if (depth)
    {
      depth_map->put (depth - 1, 0); // fill vector with nulls
      depth_map_build (root_idx, 0);
    }
}

void
PathTree::depth_map_build (NodeIdx node_idx, int dpth)
{
  Node *node = NODE_IDX (node_idx);

  Vector<NodeIdx> *node_idxs = depth_map->get (dpth);
  if (node_idxs == NULL)
    {
      node_idxs = new Vector<NodeIdx>();
      depth_map->store (dpth, node_idxs);
    }
  node_idxs->append (node_idx);

  // Recursively process all descendants
  int dsize = NUM_DESCENDANTS (node);
  for (int index = 0; index < dsize; index++)
    {
      NodeIdx desc_node_idx = node->descendants->fetch (index);
      depth_map_build (desc_node_idx, dpth + 1);
    }
}

int
PathTree::get_ftree_depth ()
{ // external use only
  ftree_reset ();
  if (!ftree_internal)
    return 0;
  return ftree_internal->get_depth ();
}

Vector<Function*>*
PathTree::get_ftree_funcs ()
{ // external use only
  ftree_reset ();
  if (!ftree_internal)
    return NULL;
  return ftree_internal->get_funcs ();
}

Vector<Function*>*
PathTree::get_funcs ()
{
  // get unique functions
  if (fn_map == NULL)
    return NULL;
  return fn_map->keySet ();
}

Vector<void*>*
PathTree::get_ftree_level (BaseMetric *bm, int dpth)
{ // external use only
  ftree_reset ();
  if (!ftree_internal)
    return NULL;
  return ftree_internal->get_level (bm, dpth);
}

Vector<void*>*
PathTree::get_level (BaseMetric *bm, int dpth)
{
  // Nodes at tree depth dpth
  if (dpth < 0 || dpth >= depth)
    return NULL;
  if (depth_map == NULL)
    return NULL;
  Vector<NodeIdx> *node_idxs = depth_map->get (dpth);
  return get_nodes (bm, node_idxs);
}

Vector<void*>*
PathTree::get_ftree_node_children (BaseMetric *bm, NodeIdx node_idx)
{ // external use only
  ftree_reset ();
  if (!ftree_internal)
    return NULL;
  return ftree_internal->get_node_children (bm, node_idx);
}

Vector<void*>*
PathTree::get_node_children (BaseMetric *bm, NodeIdx node_idx)
{
  // Nodes that are children of node_idx
  if (depth_map == NULL)
    return NULL;
  if (node_idx == 0)  // special case for root
    return get_nodes (bm, depth_map->get (0));
  if (node_idx < 0 || node_idx >= nodes)
    return NULL;
  Node *node = NODE_IDX (node_idx);
  if (node == NULL)
    return NULL;
  Vector<NodeIdx> *node_idxs = node->descendants;
  return get_nodes (bm, node_idxs);
}

Vector<void*>*
PathTree::get_nodes (BaseMetric *bm, Vector<NodeIdx> *node_idxs)
{ // used for ftree
  // capture info for node_idxs:
  //   node's idx
  //   node->ancestor idx
  //   node->instr->id
  //   mind metric value // in the future, could instead accept vector of mind
  if (node_idxs == NULL)
    return NULL;
  long sz = node_idxs->size ();
  if (sz <= 0)
    return NULL;

  bool calculate_metric = false;
  ValueTag vtype;
  int slot_idx;
  double prec;
  if (bm != NULL)
    {
      int mind = bm->get_id ();
      slot_idx = find_slot (mind); // may be -1 (CPI and IPC)
      prec = bm->get_precision ();
      vtype = bm->get_vtype ();
    }
  else
    {
      slot_idx = -1;
      prec = 1.0;
      vtype = VT_INT;
    }

  if (slot_idx >= 0)
    {
      switch (vtype)
	{
	case VT_ULLONG:
	case VT_LLONG:
	case VT_INT:
	  if (slots[slot_idx].vtype == vtype)
	    calculate_metric = true;
	  else
	    DBG (assert (false));
	  break;
	case VT_DOUBLE:
	  calculate_metric = true;
	  break;
	default:
	  break;
	}
    }

  Vector<void*> *results = new Vector<void*>(4);
  if (!calculate_metric)
    results->store (3, NULL);
  else
    {
      // Code below cribbed from Dbe.cc:dbeGetTableDataV2Data.
      // TBD: possibly create an intermediate HistData and instead call that routine
      switch (vtype)
	{
	case VT_ULLONG:
	case VT_LLONG:
	  {
	    Vector<long long> *vals = new Vector<long long>(sz);
	    for (long i = 0; i < sz; i++)
	      {
		NodeIdx node_idx = node_idxs->get (i);
		TValue val;
		val.ll = 0;
		ASN_METRIC_VAL (val, slots[slot_idx], node_idx);
		vals->append (val.ll);
	      }
	    results->store (3, vals);
	    break;
	  }
	case VT_DOUBLE:
	  {
	    Vector<double> *vals = new Vector<double>(sz);
	    TValue val;
	    val.tag = slots[slot_idx].vtype; // required for to_double();
	    for (long i = 0; i < sz; i++)
	      {
		NodeIdx node_idx = node_idxs->get (i);
		val.ll = 0;
		ASN_METRIC_VAL (val, slots[slot_idx], node_idx);
		double dval = val.to_double ();
		dval /= prec;
		vals->append (dval);
	      }
	    results->store (3, vals);
	    break;
	  }
	case VT_INT:
	  {
	    Vector<int> *vals = new Vector<int>(sz);
	    for (long i = 0; i < sz; i++)
	      {
		NodeIdx node_idx = node_idxs->get (i);
		TValue val;
		val.i = 0;
		ASN_METRIC_VAL (val, slots[slot_idx], node_idx);
		vals->append (val.i);
	      }
	    results->store (3, vals);
	    break;
	  }
	default:
	  results->store (3, NULL);
	  break;
	}
    }

  Vector<int> *nodeIdxList = new Vector<int>(sz);
  Vector<int> *ancestorNodeIdxList = new Vector<int>(sz);
  Vector<uint64_t> *idList = new Vector<uint64_t>(sz);
  for (long i = 0; i < sz; i++)
    {
      NodeIdx node_idx = node_idxs->get (i);
      Node *node = NODE_IDX (node_idx);
      NodeIdx ancestor_idx = node->ancestor;
      Histable *func = node->instr;
      nodeIdxList->append (node_idx);
      ancestorNodeIdxList->append (ancestor_idx);
      idList->append (func->id);
    }

  results->store (0, nodeIdxList);
  results->store (1, ancestorNodeIdxList);
  results->store (2, idList);
  return results;
}

void
PathTree::get_cle_metrics (Vector<Histable*> *objs)
{
  if (NULL == objs || objs->fetch (0) == get_hist_obj (NODE_IDX (root_idx)))
    // Call Tree optimization
    get_cle_metrics (objs, root_idx, 0);
  else
    // General case
    get_cle_metrics (objs, root_idx, -1, -1, 0);
}

void
PathTree::get_metrics (Vector<Function*> *functions, Histable *context)
{
  Function *fitem;
  int excl_ok, incl_ok;
  NodeIdx node_idx;
  Node *node, *anc;
  int index;

  Vec_loop (Function*, functions, index, fitem)
  {
    node_idx = fn_map->get (fitem);
    for (; node_idx; node_idx = node->funclist)
      {
	node = NODE_IDX (node_idx);
	Histable *h_obj = get_hist_obj (node, context);
	if (h_obj == NULL)
	  continue;

	// Check for recursion (inclusive metrics)
	incl_ok = 1;
	for (anc = NODE_IDX (node->ancestor); anc;
		anc = NODE_IDX (anc->ancestor))
	  {
	    if (h_obj == get_hist_obj (anc, context))
	      {
		incl_ok = 0;
		break;
	      }
	  }

	// Check for leaf nodes (exclusive metrics)
	excl_ok = 0;
	if (IS_LEAF (node))
	  excl_ok = 1;

	h_obj = get_compare_obj (h_obj);
	Hist_data::HistItem *hi = hist_data->append_hist_item (h_obj);

	if (!excl_ok)
	  hist_data->get_callsite_mark ()->put (h_obj, 1);
	MetricList *mlist = hist_data->get_metric_list ();
	for (long ind = 0, sz = mlist->size (); ind < sz; ind++)
	  {
	    if (xlate[ind] == -1)
	      continue;
	    Metric *mtr = mlist->get (ind);
	    Metric::SubType subtype = mtr->get_subtype ();
	    if (subtype == Metric::INCLUSIVE && !incl_ok)
	      continue;
	    if (subtype == Metric::EXCLUSIVE && !excl_ok)
	      continue;
	    if (IS_MVAL_ZERO (slots[xlate[ind]], node_idx))
	      continue;
	    ADD_METRIC_VAL (hi->value[ind], slots[xlate[ind]], node_idx);
	  }
      }
  }
}

void
PathTree::get_self_metrics (Vector<Histable*> *objs, NodeIdx node_idx,
			    bool seen, int dpth)
{
  Node *node = NODE_IDX (node_idx);
  Histable *cur_obj = get_hist_obj (node);
  obj_list[dpth] = cur_obj;

  bool match = false;
  int nobj = objs->size ();
  if (dpth + 1 >= nobj)
    {
      match = true;
      for (int i = 0; i < nobj; ++i)
	{
	  if (objs->fetch (i) != obj_list[dpth - nobj + 1 + i])
	    {
	      match = false;
	      break;
	    }
	}
    }

  if (match)
    {
      Hist_data::HistItem *hi = hist_data->append_hist_item (cur_obj);
      int incl_ok = !seen;
      int excl_ok = 0;
      if (IS_LEAF (node) || node == NODE_IDX (root_idx))
	excl_ok = 1;
      MetricList *mlist = hist_data->get_metric_list ();
      for (long ind = 0, sz = mlist->size (); ind < sz; ind++)
	{
	  if (xlate[ind] == -1)
	    continue;
	  if (IS_MVAL_ZERO (slots[xlate[ind]], node_idx))
	    continue;
	  Metric *mtr = mlist->get (ind);
	  Metric::SubType subtype = mtr->get_subtype ();
	  switch (subtype)
	    {
	    case Metric::INCLUSIVE:
	      if (incl_ok && hi)
		ADD_METRIC_VAL (hi->value[ind], slots[xlate[ind]], node_idx);
	      break;
	    case Metric::EXCLUSIVE:
	    case Metric::ATTRIBUTED:
	      if (excl_ok && hi)
		ADD_METRIC_VAL (hi->value[ind], slots[xlate[ind]], node_idx);
	      break;
	    case Metric::DATASPACE:
	      if (hi)
		ADD_METRIC_VAL (hi->value[ind], slots[xlate[ind]], node_idx);
	      break;
	      // ignoring the following cases (why?)
	    case Metric::STATIC:
	      break;
	    }
	}
    }

  if (dbeSession->is_interactive ())
    {
      ndone++;
      int new_percent = 95 * ndone / nodes;
      if (new_percent > percent)
	{
	  percent = new_percent;
	  theApplication->set_progress (percent, NULL);
	}
    }

  // Recursively process all descendants
  int index;
  int dsize = NUM_DESCENDANTS (node);
  for (index = 0; index < dsize; index++)
    get_self_metrics (objs, node->descendants->fetch (index),
		      seen || match, dpth + 1);
}

void
PathTree::get_self_metrics (Vector<Histable*> *objs)
{
  get_self_metrics (objs, root_idx, false, 0);
}

void
PathTree::get_self_metrics (Histable *obj, Vector<Function*> *funclist,
			    Vector<Histable*>* sel_objs)
{
  int excl_ok, incl_ok;
  NodeIdx node_idx;
  Node *node, *anc;

  if (obj == NULL)
    return;

  SourceFile *src = NULL;
  if (obj && obj->get_type () == Histable::LINE)
    {
      DbeLine *dbeline = (DbeLine*) obj;
      src = dbeline->sourceFile;
    }

  Hist_data::HistItem *hi = hist_data->append_hist_item (obj);
  for (int i = 0, sz = funclist ? funclist->size () : 0; i < sz; i++)
    {
      Function *fitem = (Function*) get_compare_obj (funclist->fetch (i));
      node_idx = fn_map->get (fitem);
      for (; node_idx; node_idx = node->funclist)
	{
	  node = NODE_IDX (node_idx);
	  if (obj && obj->get_type () == Histable::LINE)
	    {
	      Histable *h = get_hist_obj (node, src);
	      if (h == NULL)
		continue;
	      if (h->convertto (Histable::LINE) != obj->convertto (Histable::LINE))
		continue;
	    }
	  else if (get_hist_obj (node, src) != obj)
	    continue;

	  // Check for recursion (inclusive metrics)
	  incl_ok = 1;
	  for (anc = NODE_IDX (node->ancestor); anc;
		  anc = NODE_IDX (anc->ancestor))
	    {
	      if (get_hist_obj (anc, src) == obj)
		{
		  incl_ok = 0;
		  break;
		}
	      if (sel_objs != NULL)
		for (int k = 0; k < sel_objs->size (); k++)
		  if (sel_objs->fetch (k) == get_hist_obj (anc, src))
		    {
		      incl_ok = 0;
		      break;
		    }
	    }

	  // Check for leaf nodes (exclusive metrics)
	  excl_ok = 0;
	  if (IS_LEAF (node) || node == NODE_IDX (root_idx))
	    excl_ok = 1;

	  MetricList *mlist = hist_data->get_metric_list ();
	  for (long ind = 0, ind_sz = mlist->size (); ind < ind_sz; ind++)
	    {
	      if (xlate[ind] == -1)
		continue;
	      Metric *mtr = mlist->get (ind);
	      Metric::SubType subtype = mtr->get_subtype ();
	      if (subtype == Metric::INCLUSIVE && !incl_ok)
		continue;
	      if (subtype == Metric::EXCLUSIVE && !excl_ok)
		continue;
	      if (subtype == Metric::ATTRIBUTED && !excl_ok)
		continue;
	      if (IS_MVAL_ZERO (slots[xlate[ind]], node_idx))
		continue;
	      ADD_METRIC_VAL (hi->value[ind], slots[xlate[ind]], node_idx);
	    }
	}
    }
}

Vector<Histable*> *
PathTree::get_clr_instr (Histable * func)
{
  Vector<Histable*> * instrs = NULL;
  if (func->get_type () != Histable::FUNCTION)
    return NULL;
  NodeIdx node_idx = fn_map->get ((Function*) func);
  Node *node = NODE_IDX (node_idx);
  if (node == NULL)
    return new Vector<Histable*>();
  int instr_num = 0;
  for (; node; node = NODE_IDX (node->funclist))
    instr_num++;
  instrs = new Vector<Histable*>(instr_num);
  node = NODE_IDX (node_idx);
  Histable *instr = NODE_IDX (node->ancestor)->instr;
  instr_num = 0;
  instrs->store (instr_num, instr);
  node = NODE_IDX (node->funclist);
  for (; node; node = NODE_IDX (node->funclist))
    {
      instr = NODE_IDX (node->ancestor)->instr;
      instr_num++;
      instrs->store (instr_num, instr);
    }
  return instrs;
}

Vector<void*> *
PathTree::get_cle_instr (Histable * func, Vector<Histable*>*&instrs)
{
  if (func->get_type () != Histable::FUNCTION)
    return NULL;
  NodeIdx node_idx = fn_map->get ((Function*) func);
  Node *node = NODE_IDX (node_idx);
  if (node == NULL)
    {
      instrs = new Vector<Histable*>();
      return new Vector<void*>();
    }
  int instr_num = 0;
  for (; node; node = NODE_IDX (node->funclist))
    instr_num++;
  instrs = new Vector<Histable*>(instr_num);
  Vector<void*> *callee_info = new Vector<void*>(instr_num);
  node = NODE_IDX (node_idx);
  Histable *instr = node->instr;
  instr_num = 0;
  instrs->store (instr_num, instr);
  int dec_num = 0;
  NodeIdx dec_idx = 0;
  if (NUM_DESCENDANTS (node) > 0)
    {
      Vector<Histable*> * callee_instrs = new Vector<Histable*>(node->descendants->size ());
      Vec_loop (NodeIdx, node->descendants, dec_num, dec_idx)
      {
	Node * dec_node = NODE_IDX (dec_idx);
	//XXXX Note: there can be more than one instrs in one leaf function
	callee_instrs->store (dec_num, dec_node->instr);
      }
      callee_info->store (instr_num, callee_instrs);
    }
  else
    callee_info->store (instr_num, NULL);
  node = NODE_IDX (node->funclist);
  for (; node; node = NODE_IDX (node->funclist))
    {
      instr = node->instr;
      instr_num++;
      instrs->store (instr_num, instr);
      if (NUM_DESCENDANTS (node) > 0)
	{
	  Vector<Histable*> * callee_instrs = new Vector<Histable*>(node->descendants->size ());
	  Vec_loop (NodeIdx, node->descendants, dec_num, dec_idx)
	  {
	    Node * dec_node = NODE_IDX (dec_idx);
	    //XXXX Note: there can be more than one instrs in one leaf function
	    callee_instrs->store (dec_num, dec_node->instr);
	  }
	  callee_info->store (instr_num, callee_instrs);
	}
      else
	callee_info->store (instr_num, NULL);
    }
  return callee_info;
}

//
//
// The following methods are used for debugging purpose only.
//
//
static int maxdepth;
static int maxwidth;

void
PathTree::print (FILE *fd)
{
  (void) reset ();
  fprintf (fd, NTXT ("n = %lld, dn = %lld, MD = %lld\n\n"),
	   (long long) nodes, (long long) dnodes, (long long) depth);
  maxdepth = 0;
  maxwidth = 0;
  print (fd, root, 0);
  fprintf (fd, NTXT ("md = %lld, mw = %lld\n"),
	   (long long) maxdepth, (long long) maxwidth);
}

void
PathTree::print (FILE *fd, PathTree::Node *node, int lvl)
{
  const char *t;
  char *n;
  if (lvl + 1 > maxdepth)
    maxdepth = lvl + 1;
  for (int i = 0; i < lvl; i++)
    fprintf (fd, NTXT ("-"));
  Histable *instr = node->instr;
  if (instr->get_type () == Histable::LINE)
    {
      t = "L";
      n = ((DbeLine *) instr)->func->get_name ();
    }
  else if (instr->get_type () == Histable::INSTR)
    {
      t = "I";
      n = ((DbeInstr *) instr)->func->get_name ();
    }
  else
    {
      t = "O";
      n = instr->get_name ();
    }
  long long addr = (long long) instr->get_addr ();
  fprintf (fd, NTXT ("%s %s (0x%08llx) -- ndesc = %lld\n"),
	   t, n, addr, (long long) (NUM_DESCENDANTS (node)));

  // Recursively process all descendants
  int dsize = NUM_DESCENDANTS (node);
  if (dsize > maxwidth)
    maxwidth = dsize;
  for (int index = 0; index < dsize; index++)
    print (fd, NODE_IDX (node->descendants->fetch (index)), lvl + 1);
}

void
PathTree::printn (FILE *fd)
{
  int n = dbg_nodes (root);
  fprintf (fd, GTXT ("Number of nodes: %d, total size: %d\n"), n, (int) (n * sizeof (Node)));
}

void
PathTree::dumpNodes (FILE *fd, Histable *obj)
{
  const char *t;
  char *n;
  NodeIdx node_idx = fn_map->get ((Function*) obj);
  Node *node = NODE_IDX (node_idx);
  if (node == NULL)
    {
      fprintf (fd, GTXT ("No nodes associated with %s\n"), obj->get_name ());
      return;
    }
  Histable *instr = node->instr;
  for (; node; node = NODE_IDX (node->funclist))
    {
      instr = node->instr;
      if (instr->get_type () == Histable::LINE)
	{
	  t = "L";
	  n = ((DbeLine *) instr)->func->get_name ();
	}
      else if (instr->get_type () == Histable::INSTR)
	{
	  t = "I";
	  n = ((DbeInstr *) instr)->func->get_name ();
	}
      else
	{
	  t = "O";
	  n = instr->get_name ();
	}
      long long addr = (long long) instr->get_addr ();
      if (addr <= 0xFFFFFFFFU)
	fprintf (fd, NTXT ("0x%08x -- %s %s\n"), (uint32_t) addr, t, n);
      else
	fprintf (fd, NTXT ("0x%016llX -- %s %s\n"), addr, t, n);
    }
}

int
PathTree::dbg_nodes (PathTree::Node *node)
{
  int res = 1;
  int dsize = NUM_DESCENDANTS (node);
  for (int index = 0; index < dsize; index++)
    res += dbg_nodes (NODE_IDX (node->descendants->fetch (index)));
  return res;
}

static int mind_g;

int
leak_alloc_comp (const void *s1, const void *s2)
{
  // See Hist_data::sort_compare() for duplicate code
  int result = 0;
  CStack_data::CStack_item *t1, *t2;
  t1 = *(CStack_data::CStack_item **)s1;
  t2 = *(CStack_data::CStack_item **)s2;

  switch (t1->value[mind_g].tag)
    {
    case VT_INT:
      if (t1->value[mind_g].i < t2->value[mind_g].i)
	result = -1;
      else if (t1->value[mind_g].i > t2->value[mind_g].i)
	result = 1;
      else
	result = 0;
      break;
    case VT_LLONG:
      if (t1->value[mind_g].ll < t2->value[mind_g].ll)
	result = -1;
      else if (t1->value[mind_g].ll > t2->value[mind_g].ll)
	result = 1;
      else
	result = 0;
      break;
    case VT_ULLONG:
      if (t1->value[mind_g].ull < t2->value[mind_g].ull)
	result = -1;
      else if (t1->value[mind_g].ull > t2->value[mind_g].ull)
	result = 1;
      else
	result = 0;
      break;
      // ignoring the following cases (why?)
    case VT_SHORT:
    case VT_FLOAT:
    case VT_DOUBLE:
    case VT_HRTIME:
    case VT_LABEL:
    case VT_ADDRESS:
    case VT_OFFSET:
      break;
    }
  // Sort in descending order
  return -result;
}

CStack_data *
PathTree::get_cstack_data (MetricList *mlist)
{
  (void) reset ();
  CStack_data *lam = new CStack_data (mlist);
  int nmetrics = mlist->get_items ()->size ();
  mind_g = -1;
  xlate = new int[nmetrics];
  for (int mind = 0; mind < nmetrics; mind++)
    {
      xlate[mind] = -1;
      Metric *mtr = mlist->get_items ()->fetch (mind);
      if (mlist->get_sort_ref_index () == mind)
	mind_g = mind;
      xlate[mind] = find_slot (mtr->get_id ());
    }

  // now fill in the actual data
  obj_list = new Histable*[depth];
  get_cstack_list (lam, root_idx, 0);
  delete[] obj_list;

  if (mind_g >= 0)
    lam->cstack_items->sort (leak_alloc_comp);
  delete[] xlate;
  return lam;
}

void
PathTree::get_cstack_list (CStack_data *lam, NodeIdx node_idx, int dpth)
{

  Node *node = NODE_IDX (node_idx);
  obj_list[dpth] = node->instr;

  CStack_data::CStack_item *item = NULL;
  if (IS_LEAF (node))
    item = lam->new_cstack_item ();
  int nmetrics = lam->metrics->get_items ()->size ();
  bool subtree_empty = true;

  for (int mind = 0; mind < nmetrics; mind++)
    {
      if (xlate[mind] == -1)
	continue;
      if (IS_MVAL_ZERO (slots[xlate[mind]], node_idx))
	continue;
      else
	subtree_empty = false;
      if (item)
	{
	  ADD_METRIC_VAL (item->value[mind], slots[xlate[mind]], node_idx);
	  ADD_METRIC_VAL (lam->total->value[mind], slots[xlate[mind]], node_idx);
	}
    }

  if (subtree_empty)
    {
      delete item;
      return;
    }

  if (item)
    {
      // Finish processing a leaf node
      item->stack = new Vector<DbeInstr*>(dpth);
      for (int i = 1; i <= dpth; i++)
	item->stack->append ((DbeInstr*) obj_list[i]);
      lam->cstack_items->append (item);
    }
  else
    {
      // Recursively process all descendants
      int dsize = NUM_DESCENDANTS (node);
      for (int index = 0; index < dsize; index++)
	get_cstack_list (lam, node->descendants->fetch (index), dpth + 1);
    }
}

Emsg *
PathTree::fetch_stats ()
{
  if (statsq == NULL)
    return NULL;
  return statsq->fetch ();
}

void
PathTree::delete_stats ()
{
  if (statsq != NULL)
    {
      delete statsq;
      statsq = new Emsgqueue (NTXT ("statsq"));
    }
}

Emsg *
PathTree::fetch_warnings ()
{
  if (warningq == NULL)
    return NULL;
  return warningq->fetch ();
}

void
PathTree::delete_warnings ()
{
  if (warningq != NULL)
    {
      delete warningq;
      warningq = new Emsgqueue (NTXT ("warningq"));
    }
}