blob: 90ba9824eea72fd1d729834884b83cc139dd5a51 [file] [log] [blame]
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Provides the `display_debug` command which can be used to assist with log collection for feedback reports.
use std::{io, thread, time};
use crate::debugd::{DRMTraceCategories, DRMTraceSize, DRMTraceSnapshotType, Debugd};
use crate::dispatcher::{self, Arguments, Command, Dispatcher};
impl DRMTraceCategories {
fn default() -> DRMTraceCategories {
DRMTraceCategories::empty()
}
fn debug() -> DRMTraceCategories {
DRMTraceCategories::DRIVER
| DRMTraceCategories::KMS
| DRMTraceCategories::PRIME
| DRMTraceCategories::ATOMIC
| DRMTraceCategories::STATE
| DRMTraceCategories::LEASE
| DRMTraceCategories::DP
}
}
const TRACE_START_LOG: &str = "DISPLAY-DEBUG-START-TRACE";
const TRACE_STOP_LOG: &str = "DISPLAY-DEBUG-STOP-TRACE";
const DIAGNOSE_START_LOG: &str = "DISPLAY-DEBUG-START-DIAGNOSE";
const DIAGNOSE_STOP_LOG: &str = "DISPLAY-DEBUG-STOP-DIAGNOSE";
const DIAGNOSE_DISPLAYS_DISCONNECTED_LOG: &str = "DISPLAY-DEBUG-DISPLAYS-DISCONNECTED";
const DIAGNOSE_DISPLAY_RECONNECTED_LOG: &str = "DISPLAY-DEBUG-DISPLAY-RECONNECTED";
const DIAGNOSE_DISPLAY_WORKING_LOG: &str = "DISPLAY-DEBUG-DISPLAY-WORKING";
const DIAGNOSE_DISPLAY_NOT_WORKING_LOG: &str = "DISPLAY-DEBUG-DISPLAY-NOT-WORKING";
const DIAGNOSE_INTRO: &str = "The 'display_debug diagnose' tool will collect \
additional logs while walking you through a sequence of diagnostic steps. \
Upon completion, file feedback using Alt+Shift+i.";
const DIAGNOSE_BEGIN_PROMPT: &str = "Do you want to begin now?";
const DIAGNOSE_DO_DISCONNECT: &str = "Disconnect all displays and docks from \
the device. If you are using a Chromebox leave your main display connected.";
const DIAGNOSE_DISCONNECT_PROMPT: &str = "Press <enter> after you have \
disconnected all displays.";
const DIAGNOSE_WAIT_STEADY_STATE: &str = "Waiting for the system to reach a steady state...";
const DIAGNOSE_RECONNECT_PROMPT: &str = "Reconnect one display. Press <enter> when you \
are done.";
const DIAGNOSE_HAVE_MORE_DISPLAYS: &str = "Do you have any more displays to reconnect?";
const DIAGNOSE_DISPLAYS_WORKING_PROMPT: &str = "Are your displays working as expected?";
const DIAGNOSE_COMPLETE: &str = "Thanks for using the 'display_debug diagnose' tool! \
Please file feedback using Alt+Shift+i and provide detailed information including:
\t- Make and model of display(s)
\t- Make and model of dock(s) in use
\t- Make and model of dongles or converters in use";
// Represents a user's Y/N choice at a prompt.
enum Choice {
Yes,
No,
}
fn read_input() -> Result<String, io::Error> {
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.map(|_| input.trim().to_string())
}
fn prompt_yes_no(question: &str) -> Result<Choice, io::Error> {
loop {
println!("{}: Y/N", question);
let input = read_input()?;
match input.to_lowercase().as_str() {
"yes" | "y" => return Ok(Choice::Yes),
"no" | "n" => return Ok(Choice::No),
_ => {}
}
}
}
fn prompt_enter(prompt: &str) {
println!("{}", prompt);
// Read a line of input from stdin. This requires the user to have
// pressed enter. Ignore the contents.
let _ = read_input();
}
pub fn register(dispatcher: &mut Dispatcher) {
dispatcher.register_command(
Command::new(
"display_debug",
"",
"A tool to assist with collecting logs when reproducing display related issues.",
)
.set_command_callback(Some(execute_display_debug))
.register_subcommand(
Command::new(
"trace_start",
"Usage: display_debug trace_start",
"Increase size and verbosity of logging through drm_trace.",
)
.set_command_callback(Some(execute_display_debug_trace_start)),
)
.register_subcommand(
Command::new(
"trace_stop",
"Usage: display_debug trace_stop",
"Reset size and verbosity of logging through drm_trace to defaults.",
)
.set_command_callback(Some(execute_display_debug_trace_stop)),
)
.register_subcommand(
Command::new(
"trace_annotate",
"Usage: display_debug trace_annotate <message>",
"Append |message| to the drm_trace log.",
)
.set_command_callback(Some(execute_display_debug_annotate)),
)
.register_subcommand(
Command::new(
"diagnose",
"Usage: display_debug diagnose",
"Give a sequence of steps to assist in debugging display issues.",
)
.set_command_callback(Some(execute_display_debug_diagnose)),
),
);
}
fn execute_display_debug(cmd: &Command, args: &Arguments) -> Result<(), dispatcher::Error> {
dispatcher::print_help_command_callback(cmd, args)?;
// Don't consider the cases where a user calls `display_debug` with no args or as
// `display_debug help` as errors.
match args.get_args().first().map(String::as_str) {
Some("help") | None => Ok(()),
Some(command) => Err(dispatcher::Error::CommandNotFound(command.to_string())),
}
}
fn execute_display_debug_trace_start(
_cmd: &Command,
_args: &Arguments,
) -> Result<(), dispatcher::Error> {
println!("Increasing size and verbosity of drm_trace log. Call `display_debug trace_stop` to restore to default.");
let connection = Debugd::new().map_err(|_| dispatcher::Error::CommandReturnedError)?;
do_trace_start(&connection, TRACE_START_LOG).map_err(|err| {
println!("ERROR: Got unexpected result: {}", err);
dispatcher::Error::CommandReturnedError
})
}
fn do_trace_start(connection: &Debugd, log: &str) -> Result<(), dbus::Error> {
connection
.drmtrace_set_size(DRMTraceSize::Debug)
.and_then(|d| d.drmtrace_set_categories(DRMTraceCategories::debug()))
.and_then(|d| d.drmtrace_annotate_log(String::from(log)))
.map(|_| ())
}
fn execute_display_debug_trace_stop(
_cmd: &Command,
_args: &Arguments,
) -> Result<(), dispatcher::Error> {
println!("Saving drm_trace log to /var/log/display_debug/. Restoring size and verbosity of drm_trace log to default.");
let connection = Debugd::new().map_err(|_| dispatcher::Error::CommandReturnedError)?;
do_trace_stop(&connection, TRACE_STOP_LOG).map_err(|err| {
println!("ERROR: Got unexpected result: {}", err);
dispatcher::Error::CommandReturnedError
})
}
fn do_trace_stop(connection: &Debugd, log: &str) -> Result<(), dbus::Error> {
connection
.drmtrace_annotate_log(String::from(log))
.and_then(|d| d.drmtrace_snapshot(DRMTraceSnapshotType::Trace))
.and_then(|d| d.drmtrace_set_categories(DRMTraceCategories::default()))
.and_then(|d| d.drmtrace_set_size(DRMTraceSize::Default))
.map(|_| ())
}
fn execute_display_debug_annotate(
_cmd: &Command,
args: &Arguments,
) -> Result<(), dispatcher::Error> {
let tokens = args.get_args();
if tokens.is_empty() {
return Err(dispatcher::Error::CommandInvalidArguments(
"missing log argument".to_string(),
));
}
let log = tokens.join(" ");
let connection = Debugd::new().map_err(|_| dispatcher::Error::CommandReturnedError)?;
match connection.drmtrace_annotate_log(log) {
Ok(_) => Ok(()),
Err(err) => {
println!("ERROR: Got unexpected result: {}", err);
Err(dispatcher::Error::CommandReturnedError)
}
}
}
fn execute_display_debug_diagnose(
_cmd: &Command,
_args: &Arguments,
) -> Result<(), dispatcher::Error> {
// Explain to the user what this tool does and confirm they want to proceed.
println!("{}", DIAGNOSE_INTRO);
match prompt_yes_no(DIAGNOSE_BEGIN_PROMPT) {
Ok(Choice::Yes) => (),
Ok(Choice::No) => return Ok(()),
Err(err) => {
println!("Error reading input: {}", err);
return Err(dispatcher::Error::CommandReturnedError);
}
}
let connection = Debugd::new().map_err(|_| dispatcher::Error::CommandReturnedError)?;
// Annotate the log, expand logging categories.
do_trace_start(&connection, DIAGNOSE_START_LOG).map_err(|err| {
println!("ERROR: Got unexpected result: {}", err);
dispatcher::Error::CommandReturnedError
})?;
// Tell user to disconnect displays, and wait until they say they are done.
println!("{}", DIAGNOSE_DO_DISCONNECT);
prompt_enter(DIAGNOSE_DISCONNECT_PROMPT);
// Wait a few seconds after user reports they are done, to wait
// for the system to reach a steady state.
println!("{}", DIAGNOSE_WAIT_STEADY_STATE);
thread::sleep(time::Duration::from_secs(5));
// Annotate the log to annotate that the displays are disconnected and take a snapshot
// of modetest.
if let Err(err) = connection
.drmtrace_annotate_log(String::from(DIAGNOSE_DISPLAYS_DISCONNECTED_LOG))
.and_then(|d| d.drmtrace_snapshot(DRMTraceSnapshotType::Modetest))
{
eprintln!(
"Error invoking D-Bus method after display disconnection: {}",
err
)
}
loop {
prompt_enter(DIAGNOSE_RECONNECT_PROMPT);
// Wait to reach a steady state.
println!("{}", DIAGNOSE_WAIT_STEADY_STATE);
thread::sleep(time::Duration::from_secs(5));
// Annotate the log that a display has been reconnected and take a snapshot of modetest.
if let Err(err) = connection
.drmtrace_annotate_log(String::from(DIAGNOSE_DISPLAY_RECONNECTED_LOG))
.and_then(|d| d.drmtrace_snapshot(DRMTraceSnapshotType::Modetest))
{
eprintln!(
"Error invoking D-Bus method after display reconnection: {}",
err
)
}
match prompt_yes_no(DIAGNOSE_HAVE_MORE_DISPLAYS) {
Ok(Choice::Yes) => (),
Ok(Choice::No) => break,
Err(err) => {
println!("Error reading input: {}", err);
return Err(dispatcher::Error::CommandReturnedError);
}
}
}
// Ask the user if things are working, and log their response.
let log = match prompt_yes_no(DIAGNOSE_DISPLAYS_WORKING_PROMPT) {
Ok(Choice::Yes) => DIAGNOSE_DISPLAY_WORKING_LOG,
Ok(Choice::No) => DIAGNOSE_DISPLAY_NOT_WORKING_LOG,
Err(err) => {
println!("Error reading input: {}", err);
return Err(dispatcher::Error::CommandReturnedError);
}
};
if let Err(err) = connection.drmtrace_annotate_log(String::from(log)) {
eprintln!("Error annotating drm_trace log: {}", err)
}
// Annotate the log, restore default logging categories.
do_trace_stop(&connection, DIAGNOSE_STOP_LOG).map_err(|err| {
println!("ERROR: Got unexpected result: {}", err);
dispatcher::Error::CommandReturnedError
})?;
println!("{}", DIAGNOSE_COMPLETE);
Ok(())
}