| // Copyright 2019 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "src/developer/memory/metrics/printer.h" | |
| #include <lib/trace/event.h> | |
| #include <algorithm> | |
| #include <cstdint> | |
| namespace memory { | |
| const size_t kMaxFormattedStringSize = sizeof("1023.5T"); | |
| const char* FormatSize(uint64_t bytes, char* buf) { | |
| const char units[] = "BKMGTPE"; | |
| uint16_t r = 0; | |
| int ui = 0; | |
| while (bytes > 1023) { | |
| r = bytes % 1024; | |
| bytes /= 1024; | |
| ui++; | |
| } | |
| unsigned int round_up = ((r % 102) >= 51); | |
| r = (r / 102) + round_up; | |
| if (r == 10) { | |
| bytes++; | |
| r = 0; | |
| } | |
| if (r == 0) { | |
| snprintf(buf, kMaxFormattedStringSize, "%zu%c", bytes, units[ui]); | |
| } else { | |
| snprintf(buf, kMaxFormattedStringSize, "%zu.%1u%c", bytes, r, units[ui]); | |
| } | |
| return buf; | |
| } | |
| void Printer::PrintCapture(const Capture& capture, CaptureLevel level, Sorted sorted) { | |
| TRACE_DURATION("memory_metrics", "Printer::PrintCapture"); | |
| const auto& kmem = capture.kmem(); | |
| os_ << "K," << capture.time() << "," << kmem.total_bytes << "," << kmem.free_bytes << "," | |
| << kmem.wired_bytes << "," << kmem.total_heap_bytes << "," << kmem.free_heap_bytes << "," | |
| << kmem.vmo_bytes << "," << kmem.mmu_overhead_bytes << "," << kmem.ipc_bytes << "," | |
| << kmem.other_bytes << "\n"; | |
| if (level == KMEM) { | |
| return; | |
| } | |
| const auto& koid_to_process = capture.koid_to_process(); | |
| std::vector<zx_koid_t> process_koids; | |
| for (const auto& pair : koid_to_process) { | |
| process_koids.push_back(pair.first); | |
| } | |
| for (const auto& koid : process_koids) { | |
| const auto& p = koid_to_process.at(koid); | |
| os_ << "P," << p.koid << "," << p.name; | |
| for (const auto& v : p.vmos) { | |
| os_ << "," << v; | |
| } | |
| os_ << "\n"; | |
| } | |
| const auto& koid_to_vmo = capture.koid_to_vmo(); | |
| std::vector<zx_koid_t> vmo_koids; | |
| for (const auto& pair : koid_to_vmo) { | |
| vmo_koids.push_back(pair.first); | |
| } | |
| if (sorted == SORTED) { | |
| std::sort(vmo_koids.begin(), vmo_koids.end(), [&koid_to_vmo](zx_koid_t a, zx_koid_t b) { | |
| const auto& sa = koid_to_vmo.at(a); | |
| const auto& sb = koid_to_vmo.at(b); | |
| return sa.committed_bytes > sb.committed_bytes; | |
| }); | |
| } | |
| for (const auto& koid : vmo_koids) { | |
| const auto& v = koid_to_vmo.at(koid); | |
| os_ << "V," << v.koid << "," << v.name << "," << v.parent_koid << "," << v.committed_bytes | |
| << "\n"; | |
| } | |
| os_ << std::flush; | |
| } | |
| void Printer::OutputSizes(const Sizes& sizes) { | |
| if (sizes.total_bytes == sizes.private_bytes) { | |
| char private_buf[kMaxFormattedStringSize]; | |
| os_ << FormatSize(sizes.private_bytes, private_buf) << "\n"; | |
| return; | |
| } | |
| char private_buf[kMaxFormattedStringSize], scaled_buf[kMaxFormattedStringSize], | |
| total_buf[kMaxFormattedStringSize]; | |
| os_ << FormatSize(sizes.private_bytes, private_buf) << " " | |
| << FormatSize(sizes.scaled_bytes, scaled_buf) << " " | |
| << FormatSize(sizes.total_bytes, total_buf) << "\n"; | |
| } | |
| void Printer::PrintSummary(const Summary& summary, CaptureLevel level, Sorted sorted) { | |
| TRACE_DURATION("memory_metrics", "Printer::PrintSummary"); | |
| char vmo_buf[kMaxFormattedStringSize], free_buf[kMaxFormattedStringSize]; | |
| const auto& kstats = summary.kstats(); | |
| os_ << "Time: " << summary.time() << " VMO: " << FormatSize(kstats.vmo_bytes, vmo_buf) | |
| << " Free: " << FormatSize(kstats.free_bytes, free_buf) << "\n"; | |
| if (level == KMEM) { | |
| return; | |
| } | |
| const auto& summaries = summary.process_summaries(); | |
| std::vector<uint32_t> summary_order; | |
| summary_order.reserve(summaries.size()); | |
| for (uint32_t i = 0; i < summaries.size(); i++) { | |
| summary_order.push_back(i); | |
| } | |
| if (sorted == SORTED) { | |
| std::sort(summary_order.begin(), summary_order.end(), [&summaries](uint32_t ai, uint32_t bi) { | |
| const auto& a = summaries[ai]; | |
| const auto& b = summaries[bi]; | |
| return a.sizes().private_bytes > b.sizes().private_bytes; | |
| }); | |
| } | |
| for (auto i : summary_order) { | |
| const auto& s = summaries[i]; | |
| os_ << s.name() << "<" << s.koid() << "> "; | |
| OutputSizes(s.sizes()); | |
| if (level == PROCESS) { | |
| continue; | |
| } | |
| const auto& name_to_sizes = s.name_to_sizes(); | |
| std::vector<std::string> names; | |
| names.reserve(name_to_sizes.size()); | |
| for (const auto& pair : name_to_sizes) { | |
| names.push_back(pair.first); | |
| } | |
| if (sorted == SORTED) { | |
| std::sort(names.begin(), names.end(), | |
| [&name_to_sizes](const std::string& a, const std::string& b) { | |
| const auto& sa = name_to_sizes.at(a); | |
| const auto& sb = name_to_sizes.at(b); | |
| return sa.private_bytes == sb.private_bytes ? sa.scaled_bytes > sb.scaled_bytes | |
| : sa.private_bytes > sb.private_bytes; | |
| }); | |
| } | |
| for (const auto& name : names) { | |
| const auto& n_sizes = name_to_sizes.at(name); | |
| if (n_sizes.total_bytes == 0) { | |
| continue; | |
| } | |
| os_ << " " << name << " "; | |
| OutputSizes(n_sizes); | |
| } | |
| } | |
| os_ << std::flush; | |
| } | |
| void Printer::OutputSummary(const Summary& summary, Sorted sorted, zx_koid_t pid) { | |
| TRACE_DURATION("memory_metrics", "Printer::OutputSummary"); | |
| const auto& summaries = summary.process_summaries(); | |
| std::vector<ProcessSummary> sorted_summaries; | |
| if (sorted == SORTED) { | |
| sorted_summaries = summaries; | |
| std::sort(sorted_summaries.begin(), sorted_summaries.end(), | |
| [](ProcessSummary a, ProcessSummary b) { | |
| return a.sizes().private_bytes > b.sizes().private_bytes; | |
| }); | |
| } | |
| const auto time = summary.time() / 1000000000; | |
| for (const auto& s : sorted == SORTED ? sorted_summaries : summaries) { | |
| if (pid != ZX_KOID_INVALID) { | |
| if (s.koid() != pid) { | |
| continue; | |
| } | |
| const auto& name_to_sizes = s.name_to_sizes(); | |
| std::vector<std::string> names; | |
| for (const auto& pair : name_to_sizes) { | |
| names.push_back(pair.first); | |
| } | |
| if (sorted == SORTED) { | |
| std::sort(names.begin(), names.end(), [&name_to_sizes](std::string a, std::string b) { | |
| const auto& sa = name_to_sizes.at(a); | |
| const auto& sb = name_to_sizes.at(b); | |
| return sa.private_bytes == sb.private_bytes ? sa.scaled_bytes > sb.scaled_bytes | |
| : sa.private_bytes > sb.private_bytes; | |
| }); | |
| } | |
| for (const auto& name : names) { | |
| const auto& sizes = name_to_sizes.at(name); | |
| if (sizes.total_bytes == 0) { | |
| continue; | |
| } | |
| os_ << time << "," << s.koid() << "," << name << "," << sizes.private_bytes << "," | |
| << sizes.scaled_bytes << "," << sizes.total_bytes << "\n"; | |
| } | |
| continue; | |
| } | |
| auto sizes = s.sizes(); | |
| os_ << time << "," << s.koid() << "," << s.name() << "," << sizes.private_bytes << "," | |
| << sizes.scaled_bytes << "," << sizes.total_bytes << "\n"; | |
| } | |
| os_ << std::flush; | |
| } | |
| void Printer::PrintDigest(const Digest& digest) { | |
| TRACE_DURATION("memory_metrics", "Printer::PrintDigest"); | |
| for (auto const& bucket : digest.buckets()) { | |
| char size_buf[kMaxFormattedStringSize]; | |
| FormatSize(bucket.size(), size_buf); | |
| os_ << bucket.name() << ": " << size_buf << "\n"; | |
| } | |
| } | |
| void Printer::OutputDigest(const Digest& digest) { | |
| TRACE_DURATION("memory_metrics", "Printer::OutputDigest"); | |
| auto const time = digest.time() / 1000000000; | |
| for (auto const& bucket : digest.buckets()) { | |
| os_ << time << "," << bucket.name() << "," << bucket.size() << "\n"; | |
| } | |
| } | |
| } // namespace memory |