blob: 7f2422b90e5dcfd6e4dfa1eb71936cd9cd69e15e [file] [log] [blame]
// Copyright 2018 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/browser/memory_tracker/tool/internal_fragmentation_tool.h"
// C++ headers.
#include <algorithm>
#include <map>
#include <set>
#include <vector>
// Cobalt headers.
#include "cobalt/browser/memory_tracker/tool/histogram_table_csv_base.h"
#include "cobalt/browser/memory_tracker/tool/params.h"
#include "cobalt/browser/memory_tracker/tool/util.h"
#include "nb/bit_cast.h"
namespace cobalt {
namespace browser {
namespace memory_tracker {
namespace {
using nb::analytics::AllocationRecord;
using nb::analytics::AllocationVisitor;
using nb::analytics::MemoryTracker;
class FragmentationProcessor : private AllocationVisitor {
public:
explicit FragmentationProcessor(uintptr_t page_size)
: page_size_(page_size) {}
void Process(MemoryTracker* tracker, size_t* allocated_memory_in_pages,
size_t* used_memory_in_segments) {
memory_segments_.erase(memory_segments_.begin(), memory_segments_.end());
*allocated_memory_in_pages = 0;
*used_memory_in_segments = 0;
tracker->Accept(this);
// Note that because of the fine course locking used for
// the memory tracker, it's accepted that there can be
// an occasional overlapping memory segment, where the memory region
// was deallocated and then recycled during the segment collection phase.
// This will then show up as an overlapping region. This is resolved
// by selecting the first memory segment, and discarding any
// memory segments that overlap it.
// While this can skew results, the rate of overlaps in practice is so low
// that we can essentially ignore it without warning the user.
std::sort(memory_segments_.begin(), memory_segments_.end());
std::vector<Segment> copy_no_overlaps;
copy_no_overlaps.reserve(memory_segments_.size());
for (const Segment& seg : memory_segments_) {
if (copy_no_overlaps.empty() ||
!seg.Intersects(copy_no_overlaps.back())) {
copy_no_overlaps.push_back(seg);
}
}
memory_segments_.swap(copy_no_overlaps);
if (!memory_segments_.empty()) {
std::set<uintptr_t> page_ids;
std::vector<Segment> sub_segments;
for (const Segment& seg : memory_segments_) {
if (!seg.size()) { // Ignore 0-size segments.
continue;
}
// Use erase() instead of clear(), because it has stronger
// guarantees about recycling memory in the vector.
sub_segments.erase(sub_segments.begin(), sub_segments.end());
// Memory allocation segments may span multiple pages. In this
// case we will break them up and store them in sub_segments.
seg.SplitAcrossPageBoundaries(page_size_, &sub_segments);
for (const Segment& sub_seg : sub_segments) {
uintptr_t page_id = GetPageId(sub_seg.begin());
page_ids.insert(page_id);
}
}
*allocated_memory_in_pages = page_ids.size() * page_size_;
}
*used_memory_in_segments = CountUsedMemoryInSegments();
}
private:
// Implements AllocationVisitor::Visitor().
bool Visit(const void* memory,
const AllocationRecord& alloc_record) override {
const char* memory_begin = static_cast<const char*>(memory);
const char* memory_end = memory_begin + alloc_record.size;
memory_segments_.push_back(Segment(nullptr, memory_begin, memory_end));
return true;
}
uintptr_t GetPageId(const char* ptr) const {
return nb::bit_cast<uintptr_t>(ptr) / page_size_;
}
size_t CountUsedMemoryInSegments() const {
if (memory_segments_.empty()) {
return 0;
}
size_t total_bytes = 0;
const Segment* prev_segment = nullptr;
for (const Segment& seg : memory_segments_) {
size_t size = seg.size();
if (prev_segment && prev_segment->Intersects(seg)) {
// Sanity check - FragmentationProcessor::Process() is expected
// to resolve overlapping memory segments that occur from the
// concurrent nature of memory collection.
SB_NOTREACHED() << "Memory segments intersected!";
size = 0;
}
prev_segment = &seg;
total_bytes += size;
}
return total_bytes;
}
std::vector<Segment> memory_segments_;
const uintptr_t page_size_;
};
} // namespace.
InternalFragmentationTool::InternalFragmentationTool() {}
InternalFragmentationTool::~InternalFragmentationTool() {}
std::string InternalFragmentationTool::tool_name() const {
return "InternalFragmentationTool";
}
void InternalFragmentationTool::Run(Params* params) {
// Run function does almost nothing.
params->logger()->Output("InternalFragmentationTool running...\n");
Timer output_timer(base::TimeDelta::FromSeconds(30));
Timer sample_timer(base::TimeDelta::FromMilliseconds(50));
MemoryBytesHistogramCSV histogram_table;
histogram_table.set_title("Internal Fragmentation - Probably undercounts");
FragmentationProcessor visitor(SB_MEMORY_PAGE_SIZE);
// If we get a finish signal then this will break out of the loop.
while (!params->wait_for_finish_signal(250 * kSbTimeMillisecond)) {
// LOG CSV.
if (output_timer.UpdateAndIsExpired()) {
std::stringstream ss;
ss << kNewLine << histogram_table.ToString() << kNewLine << kNewLine;
params->logger()->Output(ss.str());
}
// ADD A HISTOGRAM SAMPLE.
if (sample_timer.UpdateAndIsExpired()) {
histogram_table.BeginRow(params->time_since_start());
size_t allocated_memory_in_pages = 0;
size_t used_memory = 0;
visitor.Process(params->memory_tracker(), &allocated_memory_in_pages,
&used_memory);
SB_DCHECK(allocated_memory_in_pages > used_memory)
<< "allocated_memory_in_pages: " << allocated_memory_in_pages << ","
<< "used_memory: " << used_memory;
histogram_table.AddRowValue("TotalReservedCpuMemoryInPages(MB)",
allocated_memory_in_pages);
histogram_table.AddRowValue("TotalCpuMemoryInUse(MB)", used_memory);
histogram_table.FinalizeRow();
}
// COMPRESS TABLE WHEN FULL.
//
// Table is full, therefore eliminate half of the elements.
// Reduce sample frequency to match.
if (histogram_table.NumberOfRows() >= 100) {
// Compression step.
histogram_table.RemoveOddElements();
// By doubling the sampling time this keeps the table linear with
// respect to time. If sampling time was not doubled then there
// would be time distortion in the graph.
sample_timer.ScaleTriggerTime(2.0);
sample_timer.Restart();
}
}
}
} // namespace memory_tracker
} // namespace browser
} // namespace cobalt