/* Copyright (C) 2021 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 "DbeSession.h"
#include "HeapData.h"
#include "StringBuilder.h"
#include "i18n.h"
#include "util.h"
#include "HeapActivity.h"
#include "MetricList.h"
#include "Application.h"
#include "Experiment.h"
#include "DbeView.h"
#include "Exp_Layout.h"
#include "i18n.h"

HeapActivity::HeapActivity (DbeView *_dbev)
{
  dbev = _dbev;
  hDataTotal = NULL;
  hDataObjs = NULL;
  hDataObjsCallStack = NULL;
  hasCallStack = false;
  hDataCalStkMap = NULL;
  hist_data_callstack_all = NULL;
}

void
HeapActivity::reset ()
{
  delete hDataTotal;
  hDataTotal = NULL;
  delete hDataObjsCallStack;
  hDataObjsCallStack = NULL;
  hasCallStack = false;
  hDataObjs = NULL;
  delete hDataCalStkMap;
  hDataCalStkMap = NULL;
  hist_data_callstack_all = NULL;
}

void
HeapActivity::createHistItemTotals (Hist_data *hist_data, MetricList *mlist,
				    Histable::Type hType, bool empty)
{
  int mIndex;
  Metric *mtr;
  Hist_data::HistItem *hi;
  HeapData *hData = NULL;
  if (hDataTotal == NULL)
    {
      hDataTotal = new HeapData (TOTAL_HEAPNAME);
      hDataTotal->setHistType (hType);
      hDataTotal->setStackId (TOTAL_STACK_ID);
      hDataTotal->id = 0;
    }

  hData = new HeapData (hDataTotal);
  hData->setHistType (hType);
  hi = hist_data->append_hist_item (hData);

  Vec_loop (Metric *, mlist->get_items (), mIndex, mtr)
  {
    if (!mtr->is_visible () && !mtr->is_tvisible () && !mtr->is_pvisible ())
      continue;

    Metric::Type mtype = mtr->get_type ();
    ValueTag vType = mtr->get_vtype ();

    hist_data->total->value[mIndex].tag = vType;
    hi->value[mIndex].tag = vType;
    switch (mtype)
      {
      case BaseMetric::HEAP_ALLOC_BYTES:
	if (!empty)
	  {
	    hist_data->total->value[mIndex].ll = hDataTotal->getAllocBytes ();
	    hi->value[mIndex].ll = hDataTotal->getAllocBytes ();
	  }
	else
	  {
	    hist_data->total->value[mIndex].ll = 0;
	    hi->value[mIndex].ll = 0;
	  }
	break;
      case BaseMetric::HEAP_ALLOC_CNT:
	if (!empty)
	  {
	    hist_data->total->value[mIndex].ll = hDataTotal->getAllocCnt ();
	    hi->value[mIndex].ll = hDataTotal->getAllocCnt ();
	  }
	else
	  {
	    hist_data->total->value[mIndex].ll = 0;
	    hi->value[mIndex].ll = 0;
	  }
	break;
      case BaseMetric::HEAP_LEAK_BYTES:
	if (!empty)
	  {
	    hist_data->total->value[mIndex].ll = hDataTotal->getLeakBytes ();
	    hi->value[mIndex].ll = hDataTotal->getLeakBytes ();
	  }
	else
	  {
	    hist_data->total->value[mIndex].ll = 0;
	    hi->value[mIndex].ll = 0;
	  }
	break;
      case BaseMetric::HEAP_LEAK_CNT:
	if (!empty)
	  {
	    hist_data->total->value[mIndex].ll = hDataTotal->getLeakCnt ();
	    hi->value[mIndex].ll = hDataTotal->getLeakCnt ();
	  }
	else
	  {
	    hist_data->total->value[mIndex].ll = 0;
	    hi->value[mIndex].ll = 0;
	  }
	break;
      default:
	break;
      }
  }
}

void
HeapActivity::computeHistTotals (Hist_data *hist_data, MetricList *mlist)
{
  int mIndex;
  Metric *mtr;
  Vec_loop (Metric *, mlist->get_items (), mIndex, mtr)
  {
    if (!mtr->is_visible () && !mtr->is_tvisible () && !mtr->is_pvisible ())
      continue;

    Metric::Type mtype = mtr->get_type ();
    ValueTag vType = mtr->get_vtype ();

    hist_data->total->value[mIndex].tag = vType;
    switch (mtype)
      {
      case BaseMetric::HEAP_ALLOC_BYTES:
	hist_data->total->value[mIndex].ll = hDataTotal->getAllocBytes ();
	break;
      case BaseMetric::HEAP_ALLOC_CNT:
	hist_data->total->value[mIndex].ll = hDataTotal->getAllocCnt ();
	break;
      case BaseMetric::HEAP_LEAK_BYTES:
	hist_data->total->value[mIndex].ll = hDataTotal->getLeakBytes ();
	break;
      case BaseMetric::HEAP_LEAK_CNT:
	hist_data->total->value[mIndex].ll = hDataTotal->getLeakCnt ();
	break;
      default:
	break;
      }
  }
}

void
HeapActivity::computeHistData (Hist_data *hist_data, MetricList *mlist,
			       Hist_data::Mode mode, Histable *selObj)
{

  Hist_data::HistItem *hi = NULL;

  int numObjs = hDataObjs->size ();
  int numMetrics = mlist->get_items ()->size ();
  for (int i = 0; i < numObjs; i++)
    {
      HeapData *hData = hDataObjs->fetch (i);
      if (mode == Hist_data::ALL)
	hi = hist_data->append_hist_item (hData);
      else if (mode == Hist_data::SELF)
	{
	  if (hData->id == selObj->id)
	    hi = hist_data->append_hist_item (hData);
	  else
	    continue;
	}

      for (int mIndex = 0; mIndex < numMetrics; mIndex++)
	{
	  Metric *mtr = mlist->get_items ()->fetch (mIndex);
	  if (!mtr->is_visible () && !mtr->is_tvisible ()
	      && !mtr->is_pvisible ())
	    continue;

	  Metric::Type mtype = mtr->get_type ();
	  ValueTag vType = mtr->get_vtype ();
	  hi->value[mIndex].tag = vType;
	  switch (mtype)
	    {
	    case BaseMetric::HEAP_ALLOC_BYTES:
	      hi->value[mIndex].ll = hData->getAllocBytes ();
	      break;
	    case BaseMetric::HEAP_ALLOC_CNT:
	      hi->value[mIndex].ll = hData->getAllocCnt ();
	      break;
	    case BaseMetric::HEAP_LEAK_BYTES:
	      hi->value[mIndex].ll = hData->getLeakBytes ();
	      break;
	    case BaseMetric::HEAP_LEAK_CNT:
	      hi->value[mIndex].ll = hData->getLeakCnt ();
	      break;
	    default:
	      break;
	    }
	}
    }
}

Hist_data *
HeapActivity::compute_metrics (MetricList *mlist, Histable::Type type,
			       Hist_data::Mode mode, Histable *selObj)
{
  // it's already there, just return it
  if (mode == Hist_data::ALL && type == Histable::HEAPCALLSTACK
      && hist_data_callstack_all != NULL)
    return hist_data_callstack_all;

  bool has_data = false;
  Hist_data *hist_data = NULL;
  VMode viewMode = dbev->get_view_mode ();
  switch (type)
    {
    case Histable::HEAPCALLSTACK:
      if (!hasCallStack)    // It is not computed yet
	computeCallStack (type, viewMode);

      // computeCallStack() creates hDataObjsCallStack
      // hDataObjsCallStack contains the list of call stack objects
      if (hDataObjsCallStack != NULL)
	{
	  hDataObjs = hDataObjsCallStack;
	  has_data = true;
	}
      else
	has_data = false;

      if (has_data && mode == Hist_data::ALL && hist_data_callstack_all == NULL)
	{
	  hist_data_callstack_all = new Hist_data (mlist, type, mode, true);
	  hist_data = hist_data_callstack_all;
	}
      else if (has_data)
	hist_data = new Hist_data (mlist, type, mode, false);
      else
	{
	  hist_data = new Hist_data (mlist, type, mode, false);
	  createHistItemTotals (hist_data, mlist, type, true);
	  return hist_data;
	}
      break;
    default:
      fprintf (stderr,
	       "HeapActivity cannot process data due to wrong Histable (type=%d) \n",
	       type);
      abort ();
    }

  if (mode == Hist_data::ALL || (mode == Hist_data::SELF && selObj->id == 0))
    createHistItemTotals (hist_data, mlist, type, false);
  else
    computeHistTotals (hist_data, mlist);
  computeHistData (hist_data, mlist, mode, selObj);

  // Determine by which metric to sort if any
  bool rev_sort = mlist->get_sort_rev ();
  int sort_ind = -1;
  int nmetrics = mlist->get_items ()->size ();

  for (int mind = 0; mind < nmetrics; mind++)
    if (mlist->get_sort_ref_index () == mind)
      sort_ind = mind;

  hist_data->sort (sort_ind, rev_sort);
  hist_data->compute_minmax ();

  return hist_data;
}

void
HeapActivity::computeCallStack (Histable::Type type, VMode viewMode)
{
  bool has_data = false;
  reset ();
  uint64_t stackIndex = 0;
  HeapData *hData = NULL;

  delete hDataCalStkMap;
  hDataCalStkMap = new DefaultMap<uint64_t, HeapData*>;

  delete hDataTotal;
  hDataTotal = new HeapData (TOTAL_HEAPNAME);
  hDataTotal->setHistType (type);

  // There is no call stack for total, use the index for id
  hDataTotal->id = stackIndex++;

  // get the list of io events from DbeView
  int numExps = dbeSession->nexps ();

  for (int k = 0; k < numExps; k++)
    {
      // Investigate the performance impact of processing the heap events twice.
      // This is a 2*n performance issue
      dbev->get_filtered_events (k, DATA_HEAPSZ);

      DataView *heapPkts = dbev->get_filtered_events (k, DATA_HEAP);
      if (heapPkts == NULL)
	continue;

      Experiment *exp = dbeSession->get_exp (k);
      long sz = heapPkts->getSize ();
      int pid = 0;
      int userExpId = 0;
      if (sz > 0)
	{
	  pid = exp->getPID ();
	  userExpId = exp->getUserExpId ();
	}
      for (long i = 0; i < sz; ++i)
	{
	  uint64_t nByte = heapPkts->getULongValue (PROP_HSIZE, i);
	  uint64_t stackId = (uint64_t) getStack (viewMode, heapPkts, i);
	  Heap_type heapType = (Heap_type) heapPkts->getIntValue (PROP_HTYPE, i);
	  uint64_t leaked = heapPkts->getULongValue (PROP_HLEAKED, i);
	  int64_t heapSize = heapPkts->getLongValue (PROP_HCUR_ALLOCS, i);
	  hrtime_t packetTimestamp = heapPkts->getLongValue (PROP_TSTAMP, i);
	  hrtime_t timestamp = packetTimestamp - exp->getStartTime () +
		  exp->getRelativeStartTime ();

	  switch (heapType)
	    {
	    case MMAP_TRACE:
	    case MALLOC_TRACE:
	    case REALLOC_TRACE:
	      if (stackId != 0)
		{
		  hData = hDataCalStkMap->get (stackId);
		  if (hData == NULL)
		    {
		      char *stkName = dbe_sprintf (GTXT ("Stack 0x%llx"),
						  (unsigned long long) stackId);
		      hData = new HeapData (stkName);
		      hDataCalStkMap->put (stackId, hData);
		      hData->id = (int64_t) stackId;
		      hData->setStackId (stackIndex);
		      stackIndex++;
		      hData->setHistType (type);
		    }
		}
	      else
		continue;

	      hData->addAllocEvent (nByte);
	      hDataTotal->addAllocEvent (nByte);
	      hDataTotal->setAllocStat (nByte);
	      hDataTotal->setPeakMemUsage (heapSize, hData->getStackId (),
					   timestamp, pid, userExpId);
	      if (leaked > 0)
		{
		  hData->addLeakEvent (leaked);
		  hDataTotal->addLeakEvent (leaked);
		  hDataTotal->setLeakStat (leaked);
		}
	      break;
	    case MUNMAP_TRACE:
	    case FREE_TRACE:
	      if (hData == NULL)
		hData = new HeapData (TOTAL_HEAPNAME);
	      hDataTotal->setPeakMemUsage (heapSize, hData->getStackId (),
					   timestamp, pid, userExpId);
	      break;
	    case HEAPTYPE_LAST:
	      break;
	    }
	  has_data = true;
	}
    }

  if (has_data)
    {
      hDataObjsCallStack = hDataCalStkMap->values ()->copy ();
      hasCallStack = true;
    }
}
