blob: 131894f241ed53551014c8bd7ab6e9f19a91cb96 [file] [log] [blame]
// Copyright 2017 The Android Open Source Project
//
// This software is licensed under the terms of the GNU General Public
// License version 2, as published by the Free Software Foundation, and
// may be copied, distributed, and modified under those terms.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
#include "android/snapshot/Saver.h"
#include "android/base/files/FileShareOpen.h"
#include "android/base/files/PathUtils.h"
#include "android/base/files/StdioStream.h"
#include "android/snapshot/RamLoader.h"
#include "android/snapshot/TextureSaver.h"
#include "android/snapshot/common.h"
#include "android/utils/debug.h"
#include "android/utils/path.h"
using android::base::c_str;
using android::base::PathUtils;
using android::base::StdioStream;
using android::base::StringView;
using android::base::System;
namespace android {
namespace snapshot {
Saver::Saver(const Snapshot& snapshot, RamLoader* loader, bool isOnExit,
base::StringView ramMapFile, bool ramFileShared, bool isRemapping)
: mStatus(OperationStatus::Error), mSnapshot(snapshot) {
if (path_mkdir_if_needed_no_cow(c_str(mSnapshot.dataDir()), 0777) != 0) {
return;
}
{
const auto ramFile = PathUtils::join(mSnapshot.dataDir(), kRamFileName);
auto flags = RamSaver::Flags::None;
// If we're using a file-backed RAM that is writing through,
// no need to save pages synchronously on exit.
if ((!ramMapFile.empty() &&
ramFileShared) &&
(isOnExit || isRemapping)) {
flags |= RamSaver::Flags::Async;
}
const auto compressEnvVar =
System::get()->envGet("ANDROID_SNAPSHOT_COMPRESS");
if (compressEnvVar == "1" || compressEnvVar == "yes" ||
compressEnvVar == "true") {
VERBOSE_PRINT(snapshot,
"autoconfig: enabled snapshot RAM compression from "
"environment [ANDROID_SNAPSHOT_COMPRESS=%s]",
compressEnvVar.c_str());
flags |= RamSaver::Flags::Compress;
} else if (compressEnvVar == "0" || compressEnvVar == "no" ||
compressEnvVar == "false") {
// don't enable compression
VERBOSE_PRINT(snapshot,
"autoconfig: forced no snapshot RAM compression from "
"environment [ANDROID_SNAPSHOT_COMPRESS=%s]",
compressEnvVar.c_str());
} else {
// Check if it's faster to save RAM with compression. Currently
// the heuristics are as following:
// 1. Gather three variables: free RAM, number of CPU cores and
// the disk kind.
// 2. Bad cases for uncompressed snapshots: little free RAM
// and HDD disk.
// 3. If there's a bad case and we have enough CPU cores (3+), let's
// enable compression.
auto numCores = System::get()->getCpuCoreCount();
mMemUsage = System::get()->getMemUsage();
mDiskKind =
System::get()->pathDiskKind(mSnapshot.dataDir());
if (numCores > 2) {
auto freeMb = mMemUsage.avail_phys_memory / (1024 * 1024);
if (freeMb < 1536) {
flags |= RamSaver::Flags::Compress;
VERBOSE_PRINT(
snapshot,
"Enabling RAM compression: only %u MB of free RAM",
unsigned(freeMb));
} else {
if (mDiskKind.valueOr(System::DiskKind::Ssd) ==
System::DiskKind::Hdd) {
flags |= RamSaver::Flags::Compress;
VERBOSE_PRINT(
snapshot,
"Enabling RAM compression: snapshot is on HDD");
}
}
}
}
const bool tryIncremental =
loader && !loader->hasError() && loader->hasGaps();
mIncrementallySaved = tryIncremental;
mRamSaver.emplace(ramFile, flags, tryIncremental ? loader : nullptr,
isOnExit);
if (mRamSaver->hasError()) {
mRamSaver.clear();
return;
}
}
{
const auto textures = android::base::fsopen(
PathUtils::join(mSnapshot.dataDir(), kTexturesFileName).c_str(),
"wb", android::base::FileShare::Write);
if (!textures) {
mRamSaver.clear();
return;
}
mTextureSaver = std::make_shared<TextureSaver>(
StdioStream(textures, StdioStream::kOwner));
}
mStatus = OperationStatus::NotStarted;
}
Saver::~Saver() {
const bool deleteDirectory =
mStatus != OperationStatus::Ok && (mRamSaver || mTextureSaver);
mRamSaver.clear();
mTextureSaver.reset();
if (deleteDirectory) {
path_delete_dir(c_str(mSnapshot.dataDir()));
}
}
ITextureSaverPtr Saver::textureSaver() const {
return mTextureSaver;
}
void Saver::prepare() {
// TODO: run asynchronous saving preparation here (e.g. screenshot,
// hardware info collection etc).
}
void Saver::complete(bool succeeded) {
mStatus = OperationStatus::Error;
if (!succeeded) {
return;
}
if (!mRamSaver || mRamSaver->hasError()) {
return;
}
mRamSaver->join();
if (!mTextureSaver ||
(static_cast<void>(mTextureSaver->done()), mTextureSaver->hasError())) {
return;
}
base::System::Duration ramDuration = 0;
base::System::Duration texturesDuration = 0;
if (mRamSaver->getDuration(&ramDuration) &&
mTextureSaver->getDuration(&texturesDuration)) {
mSnapshot.addSaveStats(
mIncrementallySaved,
ramDuration + texturesDuration,
0 /* ram changed bytes; unused for now */);
}
if (!mSnapshot.save()) {
return;
}
mStatus = OperationStatus::Ok;
}
void Saver::cancel() {
mStatus = OperationStatus::Canceled;
if (mRamSaver) {
mRamSaver->cancel();
}
// TODO next: texture save cancel
path_delete_dir(c_str(mSnapshot.dataDir()));
mSnapshot.saveFailure(FailureReason::Canceled);
}
} // namespace snapshot
} // namespace android