| // Copyright 2017 Google Inc. 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/malloc_logger_tool.h" |
| |
| #include <algorithm> |
| |
| #include "base/time.h" |
| #include "cobalt/base/c_val.h" |
| #include "cobalt/browser/memory_tracker/tool/buffered_file_writer.h" |
| #include "cobalt/browser/memory_tracker/tool/params.h" |
| #include "cobalt/browser/memory_tracker/tool/util.h" |
| #include "nb/memory_scope.h" |
| #include "starboard/atomic.h" |
| #include "starboard/string.h" |
| #include "starboard/types.h" |
| |
| namespace cobalt { |
| namespace browser { |
| namespace memory_tracker { |
| |
| namespace { |
| const int kAllocationRecord = 1; |
| const int kDeallocationRecord = 0; |
| const size_t kStartIndex = 5; |
| const size_t kNumAddressPrints = 2; |
| const size_t kMaxStackSize = 10; |
| const size_t kRecordLimit = 1024; |
| const NbMemoryScopeInfo kEmptyCallstackMemoryScopeInfo = {nullptr, |
| "-", "-", 0, "-", true}; |
| } // namespace |
| |
| MallocLoggerTool::MallocLoggerTool() : start_time_(NowTime()), |
| atomic_counter_(0), |
| atomic_used_memory_(SbSystemGetUsedCPUMemory()) { |
| buffered_file_writer_.reset(new BufferedFileWriter(MemoryLogPath())); |
| } |
| |
| MallocLoggerTool::~MallocLoggerTool() { |
| // No locks are used for the thread reporter, so when it's set to null |
| // we allow one second for any suspended threads to run through and finish |
| // their reporting. |
| SbMemorySetReporter(NULL); |
| SbThreadSleep(kSbTimeSecond); |
| buffered_file_writer_.reset(NULL); |
| } |
| |
| std::string MallocLoggerTool::tool_name() const { |
| return "MemoryTrackerMallocLogger"; |
| } |
| |
| void MallocLoggerTool::Run(Params* params) { |
| // Update malloc stats every second |
| params->logger()->Output("MemoryTrackerMallocLogger running..."); |
| |
| // There are some memory allocations which do not get tracked. |
| // Those allocations show up as fragmentation. |
| // It has been empirically observed that a majority (98%+) of these |
| // untracked allocations happen in the first 20 seconds of Cobalt runtime. |
| // |
| // Also, there is minimal external fragmentation (< 1 MB) during this initial |
| // period. |
| // |
| // The following piece of code resets atomic_used_memory_ at the 20 second |
| // mark, to compensate for the deviation due to untracked memory. |
| base::TimeDelta current_sample_interval = |
| base::TimeDelta::FromSeconds(20); |
| if (!params->wait_for_finish_signal(current_sample_interval.ToSbTime())) { |
| atomic_used_memory_.store(SbSystemGetUsedCPUMemory()); |
| } |
| |
| // Export fragmentation as a CVal on HUD. |
| base::CVal<base::cval::SizeInBytes> memory_fragmentation( |
| "Memory.CPU.Fragmentation", base::cval::SizeInBytes(0), |
| "Memory Fragmentation"); |
| |
| // Update CVal every 5 seconds |
| current_sample_interval = base::TimeDelta::FromSeconds(5); |
| int64_t allocated_memory = 0; |
| int64_t used_memory = 0; |
| while (!params->wait_for_finish_signal(current_sample_interval.ToSbTime())) { |
| allocated_memory = SbSystemGetUsedCPUMemory(); |
| used_memory = atomic_used_memory_.load(); |
| memory_fragmentation = static_cast<uint64>( |
| std::max(allocated_memory - used_memory, static_cast<int64_t>(0))); |
| } |
| } |
| |
| void MallocLoggerTool::LogRecord(const void* memory_block, |
| const nb::analytics::AllocationRecord& record, |
| const nb::analytics::CallStack& callstack, int type) { |
| const int log_counter = atomic_counter_.increment(); |
| const int64_t used_memory = atomic_used_memory_.load(); |
| const int64_t allocated_memory = SbSystemGetUsedCPUMemory(); |
| const int time_since_start_ms = GetTimeSinceStartMs(); |
| char buff[kRecordLimit] = {0}; |
| size_t buff_pos = 0; |
| void* addresses[kMaxStackSize]; |
| |
| const NbMemoryScopeInfo* memory_scope; |
| if (callstack.empty()) { |
| memory_scope = &kEmptyCallstackMemoryScopeInfo; |
| } else { |
| memory_scope = callstack.back(); |
| } |
| |
| int bytes_written = SbStringFormatF(buff, sizeof(buff), |
| "%u,%d,%zd,\"%s\",%d,%s,%d,%" PRId64 ",%" PRId64 ",%" PRIXPTR ",\"", |
| log_counter, type, record.size, memory_scope->file_name_, |
| memory_scope->line_number_, memory_scope->function_name_, |
| time_since_start_ms, allocated_memory, used_memory, |
| reinterpret_cast<uintptr_t>(memory_block)); |
| |
| buff_pos += static_cast<size_t>(bytes_written); |
| const size_t count = std::max(SbSystemGetStack(addresses, kMaxStackSize), 0); |
| const size_t end_index = std::min(count, kStartIndex + kNumAddressPrints); |
| // For each of the stack addresses that we care about, concat them to the |
| // buffer. This was originally written to do multiple stack addresses but |
| // this tends to overflow on some lower platforms so it's possible that |
| // this loop only iterates once. |
| for (size_t i = kStartIndex; i < end_index; ++i) { |
| void* p = addresses[i]; |
| bytes_written = |
| SbStringFormatF(buff + buff_pos, kRecordLimit - buff_pos, |
| ",%" PRIXPTR "", reinterpret_cast<uintptr_t>(p)); |
| DCHECK_GE(bytes_written, 0); |
| buff_pos += static_cast<size_t>(bytes_written); |
| } |
| |
| // Adds a "\n" at the end. |
| bytes_written = SbStringConcat(buff + buff_pos, "\"\n", |
| static_cast<int>(kRecordLimit - buff_pos)); |
| buff_pos += bytes_written; |
| buffered_file_writer_->Append(buff, buff_pos); |
| } |
| |
| void MallocLoggerTool::OnMemoryAllocation( |
| const void* memory_block, const nb::analytics::AllocationRecord& record, |
| const nb::analytics::CallStack& callstack) { |
| atomic_used_memory_.fetch_add(record.size); |
| LogRecord(memory_block, record, callstack, kAllocationRecord); |
| } |
| |
| void MallocLoggerTool::OnMemoryDeallocation( |
| const void* memory_block, const nb::analytics::AllocationRecord& record, |
| const nb::analytics::CallStack& callstack) { |
| atomic_used_memory_.fetch_sub(record.size); |
| LogRecord(memory_block, record, callstack, kDeallocationRecord); |
| } |
| |
| std::string MallocLoggerTool::MemoryLogPath() { |
| char file_name_buff[2048] = {}; |
| SbSystemGetPath(kSbSystemPathDebugOutputDirectory, file_name_buff, |
| arraysize(file_name_buff)); |
| std::string path(file_name_buff); |
| if (!path.empty()) { // Protect against a dangling "/" at end. |
| const int back_idx_signed = static_cast<int>(path.length()) - 1; |
| if (back_idx_signed >= 0) { |
| const size_t idx = back_idx_signed; |
| if (path[idx] == '/') { |
| path.erase(idx); |
| } |
| } |
| } |
| |
| base::Time time = base::Time::Now(); |
| base::Time::Exploded exploded; |
| time.LocalExplode(&exploded); |
| |
| std::stringstream ss; |
| ss << "/memory_log_" << exploded.year << "-" |
| << exploded.month << "-" << exploded.day_of_month << ":" |
| << exploded.hour << "-" << exploded.minute << "-" << exploded.second |
| << ".csv"; |
| path.append(ss.str()); |
| return path; |
| } |
| |
| base::TimeTicks MallocLoggerTool::NowTime() { |
| // NowFromSystemTime() is slower but more accurate. However it might |
| // be useful to use the faster but less accurate version if there is |
| // a speedup. |
| return base::TimeTicks::Now(); |
| } |
| |
| int MallocLoggerTool::GetTimeSinceStartMs() const { |
| base::TimeDelta dt = NowTime() - start_time_; |
| return static_cast<int>(dt.InMilliseconds()); |
| } |
| |
| } // namespace memory_tracker |
| } // namespace browser |
| } // namespace cobalt |