// Copyright 2009-2010 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "v8.h"

#include "heap-profiler.h"
#include "frames-inl.h"
#include "global-handles.h"
#include "profile-generator.h"
#include "string-stream.h"

namespace v8 {
namespace internal {


#ifdef ENABLE_LOGGING_AND_PROFILING
namespace {

// Clusterizer is a set of helper functions for converting
// object references into clusters.
class Clusterizer : public AllStatic {
 public:
  static JSObjectsCluster Clusterize(HeapObject* obj) {
    return Clusterize(obj, true);
  }
  static void InsertIntoTree(JSObjectsClusterTree* tree,
                             HeapObject* obj, bool fine_grain);
  static void InsertReferenceIntoTree(JSObjectsClusterTree* tree,
                                      const JSObjectsCluster& cluster) {
    InsertIntoTree(tree, cluster, 0);
  }

 private:
  static JSObjectsCluster Clusterize(HeapObject* obj, bool fine_grain);
  static int CalculateNetworkSize(JSObject* obj);
  static int GetObjectSize(HeapObject* obj) {
    return obj->IsJSObject() ?
        CalculateNetworkSize(JSObject::cast(obj)) : obj->Size();
  }
  static void InsertIntoTree(JSObjectsClusterTree* tree,
                             const JSObjectsCluster& cluster, int size);
};


JSObjectsCluster Clusterizer::Clusterize(HeapObject* obj, bool fine_grain) {
  if (obj->IsJSObject()) {
    JSObject* js_obj = JSObject::cast(obj);
    String* constructor = GetConstructorNameForHeapProfile(
        JSObject::cast(js_obj));
    // Differentiate Object and Array instances.
    if (fine_grain && (constructor == HEAP->Object_symbol() ||
                       constructor == HEAP->Array_symbol())) {
      return JSObjectsCluster(constructor, obj);
    } else {
      return JSObjectsCluster(constructor);
    }
  } else if (obj->IsString()) {
    return JSObjectsCluster(HEAP->String_symbol());
  } else if (obj->IsJSGlobalPropertyCell()) {
    return JSObjectsCluster(JSObjectsCluster::GLOBAL_PROPERTY);
  } else if (obj->IsCode() || obj->IsSharedFunctionInfo() || obj->IsScript()) {
    return JSObjectsCluster(JSObjectsCluster::CODE);
  }
  return JSObjectsCluster();
}


void Clusterizer::InsertIntoTree(JSObjectsClusterTree* tree,
                                 HeapObject* obj, bool fine_grain) {
  JSObjectsCluster cluster = Clusterize(obj, fine_grain);
  if (cluster.is_null()) return;
  InsertIntoTree(tree, cluster, GetObjectSize(obj));
}


void Clusterizer::InsertIntoTree(JSObjectsClusterTree* tree,
                                 const JSObjectsCluster& cluster, int size) {
  JSObjectsClusterTree::Locator loc;
  tree->Insert(cluster, &loc);
  NumberAndSizeInfo number_and_size = loc.value();
  number_and_size.increment_number(1);
  number_and_size.increment_bytes(size);
  loc.set_value(number_and_size);
}


int Clusterizer::CalculateNetworkSize(JSObject* obj) {
  int size = obj->Size();
  // If 'properties' and 'elements' are non-empty (thus, non-shared),
  // take their size into account.
  if (obj->properties() != HEAP->empty_fixed_array()) {
    size += obj->properties()->Size();
  }
  if (obj->elements() != HEAP->empty_fixed_array()) {
    size += obj->elements()->Size();
  }
  // For functions, also account non-empty context and literals sizes.
  if (obj->IsJSFunction()) {
    JSFunction* f = JSFunction::cast(obj);
    if (f->unchecked_context()->IsContext()) {
      size += f->context()->Size();
    }
    if (f->literals()->length() != 0) {
      size += f->literals()->Size();
    }
  }
  return size;
}


// A helper class for recording back references.
class ReferencesExtractor : public ObjectVisitor {
 public:
  ReferencesExtractor(const JSObjectsCluster& cluster,
                      RetainerHeapProfile* profile)
      : cluster_(cluster),
        profile_(profile),
        inside_array_(false) {
  }

  void VisitPointer(Object** o) {
    if ((*o)->IsFixedArray() && !inside_array_) {
      // Traverse one level deep for data members that are fixed arrays.
      // This covers the case of 'elements' and 'properties' of JSObject,
      // and function contexts.
      inside_array_ = true;
      FixedArray::cast(*o)->Iterate(this);
      inside_array_ = false;
    } else if ((*o)->IsHeapObject()) {
      profile_->StoreReference(cluster_, HeapObject::cast(*o));
    }
  }

  void VisitPointers(Object** start, Object** end) {
    for (Object** p = start; p < end; p++) VisitPointer(p);
  }

 private:
  const JSObjectsCluster& cluster_;
  RetainerHeapProfile* profile_;
  bool inside_array_;
};


// A printer interface implementation for the Retainers profile.
class RetainersPrinter : public RetainerHeapProfile::Printer {
 public:
  void PrintRetainers(const JSObjectsCluster& cluster,
                      const StringStream& retainers) {
    HeapStringAllocator allocator;
    StringStream stream(&allocator);
    cluster.Print(&stream);
    LOG(ISOLATE,
        HeapSampleJSRetainersEvent(
        *(stream.ToCString()), *(retainers.ToCString())));
  }
};


// Visitor for printing a cluster tree.
class ClusterTreePrinter BASE_EMBEDDED {
 public:
  explicit ClusterTreePrinter(StringStream* stream) : stream_(stream) {}
  void Call(const JSObjectsCluster& cluster,
            const NumberAndSizeInfo& number_and_size) {
    Print(stream_, cluster, number_and_size);
  }
  static void Print(StringStream* stream,
                    const JSObjectsCluster& cluster,
                    const NumberAndSizeInfo& number_and_size);

 private:
  StringStream* stream_;
};


void ClusterTreePrinter::Print(StringStream* stream,
                               const JSObjectsCluster& cluster,
                               const NumberAndSizeInfo& number_and_size) {
  stream->Put(',');
  cluster.Print(stream);
  stream->Add(";%d", number_and_size.number());
}


// Visitor for printing a retainer tree.
class SimpleRetainerTreePrinter BASE_EMBEDDED {
 public:
  explicit SimpleRetainerTreePrinter(RetainerHeapProfile::Printer* printer)
      : printer_(printer) {}
  void Call(const JSObjectsCluster& cluster, JSObjectsClusterTree* tree);

 private:
  RetainerHeapProfile::Printer* printer_;
};


void SimpleRetainerTreePrinter::Call(const JSObjectsCluster& cluster,
                                     JSObjectsClusterTree* tree) {
  HeapStringAllocator allocator;
  StringStream stream(&allocator);
  ClusterTreePrinter retainers_printer(&stream);
  tree->ForEach(&retainers_printer);
  printer_->PrintRetainers(cluster, stream);
}


// Visitor for aggregating references count of equivalent clusters.
class RetainersAggregator BASE_EMBEDDED {
 public:
  RetainersAggregator(ClustersCoarser* coarser, JSObjectsClusterTree* dest_tree)
      : coarser_(coarser), dest_tree_(dest_tree) {}
  void Call(const JSObjectsCluster& cluster,
            const NumberAndSizeInfo& number_and_size);

 private:
  ClustersCoarser* coarser_;
  JSObjectsClusterTree* dest_tree_;
};


void RetainersAggregator::Call(const JSObjectsCluster& cluster,
                               const NumberAndSizeInfo& number_and_size) {
  JSObjectsCluster eq = coarser_->GetCoarseEquivalent(cluster);
  if (eq.is_null()) eq = cluster;
  JSObjectsClusterTree::Locator loc;
  dest_tree_->Insert(eq, &loc);
  NumberAndSizeInfo aggregated_number = loc.value();
  aggregated_number.increment_number(number_and_size.number());
  loc.set_value(aggregated_number);
}


// Visitor for printing retainers tree. Aggregates equivalent retainer clusters.
class AggregatingRetainerTreePrinter BASE_EMBEDDED {
 public:
  AggregatingRetainerTreePrinter(ClustersCoarser* coarser,
                                 RetainerHeapProfile::Printer* printer)
      : coarser_(coarser), printer_(printer) {}
  void Call(const JSObjectsCluster& cluster, JSObjectsClusterTree* tree);

 private:
  ClustersCoarser* coarser_;
  RetainerHeapProfile::Printer* printer_;
};


void AggregatingRetainerTreePrinter::Call(const JSObjectsCluster& cluster,
                                          JSObjectsClusterTree* tree) {
  if (!coarser_->GetCoarseEquivalent(cluster).is_null()) return;
  JSObjectsClusterTree dest_tree_;
  RetainersAggregator retainers_aggregator(coarser_, &dest_tree_);
  tree->ForEach(&retainers_aggregator);
  HeapStringAllocator allocator;
  StringStream stream(&allocator);
  ClusterTreePrinter retainers_printer(&stream);
  dest_tree_.ForEach(&retainers_printer);
  printer_->PrintRetainers(cluster, stream);
}

}  // namespace


// A helper class for building a retainers tree, that aggregates
// all equivalent clusters.
class RetainerTreeAggregator {
 public:
  explicit RetainerTreeAggregator(ClustersCoarser* coarser)
      : coarser_(coarser) {}
  void Process(JSObjectsRetainerTree* input_tree) {
    input_tree->ForEach(this);
  }
  void Call(const JSObjectsCluster& cluster, JSObjectsClusterTree* tree);
  JSObjectsRetainerTree& output_tree() { return output_tree_; }

 private:
  ClustersCoarser* coarser_;
  JSObjectsRetainerTree output_tree_;
};


void RetainerTreeAggregator::Call(const JSObjectsCluster& cluster,
                                  JSObjectsClusterTree* tree) {
  JSObjectsCluster eq = coarser_->GetCoarseEquivalent(cluster);
  if (eq.is_null()) return;
  JSObjectsRetainerTree::Locator loc;
  if (output_tree_.Insert(eq, &loc)) {
    loc.set_value(new JSObjectsClusterTree());
  }
  RetainersAggregator retainers_aggregator(coarser_, loc.value());
  tree->ForEach(&retainers_aggregator);
}


HeapProfiler::HeapProfiler()
    : snapshots_(new HeapSnapshotsCollection()),
      next_snapshot_uid_(1) {
}


HeapProfiler::~HeapProfiler() {
  delete snapshots_;
}


void HeapProfiler::ResetSnapshots() {
  delete snapshots_;
  snapshots_ = new HeapSnapshotsCollection();
}


#endif  // ENABLE_LOGGING_AND_PROFILING

void HeapProfiler::Setup() {
#ifdef ENABLE_LOGGING_AND_PROFILING
  Isolate* isolate = Isolate::Current();
  if (isolate->heap_profiler() == NULL) {
    isolate->set_heap_profiler(new HeapProfiler());
  }
#endif
}


void HeapProfiler::TearDown() {
#ifdef ENABLE_LOGGING_AND_PROFILING
  Isolate* isolate = Isolate::Current();
  delete isolate->heap_profiler();
  isolate->set_heap_profiler(NULL);
#endif
}


#ifdef ENABLE_LOGGING_AND_PROFILING

HeapSnapshot* HeapProfiler::TakeSnapshot(const char* name,
                                         int type,
                                         v8::ActivityControl* control) {
  ASSERT(Isolate::Current()->heap_profiler() != NULL);
  return Isolate::Current()->heap_profiler()->TakeSnapshotImpl(name,
                                                               type,
                                                               control);
}


HeapSnapshot* HeapProfiler::TakeSnapshot(String* name,
                                         int type,
                                         v8::ActivityControl* control) {
  ASSERT(Isolate::Current()->heap_profiler() != NULL);
  return Isolate::Current()->heap_profiler()->TakeSnapshotImpl(name,
                                                               type,
                                                               control);
}


void HeapProfiler::DefineWrapperClass(
    uint16_t class_id, v8::HeapProfiler::WrapperInfoCallback callback) {
  ASSERT(class_id != v8::HeapProfiler::kPersistentHandleNoClassId);
  if (wrapper_callbacks_.length() <= class_id) {
    wrapper_callbacks_.AddBlock(
        NULL, class_id - wrapper_callbacks_.length() + 1);
  }
  wrapper_callbacks_[class_id] = callback;
}


v8::RetainedObjectInfo* HeapProfiler::ExecuteWrapperClassCallback(
    uint16_t class_id, Object** wrapper) {
  if (wrapper_callbacks_.length() <= class_id) return NULL;
  return wrapper_callbacks_[class_id](
      class_id, Utils::ToLocal(Handle<Object>(wrapper)));
}


HeapSnapshot* HeapProfiler::TakeSnapshotImpl(const char* name,
                                             int type,
                                             v8::ActivityControl* control) {
  HeapSnapshot::Type s_type = static_cast<HeapSnapshot::Type>(type);
  HeapSnapshot* result =
      snapshots_->NewSnapshot(s_type, name, next_snapshot_uid_++);
  bool generation_completed = true;
  switch (s_type) {
    case HeapSnapshot::kFull: {
      HEAP->CollectAllGarbage(true);
      HeapSnapshotGenerator generator(result, control);
      generation_completed = generator.GenerateSnapshot();
      break;
    }
    case HeapSnapshot::kAggregated: {
      HEAP->CollectAllGarbage(true);
      AggregatedHeapSnapshot agg_snapshot;
      AggregatedHeapSnapshotGenerator generator(&agg_snapshot);
      generator.GenerateSnapshot();
      generator.FillHeapSnapshot(result);
      break;
    }
    default:
      UNREACHABLE();
  }
  if (!generation_completed) {
    delete result;
    result = NULL;
  }
  snapshots_->SnapshotGenerationFinished(result);
  return result;
}


HeapSnapshot* HeapProfiler::TakeSnapshotImpl(String* name,
                                             int type,
                                             v8::ActivityControl* control) {
  return TakeSnapshotImpl(snapshots_->names()->GetName(name), type, control);
}


int HeapProfiler::GetSnapshotsCount() {
  HeapProfiler* profiler = Isolate::Current()->heap_profiler();
  ASSERT(profiler != NULL);
  return profiler->snapshots_->snapshots()->length();
}


HeapSnapshot* HeapProfiler::GetSnapshot(int index) {
  HeapProfiler* profiler = Isolate::Current()->heap_profiler();
  ASSERT(profiler != NULL);
  return profiler->snapshots_->snapshots()->at(index);
}


HeapSnapshot* HeapProfiler::FindSnapshot(unsigned uid) {
  HeapProfiler* profiler = Isolate::Current()->heap_profiler();
  ASSERT(profiler != NULL);
  return profiler->snapshots_->GetSnapshot(uid);
}


void HeapProfiler::DeleteAllSnapshots() {
  HeapProfiler* profiler = Isolate::Current()->heap_profiler();
  ASSERT(profiler != NULL);
  profiler->ResetSnapshots();
}


void HeapProfiler::ObjectMoveEvent(Address from, Address to) {
  snapshots_->ObjectMoveEvent(from, to);
}


const JSObjectsClusterTreeConfig::Key JSObjectsClusterTreeConfig::kNoKey;
const JSObjectsClusterTreeConfig::Value JSObjectsClusterTreeConfig::kNoValue;


ConstructorHeapProfile::ConstructorHeapProfile()
    : zscope_(Isolate::Current(), DELETE_ON_EXIT) {
}


void ConstructorHeapProfile::Call(const JSObjectsCluster& cluster,
                                  const NumberAndSizeInfo& number_and_size) {
  HeapStringAllocator allocator;
  StringStream stream(&allocator);
  cluster.Print(&stream);
  LOG(ISOLATE,
      HeapSampleJSConstructorEvent(*(stream.ToCString()),
                                   number_and_size.number(),
                                   number_and_size.bytes()));
}


void ConstructorHeapProfile::CollectStats(HeapObject* obj) {
  Clusterizer::InsertIntoTree(&js_objects_info_tree_, obj, false);
}


void ConstructorHeapProfile::PrintStats() {
  js_objects_info_tree_.ForEach(this);
}


static const char* GetConstructorName(const char* name) {
  return name[0] != '\0' ? name : "(anonymous)";
}


const char* JSObjectsCluster::GetSpecialCaseName() const {
  if (constructor_ == FromSpecialCase(ROOTS)) {
    return "(roots)";
  } else if (constructor_ == FromSpecialCase(GLOBAL_PROPERTY)) {
    return "(global property)";
  } else if (constructor_ == FromSpecialCase(CODE)) {
    return "(code)";
  } else if (constructor_ == FromSpecialCase(SELF)) {
    return "(self)";
  }
  return NULL;
}


void JSObjectsCluster::Print(StringStream* accumulator) const {
  ASSERT(!is_null());
  const char* special_case_name = GetSpecialCaseName();
  if (special_case_name != NULL) {
    accumulator->Add(special_case_name);
  } else {
    SmartPointer<char> s_name(
        constructor_->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL));
    accumulator->Add("%s", GetConstructorName(*s_name));
    if (instance_ != NULL) {
      accumulator->Add(":%p", static_cast<void*>(instance_));
    }
  }
}


void JSObjectsCluster::DebugPrint(StringStream* accumulator) const {
  if (!is_null()) {
    Print(accumulator);
  } else {
    accumulator->Add("(null cluster)");
  }
}


inline ClustersCoarser::ClusterBackRefs::ClusterBackRefs(
    const JSObjectsCluster& cluster_)
    : cluster(cluster_), refs(kInitialBackrefsListCapacity) {
}


inline ClustersCoarser::ClusterBackRefs::ClusterBackRefs(
    const ClustersCoarser::ClusterBackRefs& src)
    : cluster(src.cluster), refs(src.refs.capacity()) {
  refs.AddAll(src.refs);
}


inline ClustersCoarser::ClusterBackRefs&
    ClustersCoarser::ClusterBackRefs::operator=(
    const ClustersCoarser::ClusterBackRefs& src) {
  if (this == &src) return *this;
  cluster = src.cluster;
  refs.Clear();
  refs.AddAll(src.refs);
  return *this;
}


inline int ClustersCoarser::ClusterBackRefs::Compare(
    const ClustersCoarser::ClusterBackRefs& a,
    const ClustersCoarser::ClusterBackRefs& b) {
  int cmp = JSObjectsCluster::CompareConstructors(a.cluster, b.cluster);
  if (cmp != 0) return cmp;
  if (a.refs.length() < b.refs.length()) return -1;
  if (a.refs.length() > b.refs.length()) return 1;
  for (int i = 0; i < a.refs.length(); ++i) {
    int cmp = JSObjectsCluster::Compare(a.refs[i], b.refs[i]);
    if (cmp != 0) return cmp;
  }
  return 0;
}


ClustersCoarser::ClustersCoarser()
    : zscope_(Isolate::Current(), DELETE_ON_EXIT),
      sim_list_(ClustersCoarser::kInitialSimilarityListCapacity),
      current_pair_(NULL),
      current_set_(NULL),
      self_(NULL) {
}


void ClustersCoarser::Call(const JSObjectsCluster& cluster,
                           JSObjectsClusterTree* tree) {
  if (!cluster.can_be_coarsed()) return;
  ClusterBackRefs pair(cluster);
  ASSERT(current_pair_ == NULL);
  current_pair_ = &pair;
  current_set_ = new JSObjectsRetainerTree();
  self_ = &cluster;
  tree->ForEach(this);
  sim_list_.Add(pair);
  current_pair_ = NULL;
  current_set_ = NULL;
  self_ = NULL;
}


void ClustersCoarser::Call(const JSObjectsCluster& cluster,
                           const NumberAndSizeInfo& number_and_size) {
  ASSERT(current_pair_ != NULL);
  ASSERT(current_set_ != NULL);
  ASSERT(self_ != NULL);
  JSObjectsRetainerTree::Locator loc;
  if (JSObjectsCluster::Compare(*self_, cluster) == 0) {
    current_pair_->refs.Add(JSObjectsCluster(JSObjectsCluster::SELF));
    return;
  }
  JSObjectsCluster eq = GetCoarseEquivalent(cluster);
  if (!eq.is_null()) {
    if (current_set_->Find(eq, &loc)) return;
    current_pair_->refs.Add(eq);
    current_set_->Insert(eq, &loc);
  } else {
    current_pair_->refs.Add(cluster);
  }
}


void ClustersCoarser::Process(JSObjectsRetainerTree* tree) {
  int last_eq_clusters = -1;
  for (int i = 0; i < kMaxPassesCount; ++i) {
    sim_list_.Clear();
    const int curr_eq_clusters = DoProcess(tree);
    // If no new cluster equivalents discovered, abort processing.
    if (last_eq_clusters == curr_eq_clusters) break;
    last_eq_clusters = curr_eq_clusters;
  }
}


int ClustersCoarser::DoProcess(JSObjectsRetainerTree* tree) {
  tree->ForEach(this);
  sim_list_.Iterate(ClusterBackRefs::SortRefsIterator);
  sim_list_.Sort(ClusterBackRefsCmp);
  return FillEqualityTree();
}


JSObjectsCluster ClustersCoarser::GetCoarseEquivalent(
    const JSObjectsCluster& cluster) {
  if (!cluster.can_be_coarsed()) return JSObjectsCluster();
  EqualityTree::Locator loc;
  return eq_tree_.Find(cluster, &loc) ? loc.value() : JSObjectsCluster();
}


bool ClustersCoarser::HasAnEquivalent(const JSObjectsCluster& cluster) {
  // Return true for coarsible clusters that have a non-identical equivalent.
  if (!cluster.can_be_coarsed()) return false;
  JSObjectsCluster eq = GetCoarseEquivalent(cluster);
  return !eq.is_null() && JSObjectsCluster::Compare(cluster, eq) != 0;
}


int ClustersCoarser::FillEqualityTree() {
  int eq_clusters_count = 0;
  int eq_to = 0;
  bool first_added = false;
  for (int i = 1; i < sim_list_.length(); ++i) {
    if (ClusterBackRefs::Compare(sim_list_[i], sim_list_[eq_to]) == 0) {
      EqualityTree::Locator loc;
      if (!first_added) {
        // Add self-equivalence, if we have more than one item in this
        // equivalence class.
        eq_tree_.Insert(sim_list_[eq_to].cluster, &loc);
        loc.set_value(sim_list_[eq_to].cluster);
        first_added = true;
      }
      eq_tree_.Insert(sim_list_[i].cluster, &loc);
      loc.set_value(sim_list_[eq_to].cluster);
      ++eq_clusters_count;
    } else {
      eq_to = i;
      first_added = false;
    }
  }
  return eq_clusters_count;
}


const JSObjectsCluster ClustersCoarser::ClusterEqualityConfig::kNoKey;
const JSObjectsCluster ClustersCoarser::ClusterEqualityConfig::kNoValue;
const JSObjectsRetainerTreeConfig::Key JSObjectsRetainerTreeConfig::kNoKey;
const JSObjectsRetainerTreeConfig::Value JSObjectsRetainerTreeConfig::kNoValue =
    NULL;


RetainerHeapProfile::RetainerHeapProfile()
    : zscope_(Isolate::Current(), DELETE_ON_EXIT),
      aggregator_(NULL) {
  JSObjectsCluster roots(JSObjectsCluster::ROOTS);
  ReferencesExtractor extractor(roots, this);
  HEAP->IterateRoots(&extractor, VISIT_ONLY_STRONG);
}


RetainerHeapProfile::~RetainerHeapProfile() {
  delete aggregator_;
}


void RetainerHeapProfile::StoreReference(const JSObjectsCluster& cluster,
                                         HeapObject* ref) {
  JSObjectsCluster ref_cluster = Clusterizer::Clusterize(ref);
  if (ref_cluster.is_null()) return;
  JSObjectsRetainerTree::Locator ref_loc;
  if (retainers_tree_.Insert(ref_cluster, &ref_loc)) {
    ref_loc.set_value(new JSObjectsClusterTree());
  }
  JSObjectsClusterTree* referenced_by = ref_loc.value();
  Clusterizer::InsertReferenceIntoTree(referenced_by, cluster);
}


void RetainerHeapProfile::CollectStats(HeapObject* obj) {
  const JSObjectsCluster cluster = Clusterizer::Clusterize(obj);
  if (cluster.is_null()) return;
  ReferencesExtractor extractor(cluster, this);
  obj->Iterate(&extractor);
}


void RetainerHeapProfile::CoarseAndAggregate() {
  coarser_.Process(&retainers_tree_);
  ASSERT(aggregator_ == NULL);
  aggregator_ = new RetainerTreeAggregator(&coarser_);
  aggregator_->Process(&retainers_tree_);
}


void RetainerHeapProfile::DebugPrintStats(
    RetainerHeapProfile::Printer* printer) {
  // Print clusters that have no equivalents, aggregating their retainers.
  AggregatingRetainerTreePrinter agg_printer(&coarser_, printer);
  retainers_tree_.ForEach(&agg_printer);
  // Print clusters that have equivalents.
  SimpleRetainerTreePrinter s_printer(printer);
  aggregator_->output_tree().ForEach(&s_printer);
}


void RetainerHeapProfile::PrintStats() {
  RetainersPrinter printer;
  DebugPrintStats(&printer);
}


//
// HeapProfiler class implementation.
//
static void StackWeakReferenceCallback(Persistent<Value> object,
                                       void* trace) {
  DeleteArray(static_cast<Address*>(trace));
  object.Dispose();
}


static void PrintProducerStackTrace(Object* obj, void* trace) {
  if (!obj->IsJSObject()) return;
  String* constructor = GetConstructorNameForHeapProfile(JSObject::cast(obj));
  SmartPointer<char> s_name(
      constructor->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL));
  LOG(ISOLATE,
      HeapSampleJSProducerEvent(GetConstructorName(*s_name),
                                reinterpret_cast<Address*>(trace)));
}


void HeapProfiler::WriteSample() {
  Isolate* isolate = Isolate::Current();
  LOG(isolate, HeapSampleBeginEvent("Heap", "allocated"));
  LOG(isolate,
      HeapSampleStats(
          "Heap", "allocated", HEAP->CommittedMemory(), HEAP->SizeOfObjects()));

  AggregatedHeapSnapshot snapshot;
  AggregatedHeapSnapshotGenerator generator(&snapshot);
  generator.GenerateSnapshot();

  HistogramInfo* info = snapshot.info();
  for (int i = FIRST_NONSTRING_TYPE;
       i <= AggregatedHeapSnapshotGenerator::kAllStringsType;
       ++i) {
    if (info[i].bytes() > 0) {
      LOG(isolate,
          HeapSampleItemEvent(info[i].name(), info[i].number(),
                              info[i].bytes()));
    }
  }

  snapshot.js_cons_profile()->PrintStats();
  snapshot.js_retainer_profile()->PrintStats();

  isolate->global_handles()->IterateWeakRoots(PrintProducerStackTrace,
                                              StackWeakReferenceCallback);

  LOG(isolate, HeapSampleEndEvent("Heap", "allocated"));
}


AggregatedHeapSnapshot::AggregatedHeapSnapshot()
    : info_(NewArray<HistogramInfo>(
        AggregatedHeapSnapshotGenerator::kAllStringsType + 1)) {
#define DEF_TYPE_NAME(name) info_[name].set_name(#name);
  INSTANCE_TYPE_LIST(DEF_TYPE_NAME);
#undef DEF_TYPE_NAME
  info_[AggregatedHeapSnapshotGenerator::kAllStringsType].set_name(
      "STRING_TYPE");
}


AggregatedHeapSnapshot::~AggregatedHeapSnapshot() {
  DeleteArray(info_);
}


AggregatedHeapSnapshotGenerator::AggregatedHeapSnapshotGenerator(
    AggregatedHeapSnapshot* agg_snapshot)
    : agg_snapshot_(agg_snapshot) {
}


void AggregatedHeapSnapshotGenerator::CalculateStringsStats() {
  HistogramInfo* info = agg_snapshot_->info();
  HistogramInfo& strings = info[kAllStringsType];
  // Lump all the string types together.
#define INCREMENT_SIZE(type, size, name, camel_name)   \
  strings.increment_number(info[type].number());       \
  strings.increment_bytes(info[type].bytes());
  STRING_TYPE_LIST(INCREMENT_SIZE);
#undef INCREMENT_SIZE
}


void AggregatedHeapSnapshotGenerator::CollectStats(HeapObject* obj) {
  InstanceType type = obj->map()->instance_type();
  ASSERT(0 <= type && type <= LAST_TYPE);
  agg_snapshot_->info()[type].increment_number(1);
  agg_snapshot_->info()[type].increment_bytes(obj->Size());
}


void AggregatedHeapSnapshotGenerator::GenerateSnapshot() {
  HeapIterator iterator(HeapIterator::kFilterUnreachable);
  for (HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next()) {
    CollectStats(obj);
    agg_snapshot_->js_cons_profile()->CollectStats(obj);
    agg_snapshot_->js_retainer_profile()->CollectStats(obj);
  }
  CalculateStringsStats();
  agg_snapshot_->js_retainer_profile()->CoarseAndAggregate();
}


class CountingConstructorHeapProfileIterator {
 public:
  CountingConstructorHeapProfileIterator()
      : entities_count_(0), children_count_(0) {
  }

  void Call(const JSObjectsCluster& cluster,
            const NumberAndSizeInfo& number_and_size) {
    ++entities_count_;
    children_count_ += number_and_size.number();
  }

  int entities_count() { return entities_count_; }
  int children_count() { return children_count_; }

 private:
  int entities_count_;
  int children_count_;
};


static HeapEntry* AddEntryFromAggregatedSnapshot(HeapSnapshot* snapshot,
                                                 int* root_child_index,
                                                 HeapEntry::Type type,
                                                 const char* name,
                                                 int count,
                                                 int size,
                                                 int children_count,
                                                 int retainers_count) {
  HeapEntry* entry = snapshot->AddEntry(
      type, name, count, size, children_count, retainers_count);
  ASSERT(entry != NULL);
  snapshot->root()->SetUnidirElementReference(*root_child_index,
                                              *root_child_index + 1,
                                              entry);
  *root_child_index = *root_child_index + 1;
  return entry;
}


class AllocatingConstructorHeapProfileIterator {
 public:
  AllocatingConstructorHeapProfileIterator(HeapSnapshot* snapshot,
                                  int* root_child_index)
      : snapshot_(snapshot),
        root_child_index_(root_child_index) {
  }

  void Call(const JSObjectsCluster& cluster,
            const NumberAndSizeInfo& number_and_size) {
    const char* name = cluster.GetSpecialCaseName();
    if (name == NULL) {
      name = snapshot_->collection()->names()->GetFunctionName(
          cluster.constructor());
    }
    AddEntryFromAggregatedSnapshot(snapshot_,
                                   root_child_index_,
                                   HeapEntry::kObject,
                                   name,
                                   number_and_size.number(),
                                   number_and_size.bytes(),
                                   0,
                                   0);
  }

 private:
  HeapSnapshot* snapshot_;
  int* root_child_index_;
};


static HeapObject* ClusterAsHeapObject(const JSObjectsCluster& cluster) {
  return cluster.can_be_coarsed() ?
      reinterpret_cast<HeapObject*>(cluster.instance()) : cluster.constructor();
}


static JSObjectsCluster HeapObjectAsCluster(HeapObject* object) {
  if (object->IsString()) {
    return JSObjectsCluster(String::cast(object));
  } else {
    JSObject* js_obj = JSObject::cast(object);
    String* constructor = GetConstructorNameForHeapProfile(
        JSObject::cast(js_obj));
    return JSObjectsCluster(constructor, object);
  }
}


class CountingRetainersIterator {
 public:
  CountingRetainersIterator(const JSObjectsCluster& child_cluster,
                            HeapEntriesAllocator* allocator,
                            HeapEntriesMap* map)
      : child_(ClusterAsHeapObject(child_cluster)),
        allocator_(allocator),
        map_(map) {
    if (map_->Map(child_) == NULL)
      map_->Pair(child_, allocator_, HeapEntriesMap::kHeapEntryPlaceholder);
  }

  void Call(const JSObjectsCluster& cluster,
            const NumberAndSizeInfo& number_and_size) {
    if (map_->Map(ClusterAsHeapObject(cluster)) == NULL)
      map_->Pair(ClusterAsHeapObject(cluster),
                 allocator_,
                 HeapEntriesMap::kHeapEntryPlaceholder);
    map_->CountReference(ClusterAsHeapObject(cluster), child_);
  }

 private:
  HeapObject* child_;
  HeapEntriesAllocator* allocator_;
  HeapEntriesMap* map_;
};


class AllocatingRetainersIterator {
 public:
  AllocatingRetainersIterator(const JSObjectsCluster& child_cluster,
                              HeapEntriesAllocator*,
                              HeapEntriesMap* map)
      : child_(ClusterAsHeapObject(child_cluster)), map_(map) {
    child_entry_ = map_->Map(child_);
    ASSERT(child_entry_ != NULL);
  }

  void Call(const JSObjectsCluster& cluster,
            const NumberAndSizeInfo& number_and_size) {
    int child_index, retainer_index;
    map_->CountReference(ClusterAsHeapObject(cluster),
                         child_,
                         &child_index,
                         &retainer_index);
    map_->Map(ClusterAsHeapObject(cluster))->SetIndexedReference(
        HeapGraphEdge::kElement,
        child_index,
        number_and_size.number(),
        child_entry_,
        retainer_index);
  }

 private:
  HeapObject* child_;
  HeapEntriesMap* map_;
  HeapEntry* child_entry_;
};


template<class RetainersIterator>
class AggregatingRetainerTreeIterator {
 public:
  explicit AggregatingRetainerTreeIterator(ClustersCoarser* coarser,
                                           HeapEntriesAllocator* allocator,
                                           HeapEntriesMap* map)
      : coarser_(coarser), allocator_(allocator), map_(map) {
  }

  void Call(const JSObjectsCluster& cluster, JSObjectsClusterTree* tree) {
    if (coarser_ != NULL &&
        !coarser_->GetCoarseEquivalent(cluster).is_null()) return;
    JSObjectsClusterTree* tree_to_iterate = tree;
    ZoneScope zs(Isolate::Current(), DELETE_ON_EXIT);
    JSObjectsClusterTree dest_tree_;
    if (coarser_ != NULL) {
      RetainersAggregator retainers_aggregator(coarser_, &dest_tree_);
      tree->ForEach(&retainers_aggregator);
      tree_to_iterate = &dest_tree_;
    }
    RetainersIterator iterator(cluster, allocator_, map_);
    tree_to_iterate->ForEach(&iterator);
  }

 private:
  ClustersCoarser* coarser_;
  HeapEntriesAllocator* allocator_;
  HeapEntriesMap* map_;
};


class AggregatedRetainerTreeAllocator : public HeapEntriesAllocator {
 public:
  AggregatedRetainerTreeAllocator(HeapSnapshot* snapshot,
                                  int* root_child_index)
      : snapshot_(snapshot), root_child_index_(root_child_index) {
  }
  ~AggregatedRetainerTreeAllocator() { }

  HeapEntry* AllocateEntry(
      HeapThing ptr, int children_count, int retainers_count) {
    HeapObject* obj = reinterpret_cast<HeapObject*>(ptr);
    JSObjectsCluster cluster = HeapObjectAsCluster(obj);
    const char* name = cluster.GetSpecialCaseName();
    if (name == NULL) {
      name = snapshot_->collection()->names()->GetFunctionName(
          cluster.constructor());
    }
    return AddEntryFromAggregatedSnapshot(
        snapshot_, root_child_index_, HeapEntry::kObject, name,
        0, 0, children_count, retainers_count);
  }

 private:
  HeapSnapshot* snapshot_;
  int* root_child_index_;
};


template<class Iterator>
void AggregatedHeapSnapshotGenerator::IterateRetainers(
    HeapEntriesAllocator* allocator, HeapEntriesMap* entries_map) {
  RetainerHeapProfile* p = agg_snapshot_->js_retainer_profile();
  AggregatingRetainerTreeIterator<Iterator> agg_ret_iter_1(
      p->coarser(), allocator, entries_map);
  p->retainers_tree()->ForEach(&agg_ret_iter_1);
  AggregatingRetainerTreeIterator<Iterator> agg_ret_iter_2(
      NULL, allocator, entries_map);
  p->aggregator()->output_tree().ForEach(&agg_ret_iter_2);
}


void AggregatedHeapSnapshotGenerator::FillHeapSnapshot(HeapSnapshot* snapshot) {
  // Count the number of entities.
  int histogram_entities_count = 0;
  int histogram_children_count = 0;
  int histogram_retainers_count = 0;
  for (int i = FIRST_NONSTRING_TYPE; i <= kAllStringsType; ++i) {
    if (agg_snapshot_->info()[i].bytes() > 0) {
      ++histogram_entities_count;
    }
  }
  CountingConstructorHeapProfileIterator counting_cons_iter;
  agg_snapshot_->js_cons_profile()->ForEach(&counting_cons_iter);
  histogram_entities_count += counting_cons_iter.entities_count();
  HeapEntriesMap entries_map;
  int root_child_index = 0;
  AggregatedRetainerTreeAllocator allocator(snapshot, &root_child_index);
  IterateRetainers<CountingRetainersIterator>(&allocator, &entries_map);
  histogram_entities_count += entries_map.entries_count();
  histogram_children_count += entries_map.total_children_count();
  histogram_retainers_count += entries_map.total_retainers_count();

  // Root entry references all other entries.
  histogram_children_count += histogram_entities_count;
  int root_children_count = histogram_entities_count;
  ++histogram_entities_count;

  // Allocate and fill entries in the snapshot, allocate references.
  snapshot->AllocateEntries(histogram_entities_count,
                            histogram_children_count,
                            histogram_retainers_count);
  snapshot->AddRootEntry(root_children_count);
  for (int i = FIRST_NONSTRING_TYPE; i <= kAllStringsType; ++i) {
    if (agg_snapshot_->info()[i].bytes() > 0) {
      AddEntryFromAggregatedSnapshot(snapshot,
                                     &root_child_index,
                                     HeapEntry::kHidden,
                                     agg_snapshot_->info()[i].name(),
                                     agg_snapshot_->info()[i].number(),
                                     agg_snapshot_->info()[i].bytes(),
                                     0,
                                     0);
    }
  }
  AllocatingConstructorHeapProfileIterator alloc_cons_iter(
      snapshot, &root_child_index);
  agg_snapshot_->js_cons_profile()->ForEach(&alloc_cons_iter);
  entries_map.AllocateEntries();

  // Fill up references.
  IterateRetainers<AllocatingRetainersIterator>(&allocator, &entries_map);

  snapshot->SetDominatorsToSelf();
}


void ProducerHeapProfile::Setup() {
  can_log_ = true;
}

void ProducerHeapProfile::DoRecordJSObjectAllocation(Object* obj) {
  ASSERT(FLAG_log_producers);
  if (!can_log_) return;
  int framesCount = 0;
  for (JavaScriptFrameIterator it; !it.done(); it.Advance()) {
    ++framesCount;
  }
  if (framesCount == 0) return;
  ++framesCount;  // Reserve place for the terminator item.
  Vector<Address> stack(NewArray<Address>(framesCount), framesCount);
  int i = 0;
  for (JavaScriptFrameIterator it; !it.done(); it.Advance()) {
    stack[i++] = it.frame()->pc();
  }
  stack[i] = NULL;
  Handle<Object> handle = isolate_->global_handles()->Create(obj);
  isolate_->global_handles()->MakeWeak(handle.location(),
                                       static_cast<void*>(stack.start()),
                                       StackWeakReferenceCallback);
}


#endif  // ENABLE_LOGGING_AND_PROFILING


} }  // namespace v8::internal
