blob: 890f2a7c883ef648669ba0d4bfb472593c5a4463 [file] [log] [blame]
// Copyright 2019 The Chromium OS 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 "diagnostics/routines/subproc_routine.h"
#include <algorithm>
#include <utility>
#include <vector>
#include <base/logging.h>
#include <base/strings/string_util.h>
#include <base/process/process_handle.h>
#include "diagnostics/routines/diag_process_adapter_impl.h"
namespace diagnostics {
constexpr char kSubprocRoutineCancelled[] = "The test was canceled.";
constexpr char kSubprocRoutineErrorMessage[] =
"The test crashed or was killed.";
constexpr char kSubprocRoutineFailedMessage[] = "Test failed.";
constexpr char kSubprocRoutineFailedToLaunchProcessMessage[] =
"Could not launch the process.";
constexpr char kSubprocRoutineFailedToStopMessage[] =
"Failed to stop the routine.";
constexpr char kSubprocRoutineInvalidParametersMessage[] =
"The test could not be run due to invalid parameters.";
constexpr char kSubprocRoutineProcessCancellingMessage[] =
"Cancelled test. Waiting for cleanup...";
constexpr char kSubprocRoutineProcessRunningMessage[] =
"Test is still running.";
constexpr char kSubprocRoutineReadyMessage[] = "Routine is ready.";
constexpr char kSubprocRoutineSucceededMessage[] = "Test passed.";
constexpr int kSubprocRoutineFakeProgressPercentUnknown = 33;
constexpr int kSubprocRoutineFakeProgressPercentCancelling = 99;
grpc_api::DiagnosticRoutineStatus
GetDiagnosticRoutineStatusFromSubprocRoutineStatus(
SubprocRoutine::SubprocStatus subproc_status) {
switch (subproc_status) {
case SubprocRoutine::kSubprocStatusReady:
return grpc_api::ROUTINE_STATUS_READY;
case SubprocRoutine::kSubprocStatusLaunchFailed:
return grpc_api::ROUTINE_STATUS_FAILED_TO_START;
case SubprocRoutine::kSubprocStatusRunning:
return grpc_api::ROUTINE_STATUS_RUNNING;
case SubprocRoutine::kSubprocStatusCancelling:
return grpc_api::ROUTINE_STATUS_CANCELLING;
case SubprocRoutine::kSubprocStatusCompleteSuccess:
return grpc_api::ROUTINE_STATUS_PASSED;
case SubprocRoutine::kSubprocStatusCompleteFailure:
return grpc_api::ROUTINE_STATUS_FAILED;
case SubprocRoutine::kSubprocStatusError:
return grpc_api::ROUTINE_STATUS_ERROR;
case SubprocRoutine::kSubprocStatusCancelled:
return grpc_api::ROUTINE_STATUS_CANCELLED;
}
}
std::string GetStatusMessageFromSubprocRoutineStatus(
SubprocRoutine::SubprocStatus subproc_status) {
switch (subproc_status) {
case SubprocRoutine::kSubprocStatusReady:
return kSubprocRoutineReadyMessage;
case SubprocRoutine::kSubprocStatusLaunchFailed:
return kSubprocRoutineFailedToLaunchProcessMessage;
case SubprocRoutine::kSubprocStatusRunning:
return kSubprocRoutineProcessRunningMessage;
case SubprocRoutine::kSubprocStatusCancelling:
return kSubprocRoutineProcessCancellingMessage;
case SubprocRoutine::kSubprocStatusCompleteSuccess:
return kSubprocRoutineSucceededMessage;
case SubprocRoutine::kSubprocStatusCompleteFailure:
return kSubprocRoutineFailedMessage;
case SubprocRoutine::kSubprocStatusError:
return kSubprocRoutineErrorMessage;
case SubprocRoutine::kSubprocStatusCancelled:
return kSubprocRoutineCancelled;
}
}
SubprocRoutine::SubprocRoutine(const base::CommandLine& command_line,
int predicted_duration_in_seconds)
: SubprocRoutine(std::make_unique<DiagProcessAdapterImpl>(),
std::make_unique<base::DefaultTickClock>(),
command_line,
predicted_duration_in_seconds) {}
SubprocRoutine::SubprocRoutine(
std::unique_ptr<DiagProcessAdapter> process_adapter,
std::unique_ptr<base::TickClock> tick_clock,
const base::CommandLine& command_line,
int predicted_duration_in_seconds)
: subproc_status_(kSubprocStatusReady),
process_adapter_(std::move(process_adapter)),
tick_clock_(std::move(tick_clock)),
command_line_(command_line),
predicted_duration_in_seconds_(predicted_duration_in_seconds) {}
SubprocRoutine::~SubprocRoutine() {
// If the routine is still running, make sure to stop it so we aren't left
// with a zombie process.
KillProcess(true /*from_dtor*/);
}
void SubprocRoutine::Start() {
DCHECK_EQ(subproc_status_, kSubprocStatusReady);
DCHECK_EQ(handle_, base::kNullProcessHandle);
if (predicted_duration_in_seconds_ < 0) {
subproc_status_ = kSubprocStatusLaunchFailed;
LOG(ERROR) << kSubprocRoutineInvalidParametersMessage;
return;
}
StartProcess();
}
void SubprocRoutine::Resume() {
// Resume functionality is intended to be used by interactive routines.
// Subprocess routines are non-interactive.
LOG(ERROR) << "SubprocRoutine::Resume : subprocess diagnostic routines "
"cannot be resumed";
}
void SubprocRoutine::Cancel() {
KillProcess(false /*from_dtor*/);
}
void SubprocRoutine::PopulateStatusUpdate(
grpc_api::GetRoutineUpdateResponse* response, bool include_output) {
// Because the subproc_routine routine is non-interactive, we will never
// include a user message.
CheckProcessStatus();
response->set_status(
GetDiagnosticRoutineStatusFromSubprocRoutineStatus(subproc_status_));
response->set_progress_percent(CalculateProgressPercent());
response->set_status_message(
GetStatusMessageFromSubprocRoutineStatus(subproc_status_));
}
grpc_api::DiagnosticRoutineStatus SubprocRoutine::GetStatus() {
CheckProcessStatus();
return GetDiagnosticRoutineStatusFromSubprocRoutineStatus(subproc_status_);
}
void SubprocRoutine::StartProcess() {
if (subproc_status_ == kSubprocStatusReady) {
subproc_status_ = kSubprocStatusRunning;
// Don't bother joining the command_line_ if we aren't in verbose mode.
if (VLOG_IS_ON(1)) {
VLOG(1) << "Starting command "
<< base::JoinString(command_line_.argv(), " ");
}
if (!process_adapter_->StartProcess(command_line_.argv(), &handle_)) {
subproc_status_ = kSubprocStatusLaunchFailed;
LOG(ERROR) << kSubprocRoutineFailedToLaunchProcessMessage;
}
// Keep track of when we began the routine, in case we need to predict
// progress.
start_ticks_ = tick_clock_->NowTicks();
} else {
LOG(ERROR) << "An attempt was made to start a SubprocRoutine, but it is "
"not ready.";
}
}
void SubprocRoutine::KillProcess(bool from_dtor) {
CheckProcessStatus();
switch (subproc_status_) {
case kSubprocStatusRunning:
DCHECK(handle_ != base::kNullProcessHandle);
if (from_dtor) {
// We will not be able to keep track of this child process.
LOG(ERROR) << "Cancelling process " << handle_
<< " from diagnostics::SubprocRoutine destructor, cannot "
"guarantee process will die.";
}
subproc_status_ = kSubprocStatusCancelling;
process_adapter_->KillProcess(handle_);
break;
case kSubprocStatusCancelling:
// The process is already being killed. Do nothing.
DCHECK_NE(handle_, base::kNullProcessHandle);
break;
case kSubprocStatusCancelled:
case kSubprocStatusCompleteFailure:
case kSubprocStatusCompleteSuccess:
case kSubprocStatusError:
case kSubprocStatusLaunchFailed:
case kSubprocStatusReady:
// If the process has already exited, is exiting, or never started,
// there's no need to kill it.
DCHECK_EQ(handle_, base::kNullProcessHandle);
break;
}
}
void SubprocRoutine::CheckActiveProcessStatus() {
DCHECK_NE(handle_, base::kNullProcessHandle);
switch (process_adapter_->GetStatus(handle_)) {
case base::TERMINATION_STATUS_STILL_RUNNING:
DCHECK(subproc_status_ == kSubprocStatusCancelling ||
subproc_status_ == kSubprocStatusRunning);
break;
case base::TERMINATION_STATUS_NORMAL_TERMINATION:
// The process is gone.
handle_ = base::kNullProcessHandle;
if (subproc_status_ == kSubprocStatusCancelling) {
subproc_status_ = kSubprocStatusCancelled;
} else {
subproc_status_ = kSubprocStatusCompleteSuccess;
}
break;
case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
// The process is gone.
handle_ = base::kNullProcessHandle;
subproc_status_ = (subproc_status_ == kSubprocStatusCancelling)
? kSubprocStatusCancelled
: kSubprocStatusCompleteFailure;
break;
case base::TERMINATION_STATUS_LAUNCH_FAILED:
// The process never really was.
handle_ = base::kNullProcessHandle;
subproc_status_ = kSubprocStatusLaunchFailed;
break;
default:
// The process is mysteriously just missing.
handle_ = base::kNullProcessHandle;
subproc_status_ = (subproc_status_ == kSubprocStatusCancelling)
? kSubprocStatusCancelled
: kSubprocStatusError;
break;
}
}
void SubprocRoutine::CheckProcessStatus() {
switch (subproc_status_) {
case kSubprocStatusCancelled:
case kSubprocStatusCompleteFailure:
case kSubprocStatusCompleteSuccess:
case kSubprocStatusError:
case kSubprocStatusLaunchFailed:
case kSubprocStatusReady:
DCHECK_EQ(handle_, base::kNullProcessHandle);
break;
case kSubprocStatusCancelling:
case kSubprocStatusRunning:
CheckActiveProcessStatus();
break;
}
}
int SubprocRoutine::CalculateProgressPercent() {
switch (subproc_status_) {
case kSubprocStatusCompleteSuccess:
case kSubprocStatusCompleteFailure:
last_reported_progress_percent_ = 100;
break;
case kSubprocStatusRunning:
if (predicted_duration_in_seconds_ <= 0) {
/* when we don't know the progress, we fake at a low percentage */
last_reported_progress_percent_ =
kSubprocRoutineFakeProgressPercentUnknown;
} else {
last_reported_progress_percent_ = std::min(
100,
std::max(0, static_cast<int>(
100 * (tick_clock_->NowTicks() - start_ticks_) /
base::TimeDelta::FromSeconds(
predicted_duration_in_seconds_))));
}
break;
case kSubprocStatusCancelled:
case kSubprocStatusCancelling:
case kSubprocStatusError:
case kSubprocStatusLaunchFailed:
case kSubprocStatusReady:
break;
}
return last_reported_progress_percent_;
}
} // namespace diagnostics