From 104336699d5d72f7d4e1b7a4be89ab4d61be01dd Mon Sep 17 00:00:00 2001 From: Ryan Macnak Date: Thu, 30 Nov 2017 22:55:56 +0000 Subject: [PATCH] Make the compactor invokable from Observatory's heap map page. Add GCReason to timeline events. Bug: https://github.com/dart-lang/sdk/issues/30978 Change-Id: Ie4514ca3fb0fa6197a895e54618dfcba1dfe3a8d Reviewed-on: https://dart-review.googlesource.com/25120 Commit-Queue: Ryan Macnak Reviewed-by: Siva Annamalai --- .../lib/src/elements/heap_map.dart | 14 ++++--- .../tests/service/get_heap_map_rpc_test.dart | 39 ++++++++++++++++++ runtime/vm/heap.cc | 40 ++++++++++--------- runtime/vm/heap.h | 3 +- runtime/vm/pages.cc | 6 +-- runtime/vm/pages.h | 4 +- runtime/vm/service.cc | 12 +++--- 7 files changed, 83 insertions(+), 35 deletions(-) diff --git a/runtime/observatory/lib/src/elements/heap_map.dart b/runtime/observatory/lib/src/elements/heap_map.dart index 41e1e989d92..6e313e8a8ca 100644 --- a/runtime/observatory/lib/src/elements/heap_map.dart +++ b/runtime/observatory/lib/src/elements/heap_map.dart @@ -108,8 +108,12 @@ class HeapMapElement extends HtmlElement implements Renderable { new NavVMMenuElement(_vm, _events, queue: _r.queue), new NavIsolateMenuElement(_isolate, _events, queue: _r.queue), navMenu('heap map'), - new NavRefreshElement(label: 'GC', queue: _r.queue) - ..onRefresh.listen((_) => _refresh(gc: true)), + new NavRefreshElement(label: 'Mark-Compact', queue: _r.queue) + ..onRefresh.listen((_) => _refresh(gc: "mark-compact")), + new NavRefreshElement(label: 'Mark-Sweep', queue: _r.queue) + ..onRefresh.listen((_) => _refresh(gc: "mark-sweep")), + new NavRefreshElement(label: 'Scavenge', queue: _r.queue) + ..onRefresh.listen((_) => _refresh(gc: "scavenge")), new NavRefreshElement(queue: _r.queue) ..onRefresh.listen((_) => _refresh()), new NavNotifyElement(_notifications, queue: _r.queue) @@ -279,11 +283,11 @@ class HeapMapElement extends HtmlElement implements Renderable { }); } - Future _refresh({gc: false}) { + Future _refresh({String gc}) { final isolate = _isolate as S.Isolate; var params = {}; - if (gc) { - params['gc'] = 'full'; + if (gc != null) { + params['gc'] = gc; } return isolate .invokeRpc('_getHeapMap', params) diff --git a/runtime/observatory/tests/service/get_heap_map_rpc_test.dart b/runtime/observatory/tests/service/get_heap_map_rpc_test.dart index a566d64e8e7..f60135a6185 100644 --- a/runtime/observatory/tests/service/get_heap_map_rpc_test.dart +++ b/runtime/observatory/tests/service/get_heap_map_rpc_test.dart @@ -22,6 +22,45 @@ var tests = [ expect(result['pages'][0]['objects'].length, isPositive); expect(result['pages'][0]['objects'][0], isPositive); }, + (Isolate isolate) async { + var params = {'gc': 'scavenge'}; + var result = await isolate.invokeRpcNoUpgrade('_getHeapMap', params); + expect(result['type'], equals('HeapMap')); + expect(result['freeClassId'], isPositive); + expect(result['unitSizeBytes'], isPositive); + expect(result['pageSizeBytes'], isPositive); + expect(result['classList'], isNotNull); + expect(result['pages'].length, isPositive); + expect(result['pages'][0]['objectStart'], new isInstanceOf()); + expect(result['pages'][0]['objects'].length, isPositive); + expect(result['pages'][0]['objects'][0], isPositive); + }, + (Isolate isolate) async { + var params = {'gc': 'mark-sweep'}; + var result = await isolate.invokeRpcNoUpgrade('_getHeapMap', params); + expect(result['type'], equals('HeapMap')); + expect(result['freeClassId'], isPositive); + expect(result['unitSizeBytes'], isPositive); + expect(result['pageSizeBytes'], isPositive); + expect(result['classList'], isNotNull); + expect(result['pages'].length, isPositive); + expect(result['pages'][0]['objectStart'], new isInstanceOf()); + expect(result['pages'][0]['objects'].length, isPositive); + expect(result['pages'][0]['objects'][0], isPositive); + }, + (Isolate isolate) async { + var params = {'gc': 'mark-compact'}; + var result = await isolate.invokeRpcNoUpgrade('_getHeapMap', params); + expect(result['type'], equals('HeapMap')); + expect(result['freeClassId'], isPositive); + expect(result['unitSizeBytes'], isPositive); + expect(result['pageSizeBytes'], isPositive); + expect(result['classList'], isNotNull); + expect(result['pages'].length, isPositive); + expect(result['pages'][0]['objectStart'], new isInstanceOf()); + expect(result['pages'][0]['objects'].length, isPositive); + expect(result['pages'][0]['objects'][0], isPositive); + }, ]; main(args) async => runIsolateTests(args, tests); diff --git a/runtime/vm/heap.cc b/runtime/vm/heap.cc index 9d29b1f9aa2..2cb66ffe738 100644 --- a/runtime/vm/heap.cc +++ b/runtime/vm/heap.cc @@ -386,7 +386,7 @@ void Heap::EvacuateNewSpace(Thread* thread, GCReason reason) { NOT_IN_PRODUCT(isolate()->class_table()->UpdatePromoted()); RecordAfterGC(kNew); PrintStats(); - NOT_IN_PRODUCT(PrintStatsToTimeline(&tds)); + NOT_IN_PRODUCT(PrintStatsToTimeline(&tds, reason)); EndNewSpaceGC(); } } @@ -404,7 +404,7 @@ void Heap::CollectNewSpaceGarbage(Thread* thread, NOT_IN_PRODUCT(isolate()->class_table()->UpdatePromoted()); RecordAfterGC(kNew); PrintStats(); - NOT_IN_PRODUCT(PrintStatsToTimeline(&tds)); + NOT_IN_PRODUCT(PrintStatsToTimeline(&tds, reason)); EndNewSpaceGC(); } if ((reason == kNewSpace) && old_space_.NeedsGarbageCollection()) { @@ -421,10 +421,11 @@ void Heap::CollectOldSpaceGarbage(Thread* thread, VMTagScope tagScope(thread, VMTag::kGCOldSpaceTagId); TIMELINE_FUNCTION_GC_DURATION_BASIC(thread, "CollectOldGeneration"); NOT_IN_PRODUCT(UpdateClassHeapStatsBeforeGC(kOld)); - old_space_.MarkSweep(); + bool compact = (reason == kCompaction) || FLAG_use_compactor; + old_space_.CollectGarbage(compact); RecordAfterGC(kOld); PrintStats(); - NOT_IN_PRODUCT(PrintStatsToTimeline(&tds)); + NOT_IN_PRODUCT(PrintStatsToTimeline(&tds, reason)); // Some Code objects may have been collected so invalidate handler cache. thread->isolate()->handler_info_cache()->Clear(); thread->isolate()->catch_entry_state_cache()->Clear(); @@ -636,6 +637,8 @@ const char* Heap::GCReasonToString(GCReason gc_reason) { return "promotion"; case kOldSpace: return "old space"; + case kCompaction: + return "compaction"; case kFull: return "full"; case kIdle: @@ -829,38 +832,39 @@ void Heap::PrintStats() { #endif // !defined(PRODUCT) } -void Heap::PrintStatsToTimeline(TimelineEventScope* event) { +void Heap::PrintStatsToTimeline(TimelineEventScope* event, GCReason reason) { #if !defined(PRODUCT) if ((event == NULL) || !event->enabled()) { return; } intptr_t arguments = event->GetNumArguments(); - event->SetNumArguments(arguments + 12); - event->FormatArgument(arguments + 0, "Before.New.Used (kB)", "%" Pd "", + event->SetNumArguments(arguments + 13); + event->CopyArgument(arguments + 0, "Reason", GCReasonToString(reason)); + event->FormatArgument(arguments + 1, "Before.New.Used (kB)", "%" Pd "", RoundWordsToKB(stats_.before_.new_.used_in_words)); - event->FormatArgument(arguments + 1, "After.New.Used (kB)", "%" Pd "", + event->FormatArgument(arguments + 2, "After.New.Used (kB)", "%" Pd "", RoundWordsToKB(stats_.after_.new_.used_in_words)); - event->FormatArgument(arguments + 2, "Before.Old.Used (kB)", "%" Pd "", + event->FormatArgument(arguments + 3, "Before.Old.Used (kB)", "%" Pd "", RoundWordsToKB(stats_.before_.old_.used_in_words)); - event->FormatArgument(arguments + 3, "After.Old.Used (kB)", "%" Pd "", + event->FormatArgument(arguments + 4, "After.Old.Used (kB)", "%" Pd "", RoundWordsToKB(stats_.after_.old_.used_in_words)); - event->FormatArgument(arguments + 4, "Before.New.Capacity (kB)", "%" Pd "", + event->FormatArgument(arguments + 5, "Before.New.Capacity (kB)", "%" Pd "", RoundWordsToKB(stats_.before_.new_.capacity_in_words)); - event->FormatArgument(arguments + 5, "After.New.Capacity (kB)", "%" Pd "", + event->FormatArgument(arguments + 6, "After.New.Capacity (kB)", "%" Pd "", RoundWordsToKB(stats_.after_.new_.capacity_in_words)); - event->FormatArgument(arguments + 6, "Before.Old.Capacity (kB)", "%" Pd "", + event->FormatArgument(arguments + 7, "Before.Old.Capacity (kB)", "%" Pd "", RoundWordsToKB(stats_.before_.old_.capacity_in_words)); - event->FormatArgument(arguments + 7, "After.Old.Capacity (kB)", "%" Pd "", + event->FormatArgument(arguments + 8, "After.Old.Capacity (kB)", "%" Pd "", RoundWordsToKB(stats_.after_.old_.capacity_in_words)); - event->FormatArgument(arguments + 8, "Before.New.External (kB)", "%" Pd "", + event->FormatArgument(arguments + 9, "Before.New.External (kB)", "%" Pd "", RoundWordsToKB(stats_.before_.new_.external_in_words)); - event->FormatArgument(arguments + 9, "After.New.External (kB)", "%" Pd "", + event->FormatArgument(arguments + 10, "After.New.External (kB)", "%" Pd "", RoundWordsToKB(stats_.after_.new_.external_in_words)); - event->FormatArgument(arguments + 10, "Before.Old.External (kB)", "%" Pd "", + event->FormatArgument(arguments + 11, "Before.Old.External (kB)", "%" Pd "", RoundWordsToKB(stats_.before_.old_.external_in_words)); - event->FormatArgument(arguments + 11, "After.Old.External (kB)", "%" Pd "", + event->FormatArgument(arguments + 12, "After.Old.External (kB)", "%" Pd "", RoundWordsToKB(stats_.after_.old_.external_in_words)); #endif // !defined(PRODUCT) } diff --git a/runtime/vm/heap.h b/runtime/vm/heap.h index aa4e4be42fe..b8bcf3a06a3 100644 --- a/runtime/vm/heap.h +++ b/runtime/vm/heap.h @@ -45,6 +45,7 @@ class Heap { kNewSpace, kPromotion, kOldSpace, + kCompaction, kFull, kIdle, kGCAtAlloc, @@ -321,7 +322,7 @@ class Heap { void RecordAfterGC(Space space); void PrintStats(); void UpdateClassHeapStatsBeforeGC(Heap::Space space); - void PrintStatsToTimeline(TimelineEventScope* event); + void PrintStatsToTimeline(TimelineEventScope* event, GCReason reason); // Updates gc in progress flags. bool BeginNewSpaceGC(Thread* thread); diff --git a/runtime/vm/pages.cc b/runtime/vm/pages.cc index 2b8fcb1fc8e..05a6dbb1826 100644 --- a/runtime/vm/pages.cc +++ b/runtime/vm/pages.cc @@ -865,7 +865,7 @@ bool PageSpace::ShouldPerformIdleMarkSweep(int64_t deadline) { return estimated_mark_completion <= deadline; } -void PageSpace::MarkSweep() { +void PageSpace::CollectGarbage(bool compact) { Thread* thread = Thread::Current(); Isolate* isolate = heap_->isolate(); ASSERT(isolate == Isolate::Current()); @@ -984,7 +984,7 @@ void PageSpace::MarkSweep() { mid3 = OS::GetCurrentMonotonicMicros(); - if (FLAG_use_compactor) { + if (compact) { Compact(thread); } else if (FLAG_concurrent_sweep) { ConcurrentSweep(isolate); @@ -1039,7 +1039,7 @@ void PageSpace::MarkSweep() { ml.NotifyAll(); } - if (FLAG_use_compactor) { + if (compact) { // Const object tables are hashed by address: rehash. SafepointOperationScope safepoint(thread); thread->isolate()->RehashConstants(); diff --git a/runtime/vm/pages.h b/runtime/vm/pages.h index 9ebc20f4429..ed4882402c0 100644 --- a/runtime/vm/pages.h +++ b/runtime/vm/pages.h @@ -270,8 +270,8 @@ class PageSpace { // code. bool ShouldCollectCode(); - // Collect the garbage in the page space using mark-sweep. - void MarkSweep(); + // Collect the garbage in the page space using mark-sweep or mark-compact. + void CollectGarbage(bool compact); void AddRegionsToObjectSet(ObjectSet* set) const; diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc index f199bd69a1a..f5dd5b82450 100644 --- a/runtime/vm/service.cc +++ b/runtime/vm/service.cc @@ -3439,18 +3439,18 @@ static const MethodParameter* get_heap_map_params[] = { static bool GetHeapMap(Thread* thread, JSONStream* js) { Isolate* isolate = thread->isolate(); - bool should_collect = false; if (js->HasParam("gc")) { - if (js->ParamIs("gc", "full")) { - should_collect = true; + if (js->ParamIs("gc", "scavenge")) { + isolate->heap()->CollectGarbage(Heap::kNew); + } else if (js->ParamIs("gc", "mark-sweep")) { + isolate->heap()->CollectGarbage(Heap::kOld, Heap::kOldSpace); + } else if (js->ParamIs("gc", "mark-compact")) { + isolate->heap()->CollectGarbage(Heap::kOld, Heap::kCompaction); } else { PrintInvalidParamError(js, "gc"); return true; } } - if (should_collect) { - isolate->heap()->CollectAllGarbage(); - } isolate->heap()->PrintHeapMapToJSONStream(isolate, js); return true; }