From 51321bead2990b729365613594c0fd4f812f88d2 Mon Sep 17 00:00:00 2001 From: Matt Wozniski Date: Wed, 8 Feb 2023 19:00:41 -0500 Subject: [PATCH 01/12] Allow getting allocations by location and snapshot For each snapshot, report the allocations performed between that snapshot and the next, grouped by location, and also report which snapshot each of those allocations is freed in. Signed-off-by: Matt Wozniski --- src/memray/_memray.pyi | 8 ++ src/memray/_memray.pyx | 84 +++++++++++++++ src/memray/_memray/snapshot.cpp | 174 +++++++++++++++++++++++++++++++- src/memray/_memray/snapshot.h | 58 +++++++++++ src/memray/_memray/snapshot.pxd | 20 ++++ 5 files changed, 342 insertions(+), 2 deletions(-) diff --git a/src/memray/_memray.pyi b/src/memray/_memray.pyi index 9cbbfa1e90..22f283741a 100644 --- a/src/memray/_memray.pyi +++ b/src/memray/_memray.pyi @@ -61,6 +61,10 @@ class AllocationRecord: def __lt__(self, other: Any) -> Any: ... def __ne__(self, other: Any) -> Any: ... +class TemporalAllocationRecord(AllocationRecord): + allocated_before_snapshot: int + deallocated_before_snapshot: int + class AllocatorType(enum.IntEnum): MALLOC: int FREE: int @@ -91,6 +95,10 @@ class FileReader: self, file_name: Union[str, Path], *, report_progress: bool = False ) -> None: ... def get_allocation_records(self) -> Iterable[AllocationRecord]: ... + def get_temporal_allocation_records( + self, + merge_threads: bool, + ) -> List[TemporalAllocationRecord]: ... def get_high_watermark_allocation_records( self, merge_threads: bool = ..., diff --git a/src/memray/_memray.pyx b/src/memray/_memray.pyx index 4b72c23967..4527634933 100644 --- a/src/memray/_memray.pyx +++ b/src/memray/_memray.pyx @@ -37,10 +37,13 @@ from _memray.sink cimport Sink from _memray.sink cimport SocketSink from _memray.snapshot cimport AbstractAggregator from _memray.snapshot cimport AggregatedCaptureReaggregator +from _memray.snapshot cimport AllocationLifetime +from _memray.snapshot cimport AllocationLifetimeAggregator from _memray.snapshot cimport AllocationStatsAggregator from _memray.snapshot cimport HighWatermark from _memray.snapshot cimport HighWaterMarkAggregator from _memray.snapshot cimport HighWatermarkFinder +from _memray.snapshot cimport HighWaterMarkLocationKey from _memray.snapshot cimport Py_GetSnapshotAllocationRecords from _memray.snapshot cimport Py_ListFromSnapshotAllocationRecords from _memray.snapshot cimport SnapshotAllocationAggregator @@ -292,6 +295,45 @@ cdef class AllocationRecord: f"allocations={self.n_allocations}>") +cdef class TemporalAllocationRecord(AllocationRecord): + cdef public size_t allocated_before_snapshot + cdef public size_t deallocated_before_snapshot + + def __init__(self, record, allocated_before_snapshot, deallocated_before_snapshot): + super().__init__(record) + self.allocated_before_snapshot = allocated_before_snapshot + self.deallocated_before_snapshot = deallocated_before_snapshot + + def __repr__(self): + start = self.allocated_before_snapshot + end = self.deallocated_before_snapshot + return super().__repr__().replace(">", f", range=range({start}, {end})>") + + +cdef create_temporal_allocation_record( + AllocationLifetime lifetime, + bool merge_threads, + shared_ptr[RecordReader] reader, +): + thread_id = 0 if merge_threads else lifetime.key.thread_id + address = 0 + elem = ( + thread_id, + address, + lifetime.n_bytes, + lifetime.key.allocator, + lifetime.key.python_frame_id, + lifetime.n_allocations, + lifetime.key.native_frame_id, + lifetime.key.native_segment_generation, + ) + cdef TemporalAllocationRecord alloc = TemporalAllocationRecord( + elem, lifetime.allocatedBeforeSnapshot, lifetime.deallocatedBeforeSnapshot + ) + alloc._reader = reader + return alloc + + MemorySnapshot = collections.namedtuple("MemorySnapshot", "time rss heap") cdef class ProfileFunctionGuard: @@ -813,6 +855,48 @@ cdef class FileReader: temporary_buffer_size=threshold + 1, ) + def get_temporal_allocation_records(self, merge_threads=True): + self._ensure_not_closed() + if self._header["file_format"] == FileFormat.AGGREGATED_ALLOCATIONS: + raise NotImplementedError( + "Can't get allocation history using a pre-aggregated capture file." + ) + + cdef shared_ptr[RecordReader] reader_sp = make_shared[RecordReader]( + unique_ptr[FileSource](new FileSource(self._path)) + ) + cdef RecordReader* reader = reader_sp.get() + + cdef size_t records_to_process = self._header["stats"]["n_allocations"] + cdef ProgressIndicator progress_indicator = ProgressIndicator( + "Processing allocation records", + total=records_to_process, + report_progress=self._report_progress + ) + + cdef AllocationLifetimeAggregator aggregator + + with progress_indicator: + while records_to_process > 0: + PyErr_CheckSignals() + ret = reader.nextRecord() + if ret == RecordResult.RecordResultAllocationRecord: + aggregator.addAllocation(reader.getLatestAllocation()) + records_to_process -= 1 + progress_indicator.update(1) + elif ret == RecordResult.RecordResultMemoryRecord: + aggregator.captureSnapshot() + else: + assert ret != RecordResult.RecordResultMemorySnapshot + assert ret != RecordResult.RecordResultAggregatedAllocationRecord + break + + cdef vector[AllocationLifetime] lifetimes = aggregator.generateIndex() + return [ + create_temporal_allocation_record(lifetime, merge_threads, reader_sp) + for lifetime in lifetimes + ] + def get_allocation_records(self): self._ensure_not_closed() if self._header["file_format"] == FileFormat.AGGREGATED_ALLOCATIONS: diff --git a/src/memray/_memray/snapshot.cpp b/src/memray/_memray/snapshot.cpp index f86796c505..62004eb132 100644 --- a/src/memray/_memray/snapshot.cpp +++ b/src/memray/_memray/snapshot.cpp @@ -1,7 +1,8 @@ -#include - #include "snapshot.h" +#include +#include + namespace memray::api { bool @@ -19,6 +20,18 @@ HighWaterMarkLocationKey::operator==(const HighWaterMarkLocationKey& rhs) const && native_segment_generation == rhs.native_segment_generation && allocator == rhs.allocator; } +bool +operator<(const AllocationLifetime& lhs, const AllocationLifetime& rhs) +{ + // Sort by n_bytes if allocatedBefore/deallocatedBefore are equal, + // so that our test suite gets records in a predictable order. + return lhs.allocatedBeforeSnapshot != rhs.allocatedBeforeSnapshot + ? lhs.allocatedBeforeSnapshot < rhs.allocatedBeforeSnapshot + : lhs.deallocatedBeforeSnapshot != rhs.deallocatedBeforeSnapshot + ? lhs.deallocatedBeforeSnapshot < rhs.deallocatedBeforeSnapshot + : lhs.n_bytes < rhs.n_bytes; +} + Interval::Interval(uintptr_t begin, uintptr_t end) : begin(begin) , end(end) @@ -407,6 +420,163 @@ HighWaterMarkAggregator::visitAllocations(const allocation_callback_t& callback) return true; } +void +AllocationLifetimeAggregator::addAllocation(const Allocation& allocation_or_deallocation) +{ + // Note: Deallocation records don't tell us where the memory was allocated, + // so we need to save the records for allocations and cross-reference + // deallocations against them. + switch (hooks::allocatorKind(allocation_or_deallocation.allocator)) { + case hooks::AllocatorKind::SIMPLE_ALLOCATOR: { + const Allocation& allocation = allocation_or_deallocation; + size_t generation = d_num_snapshots; + d_ptr_to_allocation[allocation.address] = {allocation, generation}; + break; + } + case hooks::AllocatorKind::SIMPLE_DEALLOCATOR: { + const Allocation& deallocation = allocation_or_deallocation; + const auto it = d_ptr_to_allocation.find(deallocation.address); + if (it != d_ptr_to_allocation.end()) { + const auto& [allocation, generation] = it->second; + recordDeallocation(extractKey(allocation), 1, allocation.size, generation); + d_ptr_to_allocation.erase(it); + } + break; + } + case hooks::AllocatorKind::RANGED_ALLOCATOR: { + const Allocation& allocation = allocation_or_deallocation; + size_t generation = d_num_snapshots; + d_mmap_intervals.addInterval( + allocation.address, + allocation.size, + {std::make_shared(allocation), generation}); + break; + } + case hooks::AllocatorKind::RANGED_DEALLOCATOR: { + const Allocation& deallocation = allocation_or_deallocation; + auto removal_stats = + d_mmap_intervals.removeInterval(deallocation.address, deallocation.size); + for (const auto& [interval, pair] : removal_stats.freed_allocations) { + recordRangedDeallocation(pair.first, interval.size(), pair.second); + } + for (const auto& [interval, pair] : removal_stats.shrunk_allocations) { + recordRangedDeallocation(pair.first, interval.size(), pair.second); + } + for (const auto& [interval, pair] : removal_stats.split_allocations) { + recordRangedDeallocation(pair.first, interval.size(), pair.second); + } + break; + } + } +} + +HighWaterMarkLocationKey +AllocationLifetimeAggregator::extractKey(const Allocation& allocation) +{ + return {allocation.tid, + allocation.frame_index, + allocation.native_frame_id, + allocation.native_segment_generation, + allocation.allocator}; +} + +void +AllocationLifetimeAggregator::recordRangedDeallocation( + const std::shared_ptr& allocation_ptr, + size_t bytes_deallocated, + size_t generation_allocated) +{ + // We hold one reference, and the IntervalTree may or may not hold others. + // We use a count of 0 for all but the last deallocation of a range so that + // partial deallocations won't affect the count of allocations by location. + bool fully_deallocated = allocation_ptr.use_count() == 1; + recordDeallocation( + extractKey(*allocation_ptr), + (fully_deallocated ? 1 : 0), + bytes_deallocated, + generation_allocated); +} + +void +AllocationLifetimeAggregator::recordDeallocation( + const HighWaterMarkLocationKey& key, + size_t count_delta, + size_t bytes_delta, + size_t generation) +{ + if (d_num_snapshots == generation) { + // Allocated and deallocated within the same snapshot. We can ignore this. + return; + } + + auto& counts = d_allocation_history[std::make_tuple(generation, d_num_snapshots, key)]; + counts.first += count_delta; + counts.second += bytes_delta; +} + +void +AllocationLifetimeAggregator::captureSnapshot() +{ + ++d_num_snapshots; +} + +std::vector +AllocationLifetimeAggregator::generateIndex() +{ + struct KeyHash + { + size_t operator()(const std::pair& key) const + { + size_t ret = HighWaterMarkLocationKeyHash{}(std::get<1>(key)); + ret = (ret << 1) ^ std::get<0>(key); + return ret; + } + }; + + std::unordered_map, std::pair, KeyHash> + leaks; + + // Leaked simple allocations + for (const auto& [ptr, allocation_and_generation] : d_ptr_to_allocation) { + (void)ptr; + const auto& [allocation, generation] = allocation_and_generation; + auto& counts = leaks[std::make_pair(generation, extractKey(allocation))]; + counts.first += 1; + counts.second += allocation.size; + } + + // Leaked range allocations + std::unordered_set leaked_mappings; + for (const auto& [interval, allocation_ptr_and_generation] : d_mmap_intervals) { + const auto& [allocation_ptr, generation] = allocation_ptr_and_generation; + auto& counts = leaks[std::make_pair(generation, extractKey(*allocation_ptr))]; + + // Ensure we only count each allocation once, even if it's been split. + auto inserted = leaked_mappings.insert(allocation_ptr.get()).second; + counts.first += (inserted ? 1 : 0); + counts.second += interval.size(); + } + + std::vector ret; + + // Things that were leaked + for (const auto& [when_where, how_much] : leaks) { + const auto& [allocated_before, key] = when_where; + const auto& [n_allocations, n_bytes] = how_much; + ret.push_back({allocated_before, static_cast(-1), key, n_allocations, n_bytes}); + } + + // Things that weren't leaked + for (const auto& [when_where, how_much] : d_allocation_history) { + const auto& [allocated_before, deallocated_before, key] = when_where; + const auto& [n_allocations, n_bytes] = how_much; + ret.push_back({allocated_before, deallocated_before, key, n_allocations, n_bytes}); + } + + std::sort(ret.begin(), ret.end()); + return ret; +} + /** * Produce an aggregated snapshot from a vector of allocations and a index in that vector * diff --git a/src/memray/_memray/snapshot.h b/src/memray/_memray/snapshot.h index ad5f38a9ae..463126b029 100644 --- a/src/memray/_memray/snapshot.h +++ b/src/memray/_memray/snapshot.h @@ -320,6 +320,64 @@ class HighWaterMarkAggregator reduced_snapshot_map_t getAllocations(bool merge_threads, bool stop_at_high_water_mark) const; }; +struct AllocationLifetime +{ + size_t allocatedBeforeSnapshot; + size_t deallocatedBeforeSnapshot; // SIZE_MAX if never deallocated + HighWaterMarkLocationKey key; + size_t n_allocations; + size_t n_bytes; +}; + +class AllocationLifetimeAggregator +{ + public: + void addAllocation(const Allocation& allocation); + void captureSnapshot(); + + std::vector generateIndex(); + + private: + size_t d_num_snapshots{}; + + struct allocation_history_key_hash + { + size_t operator()(const std::tuple& key) const + { + size_t ret = HighWaterMarkLocationKeyHash{}(std::get<2>(key)); + ret = (ret << 1) ^ std::get<1>(key); + ret = (ret << 1) ^ std::get<0>(key); + return ret; + } + }; + + // Record of freed allocations that spanned multiple snapshots. + std::unordered_map< + std::tuple, + std::pair, + allocation_history_key_hash> + d_allocation_history; + + // Simple allocations contributing to the current heap size. + std::unordered_map> d_ptr_to_allocation; + + // Ranged allocations contributing to the current heap size. + IntervalTree, size_t>> d_mmap_intervals; + + HighWaterMarkLocationKey extractKey(const Allocation& allocation); + + void recordRangedDeallocation( + const std::shared_ptr& allocation, + size_t bytes_deallocated, + size_t generation_allocated); + + void recordDeallocation( + const HighWaterMarkLocationKey& key, + size_t count_delta, + size_t bytes_delta, + size_t generation); +}; + class AllocationStatsAggregator { public: diff --git a/src/memray/_memray/snapshot.pxd b/src/memray/_memray/snapshot.pxd index 964061bf55..39cd625f31 100644 --- a/src/memray/_memray/snapshot.pxd +++ b/src/memray/_memray/snapshot.pxd @@ -1,3 +1,4 @@ +from _memray.hooks cimport Allocator from _memray.records cimport AggregatedAllocation from _memray.records cimport Allocation from _memray.records cimport optional_frame_id_t @@ -48,6 +49,25 @@ cdef extern from "snapshot.h" namespace "memray::api": size_t getCurrentHeapSize() bool visitAllocations[T](const T& callback) except+ + cdef cppclass HighWaterMarkLocationKey: + unsigned long thread_id + size_t python_frame_id + size_t native_frame_id + size_t native_segment_generation + Allocator allocator + + cdef cppclass AllocationLifetime: + size_t allocatedBeforeSnapshot + size_t deallocatedBeforeSnapshot + HighWaterMarkLocationKey key + size_t n_allocations + size_t n_bytes + + cdef cppclass AllocationLifetimeAggregator: + void addAllocation(const Allocation& allocation) except+ + void captureSnapshot() + vector[AllocationLifetime] generateIndex() except+ + cdef cppclass AllocationStatsAggregator: void addAllocation(const Allocation&, optional_frame_id_t python_frame_id) except+ uint64_t totalAllocations() From 5c1980e80f86f498b1ad8468d441737f9bd6ce1c Mon Sep 17 00:00:00 2001 From: Matt Wozniski Date: Wed, 8 Feb 2023 18:19:10 -0500 Subject: [PATCH 02/12] flamegraph: Bootstrap new temporal_flamegraph.html Create a template page for a new type of flame graph. Initially this is just an exactly copy of our existing flame graph template, to make the changes from the original stand out as diffs in future commits. Signed-off-by: Matt Wozniski --- Makefile | 1 + .../reporters/assets/temporal_flamegraph.js | 88 +++++++++++++++++++ .../reporters/templates/flamegraph.html | 6 +- .../templates/temporal_flamegraph.html | 7 ++ webpack.config.js | 1 + 5 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 src/memray/reporters/assets/temporal_flamegraph.js create mode 100644 src/memray/reporters/templates/temporal_flamegraph.html diff --git a/Makefile b/Makefile index 936a9900ba..58f4fbe2b1 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ js_files := $(wildcard $(reporters_path)/assets/*.js) generated_js_files := \ $(reporters_path)/templates/assets/flamegraph_common.js \ $(reporters_path)/templates/assets/flamegraph.js \ + $(reporters_path)/templates/assets/temporal_flamegraph.js \ $(reporters_path)/templates/assets/table.js css_files := 'src/**/*.css' markdown_files := $(shell find . -name \*.md -not -path '*/\.*' -not -path './src/vendor/*') diff --git a/src/memray/reporters/assets/temporal_flamegraph.js b/src/memray/reporters/assets/temporal_flamegraph.js new file mode 100644 index 0000000000..fc328db737 --- /dev/null +++ b/src/memray/reporters/assets/temporal_flamegraph.js @@ -0,0 +1,88 @@ +import { debounced, initMemoryGraph, resizeMemoryGraph } from "./common"; + +import { + initThreadsDropdown, + drawChart, + handleFragments, + onFilterUninteresting, + onFilterImportSystem, + onFilterThread, + onResetZoom, + onResize, + onInvert, + getFlamegraph, +} from "./flamegraph_common"; + +window.resizeMemoryGraph = resizeMemoryGraph; + +function packedDataToTree(packedData) { + const { strings, nodes, unique_threads } = packedData; + + const node_objects = nodes.name.map((_, i) => ({ + name: strings[nodes["name"][i]], + location: [ + strings[nodes["function"][i]], + strings[nodes["filename"][i]], + nodes["lineno"][i], + ], + value: nodes["value"][i], + children: nodes["children"][i], + n_allocations: nodes["n_allocations"][i], + thread_id: strings[nodes["thread_id"][i]], + interesting: nodes["interesting"][i] !== 0, + import_system: nodes["import_system"][i] !== 0, + })); + + for (const node of node_objects) { + node["children"] = node["children"].map((idx) => node_objects[idx]); + } + + const root = node_objects[0]; + root["unique_threads"] = unique_threads.map((tid) => strings[tid]); + return root; +} + +// Main entrypoint +function main() { + data = packedDataToTree(packed_data); + initMemoryGraph(memory_records); + initThreadsDropdown(data, merge_threads); + + // Create the flamegraph renderer + drawChart(data); + + // Set zoom to correct element + if (location.hash) { + handleFragments(); + } + + // Setup event handlers + document.getElementById("invertButton").onclick = onInvert; + document.getElementById("resetZoomButton").onclick = onResetZoom; + document.getElementById("resetThreadFilterItem").onclick = onFilterThread; + let hideUninterestingCheckBox = document.getElementById("hideUninteresting"); + hideUninterestingCheckBox.onclick = onFilterUninteresting.bind(this); + let hideImportSystemCheckBox = document.getElementById("hideImportSystem"); + hideImportSystemCheckBox.onclick = onFilterImportSystem.bind(this); + // Enable filtering by default + onFilterUninteresting.bind(this)(); + + document.onkeyup = (event) => { + if (event.code == "Escape") { + onResetZoom(); + } + }; + document.getElementById("searchTerm").addEventListener("input", () => { + const termElement = document.getElementById("searchTerm"); + getFlamegraph().search(termElement.value); + }); + + window.addEventListener("popstate", handleFragments); + window.addEventListener("resize", debounced(onResize)); + + // Enable tooltips + $('[data-toggle-second="tooltip"]').tooltip(); + $('[data-toggle="tooltip"]').tooltip(); +} + +document.addEventListener("DOMContentLoaded", main); diff --git a/src/memray/reporters/templates/flamegraph.html b/src/memray/reporters/templates/flamegraph.html index 7b3f290807..a281de11e9 100644 --- a/src/memray/reporters/templates/flamegraph.html +++ b/src/memray/reporters/templates/flamegraph.html @@ -13,7 +13,7 @@
+ title="Hide CPython eval frames and Memray-related frames" checked>
@@ -72,7 +72,11 @@ + +{% block flamegraph_script %} {% endblock %} + +{% endblock %} diff --git a/src/memray/reporters/templates/temporal_flamegraph.html b/src/memray/reporters/templates/temporal_flamegraph.html new file mode 100644 index 0000000000..8baf23c38c --- /dev/null +++ b/src/memray/reporters/templates/temporal_flamegraph.html @@ -0,0 +1,7 @@ +{% extends "flamegraph.html" %} + +{% block flamegraph_script %} + +{% endblock %} diff --git a/webpack.config.js b/webpack.config.js index f3ee714d8f..5f76e3801f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,6 +6,7 @@ module.exports = { entry: { flamegraph_common: "./src/memray/reporters/assets/flamegraph_common.js", flamegraph: "./src/memray/reporters/assets/flamegraph.js", + temporal_flamegraph: "./src/memray/reporters/assets/temporal_flamegraph.js", table: "./src/memray/reporters/assets/table.js", }, output: { From 3573a9483aca8d51e2b282d0022096af7b69ad43 Mon Sep 17 00:00:00 2001 From: Matt Wozniski Date: Wed, 8 Feb 2023 18:27:34 -0500 Subject: [PATCH 03/12] flamegraph: Support temporal flame graphs Allow generating flame graphs with a temporal dimension, which are able to show the allocations performed in some range and not deallocated before the end of that range. Signed-off-by: Matt Wozniski --- src/memray/commands/common.py | 14 +- src/memray/commands/flamegraph.py | 10 + .../reporters/assets/temporal_flamegraph.js | 237 +++++++++++++++++- src/memray/reporters/flamegraph.py | 24 +- .../reporters/templates/assets/flamegraph.css | 38 +++ .../templates/assets/temporal_flamegraph.js | 9 + .../templates/temporal_flamegraph.html | 26 ++ 7 files changed, 339 insertions(+), 19 deletions(-) create mode 100644 src/memray/reporters/templates/assets/temporal_flamegraph.js diff --git a/src/memray/commands/common.py b/src/memray/commands/common.py index a03def774a..71e6fb212e 100644 --- a/src/memray/commands/common.py +++ b/src/memray/commands/common.py @@ -106,25 +106,32 @@ def write_report( show_memory_leaks: bool, temporary_allocation_threshold: int, merge_threads: Optional[bool] = None, + temporal_leaks: bool = False, **kwargs: Any, ) -> None: try: reader = FileReader(os.fspath(result_path), report_progress=True) + default_merge_threads = True if merge_threads is None else merge_threads + if reader.metadata.has_native_traces: warn_if_not_enough_symbols() if show_memory_leaks: snapshot = reader.get_leaked_allocation_records( - merge_threads=merge_threads if merge_threads is not None else True + merge_threads=default_merge_threads ) elif temporary_allocation_threshold >= 0: snapshot = reader.get_temporary_allocation_records( threshold=temporary_allocation_threshold, - merge_threads=merge_threads if merge_threads is not None else True, + merge_threads=default_merge_threads, ) + elif temporal_leaks: + snapshot = reader.get_temporal_allocation_records( + merge_threads=default_merge_threads + ) # type: ignore else: snapshot = reader.get_high_watermark_allocation_records( - merge_threads=merge_threads if merge_threads is not None else True + merge_threads=default_merge_threads ) memory_records = tuple(reader.get_memory_snapshots()) reporter = self.reporter_factory( @@ -167,6 +174,7 @@ def run( output_file, args.show_memory_leaks, args.temporary_allocation_threshold, + temporal_leaks=getattr(args, "temporal_leaks", False), **kwargs, ) diff --git a/src/memray/commands/flamegraph.py b/src/memray/commands/flamegraph.py index f49b51f9ca..859ab33a88 100644 --- a/src/memray/commands/flamegraph.py +++ b/src/memray/commands/flamegraph.py @@ -38,6 +38,16 @@ def prepare_parser(self, parser: argparse.ArgumentParser) -> None: dest="show_memory_leaks", default=False, ) + alloc_type_group.add_argument( + "--temporal-leaks", + help=( + "Generate a dynamic flame graph showing allocations performed" + " in a user-selected time range and not freed before the end" + " of that time range." + ), + action="store_true", + default=False, + ) alloc_type_group.add_argument( "--temporary-allocation-threshold", metavar="N", diff --git a/src/memray/reporters/assets/temporal_flamegraph.js b/src/memray/reporters/assets/temporal_flamegraph.js index fc328db737..407f5273a4 100644 --- a/src/memray/reporters/assets/temporal_flamegraph.js +++ b/src/memray/reporters/assets/temporal_flamegraph.js @@ -1,4 +1,4 @@ -import { debounced, initMemoryGraph, resizeMemoryGraph } from "./common"; +import { debounced } from "./common"; import { initThreadsDropdown, @@ -10,14 +10,27 @@ import { onResetZoom, onResize, onInvert, + getFilteredChart, getFlamegraph, } from "./flamegraph_common"; -window.resizeMemoryGraph = resizeMemoryGraph; +var active_plot = null; +var current_dimensions = null; -function packedDataToTree(packedData) { +var parent_index_by_child_index = (function () { + let ret = new Array(packed_data.nodes.children.length); + console.log("finding parent index for each node"); + for (const [parentIndex, children] of packed_data.nodes.children.entries()) { + children.forEach((idx) => (ret[idx] = parentIndex)); + } + console.assert(ret[0] === undefined, "root node has a parent"); + return ret; +})(); + +function packedDataToTree(packedData, rangeStart, rangeEnd) { const { strings, nodes, unique_threads } = packedData; + console.log("constructing nodes"); const node_objects = nodes.name.map((_, i) => ({ name: strings[nodes["name"][i]], location: [ @@ -25,31 +38,216 @@ function packedDataToTree(packedData) { strings[nodes["filename"][i]], nodes["lineno"][i], ], - value: nodes["value"][i], + value: 0, children: nodes["children"][i], - n_allocations: nodes["n_allocations"][i], + n_allocations: 0, thread_id: strings[nodes["thread_id"][i]], interesting: nodes["interesting"][i] !== 0, import_system: nodes["import_system"][i] !== 0, })); - for (const node of node_objects) { + console.log("mapping child indices to child nodes"); + for (const [parentIndex, node] of node_objects.entries()) { node["children"] = node["children"].map((idx) => node_objects[idx]); } - const root = node_objects[0]; - root["unique_threads"] = unique_threads.map((tid) => strings[tid]); - return root; + // We could binary search rather than using a linear scan... + console.log("finding leaked allocations"); + packedData.intervals.forEach((interval) => { + let [allocBefore, deallocBefore, nodeIndex, count, bytes] = interval; + + if ( + allocBefore >= rangeStart && + allocBefore <= rangeEnd && + deallocBefore > rangeEnd + ) { + while (nodeIndex !== undefined) { + node_objects[nodeIndex].n_allocations += count; + node_objects[nodeIndex].value += bytes; + nodeIndex = parent_index_by_child_index[nodeIndex]; + } + } + }); + + console.log( + "total leaked allocations in range: " + node_objects[0].n_allocations + ); + console.log("total leaked bytes in range: " + node_objects[0].value); + + node_objects.forEach((node) => { + node.children = node.children.filter((node) => node.n_allocations > 0); + }); + + return node_objects[0]; +} + +function initMemoryGraph(memory_records) { + console.log("init memory graph"); + const time = memory_records.map((a) => new Date(a[0])); + const resident_size = memory_records.map((a) => a[1]); + const heap_size = memory_records.map((a) => a[2]); + + var resident_size_plot = { + x: time, + y: resident_size, + mode: "lines", + name: "Resident size", + }; + + var heap_size_plot = { + x: time, + y: heap_size, + mode: "lines", + name: "Heap size", + }; + + var plot_data = [resident_size_plot, heap_size_plot]; + var config = { + responsive: true, + displayModeBar: false, + }; + var layout = { + xaxis: { + title: { + text: "Time", + }, + rangeslider: { + visible: true, + }, + }, + yaxis: { + title: { + text: "Memory Size", + }, + tickformat: ".4~s", + exponentformat: "B", + ticksuffix: "B", + }, + }; + + Plotly.newPlot("plot", plot_data, layout, config).then((plot) => { + console.assert(active_plot === null); + active_plot = plot; + }); +} + +function showLoadingAnimation() { + console.log("showLoadingAnimation"); + document.getElementById("loading").style.display = "block"; + document.getElementById("overlay").style.display = "block"; +} + +function hideLoadingAnimation() { + console.log("hideLoadingAnimation"); + document.getElementById("loading").style.display = "none"; + document.getElementById("overlay").style.display = "none"; +} + +function refreshFlamegraph(event) { + console.log("refreshing flame graph!"); + + let request_data = getRangeData(event); + console.log("range data: " + request_data); + + if ( + current_dimensions != null && + JSON.stringify(request_data) === JSON.stringify(current_dimensions) + ) { + return; + } + + console.log("showing loading animation"); + showLoadingAnimation(); + + current_dimensions = request_data; + + console.log("finding range of relevant snapshot"); + + let idx0 = 0; + let idx1 = memory_records.length - 1; + + if (request_data) { + let t0 = new Date(request_data.string1).getTime(); + for (let i = 0; i < memory_records.length; i++) { + if (memory_records[i][0] >= t0) { + idx0 = i; + break; + } + } + + idx1 = 0; + let t1 = new Date(request_data.string2).getTime(); + for (let i = memory_records.length - 1; i >= 1; i--) { + if (memory_records[i - 1][0] < t1) { + idx1 = i; + break; + } + } + } + + console.log("start index is " + idx0); + console.log("end index is " + idx1); + console.log("first possible index is 0"); + console.log("last possible index is " + (memory_records.length - 1)); + + console.log("constructing tree"); + data = packedDataToTree(packed_data, idx0, idx1); + + console.log("drawing chart"); + getFilteredChart().drawChart(data); + console.log("hiding loading animation"); + hideLoadingAnimation(); +} + +function getRangeData(event) { + console.log("getRangeData"); + let request_data = {}; + if (event.hasOwnProperty("xaxis.range[0]")) { + request_data = { + string1: event["xaxis.range[0]"], + string2: event["xaxis.range[1]"], + }; + } else if (event.hasOwnProperty("xaxis.range")) { + request_data = { + string1: event["xaxis.range"][0], + string2: event["xaxis.range"][1], + }; + } else if (active_plot !== null) { + let the_range = active_plot.layout.xaxis.range; + request_data = { + string1: the_range[0], + string2: the_range[1], + }; + } else { + return; + } + return request_data; +} + +var debounce = null; +function refreshFlamegraphDebounced(event) { + console.log("refreshFlamegraphDebounced"); + if (debounce) { + clearTimeout(debounce); + } + debounce = setTimeout(function () { + refreshFlamegraph(event); + }, 500); } // Main entrypoint function main() { - data = packedDataToTree(packed_data); + console.log("main"); + + const unique_threads = packed_data.unique_threads.map( + (tid) => packed_data.strings[tid] + ); + initThreadsDropdown({ unique_threads: unique_threads }, merge_threads); + initMemoryGraph(memory_records); - initThreadsDropdown(data, merge_threads); - // Create the flamegraph renderer - drawChart(data); + // Draw the initial flame graph + refreshFlamegraph({}); // Set zoom to correct element if (location.hash) { @@ -83,6 +281,19 @@ function main() { // Enable tooltips $('[data-toggle-second="tooltip"]').tooltip(); $('[data-toggle="tooltip"]').tooltip(); + + // Set up the reload handler + console.log("setup reload handler"); + document + .getElementById("plot") + .on("plotly_relayout", refreshFlamegraphDebounced); + + // Enable toasts + var toastElList = [].slice.call(document.querySelectorAll(".toast")); + var toastList = toastElList.map(function (toastEl) { + return new bootstrap.Toast(toastEl, { delay: 10000 }); + }); + toastList.forEach((toast) => toast.show()); } document.addEventListener("DOMContentLoaded", main); diff --git a/src/memray/reporters/flamegraph.py b/src/memray/reporters/flamegraph.py index 0a7144334e..6333a52f2e 100644 --- a/src/memray/reporters/flamegraph.py +++ b/src/memray/reporters/flamegraph.py @@ -16,6 +16,7 @@ from memray import AllocationRecord from memray import MemorySnapshot from memray import Metadata +from memray._memray import TemporalAllocationRecord from memray.reporters.frame_tools import StackFrame from memray.reporters.frame_tools import is_cpython_internal from memray.reporters.frame_tools import is_frame_from_import_system @@ -101,6 +102,7 @@ def from_snapshot( } frames = [root] + interval_list = [] node_index_by_key: Dict[Tuple[int, StackFrame, str], int] = {} @@ -163,6 +165,17 @@ def from_snapshot( current_frame["location"] = ["...", "...", 0] break + if isinstance(record, TemporalAllocationRecord): + interval_list.append( + ( + record.allocated_before_snapshot, + record.deallocated_before_snapshot, + current_frame_id, + record.n_allocations, + record.size, + ) + ) + all_strings = StringRegistry() nodes = collections.defaultdict(list) for frame in frames: @@ -170,9 +183,10 @@ def from_snapshot( nodes["function"].append(all_strings.register(frame["location"][0])) nodes["filename"].append(all_strings.register(frame["location"][1])) nodes["lineno"].append(frame["location"][2]) - nodes["value"].append(frame["value"]) nodes["children"].append(frame["children"]) - nodes["n_allocations"].append(frame["n_allocations"]) + if not interval_list: + nodes["value"].append(frame["value"]) + nodes["n_allocations"].append(frame["n_allocations"]) nodes["thread_id"].append(all_strings.register(frame["thread_id"])) nodes["interesting"].append(int(frame["interesting"])) nodes["import_system"].append(int(frame["import_system"])) @@ -185,6 +199,9 @@ def from_snapshot( "strings": all_strings.strings, } + if interval_list: + data["intervals"] = interval_list + return cls(data, memory_records=memory_records) def render( @@ -194,8 +211,9 @@ def render( show_memory_leaks: bool, merge_threads: bool, ) -> None: + kind = "temporal_flamegraph" if "intervals" in self.data else "flamegraph" html_code = render_report( - kind="flamegraph", + kind=kind, data=self.data, metadata=metadata, memory_records=self.memory_records, diff --git a/src/memray/reporters/templates/assets/flamegraph.css b/src/memray/reporters/templates/assets/flamegraph.css index 68ea106104..37f6ba590f 100644 --- a/src/memray/reporters/templates/assets/flamegraph.css +++ b/src/memray/reporters/templates/assets/flamegraph.css @@ -50,3 +50,41 @@ .tooltip-inner { max-width: 300px; } + +/* Loading animation */ + +#loading { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + text-align: center; +} + +.loading-spinner { + border: 4px solid #f3f3f3; + border-top: 4px solid #3498db; + border-radius: 50%; + width: 50px; + height: 50px; + animation: spin 2s linear infinite; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +#overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); /* semi-transparent black */ + z-index: 99; /* make sure it's on top of other elements */ +} diff --git a/src/memray/reporters/templates/assets/temporal_flamegraph.js b/src/memray/reporters/templates/assets/temporal_flamegraph.js new file mode 100644 index 0000000000..e7182df131 --- /dev/null +++ b/src/memray/reporters/templates/assets/temporal_flamegraph.js @@ -0,0 +1,9 @@ +(()=>{var n={486:function(n,t,r){var e; +/** + * @license + * Lodash + * Copyright OpenJS Foundation and other contributors + * Released under MIT license + * Based on Underscore.js 1.8.3 + * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + */n=r.nmd(n),function(){var u,i="Expected a function",o="__lodash_hash_undefined__",a="__lodash_placeholder__",c=16,f=32,l=64,s=128,h=256,p=1/0,v=9007199254740991,_=NaN,g=4294967295,d=[["ary",s],["bind",1],["bindKey",2],["curry",8],["curryRight",c],["flip",512],["partial",f],["partialRight",l],["rearg",h]],y="[object Arguments]",m="[object Array]",w="[object Boolean]",b="[object Date]",x="[object Error]",j="[object Function]",A="[object GeneratorFunction]",I="[object Map]",E="[object Number]",k="[object Object]",O="[object Promise]",B="[object RegExp]",S="[object Set]",R="[object String]",z="[object Symbol]",C="[object WeakMap]",F="[object ArrayBuffer]",L="[object DataView]",T="[object Float32Array]",W="[object Float64Array]",U="[object Int8Array]",D="[object Int16Array]",$="[object Int32Array]",M="[object Uint8Array]",P="[object Uint8ClampedArray]",N="[object Uint16Array]",q="[object Uint32Array]",Z=/\b__p \+= '';/g,K=/\b(__p \+=) '' \+/g,Y=/(__e\(.*?\)|\b__t\)) \+\n'';/g,J=/&(?:amp|lt|gt|quot|#39);/g,G=/[&<>"']/g,V=RegExp(J.source),H=RegExp(G.source),X=/<%-([\s\S]+?)%>/g,Q=/<%([\s\S]+?)%>/g,nn=/<%=([\s\S]+?)%>/g,tn=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,rn=/^\w*$/,en=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,un=/[\\^$.*+?()[\]{}|]/g,on=RegExp(un.source),an=/^\s+/,cn=/\s/,fn=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,ln=/\{\n\/\* \[wrapped with (.+)\] \*/,sn=/,? & /,hn=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,pn=/[()=,{}\[\]\/\s]/,vn=/\\(\\)?/g,_n=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,gn=/\w*$/,dn=/^[-+]0x[0-9a-f]+$/i,yn=/^0b[01]+$/i,mn=/^\[object .+?Constructor\]$/,wn=/^0o[0-7]+$/i,bn=/^(?:0|[1-9]\d*)$/,xn=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,jn=/($^)/,An=/['\n\r\u2028\u2029\\]/g,In="\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff",En="\\u2700-\\u27bf",kn="a-z\\xdf-\\xf6\\xf8-\\xff",On="A-Z\\xc0-\\xd6\\xd8-\\xde",Bn="\\ufe0e\\ufe0f",Sn="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",Rn="['’]",zn="[\\ud800-\\udfff]",Cn="["+Sn+"]",Fn="["+In+"]",Ln="\\d+",Tn="[\\u2700-\\u27bf]",Wn="["+kn+"]",Un="[^\\ud800-\\udfff"+Sn+Ln+En+kn+On+"]",Dn="\\ud83c[\\udffb-\\udfff]",$n="[^\\ud800-\\udfff]",Mn="(?:\\ud83c[\\udde6-\\uddff]){2}",Pn="[\\ud800-\\udbff][\\udc00-\\udfff]",Nn="["+On+"]",qn="(?:"+Wn+"|"+Un+")",Zn="(?:"+Nn+"|"+Un+")",Kn="(?:['’](?:d|ll|m|re|s|t|ve))?",Yn="(?:['’](?:D|LL|M|RE|S|T|VE))?",Jn="(?:"+Fn+"|"+Dn+")"+"?",Gn="[\\ufe0e\\ufe0f]?",Vn=Gn+Jn+("(?:\\u200d(?:"+[$n,Mn,Pn].join("|")+")"+Gn+Jn+")*"),Hn="(?:"+[Tn,Mn,Pn].join("|")+")"+Vn,Xn="(?:"+[$n+Fn+"?",Fn,Mn,Pn,zn].join("|")+")",Qn=RegExp(Rn,"g"),nt=RegExp(Fn,"g"),tt=RegExp(Dn+"(?="+Dn+")|"+Xn+Vn,"g"),rt=RegExp([Nn+"?"+Wn+"+"+Kn+"(?="+[Cn,Nn,"$"].join("|")+")",Zn+"+"+Yn+"(?="+[Cn,Nn+qn,"$"].join("|")+")",Nn+"?"+qn+"+"+Kn,Nn+"+"+Yn,"\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",Ln,Hn].join("|"),"g"),et=RegExp("[\\u200d\\ud800-\\udfff"+In+Bn+"]"),ut=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,it=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],ot=-1,at={};at[T]=at[W]=at[U]=at[D]=at[$]=at[M]=at[P]=at[N]=at[q]=!0,at[y]=at[m]=at[F]=at[w]=at[L]=at[b]=at[x]=at[j]=at[I]=at[E]=at[k]=at[B]=at[S]=at[R]=at[C]=!1;var ct={};ct[y]=ct[m]=ct[F]=ct[L]=ct[w]=ct[b]=ct[T]=ct[W]=ct[U]=ct[D]=ct[$]=ct[I]=ct[E]=ct[k]=ct[B]=ct[S]=ct[R]=ct[z]=ct[M]=ct[P]=ct[N]=ct[q]=!0,ct[x]=ct[j]=ct[C]=!1;var ft={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},lt=parseFloat,st=parseInt,ht="object"==typeof r.g&&r.g&&r.g.Object===Object&&r.g,pt="object"==typeof self&&self&&self.Object===Object&&self,vt=ht||pt||Function("return this")(),_t=t&&!t.nodeType&&t,gt=_t&&n&&!n.nodeType&&n,dt=gt&>.exports===_t,yt=dt&&ht.process,mt=function(){try{var n=gt&>.require&>.require("util").types;return n||yt&&yt.binding&&yt.binding("util")}catch(n){}}(),wt=mt&&mt.isArrayBuffer,bt=mt&&mt.isDate,xt=mt&&mt.isMap,jt=mt&&mt.isRegExp,At=mt&&mt.isSet,It=mt&&mt.isTypedArray;function Et(n,t,r){switch(r.length){case 0:return n.call(t);case 1:return n.call(t,r[0]);case 2:return n.call(t,r[0],r[1]);case 3:return n.call(t,r[0],r[1],r[2])}return n.apply(t,r)}function kt(n,t,r,e){for(var u=-1,i=null==n?0:n.length;++u-1}function Ct(n,t,r){for(var e=-1,u=null==n?0:n.length;++e-1;);return r}function rr(n,t){for(var r=n.length;r--&&Pt(t,n[r],0)>-1;);return r}function er(n,t){for(var r=n.length,e=0;r--;)n[r]===t&&++e;return e}var ur=Yt({À:"A",Á:"A",Â:"A",Ã:"A",Ä:"A",Å:"A",à:"a",á:"a",â:"a",ã:"a",ä:"a",å:"a",Ç:"C",ç:"c",Ð:"D",ð:"d",È:"E",É:"E",Ê:"E",Ë:"E",è:"e",é:"e",ê:"e",ë:"e",Ì:"I",Í:"I",Î:"I",Ï:"I",ì:"i",í:"i",î:"i",ï:"i",Ñ:"N",ñ:"n",Ò:"O",Ó:"O",Ô:"O",Õ:"O",Ö:"O",Ø:"O",ò:"o",ó:"o",ô:"o",õ:"o",ö:"o",ø:"o",Ù:"U",Ú:"U",Û:"U",Ü:"U",ù:"u",ú:"u",û:"u",ü:"u",Ý:"Y",ý:"y",ÿ:"y",Æ:"Ae",æ:"ae",Þ:"Th",þ:"th",ß:"ss",Ā:"A",Ă:"A",Ą:"A",ā:"a",ă:"a",ą:"a",Ć:"C",Ĉ:"C",Ċ:"C",Č:"C",ć:"c",ĉ:"c",ċ:"c",č:"c",Ď:"D",Đ:"D",ď:"d",đ:"d",Ē:"E",Ĕ:"E",Ė:"E",Ę:"E",Ě:"E",ē:"e",ĕ:"e",ė:"e",ę:"e",ě:"e",Ĝ:"G",Ğ:"G",Ġ:"G",Ģ:"G",ĝ:"g",ğ:"g",ġ:"g",ģ:"g",Ĥ:"H",Ħ:"H",ĥ:"h",ħ:"h",Ĩ:"I",Ī:"I",Ĭ:"I",Į:"I",İ:"I",ĩ:"i",ī:"i",ĭ:"i",į:"i",ı:"i",Ĵ:"J",ĵ:"j",Ķ:"K",ķ:"k",ĸ:"k",Ĺ:"L",Ļ:"L",Ľ:"L",Ŀ:"L",Ł:"L",ĺ:"l",ļ:"l",ľ:"l",ŀ:"l",ł:"l",Ń:"N",Ņ:"N",Ň:"N",Ŋ:"N",ń:"n",ņ:"n",ň:"n",ŋ:"n",Ō:"O",Ŏ:"O",Ő:"O",ō:"o",ŏ:"o",ő:"o",Ŕ:"R",Ŗ:"R",Ř:"R",ŕ:"r",ŗ:"r",ř:"r",Ś:"S",Ŝ:"S",Ş:"S",Š:"S",ś:"s",ŝ:"s",ş:"s",š:"s",Ţ:"T",Ť:"T",Ŧ:"T",ţ:"t",ť:"t",ŧ:"t",Ũ:"U",Ū:"U",Ŭ:"U",Ů:"U",Ű:"U",Ų:"U",ũ:"u",ū:"u",ŭ:"u",ů:"u",ű:"u",ų:"u",Ŵ:"W",ŵ:"w",Ŷ:"Y",ŷ:"y",Ÿ:"Y",Ź:"Z",Ż:"Z",Ž:"Z",ź:"z",ż:"z",ž:"z",IJ:"IJ",ij:"ij",Œ:"Oe",œ:"oe",ʼn:"'n",ſ:"s"}),ir=Yt({"&":"&","<":"<",">":">",'"':""","'":"'"});function or(n){return"\\"+ft[n]}function ar(n){return et.test(n)}function cr(n){var t=-1,r=Array(n.size);return n.forEach((function(n,e){r[++t]=[e,n]})),r}function fr(n,t){return function(r){return n(t(r))}}function lr(n,t){for(var r=-1,e=n.length,u=0,i=[];++r",""":'"',"'":"'"});var dr=function n(t){var r,e=(t=null==t?vt:dr.defaults(vt.Object(),t,dr.pick(vt,it))).Array,cn=t.Date,In=t.Error,En=t.Function,kn=t.Math,On=t.Object,Bn=t.RegExp,Sn=t.String,Rn=t.TypeError,zn=e.prototype,Cn=En.prototype,Fn=On.prototype,Ln=t["__core-js_shared__"],Tn=Cn.toString,Wn=Fn.hasOwnProperty,Un=0,Dn=(r=/[^.]+$/.exec(Ln&&Ln.keys&&Ln.keys.IE_PROTO||""))?"Symbol(src)_1."+r:"",$n=Fn.toString,Mn=Tn.call(On),Pn=vt._,Nn=Bn("^"+Tn.call(Wn).replace(un,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),qn=dt?t.Buffer:u,Zn=t.Symbol,Kn=t.Uint8Array,Yn=qn?qn.allocUnsafe:u,Jn=fr(On.getPrototypeOf,On),Gn=On.create,Vn=Fn.propertyIsEnumerable,Hn=zn.splice,Xn=Zn?Zn.isConcatSpreadable:u,tt=Zn?Zn.iterator:u,et=Zn?Zn.toStringTag:u,ft=function(){try{var n=pi(On,"defineProperty");return n({},"",{}),n}catch(n){}}(),ht=t.clearTimeout!==vt.clearTimeout&&t.clearTimeout,pt=cn&&cn.now!==vt.Date.now&&cn.now,_t=t.setTimeout!==vt.setTimeout&&t.setTimeout,gt=kn.ceil,yt=kn.floor,mt=On.getOwnPropertySymbols,Dt=qn?qn.isBuffer:u,Yt=t.isFinite,yr=zn.join,mr=fr(On.keys,On),wr=kn.max,br=kn.min,xr=cn.now,jr=t.parseInt,Ar=kn.random,Ir=zn.reverse,Er=pi(t,"DataView"),kr=pi(t,"Map"),Or=pi(t,"Promise"),Br=pi(t,"Set"),Sr=pi(t,"WeakMap"),Rr=pi(On,"create"),zr=Sr&&new Sr,Cr={},Fr=$i(Er),Lr=$i(kr),Tr=$i(Or),Wr=$i(Br),Ur=$i(Sr),Dr=Zn?Zn.prototype:u,$r=Dr?Dr.valueOf:u,Mr=Dr?Dr.toString:u;function Pr(n){if(ua(n)&&!Yo(n)&&!(n instanceof Kr)){if(n instanceof Zr)return n;if(Wn.call(n,"__wrapped__"))return Mi(n)}return new Zr(n)}var Nr=function(){function n(){}return function(t){if(!ea(t))return{};if(Gn)return Gn(t);n.prototype=t;var r=new n;return n.prototype=u,r}}();function qr(){}function Zr(n,t){this.__wrapped__=n,this.__actions__=[],this.__chain__=!!t,this.__index__=0,this.__values__=u}function Kr(n){this.__wrapped__=n,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=g,this.__views__=[]}function Yr(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t=t?n:t)),n}function le(n,t,r,e,i,o){var a,c=1&t,f=2&t,l=4&t;if(r&&(a=i?r(n,e,i,o):r(n)),a!==u)return a;if(!ea(n))return n;var s=Yo(n);if(s){if(a=function(n){var t=n.length,r=new n.constructor(t);t&&"string"==typeof n[0]&&Wn.call(n,"index")&&(r.index=n.index,r.input=n.input);return r}(n),!c)return Ru(n,a)}else{var h=gi(n),p=h==j||h==A;if(Ho(n))return Iu(n,c);if(h==k||h==y||p&&!i){if(a=f||p?{}:yi(n),!c)return f?function(n,t){return zu(n,_i(n),t)}(n,function(n,t){return n&&zu(t,La(t),n)}(a,n)):function(n,t){return zu(n,vi(n),t)}(n,oe(a,n))}else{if(!ct[h])return i?n:{};a=function(n,t,r){var e=n.constructor;switch(t){case F:return Eu(n);case w:case b:return new e(+n);case L:return function(n,t){var r=t?Eu(n.buffer):n.buffer;return new n.constructor(r,n.byteOffset,n.byteLength)}(n,r);case T:case W:case U:case D:case $:case M:case P:case N:case q:return ku(n,r);case I:return new e;case E:case R:return new e(n);case B:return function(n){var t=new n.constructor(n.source,gn.exec(n));return t.lastIndex=n.lastIndex,t}(n);case S:return new e;case z:return u=n,$r?On($r.call(u)):{}}var u}(n,h,c)}}o||(o=new Hr);var v=o.get(n);if(v)return v;o.set(n,a),fa(n)?n.forEach((function(e){a.add(le(e,t,r,e,n,o))})):ia(n)&&n.forEach((function(e,u){a.set(u,le(e,t,r,u,n,o))}));var _=s?u:(l?f?oi:ii:f?La:Fa)(n);return Ot(_||n,(function(e,u){_&&(e=n[u=e]),ee(a,u,le(e,t,r,u,n,o))})),a}function se(n,t,r){var e=r.length;if(null==n)return!e;for(n=On(n);e--;){var i=r[e],o=t[i],a=n[i];if(a===u&&!(i in n)||!o(a))return!1}return!0}function he(n,t,r){if("function"!=typeof n)throw new Rn(i);return Ci((function(){n.apply(u,r)}),t)}function pe(n,t,r,e){var u=-1,i=zt,o=!0,a=n.length,c=[],f=t.length;if(!a)return c;r&&(t=Ft(t,Xt(r))),e?(i=Ct,o=!1):t.length>=200&&(i=nr,o=!1,t=new Vr(t));n:for(;++u-1},Jr.prototype.set=function(n,t){var r=this.__data__,e=ue(r,n);return e<0?(++this.size,r.push([n,t])):r[e][1]=t,this},Gr.prototype.clear=function(){this.size=0,this.__data__={hash:new Yr,map:new(kr||Jr),string:new Yr}},Gr.prototype.delete=function(n){var t=si(this,n).delete(n);return this.size-=t?1:0,t},Gr.prototype.get=function(n){return si(this,n).get(n)},Gr.prototype.has=function(n){return si(this,n).has(n)},Gr.prototype.set=function(n,t){var r=si(this,n),e=r.size;return r.set(n,t),this.size+=r.size==e?0:1,this},Vr.prototype.add=Vr.prototype.push=function(n){return this.__data__.set(n,o),this},Vr.prototype.has=function(n){return this.__data__.has(n)},Hr.prototype.clear=function(){this.__data__=new Jr,this.size=0},Hr.prototype.delete=function(n){var t=this.__data__,r=t.delete(n);return this.size=t.size,r},Hr.prototype.get=function(n){return this.__data__.get(n)},Hr.prototype.has=function(n){return this.__data__.has(n)},Hr.prototype.set=function(n,t){var r=this.__data__;if(r instanceof Jr){var e=r.__data__;if(!kr||e.length<199)return e.push([n,t]),this.size=++r.size,this;r=this.__data__=new Gr(e)}return r.set(n,t),this.size=r.size,this};var ve=Lu(xe),_e=Lu(je,!0);function ge(n,t){var r=!0;return ve(n,(function(n,e,u){return r=!!t(n,e,u)})),r}function de(n,t,r){for(var e=-1,i=n.length;++e0&&r(a)?t>1?me(a,t-1,r,e,u):Lt(u,a):e||(u[u.length]=a)}return u}var we=Tu(),be=Tu(!0);function xe(n,t){return n&&we(n,t,Fa)}function je(n,t){return n&&be(n,t,Fa)}function Ae(n,t){return Rt(t,(function(t){return na(n[t])}))}function Ie(n,t){for(var r=0,e=(t=bu(t,n)).length;null!=n&&rt}function Be(n,t){return null!=n&&Wn.call(n,t)}function Se(n,t){return null!=n&&t in On(n)}function Re(n,t,r){for(var i=r?Ct:zt,o=n[0].length,a=n.length,c=a,f=e(a),l=1/0,s=[];c--;){var h=n[c];c&&t&&(h=Ft(h,Xt(t))),l=br(h.length,l),f[c]=!r&&(t||o>=120&&h.length>=120)?new Vr(c&&h):u}h=n[0];var p=-1,v=f[0];n:for(;++p=a?c:c*("desc"==r[e]?-1:1)}return n.index-t.index}(n,t,r)}))}function Ye(n,t,r){for(var e=-1,u=t.length,i={};++e-1;)a!==n&&Hn.call(a,c,1),Hn.call(n,c,1);return n}function Ge(n,t){for(var r=n?t.length:0,e=r-1;r--;){var u=t[r];if(r==e||u!==i){var i=u;wi(u)?Hn.call(n,u,1):pu(n,u)}}return n}function Ve(n,t){return n+yt(Ar()*(t-n+1))}function He(n,t){var r="";if(!n||t<1||t>v)return r;do{t%2&&(r+=n),(t=yt(t/2))&&(n+=n)}while(t);return r}function Xe(n,t){return Fi(Oi(n,t,oc),n+"")}function Qe(n){return Qr(Na(n))}function nu(n,t){var r=Na(n);return Wi(r,fe(t,0,r.length))}function tu(n,t,r,e){if(!ea(n))return n;for(var i=-1,o=(t=bu(t,n)).length,a=o-1,c=n;null!=c&&++ii?0:i+t),(r=r>i?i:r)<0&&(r+=i),i=t>r?0:r-t>>>0,t>>>=0;for(var o=e(i);++u>>1,o=n[i];null!==o&&!sa(o)&&(r?o<=t:o=200){var f=t?null:Hu(n);if(f)return sr(f);o=!1,u=nr,c=new Vr}else c=t?[]:a;n:for(;++e=e?n:iu(n,t,r)}var Au=ht||function(n){return vt.clearTimeout(n)};function Iu(n,t){if(t)return n.slice();var r=n.length,e=Yn?Yn(r):new n.constructor(r);return n.copy(e),e}function Eu(n){var t=new n.constructor(n.byteLength);return new Kn(t).set(new Kn(n)),t}function ku(n,t){var r=t?Eu(n.buffer):n.buffer;return new n.constructor(r,n.byteOffset,n.length)}function Ou(n,t){if(n!==t){var r=n!==u,e=null===n,i=n==n,o=sa(n),a=t!==u,c=null===t,f=t==t,l=sa(t);if(!c&&!l&&!o&&n>t||o&&a&&f&&!c&&!l||e&&a&&f||!r&&f||!i)return 1;if(!e&&!o&&!l&&n1?r[i-1]:u,a=i>2?r[2]:u;for(o=n.length>3&&"function"==typeof o?(i--,o):u,a&&bi(r[0],r[1],a)&&(o=i<3?u:o,i=1),t=On(t);++e-1?i[o?t[a]:a]:u}}function Mu(n){return ui((function(t){var r=t.length,e=r,o=Zr.prototype.thru;for(n&&t.reverse();e--;){var a=t[e];if("function"!=typeof a)throw new Rn(i);if(o&&!c&&"wrapper"==ci(a))var c=new Zr([],!0)}for(e=c?e:r;++e1&&m.reverse(),p&&lc))return!1;var l=o.get(n),s=o.get(t);if(l&&s)return l==t&&s==n;var h=-1,p=!0,v=2&r?new Vr:u;for(o.set(n,t),o.set(t,n);++h-1&&n%1==0&&n1?"& ":"")+t[e],t=t.join(r>2?", ":" "),n.replace(fn,"{\n/* [wrapped with "+t+"] */\n")}(e,function(n,t){return Ot(d,(function(r){var e="_."+r[0];t&r[1]&&!zt(n,e)&&n.push(e)})),n.sort()}(function(n){var t=n.match(ln);return t?t[1].split(sn):[]}(e),r)))}function Ti(n){var t=0,r=0;return function(){var e=xr(),i=16-(e-r);if(r=e,i>0){if(++t>=800)return arguments[0]}else t=0;return n.apply(u,arguments)}}function Wi(n,t){var r=-1,e=n.length,i=e-1;for(t=t===u?e:t;++r1?n[t-1]:u;return r="function"==typeof r?(n.pop(),r):u,ao(n,r)}));function vo(n){var t=Pr(n);return t.__chain__=!0,t}function _o(n,t){return t(n)}var go=ui((function(n){var t=n.length,r=t?n[0]:0,e=this.__wrapped__,i=function(t){return ce(t,n)};return!(t>1||this.__actions__.length)&&e instanceof Kr&&wi(r)?((e=e.slice(r,+r+(t?1:0))).__actions__.push({func:_o,args:[i],thisArg:u}),new Zr(e,this.__chain__).thru((function(n){return t&&!n.length&&n.push(u),n}))):this.thru(i)}));var yo=Cu((function(n,t,r){Wn.call(n,r)?++n[r]:ae(n,r,1)}));var mo=$u(Zi),wo=$u(Ki);function bo(n,t){return(Yo(n)?Ot:ve)(n,li(t,3))}function xo(n,t){return(Yo(n)?Bt:_e)(n,li(t,3))}var jo=Cu((function(n,t,r){Wn.call(n,r)?n[r].push(t):ae(n,r,[t])}));var Ao=Xe((function(n,t,r){var u=-1,i="function"==typeof t,o=Go(n)?e(n.length):[];return ve(n,(function(n){o[++u]=i?Et(t,n,r):ze(n,t,r)})),o})),Io=Cu((function(n,t,r){ae(n,r,t)}));function Eo(n,t){return(Yo(n)?Ft:Me)(n,li(t,3))}var ko=Cu((function(n,t,r){n[r?0:1].push(t)}),(function(){return[[],[]]}));var Oo=Xe((function(n,t){if(null==n)return[];var r=t.length;return r>1&&bi(n,t[0],t[1])?t=[]:r>2&&bi(t[0],t[1],t[2])&&(t=[t[0]]),Ke(n,me(t,1),[])})),Bo=pt||function(){return vt.Date.now()};function So(n,t,r){return t=r?u:t,t=n&&null==t?n.length:t,Qu(n,s,u,u,u,u,t)}function Ro(n,t){var r;if("function"!=typeof t)throw new Rn(i);return n=da(n),function(){return--n>0&&(r=t.apply(this,arguments)),n<=1&&(t=u),r}}var zo=Xe((function(n,t,r){var e=1;if(r.length){var u=lr(r,fi(zo));e|=f}return Qu(n,e,t,r,u)})),Co=Xe((function(n,t,r){var e=3;if(r.length){var u=lr(r,fi(Co));e|=f}return Qu(t,e,n,r,u)}));function Fo(n,t,r){var e,o,a,c,f,l,s=0,h=!1,p=!1,v=!0;if("function"!=typeof n)throw new Rn(i);function _(t){var r=e,i=o;return e=o=u,s=t,c=n.apply(i,r)}function g(n){return s=n,f=Ci(y,t),h?_(n):c}function d(n){var r=n-l;return l===u||r>=t||r<0||p&&n-s>=a}function y(){var n=Bo();if(d(n))return m(n);f=Ci(y,function(n){var r=t-(n-l);return p?br(r,a-(n-s)):r}(n))}function m(n){return f=u,v&&e?_(n):(e=o=u,c)}function w(){var n=Bo(),r=d(n);if(e=arguments,o=this,l=n,r){if(f===u)return g(l);if(p)return Au(f),f=Ci(y,t),_(l)}return f===u&&(f=Ci(y,t)),c}return t=ma(t)||0,ea(r)&&(h=!!r.leading,a=(p="maxWait"in r)?wr(ma(r.maxWait)||0,t):a,v="trailing"in r?!!r.trailing:v),w.cancel=function(){f!==u&&Au(f),s=0,e=l=o=f=u},w.flush=function(){return f===u?c:m(Bo())},w}var Lo=Xe((function(n,t){return he(n,1,t)})),To=Xe((function(n,t,r){return he(n,ma(t)||0,r)}));function Wo(n,t){if("function"!=typeof n||null!=t&&"function"!=typeof t)throw new Rn(i);var r=function(){var e=arguments,u=t?t.apply(this,e):e[0],i=r.cache;if(i.has(u))return i.get(u);var o=n.apply(this,e);return r.cache=i.set(u,o)||i,o};return r.cache=new(Wo.Cache||Gr),r}function Uo(n){if("function"!=typeof n)throw new Rn(i);return function(){var t=arguments;switch(t.length){case 0:return!n.call(this);case 1:return!n.call(this,t[0]);case 2:return!n.call(this,t[0],t[1]);case 3:return!n.call(this,t[0],t[1],t[2])}return!n.apply(this,t)}}Wo.Cache=Gr;var Do=xu((function(n,t){var r=(t=1==t.length&&Yo(t[0])?Ft(t[0],Xt(li())):Ft(me(t,1),Xt(li()))).length;return Xe((function(e){for(var u=-1,i=br(e.length,r);++u=t})),Ko=Ce(function(){return arguments}())?Ce:function(n){return ua(n)&&Wn.call(n,"callee")&&!Vn.call(n,"callee")},Yo=e.isArray,Jo=wt?Xt(wt):function(n){return ua(n)&&ke(n)==F};function Go(n){return null!=n&&ra(n.length)&&!na(n)}function Vo(n){return ua(n)&&Go(n)}var Ho=Dt||mc,Xo=bt?Xt(bt):function(n){return ua(n)&&ke(n)==b};function Qo(n){if(!ua(n))return!1;var t=ke(n);return t==x||"[object DOMException]"==t||"string"==typeof n.message&&"string"==typeof n.name&&!aa(n)}function na(n){if(!ea(n))return!1;var t=ke(n);return t==j||t==A||"[object AsyncFunction]"==t||"[object Proxy]"==t}function ta(n){return"number"==typeof n&&n==da(n)}function ra(n){return"number"==typeof n&&n>-1&&n%1==0&&n<=v}function ea(n){var t=typeof n;return null!=n&&("object"==t||"function"==t)}function ua(n){return null!=n&&"object"==typeof n}var ia=xt?Xt(xt):function(n){return ua(n)&&gi(n)==I};function oa(n){return"number"==typeof n||ua(n)&&ke(n)==E}function aa(n){if(!ua(n)||ke(n)!=k)return!1;var t=Jn(n);if(null===t)return!0;var r=Wn.call(t,"constructor")&&t.constructor;return"function"==typeof r&&r instanceof r&&Tn.call(r)==Mn}var ca=jt?Xt(jt):function(n){return ua(n)&&ke(n)==B};var fa=At?Xt(At):function(n){return ua(n)&&gi(n)==S};function la(n){return"string"==typeof n||!Yo(n)&&ua(n)&&ke(n)==R}function sa(n){return"symbol"==typeof n||ua(n)&&ke(n)==z}var ha=It?Xt(It):function(n){return ua(n)&&ra(n.length)&&!!at[ke(n)]};var pa=Ju($e),va=Ju((function(n,t){return n<=t}));function _a(n){if(!n)return[];if(Go(n))return la(n)?vr(n):Ru(n);if(tt&&n[tt])return function(n){for(var t,r=[];!(t=n.next()).done;)r.push(t.value);return r}(n[tt]());var t=gi(n);return(t==I?cr:t==S?sr:Na)(n)}function ga(n){return n?(n=ma(n))===p||n===-1/0?17976931348623157e292*(n<0?-1:1):n==n?n:0:0===n?n:0}function da(n){var t=ga(n),r=t%1;return t==t?r?t-r:t:0}function ya(n){return n?fe(da(n),0,g):0}function ma(n){if("number"==typeof n)return n;if(sa(n))return _;if(ea(n)){var t="function"==typeof n.valueOf?n.valueOf():n;n=ea(t)?t+"":t}if("string"!=typeof n)return 0===n?n:+n;n=Ht(n);var r=yn.test(n);return r||wn.test(n)?st(n.slice(2),r?2:8):dn.test(n)?_:+n}function wa(n){return zu(n,La(n))}function ba(n){return null==n?"":su(n)}var xa=Fu((function(n,t){if(Ii(t)||Go(t))zu(t,Fa(t),n);else for(var r in t)Wn.call(t,r)&&ee(n,r,t[r])})),ja=Fu((function(n,t){zu(t,La(t),n)})),Aa=Fu((function(n,t,r,e){zu(t,La(t),n,e)})),Ia=Fu((function(n,t,r,e){zu(t,Fa(t),n,e)})),Ea=ui(ce);var ka=Xe((function(n,t){n=On(n);var r=-1,e=t.length,i=e>2?t[2]:u;for(i&&bi(t[0],t[1],i)&&(e=1);++r1),t})),zu(n,oi(n),r),e&&(r=le(r,7,ri));for(var u=t.length;u--;)pu(r,t[u]);return r}));var Da=ui((function(n,t){return null==n?{}:function(n,t){return Ye(n,t,(function(t,r){return Sa(n,r)}))}(n,t)}));function $a(n,t){if(null==n)return{};var r=Ft(oi(n),(function(n){return[n]}));return t=li(t),Ye(n,r,(function(n,r){return t(n,r[0])}))}var Ma=Xu(Fa),Pa=Xu(La);function Na(n){return null==n?[]:Qt(n,Fa(n))}var qa=Uu((function(n,t,r){return t=t.toLowerCase(),n+(r?Za(t):t)}));function Za(n){return Qa(ba(n).toLowerCase())}function Ka(n){return(n=ba(n))&&n.replace(xn,ur).replace(nt,"")}var Ya=Uu((function(n,t,r){return n+(r?"-":"")+t.toLowerCase()})),Ja=Uu((function(n,t,r){return n+(r?" ":"")+t.toLowerCase()})),Ga=Wu("toLowerCase");var Va=Uu((function(n,t,r){return n+(r?"_":"")+t.toLowerCase()}));var Ha=Uu((function(n,t,r){return n+(r?" ":"")+Qa(t)}));var Xa=Uu((function(n,t,r){return n+(r?" ":"")+t.toUpperCase()})),Qa=Wu("toUpperCase");function nc(n,t,r){return n=ba(n),(t=r?u:t)===u?function(n){return ut.test(n)}(n)?function(n){return n.match(rt)||[]}(n):function(n){return n.match(hn)||[]}(n):n.match(t)||[]}var tc=Xe((function(n,t){try{return Et(n,u,t)}catch(n){return Qo(n)?n:new In(n)}})),rc=ui((function(n,t){return Ot(t,(function(t){t=Di(t),ae(n,t,zo(n[t],n))})),n}));function ec(n){return function(){return n}}var uc=Mu(),ic=Mu(!0);function oc(n){return n}function ac(n){return We("function"==typeof n?n:le(n,1))}var cc=Xe((function(n,t){return function(r){return ze(r,n,t)}})),fc=Xe((function(n,t){return function(r){return ze(n,r,t)}}));function lc(n,t,r){var e=Fa(t),u=Ae(t,e);null!=r||ea(t)&&(u.length||!e.length)||(r=t,t=n,n=this,u=Ae(t,Fa(t)));var i=!(ea(r)&&"chain"in r&&!r.chain),o=na(n);return Ot(u,(function(r){var e=t[r];n[r]=e,o&&(n.prototype[r]=function(){var t=this.__chain__;if(i||t){var r=n(this.__wrapped__),u=r.__actions__=Ru(this.__actions__);return u.push({func:e,args:arguments,thisArg:n}),r.__chain__=t,r}return e.apply(n,Lt([this.value()],arguments))})})),n}function sc(){}var hc=Zu(Ft),pc=Zu(St),vc=Zu(Ut);function _c(n){return xi(n)?Kt(Di(n)):function(n){return function(t){return Ie(t,n)}}(n)}var gc=Yu(),dc=Yu(!0);function yc(){return[]}function mc(){return!1}var wc=qu((function(n,t){return n+t}),0),bc=Vu("ceil"),xc=qu((function(n,t){return n/t}),1),jc=Vu("floor");var Ac,Ic=qu((function(n,t){return n*t}),1),Ec=Vu("round"),kc=qu((function(n,t){return n-t}),0);return Pr.after=function(n,t){if("function"!=typeof t)throw new Rn(i);return n=da(n),function(){if(--n<1)return t.apply(this,arguments)}},Pr.ary=So,Pr.assign=xa,Pr.assignIn=ja,Pr.assignInWith=Aa,Pr.assignWith=Ia,Pr.at=Ea,Pr.before=Ro,Pr.bind=zo,Pr.bindAll=rc,Pr.bindKey=Co,Pr.castArray=function(){if(!arguments.length)return[];var n=arguments[0];return Yo(n)?n:[n]},Pr.chain=vo,Pr.chunk=function(n,t,r){t=(r?bi(n,t,r):t===u)?1:wr(da(t),0);var i=null==n?0:n.length;if(!i||t<1)return[];for(var o=0,a=0,c=e(gt(i/t));oi?0:i+r),(e=e===u||e>i?i:da(e))<0&&(e+=i),e=r>e?0:ya(e);r>>0)?(n=ba(n))&&("string"==typeof t||null!=t&&!ca(t))&&!(t=su(t))&&ar(n)?ju(vr(n),0,r):n.split(t,r):[]},Pr.spread=function(n,t){if("function"!=typeof n)throw new Rn(i);return t=null==t?0:wr(da(t),0),Xe((function(r){var e=r[t],u=ju(r,0,t);return e&&Lt(u,e),Et(n,this,u)}))},Pr.tail=function(n){var t=null==n?0:n.length;return t?iu(n,1,t):[]},Pr.take=function(n,t,r){return n&&n.length?iu(n,0,(t=r||t===u?1:da(t))<0?0:t):[]},Pr.takeRight=function(n,t,r){var e=null==n?0:n.length;return e?iu(n,(t=e-(t=r||t===u?1:da(t)))<0?0:t,e):[]},Pr.takeRightWhile=function(n,t){return n&&n.length?_u(n,li(t,3),!1,!0):[]},Pr.takeWhile=function(n,t){return n&&n.length?_u(n,li(t,3)):[]},Pr.tap=function(n,t){return t(n),n},Pr.throttle=function(n,t,r){var e=!0,u=!0;if("function"!=typeof n)throw new Rn(i);return ea(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u),Fo(n,t,{leading:e,maxWait:t,trailing:u})},Pr.thru=_o,Pr.toArray=_a,Pr.toPairs=Ma,Pr.toPairsIn=Pa,Pr.toPath=function(n){return Yo(n)?Ft(n,Di):sa(n)?[n]:Ru(Ui(ba(n)))},Pr.toPlainObject=wa,Pr.transform=function(n,t,r){var e=Yo(n),u=e||Ho(n)||ha(n);if(t=li(t,4),null==r){var i=n&&n.constructor;r=u?e?new i:[]:ea(n)&&na(i)?Nr(Jn(n)):{}}return(u?Ot:xe)(n,(function(n,e,u){return t(r,n,e,u)})),r},Pr.unary=function(n){return So(n,1)},Pr.union=eo,Pr.unionBy=uo,Pr.unionWith=io,Pr.uniq=function(n){return n&&n.length?hu(n):[]},Pr.uniqBy=function(n,t){return n&&n.length?hu(n,li(t,2)):[]},Pr.uniqWith=function(n,t){return t="function"==typeof t?t:u,n&&n.length?hu(n,u,t):[]},Pr.unset=function(n,t){return null==n||pu(n,t)},Pr.unzip=oo,Pr.unzipWith=ao,Pr.update=function(n,t,r){return null==n?n:vu(n,t,wu(r))},Pr.updateWith=function(n,t,r,e){return e="function"==typeof e?e:u,null==n?n:vu(n,t,wu(r),e)},Pr.values=Na,Pr.valuesIn=function(n){return null==n?[]:Qt(n,La(n))},Pr.without=co,Pr.words=nc,Pr.wrap=function(n,t){return $o(wu(t),n)},Pr.xor=fo,Pr.xorBy=lo,Pr.xorWith=so,Pr.zip=ho,Pr.zipObject=function(n,t){return yu(n||[],t||[],ee)},Pr.zipObjectDeep=function(n,t){return yu(n||[],t||[],tu)},Pr.zipWith=po,Pr.entries=Ma,Pr.entriesIn=Pa,Pr.extend=ja,Pr.extendWith=Aa,lc(Pr,Pr),Pr.add=wc,Pr.attempt=tc,Pr.camelCase=qa,Pr.capitalize=Za,Pr.ceil=bc,Pr.clamp=function(n,t,r){return r===u&&(r=t,t=u),r!==u&&(r=(r=ma(r))==r?r:0),t!==u&&(t=(t=ma(t))==t?t:0),fe(ma(n),t,r)},Pr.clone=function(n){return le(n,4)},Pr.cloneDeep=function(n){return le(n,5)},Pr.cloneDeepWith=function(n,t){return le(n,5,t="function"==typeof t?t:u)},Pr.cloneWith=function(n,t){return le(n,4,t="function"==typeof t?t:u)},Pr.conformsTo=function(n,t){return null==t||se(n,t,Fa(t))},Pr.deburr=Ka,Pr.defaultTo=function(n,t){return null==n||n!=n?t:n},Pr.divide=xc,Pr.endsWith=function(n,t,r){n=ba(n),t=su(t);var e=n.length,i=r=r===u?e:fe(da(r),0,e);return(r-=t.length)>=0&&n.slice(r,i)==t},Pr.eq=No,Pr.escape=function(n){return(n=ba(n))&&H.test(n)?n.replace(G,ir):n},Pr.escapeRegExp=function(n){return(n=ba(n))&&on.test(n)?n.replace(un,"\\$&"):n},Pr.every=function(n,t,r){var e=Yo(n)?St:ge;return r&&bi(n,t,r)&&(t=u),e(n,li(t,3))},Pr.find=mo,Pr.findIndex=Zi,Pr.findKey=function(n,t){return $t(n,li(t,3),xe)},Pr.findLast=wo,Pr.findLastIndex=Ki,Pr.findLastKey=function(n,t){return $t(n,li(t,3),je)},Pr.floor=jc,Pr.forEach=bo,Pr.forEachRight=xo,Pr.forIn=function(n,t){return null==n?n:we(n,li(t,3),La)},Pr.forInRight=function(n,t){return null==n?n:be(n,li(t,3),La)},Pr.forOwn=function(n,t){return n&&xe(n,li(t,3))},Pr.forOwnRight=function(n,t){return n&&je(n,li(t,3))},Pr.get=Ba,Pr.gt=qo,Pr.gte=Zo,Pr.has=function(n,t){return null!=n&&di(n,t,Be)},Pr.hasIn=Sa,Pr.head=Ji,Pr.identity=oc,Pr.includes=function(n,t,r,e){n=Go(n)?n:Na(n),r=r&&!e?da(r):0;var u=n.length;return r<0&&(r=wr(u+r,0)),la(n)?r<=u&&n.indexOf(t,r)>-1:!!u&&Pt(n,t,r)>-1},Pr.indexOf=function(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var u=null==r?0:da(r);return u<0&&(u=wr(e+u,0)),Pt(n,t,u)},Pr.inRange=function(n,t,r){return t=ga(t),r===u?(r=t,t=0):r=ga(r),function(n,t,r){return n>=br(t,r)&&n=-9007199254740991&&n<=v},Pr.isSet=fa,Pr.isString=la,Pr.isSymbol=sa,Pr.isTypedArray=ha,Pr.isUndefined=function(n){return n===u},Pr.isWeakMap=function(n){return ua(n)&&gi(n)==C},Pr.isWeakSet=function(n){return ua(n)&&"[object WeakSet]"==ke(n)},Pr.join=function(n,t){return null==n?"":yr.call(n,t)},Pr.kebabCase=Ya,Pr.last=Xi,Pr.lastIndexOf=function(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var i=e;return r!==u&&(i=(i=da(r))<0?wr(e+i,0):br(i,e-1)),t==t?function(n,t,r){for(var e=r+1;e--;)if(n[e]===t)return e;return e}(n,t,i):Mt(n,qt,i,!0)},Pr.lowerCase=Ja,Pr.lowerFirst=Ga,Pr.lt=pa,Pr.lte=va,Pr.max=function(n){return n&&n.length?de(n,oc,Oe):u},Pr.maxBy=function(n,t){return n&&n.length?de(n,li(t,2),Oe):u},Pr.mean=function(n){return Zt(n,oc)},Pr.meanBy=function(n,t){return Zt(n,li(t,2))},Pr.min=function(n){return n&&n.length?de(n,oc,$e):u},Pr.minBy=function(n,t){return n&&n.length?de(n,li(t,2),$e):u},Pr.stubArray=yc,Pr.stubFalse=mc,Pr.stubObject=function(){return{}},Pr.stubString=function(){return""},Pr.stubTrue=function(){return!0},Pr.multiply=Ic,Pr.nth=function(n,t){return n&&n.length?Ze(n,da(t)):u},Pr.noConflict=function(){return vt._===this&&(vt._=Pn),this},Pr.noop=sc,Pr.now=Bo,Pr.pad=function(n,t,r){n=ba(n);var e=(t=da(t))?pr(n):0;if(!t||e>=t)return n;var u=(t-e)/2;return Ku(yt(u),r)+n+Ku(gt(u),r)},Pr.padEnd=function(n,t,r){n=ba(n);var e=(t=da(t))?pr(n):0;return t&&et){var e=n;n=t,t=e}if(r||n%1||t%1){var i=Ar();return br(n+i*(t-n+lt("1e-"+((i+"").length-1))),t)}return Ve(n,t)},Pr.reduce=function(n,t,r){var e=Yo(n)?Tt:Jt,u=arguments.length<3;return e(n,li(t,4),r,u,ve)},Pr.reduceRight=function(n,t,r){var e=Yo(n)?Wt:Jt,u=arguments.length<3;return e(n,li(t,4),r,u,_e)},Pr.repeat=function(n,t,r){return t=(r?bi(n,t,r):t===u)?1:da(t),He(ba(n),t)},Pr.replace=function(){var n=arguments,t=ba(n[0]);return n.length<3?t:t.replace(n[1],n[2])},Pr.result=function(n,t,r){var e=-1,i=(t=bu(t,n)).length;for(i||(i=1,n=u);++ev)return[];var r=g,e=br(n,g);t=li(t),n-=g;for(var u=Vt(e,t);++r=o)return n;var c=r-pr(e);if(c<1)return e;var f=a?ju(a,0,c).join(""):n.slice(0,c);if(i===u)return f+e;if(a&&(c+=f.length-c),ca(i)){if(n.slice(c).search(i)){var l,s=f;for(i.global||(i=Bn(i.source,ba(gn.exec(i))+"g")),i.lastIndex=0;l=i.exec(s);)var h=l.index;f=f.slice(0,h===u?c:h)}}else if(n.indexOf(su(i),c)!=c){var p=f.lastIndexOf(i);p>-1&&(f=f.slice(0,p))}return f+e},Pr.unescape=function(n){return(n=ba(n))&&V.test(n)?n.replace(J,gr):n},Pr.uniqueId=function(n){var t=++Un;return ba(n)+t},Pr.upperCase=Xa,Pr.upperFirst=Qa,Pr.each=bo,Pr.eachRight=xo,Pr.first=Ji,lc(Pr,(Ac={},xe(Pr,(function(n,t){Wn.call(Pr.prototype,t)||(Ac[t]=n)})),Ac),{chain:!1}),Pr.VERSION="4.17.21",Ot(["bind","bindKey","curry","curryRight","partial","partialRight"],(function(n){Pr[n].placeholder=Pr})),Ot(["drop","take"],(function(n,t){Kr.prototype[n]=function(r){r=r===u?1:wr(da(r),0);var e=this.__filtered__&&!t?new Kr(this):this.clone();return e.__filtered__?e.__takeCount__=br(r,e.__takeCount__):e.__views__.push({size:br(r,g),type:n+(e.__dir__<0?"Right":"")}),e},Kr.prototype[n+"Right"]=function(t){return this.reverse()[n](t).reverse()}})),Ot(["filter","map","takeWhile"],(function(n,t){var r=t+1,e=1==r||3==r;Kr.prototype[n]=function(n){var t=this.clone();return t.__iteratees__.push({iteratee:li(n,3),type:r}),t.__filtered__=t.__filtered__||e,t}})),Ot(["head","last"],(function(n,t){var r="take"+(t?"Right":"");Kr.prototype[n]=function(){return this[r](1).value()[0]}})),Ot(["initial","tail"],(function(n,t){var r="drop"+(t?"":"Right");Kr.prototype[n]=function(){return this.__filtered__?new Kr(this):this[r](1)}})),Kr.prototype.compact=function(){return this.filter(oc)},Kr.prototype.find=function(n){return this.filter(n).head()},Kr.prototype.findLast=function(n){return this.reverse().find(n)},Kr.prototype.invokeMap=Xe((function(n,t){return"function"==typeof n?new Kr(this):this.map((function(r){return ze(r,n,t)}))})),Kr.prototype.reject=function(n){return this.filter(Uo(li(n)))},Kr.prototype.slice=function(n,t){n=da(n);var r=this;return r.__filtered__&&(n>0||t<0)?new Kr(r):(n<0?r=r.takeRight(-n):n&&(r=r.drop(n)),t!==u&&(r=(t=da(t))<0?r.dropRight(-t):r.take(t-n)),r)},Kr.prototype.takeRightWhile=function(n){return this.reverse().takeWhile(n).reverse()},Kr.prototype.toArray=function(){return this.take(g)},xe(Kr.prototype,(function(n,t){var r=/^(?:filter|find|map|reject)|While$/.test(t),e=/^(?:head|last)$/.test(t),i=Pr[e?"take"+("last"==t?"Right":""):t],o=e||/^find/.test(t);i&&(Pr.prototype[t]=function(){var t=this.__wrapped__,a=e?[1]:arguments,c=t instanceof Kr,f=a[0],l=c||Yo(t),s=function(n){var t=i.apply(Pr,Lt([n],a));return e&&h?t[0]:t};l&&r&&"function"==typeof f&&1!=f.length&&(c=l=!1);var h=this.__chain__,p=!!this.__actions__.length,v=o&&!h,_=c&&!p;if(!o&&l){t=_?t:new Kr(this);var g=n.apply(t,a);return g.__actions__.push({func:_o,args:[s],thisArg:u}),new Zr(g,h)}return v&&_?n.apply(this,a):(g=this.thru(s),v?e?g.value()[0]:g.value():g)})})),Ot(["pop","push","shift","sort","splice","unshift"],(function(n){var t=zn[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=/^(?:pop|shift)$/.test(n);Pr.prototype[n]=function(){var n=arguments;if(e&&!this.__chain__){var u=this.value();return t.apply(Yo(u)?u:[],n)}return this[r]((function(r){return t.apply(Yo(r)?r:[],n)}))}})),xe(Kr.prototype,(function(n,t){var r=Pr[t];if(r){var e=r.name+"";Wn.call(Cr,e)||(Cr[e]=[]),Cr[e].push({name:t,func:r})}})),Cr[Pu(u,2).name]=[{name:"wrapper",func:u}],Kr.prototype.clone=function(){var n=new Kr(this.__wrapped__);return n.__actions__=Ru(this.__actions__),n.__dir__=this.__dir__,n.__filtered__=this.__filtered__,n.__iteratees__=Ru(this.__iteratees__),n.__takeCount__=this.__takeCount__,n.__views__=Ru(this.__views__),n},Kr.prototype.reverse=function(){if(this.__filtered__){var n=new Kr(this);n.__dir__=-1,n.__filtered__=!0}else(n=this.clone()).__dir__*=-1;return n},Kr.prototype.value=function(){var n=this.__wrapped__.value(),t=this.__dir__,r=Yo(n),e=t<0,u=r?n.length:0,i=function(n,t,r){var e=-1,u=r.length;for(;++e=this.__values__.length;return{done:n,value:n?u:this.__values__[this.__index__++]}},Pr.prototype.plant=function(n){for(var t,r=this;r instanceof qr;){var e=Mi(r);e.__index__=0,e.__values__=u,t?i.__wrapped__=e:t=e;var i=e;r=r.__wrapped__}return i.__wrapped__=n,t},Pr.prototype.reverse=function(){var n=this.__wrapped__;if(n instanceof Kr){var t=n;return this.__actions__.length&&(t=new Kr(this)),(t=t.reverse()).__actions__.push({func:_o,args:[ro],thisArg:u}),new Zr(t,this.__chain__)}return this.thru(ro)},Pr.prototype.toJSON=Pr.prototype.valueOf=Pr.prototype.value=function(){return gu(this.__wrapped__,this.__actions__)},Pr.prototype.first=Pr.prototype.head,tt&&(Pr.prototype[tt]=function(){return this}),Pr}();vt._=dr,(e=function(){return dr}.call(t,r,t,n))===u||(n.exports=e)}.call(this)},625:(n,t,r)=>{"use strict";r.d(t,{Fc:()=>l,NO:()=>c,O:()=>s,YD:()=>o,g5:()=>h,gB:()=>i,gf:()=>a});var e=r(486),u=r.n(e);function i(n,t=1){if(Math.abs(n)<1024)return n+" B";const r=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"];let e=-1;const u=10**t;do{n/=1024,++e}while(Math.round(Math.abs(n)*u)/u>=1024&&e1?"s":"";let i=`${e}
${t} total
${`${n.n_allocations} allocation${u}`}`;return!1===r&&(i=i.concat(`
Thread ID: ${n.thread_id}`)),i}function c(n,t){return function(n,t){let r=u().cloneDeep(n.children);const e=u().filter(r,(function n(r){return r.children&&r.children.length>0&&(r.children=u().filter(r.children,n)),t(r)}));return u().defaults({children:e},n)}(n,(n=>n.thread_id===t))}function f(n,t){function r(n){let e=[];if(t(n)){e=[];for(const t of n.children)e.push(...r(t));let t=u().clone(n);t.children=e,e=[t]}else for(const t of n.children)e.push(...r(t));return e}let e=[];for(let t of n.children)e.push(...r(t));return u().defaults({children:e},n)}function l(n){return f(n,(n=>n.interesting))}function s(n){return f(n,(n=>!n.import_system))}function h(n){return u().reduce(n,((n,t)=>(n.n_allocations+=t.n_allocations,n.value+=t.value,n)),{n_allocations:0,value:0})}},501:(n,t,r)=>{"use strict";r.d(t,{Cd:()=>f,Ji:()=>g,N4:()=>x,Xx:()=>v,YX:()=>l,Z1:()=>b,bf:()=>d,cW:()=>A,ib:()=>m,sO:()=>w});var e=r(625);const u="filter_uninteresting",i="filter_import_system",o="filter_thread";var a=null;let c=new class{constructor(){this.filters={}}registerFilter(n,t){this.filters[n]=t}unRegisterFilter(n){delete this.filters[n]}drawChart(n){let t=n;_.forOwn(this.filters,(n=>{t=n(t)})),function(n){a&&(a.destroy(),d3.selectAll(".d3-flame-graph-tip").remove());a=flamegraph().width(y()).transitionDuration(250).transitionEase(d3.easeCubic).inverted(!0).cellHeight(20).minFrameSize(2).setColorMapper(j).onClick(p).tooltip(d3.tip().attr("class","d3-flame-graph-tip").html((n=>{const t=(0,e.gB)(n.data.value);return(0,e.gf)(n.data,t,merge_threads)})).direction((n=>{const t=(n.x1+n.x0)/2;return.25.25?"w":"n"}))),d3.select("#chart").datum(n).call(a),a.width(y())}(t),a.merge([])}};function f(){return a}function l(){return c}function s(){return location.hash?parseInt(location.hash.substring(1),10):0}function h(){document.getElementById("resetZoomButton").disabled=0==s()}function p(n){n.id!=s()&&(history.pushState({id:n.id},n.data.name,`#${n.id}`),h())}function v(){const n=s(),t=a.findById(n);t&&(a.zoomTo(t),h())}function g(){a.inverted(!a.inverted()),a.resetZoom()}function d(){a.resetZoom()}function y(){return document.getElementById("chart").clientWidth}function m(){c.drawChart(data),location.hash&&v()}function w(){const n=this.dataset.thread;"-0x1"===n?c.unRegisterFilter(o):c.registerFilter(o,(t=>{let r=(0,e.NO)(t,n);const u=(0,e.g5)(r.children);return _.defaults(u,r),r.n_allocations=u.n_allocations,r.value=u.value,r})),c.drawChart(data)}function b(){void 0===this.hideUninterestingFrames&&(this.hideUninterestingFrames=!0),!0===this.hideUninterestingFrames?(this.hideUninterestingFrames=!0,c.registerFilter(u,(n=>(0,e.Fc)(n)))):c.unRegisterFilter(u),this.hideUninterestingFrames=!this.hideUninterestingFrames,c.drawChart(data)}function x(){void 0===this.hideImportSystemFrames&&(this.hideImportSystemFrames=!0),!0===this.hideImportSystemFrames?(this.hideImportSystemFrames=!0,c.registerFilter(i,(n=>(0,e.O)(n)))):c.unRegisterFilter(i),this.hideImportSystemFrames=!this.hideImportSystemFrames,c.drawChart(data)}function j(n,t){return n.highlight?"orange":n.data.name&&n.data.location?(e=n.data.location[1],"py"==(r=void 0===e?e:e.substring(e.lastIndexOf(".")+1,e.length)||e)?d3.schemePastel1[2]:"c"==r||"cpp"==r||"h"==r?d3.schemePastel1[5]:d3.schemePastel1[8]):"#EEE";var r,e}function A(n,t){if(!0===t)return;const r=n.unique_threads;if(!r||r.length<=1)return;document.getElementById("threadsDropdown").removeAttribute("hidden");const e=document.getElementById("threadsDropdownList");for(const n of r){let t=document.createElement("a");t.className="dropdown-item",t.dataset.thread=n,t.text=n,t.onclick=w,e.appendChild(t)}}}},t={};function r(e){var u=t[e];if(void 0!==u)return u.exports;var i=t[e]={id:e,loaded:!1,exports:{}};return n[e].call(i.exports,i,i.exports,r),i.loaded=!0,i.exports}r.n=n=>{var t=n&&n.__esModule?()=>n.default:()=>n;return r.d(t,{a:t}),t},r.d=(n,t)=>{for(var e in t)r.o(t,e)&&!r.o(n,e)&&Object.defineProperty(n,e,{enumerable:!0,get:t[e]})},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(n){if("object"==typeof window)return window}}(),r.o=(n,t)=>Object.prototype.hasOwnProperty.call(n,t),r.nmd=n=>(n.paths=[],n.children||(n.children=[]),n),(()=>{"use strict";var n=r(625),t=r(501),e=null,u=null,i=function(){let n=new Array(packed_data.nodes.children.length);console.log("finding parent index for each node");for(const[t,r]of packed_data.nodes.children.entries())r.forEach((r=>n[r]=t));return console.assert(void 0===n[0],"root node has a parent"),n}();function o(n){console.log("refreshing flame graph!");let r=function(n){console.log("getRangeData");let t={};if(n.hasOwnProperty("xaxis.range[0]"))t={string1:n["xaxis.range[0]"],string2:n["xaxis.range[1]"]};else if(n.hasOwnProperty("xaxis.range"))t={string1:n["xaxis.range"][0],string2:n["xaxis.range"][1]};else{if(null===e)return;{let n=e.layout.xaxis.range;t={string1:n[0],string2:n[1]}}}return t}(n);if(console.log("range data: "+r),null!=u&&JSON.stringify(r)===JSON.stringify(u))return;console.log("showing loading animation"),console.log("showLoadingAnimation"),document.getElementById("loading").style.display="block",document.getElementById("overlay").style.display="block",u=r,console.log("finding range of relevant snapshot");let o=0,a=memory_records.length-1;if(r){let n=new Date(r.string1).getTime();for(let t=0;t=n){o=t;break}a=0;let t=new Date(r.string2).getTime();for(let n=memory_records.length-1;n>=1;n--)if(memory_records[n-1][0]({name:e[u.name[t]],location:[e[u.function[t]],e[u.filename[t]],u.lineno[t]],value:0,children:u.children[t],n_allocations:0,thread_id:e[u.thread_id[t]],interesting:0!==u.interesting[t],import_system:0!==u.import_system[t]})));console.log("mapping child indices to child nodes");for(const[n,t]of a.entries())t.children=t.children.map((n=>a[n]));return console.log("finding leaked allocations"),n.intervals.forEach((n=>{let[e,u,o,c,f]=n;if(e>=t&&e<=r&&u>r)for(;void 0!==o;)a[o].n_allocations+=c,a[o].value+=f,o=i[o]})),console.log("total leaked allocations in range: "+a[0].n_allocations),console.log("total leaked bytes in range: "+a[0].value),a.forEach((n=>{n.children=n.children.filter((n=>n.n_allocations>0))})),a[0]}(packed_data,o,a),console.log("drawing chart"),(0,t.YX)().drawChart(data),console.log("hiding loading animation"),console.log("hideLoadingAnimation"),document.getElementById("loading").style.display="none",document.getElementById("overlay").style.display="none"}var a=null;function c(n){console.log("refreshFlamegraphDebounced"),a&&clearTimeout(a),a=setTimeout((function(){o(n)}),500)}document.addEventListener("DOMContentLoaded",(function(){console.log("main");const r=packed_data.unique_threads.map((n=>packed_data.strings[n]));(0,t.cW)({unique_threads:r},merge_threads),function(n){console.log("init memory graph");const t=n.map((n=>new Date(n[0])));var r=[{x:t,y:n.map((n=>n[1])),mode:"lines",name:"Resident size"},{x:t,y:n.map((n=>n[2])),mode:"lines",name:"Heap size"}];Plotly.newPlot("plot",r,{xaxis:{title:{text:"Time"},rangeslider:{visible:!0}},yaxis:{title:{text:"Memory Size"},tickformat:".4~s",exponentformat:"B",ticksuffix:"B"}},{responsive:!0,displayModeBar:!1}).then((n=>{console.assert(null===e),e=n}))}(memory_records),o({}),location.hash&&(0,t.Xx)(),document.getElementById("invertButton").onclick=t.Ji,document.getElementById("resetZoomButton").onclick=t.bf,document.getElementById("resetThreadFilterItem").onclick=t.sO,document.getElementById("hideUninteresting").onclick=t.Z1.bind(this),document.getElementById("hideImportSystem").onclick=t.N4.bind(this),t.Z1.bind(this)(),document.onkeyup=n=>{"Escape"==n.code&&(0,t.bf)()},document.getElementById("searchTerm").addEventListener("input",(()=>{const n=document.getElementById("searchTerm");(0,t.Cd)().search(n.value)})),window.addEventListener("popstate",t.Xx),window.addEventListener("resize",(0,n.YD)(t.ib)),$('[data-toggle-second="tooltip"]').tooltip(),$('[data-toggle="tooltip"]').tooltip(),console.log("setup reload handler"),document.getElementById("plot").on("plotly_relayout",c),[].slice.call(document.querySelectorAll(".toast")).map((function(n){return new bootstrap.Toast(n,{delay:1e4})})).forEach((n=>n.show()))}))})()})(); \ No newline at end of file diff --git a/src/memray/reporters/templates/temporal_flamegraph.html b/src/memray/reporters/templates/temporal_flamegraph.html index 8baf23c38c..992861a2b4 100644 --- a/src/memray/reporters/templates/temporal_flamegraph.html +++ b/src/memray/reporters/templates/temporal_flamegraph.html @@ -1,5 +1,31 @@ {% extends "flamegraph.html" %} +{% block content %} +
+
+ How to use this plot + +
+
+ You can move the plot slider to select different ranges for the flame + graph. The flame graph shows the allocations that are created in the + selected range that are not deallocated before the end of the range. +
+
+ +
+ + +
+
+
+{% endblock %} + {% block flamegraph_script %}