[tracing] Emit heap statistics at every GC.

When the 'disabled-by-default-v8.gc' category is enabled, emit an instant event
with heap statistics after every GC. The data that's emitted is the same as what
the V8 API gives you with `GetHeapStatistics()` and `GetHeapSpaceStatistics()`.

We generate JSON with the following format:

```
{
 "isolate": "0x55dd5cf03b50",
 "id": 1,
 "time_ms": 42.619,

 "total_heap_size": 3981312,
 "total_heap_size_executable": 573440,
 "total_physical_size": 2820440,
 "total_available_size": 2195254440,

 "used_heap_size": 1799616,
 "heap_size_limit": 2197815296,
 "malloced_memory": 251024,
 "external_memory": 2981,
 "peak_malloced_memory": 589280,

 "spaces": [
   {
     "name": "read_only_space",
     "size": 262144,
     "used_size": 32568,
     "available_size": 229256,
     "physical_size": 32888
   },
   {
     "name": "new_space",
     "size": 2097152,
     "used_size": 903392,
     "available_size": 143904,
     "physical_size": 1856136
   },
   ...
 ]
}
```

Bug: v8:9186
Change-Id: I0d07aa37b65d45778d6b47dbe6e07a9dd25d1097
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1619763
Reviewed-by: Ulan Degenbaev <ulan@chromium.org>
Commit-Queue: Pierre Langlois <pierre.langlois@arm.com>
Cr-Commit-Position: refs/heads/master@{#61667}
diff --git a/src/heap/gc-tracer.cc b/src/heap/gc-tracer.cc
index 9ae2435..bd25740 100644
--- a/src/heap/gc-tracer.cc
+++ b/src/heap/gc-tracer.cc
@@ -363,6 +363,16 @@
   if (FLAG_trace_gc) {
     heap_->PrintShortHeapStatistics();
   }
+
+  if (V8_UNLIKELY(TracingFlags::gc.load(std::memory_order_relaxed) &
+                  v8::tracing::TracingCategoryObserver::ENABLED_BY_TRACING)) {
+    std::stringstream heap_stats;
+    heap_->DumpJSONHeapStatistics(heap_stats);
+
+    TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("v8.gc"), "V8.GC_Heap_Stats",
+                         TRACE_EVENT_SCOPE_THREAD, "stats",
+                         TRACE_STR_COPY(heap_stats.str().c_str()));
+  }
 }
 
 
diff --git a/src/heap/heap.cc b/src/heap/heap.cc
index 6484b5e..ee2a4ce 100644
--- a/src/heap/heap.cc
+++ b/src/heap/heap.cc
@@ -442,6 +442,63 @@
                total_gc_time_ms_);
 }
 
+void Heap::DumpJSONHeapStatistics(std::stringstream& stream) {
+  HeapStatistics stats;
+  reinterpret_cast<v8::Isolate*>(isolate())->GetHeapStatistics(&stats);
+
+// clang-format off
+#define DICT(s) "{" << s << "}"
+#define LIST(s) "[" << s << "]"
+#define ESCAPE(s) "\"" << s << "\""
+#define MEMBER(s) ESCAPE(s) << ":"
+
+  auto SpaceStatistics = [this](int space_index) {
+    HeapSpaceStatistics space_stats;
+    reinterpret_cast<v8::Isolate*>(isolate())->GetHeapSpaceStatistics(
+        &space_stats, space_index);
+    std::stringstream stream;
+    stream << DICT(
+      MEMBER("name")
+        << ESCAPE(GetSpaceName(static_cast<AllocationSpace>(space_index)))
+        << ","
+      MEMBER("size") << space_stats.space_size() << ","
+      MEMBER("used_size") << space_stats.space_used_size() << ","
+      MEMBER("available_size") << space_stats.space_available_size() << ","
+      MEMBER("physical_size") << space_stats.physical_space_size());
+    return stream.str();
+  };
+
+  stream << DICT(
+    MEMBER("isolate") << ESCAPE(reinterpret_cast<void*>(isolate())) << ","
+    MEMBER("id") << gc_count() << ","
+    MEMBER("time_ms") << isolate()->time_millis_since_init() << ","
+    MEMBER("total_heap_size") << stats.total_heap_size() << ","
+    MEMBER("total_heap_size_executable")
+      << stats.total_heap_size_executable() << ","
+    MEMBER("total_physical_size") << stats.total_physical_size() << ","
+    MEMBER("total_available_size") << stats.total_available_size() << ","
+    MEMBER("used_heap_size") << stats.used_heap_size() << ","
+    MEMBER("heap_size_limit") << stats.heap_size_limit() << ","
+    MEMBER("malloced_memory") << stats.malloced_memory() << ","
+    MEMBER("external_memory") << stats.external_memory() << ","
+    MEMBER("peak_malloced_memory") << stats.peak_malloced_memory() << ","
+    MEMBER("pages") << LIST(
+      SpaceStatistics(RO_SPACE)      << "," <<
+      SpaceStatistics(NEW_SPACE)     << "," <<
+      SpaceStatistics(OLD_SPACE)     << "," <<
+      SpaceStatistics(CODE_SPACE)    << "," <<
+      SpaceStatistics(MAP_SPACE)     << "," <<
+      SpaceStatistics(LO_SPACE)      << "," <<
+      SpaceStatistics(CODE_LO_SPACE) << "," <<
+      SpaceStatistics(NEW_LO_SPACE)));
+
+#undef DICT
+#undef LIST
+#undef ESCAPE
+#undef MEMBER
+  // clang-format on
+}
+
 void Heap::ReportStatisticsAfterGC() {
   for (int i = 0; i < static_cast<int>(v8::Isolate::kUseCounterFeatureCount);
        ++i) {
diff --git a/src/heap/heap.h b/src/heap/heap.h
index c2d12f7..73c1d3f 100644
--- a/src/heap/heap.h
+++ b/src/heap/heap.h
@@ -474,6 +474,9 @@
   // Print short heap statistics.
   void PrintShortHeapStatistics();
 
+  // Dump heap statistics in JSON format.
+  void DumpJSONHeapStatistics(std::stringstream& stream);
+
   bool write_protect_code_memory() const { return write_protect_code_memory_; }
 
   uintptr_t code_space_memory_modification_scope_depth() {
diff --git a/src/logging/counters.cc b/src/logging/counters.cc
index 58f35e8..9feed03 100644
--- a/src/logging/counters.cc
+++ b/src/logging/counters.cc
@@ -18,6 +18,7 @@
 namespace internal {
 
 std::atomic_uint TracingFlags::runtime_stats{0};
+std::atomic_uint TracingFlags::gc{0};
 std::atomic_uint TracingFlags::gc_stats{0};
 std::atomic_uint TracingFlags::ic_stats{0};
 
diff --git a/src/logging/counters.h b/src/logging/counters.h
index e7fa916..2e21c9b 100644
--- a/src/logging/counters.h
+++ b/src/logging/counters.h
@@ -30,6 +30,7 @@
 
 struct TracingFlags {
   static V8_EXPORT_PRIVATE std::atomic_uint runtime_stats;
+  static V8_EXPORT_PRIVATE std::atomic_uint gc;
   static V8_EXPORT_PRIVATE std::atomic_uint gc_stats;
   static V8_EXPORT_PRIVATE std::atomic_uint ic_stats;
 
@@ -37,6 +38,10 @@
     return runtime_stats.load(std::memory_order_relaxed) != 0;
   }
 
+  static bool is_gc_enabled() {
+    return gc.load(std::memory_order_relaxed) != 0;
+  }
+
   static bool is_gc_stats_enabled() {
     return gc_stats.load(std::memory_order_relaxed) != 0;
   }
diff --git a/src/tracing/tracing-category-observer.cc b/src/tracing/tracing-category-observer.cc
index 276aed1..f524591 100644
--- a/src/tracing/tracing-category-observer.cc
+++ b/src/tracing/tracing-category-observer.cc
@@ -40,6 +40,11 @@
     i::TracingFlags::runtime_stats.fetch_or(ENABLED_BY_SAMPLING,
                                             std::memory_order_relaxed);
   }
+  TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("v8.gc"),
+                                     &enabled);
+  if (enabled) {
+    i::TracingFlags::gc.fetch_or(ENABLED_BY_TRACING, std::memory_order_relaxed);
+  }
   TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("v8.gc_stats"),
                                      &enabled);
   if (enabled) {
@@ -58,6 +63,8 @@
   i::TracingFlags::runtime_stats.fetch_and(
       ~(ENABLED_BY_TRACING | ENABLED_BY_SAMPLING), std::memory_order_relaxed);
 
+  i::TracingFlags::gc.fetch_and(~ENABLED_BY_TRACING, std::memory_order_relaxed);
+
   i::TracingFlags::gc_stats.fetch_and(~ENABLED_BY_TRACING,
                                       std::memory_order_relaxed);