blob: 79ced90004f41a55669fb317bf8f8b081248b7ae [file] [log] [blame]
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "android/metrics/AdbLivenessChecker.h"
#include "android/base/Log.h"
#include "android/base/files/PathUtils.h"
#include "android/base/system/System.h"
#include "android/base/threads/ParallelTask.h"
#include "android/emulation/ConfigDirs.h"
#include "android/globals.h"
#include "android/metrics/proto/studio_stats.pb.h"
#include <istream>
namespace android {
namespace metrics {
using android::ConfigDirs;
using android::base::PathUtils;
using android::base::System;
using std::shared_ptr;
static const int kMaxAttempts = 3;
// static
AdbLivenessChecker::Ptr AdbLivenessChecker::create(
android::emulation::AdbInterface* adb,
android::base::Looper* looper,
MetricsReporter* reporter,
android::base::StringView emulatorName,
android::base::Looper::Duration checkIntervalMs) {
auto inst = Ptr(new AdbLivenessChecker(adb, looper, reporter, emulatorName,
checkIntervalMs));
return inst;
}
namespace {
struct LivenessStatus {
bool online;
bool bootComplete;
};
} // namespace
static LivenessStatus sLivenessStatus = {};
// static
bool AdbLivenessChecker::isEmulatorOnline() {
return sLivenessStatus.online;
}
bool AdbLivenessChecker::isEmulatorBooted() {
return sLivenessStatus.bootComplete;
}
static long bootCheckTaskTimeoutMs(
android::base::Looper::Duration adbTimeoutMs) {
if (avdInfo_is_x86ish(android_avdInfo)) {
return std::max<long>(adbTimeoutMs / 100, 250);
}
return std::max<long>(adbTimeoutMs / 10, 5000);
}
AdbLivenessChecker::AdbLivenessChecker(
android::emulation::AdbInterface* adb,
android::base::Looper* looper,
MetricsReporter* reporter,
android::base::StringView emulatorName,
android::base::Looper::Duration checkIntervalMs)
: mAdb(adb),
mLooper(looper),
mReporter(reporter),
mEmulatorName(emulatorName),
mCheckIntervalMs(checkIntervalMs),
// We use raw pointer to |this| instead of a shared_ptr to avoid cicrular
// ownership. mRecurrentTask promises to cancel any outstanding tasks when
// it's destructed.
mRecurrentTask(looper,
[this]() { return adbCheckRequest(); },
mCheckIntervalMs),
mBootCheckTask(looper,
[this]() { return runBootCheck(); },
bootCheckTaskTimeoutMs(mCheckIntervalMs)),
// TODO: bug 115582308
// Remove the liveness checker.
// Until we figure out how to run adb from socket directly.
//
// The liveness checker is only meant to measure one extremely narrow
// thing: the edge trigger for online -> offline. This one thing, that
// we don't even look at very much in metrics, and whose rate of going
// offline is very low already, is a guaranteed onerous burden for users
// as it is a heavyweight operation that starts a subprocess and involves
// temp file handling, and easily falls over or overloads the user's
// machine with adb procesess when the smallest thing is out of whack,
// such as when there are mismatched ADB servers running on the host.
mRemainingAttempts(0) {
// Don't call start() here: start() launches a parallel task that calls
// shared_from_this(), which needs at least one shared pointer owning
// |this|. We can't guarantee that until the constructor call returns.
}
AdbLivenessChecker::~AdbLivenessChecker() {
if (mDevicesCommand) {
mDevicesCommand->cancel();
}
if (mShellExitCommand) {
mShellExitCommand->cancel();
}
if (mBootCompleteCommand) {
mBootCompleteCommand->cancel();
}
stop();
}
void AdbLivenessChecker::start() {
// TODO: bug 115582308
// mRecurrentTask.start(true);
// mBootCheckTask.start(true);
}
void AdbLivenessChecker::stop() {
// TODO: bug 115582308
// mRecurrentTask.stopAndWait();
// mBootCheckTask.stopAndWait();
}
bool AdbLivenessChecker::adbCheckRequest() {
if (!mRemainingAttempts) {
LOG(VERBOSE) << "Quitting adb liveness check";
return false;
}
if (mIsCheckRunning) {
return true;
}
mIsCheckRunning = true;
#if 0
// NOTE: Capture a shared_ptr to |this| to guarantee object lifetime while
// the parallel task is running.
auto weakSelf = std::weak_ptr<AdbLivenessChecker>(shared_from_this());
auto taskFunction = [weakSelf](CheckResult* outResult) {
if (const auto self = weakSelf.lock()) {
self->runCheckBlocking(outResult);
}
};
auto taskDoneFunction = [weakSelf](const CheckResult& result) {
if (const auto self = weakSelf.lock()) {
self->reportCheckResult(result);
}
};
if (!android::base::runParallelTask<CheckResult>(mLooper, taskFunction,
taskDoneFunction)) {
mIsCheckRunning = false;
return false;
}
#endif
runCheckNonBlocking();
return true;
}
bool AdbLivenessChecker::runBootCheck() {
if (sLivenessStatus.bootComplete) {
return false;
}
if (mBootCompleteCommand) {
return true;
}
mBootCompleteCommand = mAdb->runAdbCommand(
{"shell", "getprop", "dev.bootcomplete"},
[this](const android::emulation::OptionalAdbCommandResult& result) {
if (!result || result->exit_code || !result->output) {
sLivenessStatus.bootComplete = false;
} else {
char out = 0;
if (bool(*result->output >> out) && out == '1') {
sLivenessStatus.bootComplete = true;
mBootCheckTask.stopAsync();
dropBootMetrics(BootResult::kBootCompleted);
} else {
sLivenessStatus.bootComplete = false;
}
}
mBootCompleteCommand.reset();
},
mBootCheckTask.taskIntervalMs(), true);
return true;
}
void AdbLivenessChecker::runCheckBlocking(CheckResult* outResult) const {
System::ProcessExitCode exitCode;
const std::string& adbPath = mAdb->adbPath();
std::string serial = mAdb->serialString();
if (serial.empty())
serial = mEmulatorName;
const std::vector<std::string> adbServerAliveCmd = {adbPath, "devices"};
if (!System::get()->runCommand(
adbServerAliveCmd,
System::RunOptions::WaitForCompletion |
System::RunOptions::TerminateOnTimeout,
mCheckIntervalMs / 3, &exitCode) ||
exitCode != 0) {
*outResult = CheckResult::kFailureAdbServerDead;
return;
}
const std::vector<std::string> emulatorAliveCmd = {adbPath, "-s", serial,
"shell", "exit"};
if (!System::get()->runCommand(
emulatorAliveCmd,
System::RunOptions::WaitForCompletion |
System::RunOptions::TerminateOnTimeout,
mCheckIntervalMs / 3, &exitCode) ||
exitCode != 0) {
*outResult = CheckResult::kFailureEmulatorDead;
return;
}
*outResult = AdbLivenessChecker::CheckResult::kOnline;
}
void AdbLivenessChecker::runCheckNonBlocking() {
if (mDevicesCommand || mShellExitCommand || mBootCompleteCommand) {
// already in progress
return;
}
mDevicesCommand = mAdb->runAdbCommand(
{"devices"},
[this](const android::emulation::OptionalAdbCommandResult& result) {
if (!result || result->exit_code) {
reportCheckResult(CheckResult::kFailureAdbServerDead);
sLivenessStatus.online = false;
sLivenessStatus.bootComplete = false;
if (!mBootCheckTask.inFlight()) {
mBootCheckTask.start(true);
}
} else {
mShellExitCommand = mAdb->runAdbCommand(
{"shell", "exit"},
[this](const android::emulation::
OptionalAdbCommandResult& result2) {
if (!result2 || result2->exit_code) {
reportCheckResult(
CheckResult::kFailureEmulatorDead);
sLivenessStatus.online = false;
sLivenessStatus.bootComplete = false;
if (!mBootCheckTask.inFlight()) {
mBootCheckTask.start(true);
}
} else {
reportCheckResult(CheckResult::kOnline);
sLivenessStatus.online = true;
}
mShellExitCommand.reset();
},
mCheckIntervalMs / 3, false);
}
mDevicesCommand.reset();
},
mCheckIntervalMs / 3, false);
}
void AdbLivenessChecker::reportCheckResult(const CheckResult& result) {
mIsCheckRunning = false;
// |mIsOnline| starts off set to false. We set it to true if we successfully
// talk to the emulator at least once. This allows us to distinguish the
// cases where adb is missing or emulator doesn't boot at all from cases
// where emulator boots and then disappears from adb.
if (!mIsOnline) {
switch (result) {
case CheckResult::kFailureAdbServerDead:
dropMetrics(CheckResult::kFailureNoAdb);
break;
case CheckResult::kOnline:
dropMetrics(result);
mIsOnline = true;
break;
default:
dropMetrics(CheckResult::kNoResult);
}
return;
}
if (result == CheckResult::kOnline) {
mRemainingAttempts = 0;
dropMetrics(result);
} else if (--mRemainingAttempts == 0) {
LOG(VERBOSE) << "Reporting error: " << static_cast<int>(result);
dropMetrics(result);
} else {
LOG(VERBOSE) << "Encountered error. mRemainingAttempts: "
<< mRemainingAttempts;
}
}
void AdbLivenessChecker::dropMetrics(const CheckResult& result) {
// Only report state if it changes. We start with kNoResult, and we only
// report kNoResult or kFailureNoAdb until emulator starts.
if (mCurrentState != result) {
mCurrentState = result;
mReporter->report([result](android_studio::AndroidStudioEvent* event) {
event->mutable_emulator_details()->set_adb_liveness(
static_cast<android_studio::EmulatorDetails::
EmulatorAdbLiveness>(
static_cast<int>(result)));
});
}
}
void AdbLivenessChecker::dropBootMetrics(const BootResult& result) {
// We only report if boot is detected as successful, or if it is detected
// as failing. In both cases, we want to collect the time taken to reach
// that conclusion.
auto timeMs = System::get()->getUnixTimeUs() / 1000;
auto durationMs = timeMs - mReporter->getStartTimeMs();
mReporter->report([result, durationMs](android_studio::AndroidStudioEvent* event) {
event->mutable_emulator_details()->mutable_boot_info()->set_boot_status(
static_cast<android_studio::EmulatorBootInfo::BootStatus>(
static_cast<int>(result)));
event->mutable_emulator_details()->mutable_boot_info()->set_duration_ms(
durationMs);
});
}
} // namespace metrics
} // namespace android