blob: 4769ef6f879747722ef655192675d3d1e8396b65 [file] [log] [blame]
// 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 <fuchsia/memory/cpp/fidl.h>
#include <fuchsia/sys/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fdio/directory.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/sys/cpp/service_directory.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace-provider/fdio_connect.h>
#include <lib/trace-provider/provider.h>
#include <lib/trace/observer.h>
#include <lib/zx/clock.h>
#include <zircon/status.h>
#include <condition_variable>
#include <filesystem>
#include <iostream>
#include <mutex>
#include <utility>
#include "src/developer/memory/metrics/bucket_match.h"
#include "src/developer/memory/metrics/capture.h"
#include "src/developer/memory/metrics/digest.h"
#include "src/developer/memory/metrics/printer.h"
#include "src/developer/memory/metrics/summary.h"
#include "src/lib/fxl/command_line.h"
#include "src/lib/fxl/strings/string_number_conversions.h"
using namespace memory;
// Returns a Digester using the memory monitor configuration if available.
std::vector<BucketMatch> GetBucketMatchesFromConfig() {
if (!std::filesystem::exists("/hub/c/memory_monitor.cmx")) {
FX_LOGS(WARNING) << "No memory monitor component found, no bucket will be used.";
return {};
}
std::string instance_id =
std::filesystem::directory_iterator("/hub/c/memory_monitor.cmx")->path();
std::string full_path = instance_id + "/out/debug/buckets.json";
std::string config_str;
if (!files::ReadFileToString(full_path, &config_str)) {
FX_LOGS(ERROR) << "Unable to read configuration, no bucket will be used";
return {};
}
auto matches = BucketMatch::ReadBucketMatchesFromConfig(config_str);
FX_CHECK(matches.has_value());
return *matches;
}
void SignalMemoryPressure(fuchsia::memorypressure::Level level) {
fuchsia::memory::DebuggerSyncPtr memdebug;
auto context = sys::ComponentContext::Create();
if (!std::filesystem::exists("/hub/c/memory_monitor.cmx")) {
FX_LOGS(ERROR) << "No memory_monitor component found.";
return;
}
// Taking the first path in the component directory is sufficient here because there's only a
// single instance of memory_monitor on a running system.
std::string component_instance_path =
std::filesystem::directory_iterator("/hub/c/memory_monitor.cmx")->path();
std::string service_path =
component_instance_path + "/out/debug/" + fuchsia::memory::Debugger::Name_;
zx_status_t status =
fdio_service_connect(service_path.c_str(), memdebug.NewRequest().TakeChannel().release());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Could not connect to " << fuchsia::memory::Debugger::Name_;
return;
}
memdebug->SignalMemoryPressure(level);
}
int Mem(const fxl::CommandLine& command_line) {
if (command_line.HasOption("signal")) {
std::string level_value;
FX_CHECK(command_line.GetOptionValue("signal", &level_value));
fuchsia::memorypressure::Level level;
if (level_value == "NORMAL") {
level = fuchsia::memorypressure::Level::NORMAL;
} else if (level_value == "WARNING") {
level = fuchsia::memorypressure::Level::WARNING;
} else if (level_value == "CRITICAL") {
level = fuchsia::memorypressure::Level::CRITICAL;
} else {
std::cerr << "Invalid value for --signal: " << level_value;
return EXIT_FAILURE;
}
SignalMemoryPressure(level);
return EXIT_SUCCESS;
}
CaptureState capture_state;
auto s = Capture::GetCaptureState(&capture_state);
Printer printer(std::cout);
if (s != ZX_OK) {
std::cerr << "Error getting capture state: " << zx_status_get_string(s);
return EXIT_FAILURE;
}
if (command_line.HasOption("print")) {
Capture capture;
auto s = Capture::GetCapture(&capture, capture_state, VMO);
if (s != ZX_OK) {
std::cerr << "Error getting capture: " << zx_status_get_string(s);
return EXIT_FAILURE;
}
printer.PrintCapture(capture);
return EXIT_SUCCESS;
}
if (command_line.HasOption("output")) {
zx_koid_t pid = 0;
std::string pid_value;
if (command_line.GetOptionValue("pid", &pid_value)) {
if (!fxl::StringToNumberWithError(pid_value, &pid)) {
std::cerr << "Invalid value for --pid: " << pid_value;
return EXIT_FAILURE;
}
}
uint64_t repeat = 0;
std::string repeat_value;
if (command_line.GetOptionValue("repeat", &repeat_value)) {
if (!fxl::StringToNumberWithError(repeat_value, &repeat)) {
std::cerr << "Invalid value for --repeat: " << repeat_value;
return EXIT_FAILURE;
}
}
zx::duration repeat_secs = zx::sec(repeat);
zx::time start = zx::clock::get_monotonic();
Namer namer(Summary::kNameMatches);
for (int64_t i = 1; true; i++) {
Capture capture;
auto s = Capture::GetCapture(&capture, capture_state, VMO);
if (s != ZX_OK) {
std::cerr << "Error getting capture: " << zx_status_get_string(s);
return EXIT_FAILURE;
}
if (command_line.HasOption("digest")) {
Digester digester(GetBucketMatchesFromConfig());
Digest d(capture, &digester);
printer.OutputDigest(d);
} else {
printer.OutputSummary(Summary(capture, &namer), UNSORTED, pid);
}
if (repeat == 0) {
break;
}
zx::time next = start + (repeat_secs * i);
if (next <= zx::clock::get_monotonic()) {
next = zx::clock::get_monotonic() + repeat_secs;
}
zx::nanosleep(next);
}
return EXIT_SUCCESS;
}
Capture capture;
s = Capture::GetCapture(&capture, capture_state, VMO);
if (s != ZX_OK) {
std::cerr << "Error getting capture: " << zx_status_get_string(s);
return EXIT_FAILURE;
}
if (command_line.HasOption("digest")) {
Digester digester(GetBucketMatchesFromConfig());
Digest digest(capture, &digester);
printer.PrintDigest(digest);
if (command_line.HasOption("undigested")) {
std::cout << capture.koid_to_vmo().size() << " VMOs, " << digest.undigested_vmos().size()
<< " Undigested\n";
Namer namer(Summary::kNameMatches);
Summary undigested_summary(capture, &namer, digest.undigested_vmos());
std::cout << undigested_summary.process_summaries().size() << " Process summaries\n";
printer.PrintSummary(undigested_summary, memory::VMO, SORTED);
}
return EXIT_SUCCESS;
}
printer.PrintSummary(Summary(capture, Summary::kNameMatches), VMO, SORTED);
return EXIT_SUCCESS;
}
int main(int argc, const char** argv) {
auto command_line = fxl::CommandLineFromArgcArgv(argc, argv);
if (command_line.HasOption("help")) {
std::cout << "Usage: mem [options]\n"
" Prints system-wide task and memory\n\n"
" Memory numbers are triplets P,S,T\n"
" P: Private bytes\n"
" S: Total bytes scaled by 1/# processes sharing each byte\n"
" T: Total bytes\n"
" S and T are inclusive of P\n\n"
" Options:\n"
" [default] Human readable representation of process and vmo groups\n"
" --trace Enable tracing support\n"
" --print Machine readable representation of proccess and vmos\n"
" --output CSV of process memory\n"
" --repeat=N Runs forever, outputing every N seconds\n"
" --pid=N Output vmo groups of process pid instead\n"
" --digest Prints a bucketized digest of memory usage\n"
" --signal=L Signal userspace clients with memory pressure level L\n"
" where L can be CRITICAL, WARNING or NORMAL. Clients can\n"
" use this command to test their response to memory pressure.\n"
" Does not affect the real memory pressure level on the system,\n"
" or trigger any kernel memory reclamation tasks.\n";
return EXIT_SUCCESS;
}
if (command_line.HasOption("trace")) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
loop.StartThread("provider loop");
trace::TraceProviderWithFdio trace_provider(loop.dispatcher(), "mem");
trace::TraceObserver trace_observer;
std::mutex m;
std::condition_variable cv;
bool got_start = false;
trace_observer.Start(loop.dispatcher(), [&m, &cv, &got_start] {
std::unique_lock<std::mutex> lock(m);
got_start = true;
cv.notify_all();
});
std::unique_lock<std::mutex> l(m);
cv.wait(l, [&got_start] { return got_start; });
return Mem(command_line);
}
return Mem(command_line);
}