/*--------------------------------------------------------------------*/
/*--- Support functions for xtree memory reports. m_xtmemory.c     ---*/
/*--------------------------------------------------------------------*/

/*
   This file is part of Valgrind, a dynamic binary instrumentation
   framework.

   Copyright (C) 2016-2017 Philippe Waroquiers

   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 2 of the
   License, 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, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307, USA.

   The GNU General Public License is contained in the file COPYING.
*/

#include "pub_core_libcassert.h"
#include "pub_core_libcbase.h"
#include "pub_core_libcprint.h"
#include "pub_core_libcproc.h"
#include "pub_core_mallocfree.h"
#include "pub_core_options.h"
#include "pub_core_xarray.h"
#include "pub_core_xtree.h"
#include "pub_core_xtmemory.h"    /* self */

static void VG_(XT_Allocs_init)(void* xt_allocs)
{
   VG_(memset) (xt_allocs, 0, sizeof(XT_Allocs));
}
static void VG_(XT_Allocs_add) (void* to, const void* xt_allocs)
{
   XT_Allocs* xto = to;
   const XT_Allocs* xta = xt_allocs;

   xto->nbytes  += xta->nbytes;
   xto->nblocks += xta->nblocks;
}
static void VG_(XT_Allocs_sub) (void* from, const void* xt_allocs)
{
   XT_Allocs* xfrom = from;
   const XT_Allocs* xta = xt_allocs;

   xfrom->nbytes  -= xta->nbytes;
   xfrom->nblocks -= xta->nblocks;
}
static const HChar* VG_(XT_Allocs_img) (const void* xt_allocs)
{
   static HChar buf[100];

   const XT_Allocs* xta = xt_allocs;
   
   if (xta->nbytes > 0 || xta->nblocks > 0) {
      VG_(sprintf) (buf, "%lu %lu",
                    xta->nbytes, xta->nblocks);
      return buf;
   } else {
      return NULL;
   }
}
const HChar* XT_Allocs_events = "curB : currently allocated Bytes"   ","
                                "curBk : currently allocated Blocks";

/* Type and functions for full xtree memory profiling. */
static XTree* full_xt;
typedef
   struct _XT_Full {
      // Current nr of bytes/blocks allocated by this ec
      SizeT cur_alloc_nbytes;
      SizeT cur_alloc_nblocks;

      // Total/cumulative nr of bytes/blocks allocated by this ec
      ULong tot_alloc_nbytes;
      ULong tot_alloc_nblocks;

      // Total/cumulative nr of bytes/blocks freed by this ec
      ULong tot_freed_nbytes;
      ULong tot_freed_nblocks;
   } XT_Full;
/* Note: normally, an ec should never be used as both an alloc_ec and
   a free_ec. This implies that we should never have a XT_Full that has
   at the same time some alloc and some freed components > 0.
   We however still will support this possibility, just in case very
   strange ec are produced and/or given by the tool. */

static void VG_(XT_Full_init)(void* xtfull)
{
   VG_(memset) (xtfull, 0, sizeof(XT_Full));
}
static void VG_(XT_Full_add) (void* to, const void* xtfull)
{
   XT_Full* xto = to;
   const XT_Full* xtf = xtfull;

   xto->cur_alloc_nbytes  += xtf->cur_alloc_nbytes;
   xto->cur_alloc_nblocks += xtf->cur_alloc_nblocks;
   xto->tot_alloc_nbytes  += xtf->tot_alloc_nbytes;
   xto->tot_alloc_nblocks += xtf->tot_alloc_nblocks;
   xto->tot_freed_nbytes  += xtf->tot_freed_nbytes;
   xto->tot_freed_nblocks += xtf->tot_freed_nblocks;
}
static void VG_(XT_Full_sub) (void* from, const void* xtfull)
{
   XT_Full* xfrom = from;
   const XT_Full* xtf = xtfull;

   xfrom->cur_alloc_nbytes  -= xtf->cur_alloc_nbytes;
   xfrom->cur_alloc_nblocks -= xtf->cur_alloc_nblocks;
   xfrom->tot_alloc_nbytes  -= xtf->tot_alloc_nbytes;
   xfrom->tot_alloc_nblocks -= xtf->tot_alloc_nblocks;
   xfrom->tot_freed_nbytes  -= xtf->tot_freed_nbytes;
   xfrom->tot_freed_nblocks -= xtf->tot_freed_nblocks;
}
static const HChar* VG_(XT_Full_img) (const void* xtfull)
{
   static HChar buf[300];

   const XT_Full* xtf = xtfull;
   
   if (   xtf->cur_alloc_nbytes  > 0
       || xtf->cur_alloc_nblocks > 0
       || xtf->tot_alloc_nbytes  > 0
       || xtf->tot_alloc_nblocks > 0
       || xtf->tot_freed_nbytes  > 0
       || xtf->tot_freed_nblocks > 0) {
      VG_(sprintf) (buf, 
                    "%lu %lu "
                    "%llu %llu "
                    "%llu %llu",
                    xtf->cur_alloc_nbytes, xtf->cur_alloc_nblocks,
                    xtf->tot_alloc_nbytes, xtf->tot_alloc_nblocks,
                    xtf->tot_freed_nbytes, xtf->tot_freed_nblocks);
      return buf;
   } else {
      return NULL;
   }
}
static const HChar* XT_Full_events = 
   "curB : currently allocated Bytes"   ","
   "curBk : currently allocated Blocks" ","
   "totB : total allocated Bytes"       ","
   "totBk : total allocated Blocks"     ","
   "totFdB : total Freed Bytes"         ","
   "totFdBk : total Freed Blocks";
void VG_(XTMemory_Full_init)(XT_filter_IPs_t filter_IPs_fn)
{
   full_xt = VG_(XT_create) (VG_(malloc),
                             "m_xtree.full_xt",
                             VG_(free),
                             sizeof(XT_Full),
                             VG_(XT_Full_init),
                             VG_(XT_Full_add),
                             VG_(XT_Full_sub),
                             filter_IPs_fn);
}
void VG_(XTMemory_Full_alloc)(SizeT szB,
                              ExeContext* ec_alloc)
{
   XT_Full xtf = {szB, 1, szB, 1, 0, 0};
   VG_(XT_add_to_ec)(full_xt, ec_alloc, &xtf);
}
void VG_(XTMemory_Full_free)(SizeT szB,
                             ExeContext* ec_alloc,
                             ExeContext* ec_free)
{
   // substract from ec_alloc the freed memory.
   XT_Full xtf_sub = {szB, 1, 0, 0, 0, 0};
   VG_(XT_sub_from_ec)(full_xt, ec_alloc, &xtf_sub);

   // add to ec_free the freed memory
   XT_Full xtf_add = {0, 0, 0, 0, szB, 1};
   VG_(XT_add_to_ec)(full_xt, ec_free, &xtf_add);
}

void VG_(XTMemory_Full_resize_in_place)(SizeT oldSzB, SizeT newSzB,
                                        ExeContext* ec_alloc)
{
   if (oldSzB > newSzB) {
      XT_Full xtf = {oldSzB - newSzB, 0, oldSzB - newSzB, 0, 0, 0};
      VG_(XT_sub_from_ec)(full_xt, ec_alloc, &xtf);
   } else {
      XT_Full xtf = {newSzB - oldSzB, 0, newSzB - oldSzB, 0, 0, 0};
      VG_(XT_add_to_ec)(full_xt, ec_alloc, &xtf);
   }
}

// Indicates which event nr the report_value function must return.
static UInt event_report_value_id;
static ULong XT_Full_report_value(const void* xtfull)
{
   const XT_Full* xtf = xtfull;   
   switch (event_report_value_id) {
      case 0: return (ULong) xtf->cur_alloc_nbytes;
      case 1: return (ULong) xtf->cur_alloc_nblocks;
      case 2: return xtf->tot_alloc_nbytes;
      case 3: return xtf->tot_alloc_nblocks;
      case 4: return xtf->tot_freed_nbytes;
      case 5: return xtf->tot_freed_nblocks;
      default: vg_assert(0);
   }
}
static ULong XT_Allocs_report_value(const void* xt_allocs)
{
   const XT_Allocs* xta = xt_allocs;   
   switch (event_report_value_id) {
      case 0: return (ULong) xta->nbytes;
      case 1: return (ULong) xta->nblocks;
      default: vg_assert(0);
   }
}

static void produce_report(XTree* xt, const HChar* filename,
                           const HChar* events,
                           const HChar* (*img_value) (const void* value),
                           ULong (*report_value)(const void* value))
{
   /* The user can control the kind of report using filename extension. */
   if (VG_(strstr)(filename, ".ms")) {
      /* If needed, some harcoded value below could become parameters. */
      MsFile* fp;
      Massif_Header header = (Massif_Header) {
         .snapshot_n    = 0,
         .time          = VG_(read_millisecond_timer)(),
         .sz_B          = 0ul,
         .extra_B       = 0ul,
         .stacks_B      = 0ul,
         .detailed      = True,
         .peak          = False,
         .top_node_desc = NULL,
         .sig_threshold = 0.00000000000001
         // Currently, we take a very small float value to not output
         // the 0 values, but still output all the rest.
      };

      // Variables to parse events
      HChar strtok_events[VG_(strlen)(events)+1];
      HChar* e;
      HChar* ssaveptr;

      fp = VG_(XT_massif_open)(filename,
                               "xtree.produce_report",
                               NULL,
                               "ms");

      event_report_value_id = 0;
      VG_(strcpy)(strtok_events, events);
      for (e = VG_(strtok_r) (strtok_events, ",", &ssaveptr); 
           e != NULL; 
           e = VG_(strtok_r) (NULL, ",", &ssaveptr)) {
         header.top_node_desc = e;
         VG_(XT_massif_print)(fp, xt, &header, report_value);
         header.snapshot_n++;
         event_report_value_id++;
      }

      VG_(XT_massif_close)(fp);
   } else
      VG_(XT_callgrind_print)(xt,
                             filename,
                             events,
                             img_value);
}

void VG_(XTMemory_report) 
     (const HChar* filename, Bool fini,
      void (*next_block)(XT_Allocs* xta, ExeContext** ec_alloc),
      XT_filter_IPs_t filter_IPs_fn)
{
   HChar* expanded_filename;

   if (fini && VG_(clo_xtree_memory) == Vg_XTMemory_None)
      return;

   expanded_filename 
      = VG_(expand_file_name)("--xtree-memory-file",
                              (filename == NULL) ?
                              (fini ? 
                               VG_(clo_xtree_memory_file)
                               : "xtmemory.kcg.%p.%n")
                              : filename);

   /* fini is False => even if user kept --xtree-memory=none, we
      produce a report when explicitely requested e.g. via a monitor
      command. */
   switch (VG_(clo_xtree_memory)) {
      case Vg_XTMemory_None:
      case Vg_XTMemory_Allocs: {
         XTree* xt;
         XT_Allocs  xta;
         ExeContext* ec_alloc;

         xt = VG_(XT_create) (VG_(malloc),
                              "VG_(XTMemory_report)",
                              VG_(free),
                              sizeof(XT_Allocs),
                              VG_(XT_Allocs_init),
                              VG_(XT_Allocs_add),
                              VG_(XT_Allocs_sub),
                              filter_IPs_fn);
         (*next_block)(&xta, &ec_alloc);
         while ( xta.nblocks > 0 ) {
            VG_(XT_add_to_ec) (xt, ec_alloc, &xta);
            (*next_block)(&xta, &ec_alloc);
         }

         produce_report(xt, expanded_filename,
                        XT_Allocs_events, VG_(XT_Allocs_img),
                        XT_Allocs_report_value);

         VG_(XT_delete)(xt);
         break;
      }
      case Vg_XTMemory_Full:
         produce_report(full_xt, expanded_filename,
                        XT_Full_events, VG_(XT_Full_img),
                        XT_Full_report_value);
         break;
      default: 
         vg_assert(0);
   }
   if (VG_(clo_verbosity) >= 1 || !fini)
      VG_(umsg)("xtree memory report: %s\n", expanded_filename);

   VG_(free)(expanded_filename);
}

/*--------------------------------------------------------------------*/
/*--- end                                                m_xtree.c ---*/
/*--------------------------------------------------------------------*/