blob: 3f1029fe8460857c5a93a226cf8c7b09de193f8f [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 "src/developer/debug/zxdb/console/commands/verb_ps.h"
#include <iomanip>
#include <optional>
#include <set>
#include <sstream>
#include <string_view>
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/client/system.h"
#include "src/developer/debug/zxdb/client/target.h"
#include "src/developer/debug/zxdb/console/command.h"
#include "src/developer/debug/zxdb/console/console.h"
#include "src/developer/debug/zxdb/console/output_buffer.h"
#include "src/developer/debug/zxdb/console/string_util.h"
#include "src/developer/debug/zxdb/console/verbs.h"
namespace zxdb {
namespace {
// Computes the set of attached job and process koids so they can be marked in the output.
std::set<uint64_t> ComputeAttachedKoidMap() {
std::set<uint64_t> attached;
System& system = Console::get()->context().session()->system();
for (Target* target : system.GetTargets()) {
if (Process* process = target->GetProcess())
attached.insert(process->GetKoid());
}
return attached;
}
void OutputProcessTreeRecord(const debug_ipc::ProcessTreeRecord& rec, int indent,
const std::set<uint64_t>& attached, OutputBuffer* output) {
// Row marker for attached processes/jobs.
std::string prefix;
Syntax syntax = Syntax::kNormal;
if (auto found = attached.find(rec.koid); found != attached.end()) {
syntax = Syntax::kHeading;
prefix = GetCurrentRowMarker();
} else {
prefix = " "; // Account for no prefix so everything is aligned.
}
// Indentation.
prefix.append(indent * 2, ' ');
// Record type.
switch (rec.type) {
case debug_ipc::TaskType::kJob:
prefix.append("j: ");
break;
case debug_ipc::TaskType::kProcess:
prefix.append("p: ");
break;
default:
prefix.append("?: ");
break;
}
output->Append(syntax, prefix);
output->Append(Syntax::kSpecial, std::to_string(rec.koid));
if (!rec.name.empty())
output->Append(syntax, " " + rec.name);
// Note if there are multiple components associated a particular job, we output
// nothing here since there is either not enough information to be helpful or
// too much information in the process graph.
if (rec.components.size() == 1) {
output->Append(" " + rec.components[0].moniker, TextForegroundColor::kCyan);
output->Append(" " + rec.components[0].url, TextForegroundColor::kGray);
}
output->Append(syntax, "\n");
for (const auto& child : rec.children)
OutputProcessTreeRecord(child, indent + 1, attached, output);
}
// Recursively filters the given process tree. All jobs and processes that contain the given filter
// string in their name are matched. These are added to the result, along with any parent job nodes
// required to get to the matched records.
std::optional<debug_ipc::ProcessTreeRecord> FilterProcessTree(
const debug_ipc::ProcessTreeRecord& rec, const std::string& filter) {
debug_ipc::ProcessTreeRecord result;
// A record matches if its (job) name or component name matches.
bool matched = rec.name.find(filter) != std::string::npos;
if (!matched && rec.components.size() == 1) {
// Use the base name of the URL as the "component name".
// e.g. "fuchsia-pkg://url#meta/foobar.cm" has a component name of "foobar.cm".
std::string_view url = rec.components[0].url;
std::string_view name = url.substr(url.find_last_of('/') + 1);
matched = name.find(filter) != std::string_view::npos;
} else if (!matched && !rec.components.empty()) {
for (const auto& component : rec.components) {
std::string_view url = component.url;
std::string_view name = url.substr(url.find_last_of('/') + 1);
matched = name.find(filter) != std::string_view::npos;
if (matched)
break;
}
}
// If a record matches, show all its children.
if (matched) {
result.children = rec.children;
} else {
for (const auto& child : rec.children) {
if (auto matched_child = FilterProcessTree(child, filter))
result.children.push_back(*matched_child);
}
}
// Return the node when it matches or any of its children do.
if (matched || !result.children.empty()) {
result.type = rec.type;
result.koid = rec.koid;
result.name = rec.name;
result.components = rec.components;
return result;
}
return std::nullopt;
}
void OnListProcessesComplete(fxl::RefPtr<CommandContext> cmd_context, const std::string& filter,
const debug_ipc::ProcessTreeReply& reply) {
std::set<uint64_t> attached = ComputeAttachedKoidMap();
OutputBuffer out;
if (filter.empty()) {
// Output everything.
OutputProcessTreeRecord(reply.root, 0, attached, &out);
} else {
// Filter the results.
if (auto filtered = FilterProcessTree(reply.root, filter)) {
OutputProcessTreeRecord(*filtered, 0, attached, &out);
} else {
out.Append("No processes or jobs matching \"" + filter + "\".\n");
}
}
cmd_context->Output(out);
}
const char kPsShortHelp[] = "ps: Prints the process tree of the debugged system.";
const char kPsUsage[] = "ps [ <filter-string> ]";
const char kPsHelp[] = R"(
Prints the process tree of the debugged system.
If a filter-string is provided only jobs and processes whose names contain the
given case-sensitive substring. It does not support regular expressions.
If a job is the root job of a component, the component information will also
be printed.
Jobs are annotated with "j: <job koid>"
Processes are annotated with "p: <process koid>")";
void RunVerbPs(const Command& cmd, fxl::RefPtr<CommandContext> cmd_context) {
std::string filter_string;
if (!cmd.args().empty())
filter_string = cmd.args()[0];
cmd_context->GetConsoleContext()->session()->system().GetProcessTree(
[filter_string, cmd_context](const Err& err, debug_ipc::ProcessTreeReply reply) {
if (err.has_error())
return cmd_context->ReportError(err);
OnListProcessesComplete(cmd_context, filter_string, reply);
});
}
} // namespace
VerbRecord GetPsVerbRecord() {
VerbRecord record(&RunVerbPs, {"ps"}, kPsShortHelp, kPsUsage, kPsHelp, CommandGroup::kGeneral);
record.param_type = VerbRecord::kOneParam; // Allow spaces in the filter string.
return record;
}
} // namespace zxdb