Merge "Direct report mode support in sensor service and client"
diff --git a/cmds/installd/Android.bp b/cmds/installd/Android.bp
index 93174bf..33db6db 100644
--- a/cmds/installd/Android.bp
+++ b/cmds/installd/Android.bp
@@ -6,6 +6,8 @@
"-Werror",
],
srcs: [
+ "CacheItem.cpp",
+ "CacheTracker.cpp",
"InstalldNativeService.cpp",
"dexopt.cpp",
"globals.cpp",
diff --git a/cmds/installd/CacheItem.cpp b/cmds/installd/CacheItem.cpp
new file mode 100644
index 0000000..d1bdded
--- /dev/null
+++ b/cmds/installd/CacheItem.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 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 "CacheItem.h"
+
+#include <stdint.h>
+#include <inttypes.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include "utils.h"
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace installd {
+
+CacheItem::CacheItem(const std::shared_ptr<CacheItem>& parent, FTSENT* p) : mParent(parent) {
+ level = p->fts_level;
+ directory = S_ISDIR(p->fts_statp->st_mode);
+ size = p->fts_statp->st_blocks * 512;
+ modified = p->fts_statp->st_mtime;
+ mName = p->fts_path;
+}
+
+CacheItem::~CacheItem() {
+}
+
+std::string CacheItem::toString() {
+ return StringPrintf("%s size=%" PRId64 " mod=%ld", buildPath().c_str(), size, modified);
+}
+
+std::string CacheItem::buildPath() {
+ std::string res = mName;
+ std::shared_ptr<CacheItem> parent = mParent;
+ while (parent) {
+ res.insert(0, parent->mName);
+ parent = parent->mParent;
+ }
+ return res;
+}
+
+int CacheItem::purge() {
+ auto path = buildPath();
+ if (directory) {
+ return delete_dir_contents_and_dir(path, true);
+ } else {
+ int res = unlink(path.c_str());
+ if (res != 0) {
+ PLOG(WARNING) << "Failed to unlink " << path;
+ }
+ return res;
+ }
+}
+
+} // namespace installd
+} // namespace android
diff --git a/cmds/installd/CacheItem.h b/cmds/installd/CacheItem.h
new file mode 100644
index 0000000..bec8bc8
--- /dev/null
+++ b/cmds/installd/CacheItem.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef ANDROID_INSTALLD_CACHE_ITEM_H
+#define ANDROID_INSTALLD_CACHE_ITEM_H
+
+#include <memory>
+#include <string>
+
+#include <fts.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <android-base/macros.h>
+
+namespace android {
+namespace installd {
+
+/**
+ * Single cache item that can be purged to free up space. This may be an
+ * isolated file, or an entire directory tree that should be atomically
+ * deleted.
+ */
+class CacheItem {
+public:
+ CacheItem(const std::shared_ptr<CacheItem>& parent, FTSENT* p);
+ ~CacheItem();
+
+ std::string toString();
+ std::string buildPath();
+
+ int purge();
+
+ short level;
+ bool directory;
+ int64_t size;
+ time_t modified;
+
+private:
+ std::shared_ptr<CacheItem> mParent;
+ std::string mName;
+
+ DISALLOW_COPY_AND_ASSIGN(CacheItem);
+};
+
+} // namespace installd
+} // namespace android
+
+#endif // ANDROID_INSTALLD_CACHE_ITEM_H
diff --git a/cmds/installd/CacheTracker.cpp b/cmds/installd/CacheTracker.cpp
new file mode 100644
index 0000000..23c4330
--- /dev/null
+++ b/cmds/installd/CacheTracker.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_PACKAGE_MANAGER
+
+#include "CacheTracker.h"
+
+#include <fts.h>
+#include <sys/quota.h>
+#include <utils/Trace.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include "utils.h"
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace installd {
+
+CacheTracker::CacheTracker(userid_t userId, appid_t appId, const std::string& quotaDevice) :
+ cacheUsed(0), cacheQuota(0), mUserId(userId), mAppId(appId), mQuotaDevice(quotaDevice),
+ mItemsLoaded(false) {
+}
+
+CacheTracker::~CacheTracker() {
+}
+
+std::string CacheTracker::toString() {
+ return StringPrintf("UID=%d used=%" PRId64 " quota=%" PRId64 " ratio=%d",
+ multiuser_get_uid(mUserId, mAppId), cacheUsed, cacheQuota, getCacheRatio());
+}
+
+void CacheTracker::addDataPath(const std::string& dataPath) {
+ mDataPaths.push_back(dataPath);
+}
+
+void CacheTracker::loadStats() {
+ int cacheGid = multiuser_get_cache_gid(mUserId, mAppId);
+ if (cacheGid != -1 && !mQuotaDevice.empty()) {
+ ATRACE_BEGIN("loadStats quota");
+ struct dqblk dq;
+ if (quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), mQuotaDevice.c_str(), cacheGid,
+ reinterpret_cast<char*>(&dq)) != 0) {
+ ATRACE_END();
+ if (errno != ESRCH) {
+ PLOG(ERROR) << "Failed to quotactl " << mQuotaDevice << " for GID " << cacheGid;
+ }
+ } else {
+ cacheUsed = dq.dqb_curspace;
+ ATRACE_END();
+ return;
+ }
+ }
+
+ ATRACE_BEGIN("loadStats tree");
+ cacheUsed = 0;
+ for (auto path : mDataPaths) {
+ auto cachePath = read_path_inode(path, "cache", kXattrInodeCache);
+ auto codeCachePath = read_path_inode(path, "code_cache", kXattrInodeCodeCache);
+ calculate_tree_size(cachePath, &cacheUsed);
+ calculate_tree_size(codeCachePath, &cacheUsed);
+ }
+ ATRACE_END();
+}
+
+void CacheTracker::loadItemsFrom(const std::string& path) {
+ FTS *fts;
+ FTSENT *p;
+ char *argv[] = { (char*) path.c_str(), nullptr };
+ if (!(fts = fts_open(argv, FTS_PHYSICAL | FTS_XDEV, NULL))) {
+ PLOG(WARNING) << "Failed to fts_open " << path;
+ return;
+ }
+ // TODO: add support for "user.atomic" and "user.tombstone" xattrs
+ while ((p = fts_read(fts)) != NULL) {
+ switch (p->fts_info) {
+ case FTS_D:
+ // Track the newest mtime of anything inside so we consider
+ // deleting the directory last
+ p->fts_number = p->fts_statp->st_mtime;
+ break;
+ case FTS_DP:
+ p->fts_statp->st_mtime = p->fts_number;
+
+ // Ignore the actual top-level cache directories
+ if (p->fts_level == 0) break;
+ case FTS_DEFAULT:
+ case FTS_F:
+ case FTS_SL:
+ case FTS_SLNONE:
+ // TODO: optimize path memory footprint
+ items.push_back(std::shared_ptr<CacheItem>(new CacheItem(nullptr, p)));
+
+ // Track the newest modified item under this tree
+ p->fts_parent->fts_number =
+ std::max(p->fts_parent->fts_number, p->fts_statp->st_mtime);
+ break;
+ }
+ }
+ fts_close(fts);
+}
+
+void CacheTracker::loadItems() {
+ items.clear();
+
+ ATRACE_BEGIN("loadItems");
+ for (auto path : mDataPaths) {
+ loadItemsFrom(read_path_inode(path, "cache", kXattrInodeCache));
+ loadItemsFrom(read_path_inode(path, "code_cache", kXattrInodeCodeCache));
+ }
+ ATRACE_END();
+
+ ATRACE_BEGIN("sortItems");
+ auto cmp = [](std::shared_ptr<CacheItem> left, std::shared_ptr<CacheItem> right) {
+ // TODO: sort dotfiles last
+ // TODO: sort code_cache last
+ if (left->modified != right->modified) {
+ return (left->modified > right->modified);
+ }
+ if (left->level != right->level) {
+ return (left->level < right->level);
+ }
+ return left->directory;
+ };
+ std::sort(items.begin(), items.end(), cmp);
+ ATRACE_END();
+}
+
+void CacheTracker::ensureItems() {
+ if (mItemsLoaded) {
+ return;
+ } else {
+ loadItems();
+ mItemsLoaded = true;
+ }
+}
+
+int CacheTracker::getCacheRatio() {
+ if (cacheQuota == 0) {
+ return 0;
+ } else {
+ return (cacheUsed * 10000) / cacheQuota;
+ }
+}
+
+} // namespace installd
+} // namespace android
diff --git a/cmds/installd/CacheTracker.h b/cmds/installd/CacheTracker.h
new file mode 100644
index 0000000..91692d7
--- /dev/null
+++ b/cmds/installd/CacheTracker.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef ANDROID_INSTALLD_CACHE_TRACKER_H
+#define ANDROID_INSTALLD_CACHE_TRACKER_H
+
+#include <memory>
+#include <string>
+#include <queue>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <android-base/macros.h>
+#include <cutils/multiuser.h>
+
+#include "CacheItem.h"
+
+namespace android {
+namespace installd {
+
+/**
+ * Cache tracker for a single UID. Each tracker is used in two modes: first
+ * for loading lightweight "stats", and then by loading detailed "items"
+ * which can then be purged to free up space.
+ */
+class CacheTracker {
+public:
+ CacheTracker(userid_t userId, appid_t appId, const std::string& quotaDevice);
+ ~CacheTracker();
+
+ std::string toString();
+
+ void addDataPath(const std::string& dataPath);
+
+ void loadStats();
+ void loadItems();
+
+ void ensureItems();
+
+ int getCacheRatio();
+
+ int64_t cacheUsed;
+ int64_t cacheQuota;
+
+ std::vector<std::shared_ptr<CacheItem>> items;
+
+private:
+ userid_t mUserId;
+ appid_t mAppId;
+ std::string mQuotaDevice;
+ bool mItemsLoaded;
+
+ std::vector<std::string> mDataPaths;
+
+ void loadItemsFrom(const std::string& path);
+
+ DISALLOW_COPY_AND_ASSIGN(CacheTracker);
+};
+
+} // namespace installd
+} // namespace android
+
+#endif // ANDROID_INSTALLD_CACHE_TRACKER_H
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
index 987e8da..ede31fc 100644
--- a/cmds/installd/InstalldNativeService.cpp
+++ b/cmds/installd/InstalldNativeService.cpp
@@ -56,6 +56,7 @@
#include "otapreopt_utils.h"
#include "utils.h"
+#include "CacheTracker.h"
#include "MatchExtensionGen.h"
#ifndef LOG_TAG
@@ -88,6 +89,8 @@
static constexpr int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
static constexpr int FLAG_CLEAR_CODE_CACHE_ONLY = 1 << 9;
static constexpr int FLAG_USE_QUOTA = 1 << 12;
+static constexpr int FLAG_FREE_CACHE_V2 = 1 << 13;
+static constexpr int FLAG_FREE_CACHE_NOOP = 1 << 14;
namespace {
@@ -201,11 +204,18 @@
}
std::lock_guard<std::recursive_mutex> lock(mLock);
- out << "installd is happy!" << endl << endl;
- out << "Devices with quota support:" << endl;
+ out << "installd is happy!" << endl;
+
+ out << endl << "Devices with quota support:" << endl;
for (const auto& n : mQuotaDevices) {
out << " " << n.first << " = " << n.second << endl;
}
+
+ out << endl << "Per-UID cache quotas:" << endl;
+ for (const auto& n : mCacheQuotas) {
+ out << " " << n.first << " = " << n.second << endl;
+ }
+
out << endl;
out.flush();
@@ -810,46 +820,164 @@
* when just reading from the cache, which is pretty awful.
*/
binder::Status InstalldNativeService::freeCache(const std::unique_ptr<std::string>& uuid,
- int64_t freeStorageSize) {
+ int64_t freeStorageSize, int32_t flags) {
ENFORCE_UID(AID_SYSTEM);
CHECK_ARGUMENT_UUID(uuid);
std::lock_guard<std::recursive_mutex> lock(mLock);
+ // TODO: remove this once framework is more robust
+ invalidateMounts();
+
const char* uuid_ = uuid ? uuid->c_str() : nullptr;
- cache_t* cache;
- int64_t avail;
-
auto data_path = create_data_path(uuid_);
+ auto device = findQuotaDeviceForUuid(uuid);
+ auto noop = (flags & FLAG_FREE_CACHE_NOOP);
- avail = data_disk_free(data_path);
- if (avail < 0) {
+ int64_t free = data_disk_free(data_path);
+ int64_t needed = freeStorageSize - free;
+ if (free < 0) {
return error("Failed to determine free space for " + data_path);
- }
-
- ALOGI("free_cache(%" PRId64 ") avail %" PRId64 "\n", freeStorageSize, avail);
- if (avail >= freeStorageSize) {
+ } else if (free >= freeStorageSize) {
return ok();
}
- cache = start_cache_collection();
+ LOG(DEBUG) << "Found " << data_path << " with " << free << " free; caller requested "
+ << freeStorageSize;
- auto users = get_known_users(uuid_);
- for (auto user : users) {
- add_cache_files(cache, create_data_user_ce_path(uuid_, user));
- add_cache_files(cache, create_data_user_de_path(uuid_, user));
- add_cache_files(cache,
- StringPrintf("%s/Android/data", create_data_media_path(uuid_, user).c_str()));
+ if (flags & FLAG_FREE_CACHE_V2) {
+ // This new cache strategy fairly removes files from UIDs by deleting
+ // files from the UIDs which are most over their allocated quota
+
+ // 1. Create trackers for every known UID
+ ATRACE_BEGIN("create");
+ std::unordered_map<uid_t, std::shared_ptr<CacheTracker>> trackers;
+ for (auto user : get_known_users(uuid_)) {
+ FTS *fts;
+ FTSENT *p;
+ char *argv[] = {
+ (char*) create_data_user_ce_path(uuid_, user).c_str(),
+ (char*) create_data_user_de_path(uuid_, user).c_str(),
+ nullptr
+ };
+ if (!(fts = fts_open(argv, FTS_PHYSICAL | FTS_XDEV, NULL))) {
+ return error("Failed to fts_open");
+ }
+ while ((p = fts_read(fts)) != NULL) {
+ if (p->fts_info == FTS_D && p->fts_level == 1) {
+ uid_t uid = p->fts_statp->st_uid;
+ auto search = trackers.find(uid);
+ if (search != trackers.end()) {
+ search->second->addDataPath(p->fts_path);
+ } else {
+ auto tracker = std::shared_ptr<CacheTracker>(new CacheTracker(
+ multiuser_get_user_id(uid), multiuser_get_app_id(uid), device));
+ tracker->addDataPath(p->fts_path);
+ tracker->cacheQuota = mCacheQuotas[uid];
+ if (tracker->cacheQuota == 0) {
+ LOG(WARNING) << "UID " << uid << " has no cache quota; assuming 64MB";
+ tracker->cacheQuota = 67108864;
+ }
+ trackers[uid] = tracker;
+ }
+ fts_set(fts, p, FTS_SKIP);
+ }
+ }
+ fts_close(fts);
+ }
+ ATRACE_END();
+
+ // 2. Populate tracker stats and insert into priority queue
+ ATRACE_BEGIN("populate");
+ auto cmp = [](std::shared_ptr<CacheTracker> left, std::shared_ptr<CacheTracker> right) {
+ return (left->getCacheRatio() < right->getCacheRatio());
+ };
+ std::priority_queue<std::shared_ptr<CacheTracker>,
+ std::vector<std::shared_ptr<CacheTracker>>, decltype(cmp)> queue(cmp);
+ for (const auto& it : trackers) {
+ it.second->loadStats();
+ queue.push(it.second);
+ }
+ ATRACE_END();
+
+ // 3. Bounce across the queue, freeing items from whichever tracker is
+ // the most over their assigned quota
+ ATRACE_BEGIN("bounce");
+ std::shared_ptr<CacheTracker> active;
+ while (active || !queue.empty()) {
+ // Find the best tracker to work with; this might involve swapping
+ // if the active tracker is no longer the most over quota
+ bool nextBetter = active && !queue.empty()
+ && active->getCacheRatio() < queue.top()->getCacheRatio();
+ if (!active || nextBetter) {
+ if (active) {
+ // Current tracker still has items, so we'll consider it
+ // again later once it bubbles up to surface
+ queue.push(active);
+ }
+ active = queue.top(); queue.pop();
+ active->ensureItems();
+ continue;
+ }
+
+ // If no items remain, go find another tracker
+ if (active->items.empty()) {
+ active = nullptr;
+ continue;
+ } else {
+ auto item = active->items.back();
+ active->items.pop_back();
+
+ LOG(DEBUG) << "Purging " << item->toString() << " from " << active->toString();
+ if (!noop) {
+ item->purge();
+ }
+ active->cacheUsed -= item->size;
+ needed -= item->size;
+ }
+
+ // Verify that we're actually done before bailing, since sneaky
+ // apps might be using hardlinks
+ if (needed <= 0) {
+ free = data_disk_free(data_path);
+ needed = freeStorageSize - free;
+ if (needed <= 0) {
+ break;
+ } else {
+ LOG(WARNING) << "Expected to be done but still need " << needed;
+ }
+ }
+ }
+ ATRACE_END();
+
+ } else {
+ ATRACE_BEGIN("start");
+ cache_t* cache = start_cache_collection();
+ ATRACE_END();
+
+ ATRACE_BEGIN("add");
+ for (auto user : get_known_users(uuid_)) {
+ add_cache_files(cache, create_data_user_ce_path(uuid_, user));
+ add_cache_files(cache, create_data_user_de_path(uuid_, user));
+ add_cache_files(cache,
+ StringPrintf("%s/Android/data", create_data_media_path(uuid_, user).c_str()));
+ }
+ ATRACE_END();
+
+ ATRACE_BEGIN("clear");
+ clear_cache_files(data_path, cache, freeStorageSize);
+ ATRACE_END();
+
+ ATRACE_BEGIN("finish");
+ finish_cache_collection(cache);
+ ATRACE_END();
}
- clear_cache_files(data_path, cache, freeStorageSize);
- finish_cache_collection(cache);
-
- avail = data_disk_free(data_path);
- if (avail >= freeStorageSize) {
+ free = data_disk_free(data_path);
+ if (free >= freeStorageSize) {
return ok();
} else {
return error(StringPrintf("Failed to free up %" PRId64 " on %s; final free space %" PRId64,
- freeStorageSize, data_path.c_str(), avail));
+ freeStorageSize, data_path.c_str(), free));
}
}
@@ -901,7 +1029,7 @@
#endif
static void collectQuotaStats(const std::string& device, int32_t userId,
- int32_t appId, struct stats* stats, struct stats* extStats ATTRIBUTE_UNUSED) {
+ int32_t appId, struct stats* stats, struct stats* extStats) {
if (device.empty()) return;
struct dqblk dq;
@@ -928,13 +1056,28 @@
}
} else {
#if MEASURE_DEBUG
- LOG(DEBUG) << "quotactl() for GID " << cacheGid << " " << dq.dqb_curspace;
+ LOG(DEBUG) << "quotactl() for GID " << cacheGid << " " << dq.dqb_curspace;
#endif
stats->cacheSize += dq.dqb_curspace;
}
}
- int sharedGid = multiuser_get_shared_app_gid(uid);
+ int extGid = multiuser_get_ext_gid(userId, appId);
+ if (extGid != -1) {
+ if (quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), device.c_str(), extGid,
+ reinterpret_cast<char*>(&dq)) != 0) {
+ if (errno != ESRCH) {
+ PLOG(ERROR) << "Failed to quotactl " << device << " for GID " << extGid;
+ }
+ } else {
+#if MEASURE_DEBUG
+ LOG(DEBUG) << "quotactl() for GID " << extGid << " " << dq.dqb_curspace;
+#endif
+ extStats->dataSize += dq.dqb_curspace;
+ }
+ }
+
+ int sharedGid = multiuser_get_shared_gid(userId, appId);
if (sharedGid != -1) {
if (quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), device.c_str(), sharedGid,
reinterpret_cast<char*>(&dq)) != 0) {
@@ -943,15 +1086,11 @@
}
} else {
#if MEASURE_DEBUG
- LOG(DEBUG) << "quotactl() for GID " << sharedGid << " " << dq.dqb_curspace;
+ LOG(DEBUG) << "quotactl() for GID " << sharedGid << " " << dq.dqb_curspace;
#endif
stats->codeSize += dq.dqb_curspace;
}
}
-
-#if MEASURE_EXTERNAL
- // TODO: measure using external GIDs
-#endif
}
static void collectManualStats(const std::string& path, struct stats* stats) {
@@ -1037,6 +1176,40 @@
closedir(d);
}
+static void collectManualExternalStatsForUser(const std::string& path, struct stats* stats) {
+ FTS *fts;
+ FTSENT *p;
+ char *argv[] = { (char*) path.c_str(), nullptr };
+ if (!(fts = fts_open(argv, FTS_PHYSICAL | FTS_XDEV, NULL))) {
+ PLOG(ERROR) << "Failed to fts_open " << path;
+ return;
+ }
+ while ((p = fts_read(fts)) != NULL) {
+ switch (p->fts_info) {
+ case FTS_D:
+ if (p->fts_level == 4
+ && !strcmp(p->fts_name, "cache")
+ && !strcmp(p->fts_parent->fts_parent->fts_name, "data")
+ && !strcmp(p->fts_parent->fts_parent->fts_parent->fts_name, "Android")) {
+ p->fts_number = 1;
+ }
+ p->fts_number = p->fts_parent->fts_number;
+ // Fall through to count the directory
+ case FTS_DEFAULT:
+ case FTS_F:
+ case FTS_SL:
+ case FTS_SLNONE:
+ int64_t size = (p->fts_statp->st_blocks * 512);
+ if (p->fts_number == 1) {
+ stats->cacheSize += size;
+ }
+ stats->dataSize += size;
+ break;
+ }
+ }
+ fts_close(fts);
+}
+
binder::Status InstalldNativeService::getAppSize(const std::unique_ptr<std::string>& uuid,
const std::vector<std::string>& packageNames, int32_t userId, int32_t flags,
int32_t appId, const std::vector<int64_t>& ceDataInodes,
@@ -1123,14 +1296,12 @@
calculate_tree_size(refProfilePath, &stats.codeSize);
ATRACE_END();
-#if MEASURE_EXTERNAL
ATRACE_BEGIN("external");
auto extPath = create_data_media_package_path(uuid_, userId, pkgname, "data");
collectManualStats(extPath, &extStats);
auto mediaPath = create_data_media_package_path(uuid_, userId, pkgname, "media");
calculate_tree_size(mediaPath, &extStats.dataSize);
ATRACE_END();
-#endif
}
ATRACE_BEGIN("dalvik");
@@ -1179,17 +1350,28 @@
const char* uuid_ = uuid ? uuid->c_str() : nullptr;
- ATRACE_BEGIN("obb");
- auto obbPath = create_data_path(uuid_) + "/media/obb";
- calculate_tree_size(obbPath, &extStats.codeSize);
- ATRACE_END();
-
auto device = findQuotaDeviceForUuid(uuid);
if (device.empty()) {
flags &= ~FLAG_USE_QUOTA;
}
if (flags & FLAG_USE_QUOTA) {
+ struct dqblk dq;
+
+ ATRACE_BEGIN("obb");
+ if (quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), device.c_str(), AID_MEDIA_OBB,
+ reinterpret_cast<char*>(&dq)) != 0) {
+ if (errno != ESRCH) {
+ PLOG(ERROR) << "Failed to quotactl " << device << " for GID " << AID_MEDIA_OBB;
+ }
+ } else {
+#if MEASURE_DEBUG
+ LOG(DEBUG) << "quotactl() for GID " << AID_MEDIA_OBB << " " << dq.dqb_curspace;
+#endif
+ extStats.codeSize += dq.dqb_curspace;
+ }
+ ATRACE_END();
+
ATRACE_BEGIN("code");
calculate_tree_size(create_data_app_path(uuid_), &stats.codeSize, -1, -1, true);
ATRACE_END();
@@ -1208,11 +1390,20 @@
calculate_tree_size(refProfilePath, &stats.codeSize, -1, -1, true);
ATRACE_END();
-#if MEASURE_EXTERNAL
ATRACE_BEGIN("external");
- // TODO: measure external storage paths
- ATRACE_END();
+ uid_t uid = multiuser_get_uid(userId, AID_MEDIA_RW);
+ if (quotactl(QCMD(Q_GETQUOTA, USRQUOTA), device.c_str(), uid,
+ reinterpret_cast<char*>(&dq)) != 0) {
+ if (errno != ESRCH) {
+ PLOG(ERROR) << "Failed to quotactl " << device << " for UID " << uid;
+ }
+ } else {
+#if MEASURE_DEBUG
+ LOG(DEBUG) << "quotactl() for UID " << uid << " " << dq.dqb_curspace;
#endif
+ extStats.dataSize += dq.dqb_curspace;
+ }
+ ATRACE_END();
ATRACE_BEGIN("dalvik");
calculate_tree_size(create_data_dalvik_cache_path(), &stats.codeSize,
@@ -1233,6 +1424,11 @@
}
ATRACE_END();
} else {
+ ATRACE_BEGIN("obb");
+ auto obbPath = create_data_path(uuid_) + "/media/obb";
+ calculate_tree_size(obbPath, &extStats.codeSize);
+ ATRACE_END();
+
ATRACE_BEGIN("code");
calculate_tree_size(create_data_app_path(uuid_), &stats.codeSize);
ATRACE_END();
@@ -1251,11 +1447,14 @@
calculate_tree_size(refProfilePath, &stats.codeSize);
ATRACE_END();
-#if MEASURE_EXTERNAL
ATRACE_BEGIN("external");
- // TODO: measure external storage paths
- ATRACE_END();
+ auto dataMediaPath = create_data_media_path(uuid_, userId);
+ collectManualExternalStatsForUser(dataMediaPath, &extStats);
+#if MEASURE_DEBUG
+ LOG(DEBUG) << "Measured external data " << extStats.dataSize << " cache "
+ << extStats.cacheSize;
#endif
+ ATRACE_END();
ATRACE_BEGIN("dalvik");
calculate_tree_size(create_data_dalvik_cache_path(), &stats.codeSize);
@@ -1390,6 +1589,18 @@
return ok();
}
+binder::Status InstalldNativeService::setAppQuota(const std::unique_ptr<std::string>& uuid,
+ int32_t userId, int32_t appId, int64_t cacheQuota) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_UUID(uuid);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ int32_t uid = multiuser_get_uid(userId, appId);
+ mCacheQuotas[uid] = cacheQuota;
+
+ return ok();
+}
+
// Dumps the contents of a profile file, using pkgname's dex files for pretty
// printing the result.
binder::Status InstalldNativeService::dumpProfiles(int32_t uid, const std::string& packageName,
diff --git a/cmds/installd/InstalldNativeService.h b/cmds/installd/InstalldNativeService.h
index 0208fb1..0a9f12f 100644
--- a/cmds/installd/InstalldNativeService.h
+++ b/cmds/installd/InstalldNativeService.h
@@ -24,6 +24,7 @@
#include <vector>
#include <unordered_map>
+#include <android-base/macros.h>
#include <binder/BinderService.h>
#include <cutils/multiuser.h>
@@ -67,6 +68,9 @@
binder::Status getExternalSize(const std::unique_ptr<std::string>& uuid,
int32_t userId, int32_t flags, std::vector<int64_t>* _aidl_return);
+ binder::Status setAppQuota(const std::unique_ptr<std::string>& uuid,
+ int32_t userId, int32_t appId, int64_t cacheQuota);
+
binder::Status moveCompleteApp(const std::unique_ptr<std::string>& fromUuid,
const std::unique_ptr<std::string>& toUuid, const std::string& packageName,
const std::string& dataAppName, int32_t appId, const std::string& seInfo,
@@ -90,7 +94,8 @@
int32_t uid);
binder::Status rmPackageDir(const std::string& packageDir);
binder::Status markBootComplete(const std::string& instructionSet);
- binder::Status freeCache(const std::unique_ptr<std::string>& uuid, int64_t freeStorageSize);
+ binder::Status freeCache(const std::unique_ptr<std::string>& uuid, int64_t freeStorageSize,
+ int32_t flags);
binder::Status linkNativeLibraryDirectory(const std::unique_ptr<std::string>& uuid,
const std::string& packageName, const std::string& nativeLibPath32, int32_t userId);
binder::Status createOatDir(const std::string& oatDir, const std::string& instructionSet);
@@ -108,6 +113,8 @@
/* Map from mount point to underlying device node */
std::unordered_map<std::string, std::string> mQuotaDevices;
+ /* Map from UID to cache quota size */
+ std::unordered_map<uid_t, int64_t> mCacheQuotas;
std::string findQuotaDeviceForUuid(const std::unique_ptr<std::string>& uuid);
};
diff --git a/cmds/installd/binder/android/os/IInstalld.aidl b/cmds/installd/binder/android/os/IInstalld.aidl
index 2f12ea9..aa5e4f2 100644
--- a/cmds/installd/binder/android/os/IInstalld.aidl
+++ b/cmds/installd/binder/android/os/IInstalld.aidl
@@ -38,6 +38,8 @@
long[] getUserSize(@nullable @utf8InCpp String uuid, int userId, int flags, in int[] appIds);
long[] getExternalSize(@nullable @utf8InCpp String uuid, int userId, int flags);
+ void setAppQuota(@nullable @utf8InCpp String uuid, int userId, int appId, long cacheQuota);
+
void moveCompleteApp(@nullable @utf8InCpp String fromUuid, @nullable @utf8InCpp String toUuid,
@utf8InCpp String packageName, @utf8InCpp String dataAppName, int appId,
@utf8InCpp String seInfo, int targetSdkVersion);
@@ -58,7 +60,7 @@
void idmap(@utf8InCpp String targetApkPath, @utf8InCpp String overlayApkPath, int uid);
void rmPackageDir(@utf8InCpp String packageDir);
void markBootComplete(@utf8InCpp String instructionSet);
- void freeCache(@nullable @utf8InCpp String uuid, long freeStorageSize);
+ void freeCache(@nullable @utf8InCpp String uuid, long freeStorageSize, int flags);
void linkNativeLibraryDirectory(@nullable @utf8InCpp String uuid,
@utf8InCpp String packageName, @utf8InCpp String nativeLibPath32, int userId);
void createOatDir(@utf8InCpp String oatDir, @utf8InCpp String instructionSet);
diff --git a/cmds/installd/utils.h b/cmds/installd/utils.h
index f2f0cbb..5e396c7 100644
--- a/cmds/installd/utils.h
+++ b/cmds/installd/utils.h
@@ -31,7 +31,6 @@
#include <installd_constants.h>
#define MEASURE_DEBUG 0
-#define MEASURE_EXTERNAL 0
namespace android {
namespace installd {
diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp
index df2526c..46b29ca 100644
--- a/vulkan/libvulkan/driver.cpp
+++ b/vulkan/libvulkan/driver.cpp
@@ -467,6 +467,10 @@
name = VK_ANDROID_NATIVE_BUFFER_EXTENSION_NAME;
ext_bit = ProcHook::ANDROID_native_buffer;
break;
+ case ProcHook::GOOGLE_display_timing:
+ hook_extensions_.set(ext_bit);
+ // return now as these extensions do not require HAL support
+ return;
case ProcHook::EXTENSION_UNKNOWN:
// HAL's extensions
break;
@@ -725,10 +729,12 @@
uint32_t* pPropertyCount,
VkExtensionProperties* pProperties) {
const InstanceData& data = GetData(physicalDevice);
- static const std::array<VkExtensionProperties, 1> loader_extensions = {{
+ static const std::array<VkExtensionProperties, 2> loader_extensions = {{
// WSI extensions
{VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME,
VK_KHR_INCREMENTAL_PRESENT_SPEC_VERSION},
+ {VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME,
+ VK_GOOGLE_DISPLAY_TIMING_SPEC_VERSION},
}};
// enumerate our extensions first
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 8427d90..af56650 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -20,6 +20,7 @@
#include <gui/BufferQueue.h>
#include <sync/sync.h>
#include <utils/StrongPointer.h>
+#include <utils/SortedVector.h>
#include "driver.h"
@@ -105,6 +106,69 @@
}
}
+class TimingInfo {
+ public:
+ TimingInfo()
+ : vals_{0, 0, 0, 0, 0},
+ timestamp_desired_present_time_(0),
+ timestamp_actual_present_time_(0),
+ timestamp_render_complete_time_(0),
+ timestamp_composition_latch_time_(0) {}
+ TimingInfo(const VkPresentTimeGOOGLE* qp)
+ : vals_{qp->presentID, qp->desiredPresentTime, 0, 0, 0},
+ timestamp_desired_present_time_(0),
+ timestamp_actual_present_time_(0),
+ timestamp_render_complete_time_(0),
+ timestamp_composition_latch_time_(0) {}
+ bool ready() {
+ return (timestamp_desired_present_time_ &&
+ timestamp_actual_present_time_ &&
+ timestamp_render_complete_time_ &&
+ timestamp_composition_latch_time_);
+ }
+ void calculate(uint64_t rdur) {
+ vals_.actualPresentTime = timestamp_actual_present_time_;
+ uint64_t margin = (timestamp_composition_latch_time_ -
+ timestamp_render_complete_time_);
+ // Calculate vals_.earliestPresentTime, and potentially adjust
+ // vals_.presentMargin. The initial value of vals_.earliestPresentTime
+ // is vals_.actualPresentTime. If we can subtract rdur (the duration
+ // of a refresh cycle) from vals_.earliestPresentTime (and also from
+ // vals_.presentMargin) and still leave a positive margin, then we can
+ // report to the application that it could have presented earlier than
+ // it did (per the extension specification). If for some reason, we
+ // can do this subtraction repeatedly, we do, since
+ // vals_.earliestPresentTime really is supposed to be the "earliest".
+ uint64_t early_time = vals_.actualPresentTime;
+ while ((margin > rdur) &&
+ ((early_time - rdur) > timestamp_composition_latch_time_)) {
+ early_time -= rdur;
+ margin -= rdur;
+ }
+ vals_.earliestPresentTime = early_time;
+ vals_.presentMargin = margin;
+ }
+ void get_values(VkPastPresentationTimingGOOGLE* values) { *values = vals_; }
+
+ public:
+ VkPastPresentationTimingGOOGLE vals_;
+
+ uint64_t timestamp_desired_present_time_;
+ uint64_t timestamp_actual_present_time_;
+ uint64_t timestamp_render_complete_time_;
+ uint64_t timestamp_composition_latch_time_;
+};
+
+static inline int compare_type(const TimingInfo& lhs, const TimingInfo& rhs) {
+ // TODO(ianelliott): Change this from presentID to the frame ID once
+ // brianderson lands the appropriate patch:
+ if (lhs.vals_.presentID < rhs.vals_.presentID)
+ return -1;
+ if (lhs.vals_.presentID > rhs.vals_.presentID)
+ return 1;
+ return 0;
+}
+
// ----------------------------------------------------------------------------
struct Surface {
@@ -120,11 +184,19 @@
return reinterpret_cast<Surface*>(handle);
}
+// Maximum number of TimingInfo structs to keep per swapchain:
+enum { MAX_TIMING_INFOS = 10 };
+// Minimum number of frames to look for in the past (so we don't cause
+// syncronous requests to Surface Flinger):
+enum { MIN_NUM_FRAMES_AGO = 5 };
+
struct Swapchain {
Swapchain(Surface& surface_, uint32_t num_images_)
: surface(surface_),
num_images(num_images_),
- frame_timestamps_enabled(false) {}
+ frame_timestamps_enabled(false) {
+ timing.clear();
+ }
Surface& surface;
uint32_t num_images;
@@ -141,6 +213,8 @@
int dequeue_fence;
bool dequeued;
} images[android::BufferQueue::NUM_BUFFER_SLOTS];
+
+ android::SortedVector<TimingInfo> timing;
};
VkSwapchainKHR HandleFromSwapchain(Swapchain* swapchain) {
@@ -208,6 +282,112 @@
ReleaseSwapchainImage(device, nullptr, -1, swapchain->images[i]);
}
swapchain->surface.swapchain_handle = VK_NULL_HANDLE;
+ swapchain->timing.clear();
+}
+
+uint32_t get_num_ready_timings(Swapchain& swapchain) {
+ uint32_t num_ready = 0;
+ uint32_t num_timings = static_cast<uint32_t>(swapchain.timing.size());
+ uint32_t frames_ago = num_timings;
+ for (uint32_t i = 0; i < num_timings; i++) {
+ TimingInfo* ti = &swapchain.timing.editItemAt(i);
+ if (ti) {
+ if (ti->ready()) {
+ // This TimingInfo is ready to be reported to the user. Add it
+ // to the num_ready.
+ num_ready++;
+ } else {
+ // This TimingInfo is not yet ready to be reported to the user,
+ // and so we should look for any available timestamps that
+ // might make it ready.
+ int64_t desired_present_time = 0;
+ int64_t render_complete_time = 0;
+ int64_t composition_latch_time = 0;
+ int64_t actual_present_time = 0;
+ for (uint32_t f = MIN_NUM_FRAMES_AGO; f < frames_ago; f++) {
+ // Obtain timestamps:
+ int ret = native_window_get_frame_timestamps(
+ swapchain.surface.window.get(), f,
+ &desired_present_time, &render_complete_time,
+ &composition_latch_time,
+ NULL, //&first_composition_start_time,
+ NULL, //&last_composition_start_time,
+ NULL, //&composition_finish_time,
+ // TODO(ianelliott): Maybe ask if this one is
+ // supported, at startup time (since it may not be
+ // supported):
+ &actual_present_time,
+ NULL, //&display_retire_time,
+ NULL, //&dequeue_ready_time,
+ NULL /*&reads_done_time*/);
+ if (ret) {
+ break;
+ } else if (!ret) {
+ // We obtained at least one valid timestamp. See if it
+ // is for the present represented by this TimingInfo:
+ if (static_cast<uint64_t>(desired_present_time) ==
+ ti->vals_.desiredPresentTime) {
+ // Record the timestamp(s) we received, and then
+ // see if this TimingInfo is ready to be reported
+ // to the user:
+ ti->timestamp_desired_present_time_ =
+ static_cast<uint64_t>(desired_present_time);
+ ti->timestamp_actual_present_time_ =
+ static_cast<uint64_t>(actual_present_time);
+ ti->timestamp_render_complete_time_ =
+ static_cast<uint64_t>(render_complete_time);
+ ti->timestamp_composition_latch_time_ =
+ static_cast<uint64_t>(composition_latch_time);
+
+ if (ti->ready()) {
+ // The TimingInfo has received enough
+ // timestamps, and should now use those
+ // timestamps to calculate the info that should
+ // be reported to the user:
+ //
+ // FIXME: GET ACTUAL VALUE RATHER THAN HARD-CODE
+ // IT:
+ ti->calculate(16666666);
+ num_ready++;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ return num_ready;
+}
+
+// TODO(ianelliott): DEAL WITH RETURN VALUE (e.g. VK_INCOMPLETE)!!!
+void copy_ready_timings(Swapchain& swapchain,
+ uint32_t* count,
+ VkPastPresentationTimingGOOGLE* timings) {
+ uint32_t num_copied = 0;
+ uint32_t num_timings = static_cast<uint32_t>(swapchain.timing.size());
+ if (*count < num_timings) {
+ num_timings = *count;
+ }
+ for (uint32_t i = 0; i < num_timings; i++) {
+ TimingInfo* ti = &swapchain.timing.editItemAt(i);
+ if (ti && ti->ready()) {
+ ti->get_values(&timings[num_copied]);
+ num_copied++;
+ // We only report the values for a given present once, so remove
+ // them from swapchain.timing:
+ //
+ // TODO(ianelliott): SEE WHAT HAPPENS TO THE LOOP WHEN THE
+ // FOLLOWING IS DONE:
+ swapchain.timing.removeAt(i);
+ i--;
+ num_timings--;
+ if (*count == num_copied) {
+ break;
+ }
+ }
+ }
+ *count = num_copied;
}
} // anonymous namespace
@@ -1028,16 +1208,31 @@
}
if (time) {
if (!swapchain.frame_timestamps_enabled) {
+ ALOGV(
+ "Calling "
+ "native_window_enable_frame_timestamps(true)");
native_window_enable_frame_timestamps(window, true);
swapchain.frame_timestamps_enabled = true;
}
- // TODO(ianelliott): need to store the presentID (and
- // desiredPresentTime), so it can be later correlated to
- // this present. Probably modify the following function
- // (and below) to plumb a path to store it in FrameEvents
- // code, on the producer side.
- native_window_set_buffers_timestamp(
- window, static_cast<int64_t>(time->desiredPresentTime));
+ // Record this presentID and desiredPresentTime so it can
+ // be later correlated to this present.
+ TimingInfo timing_record(time);
+ swapchain.timing.add(timing_record);
+ uint32_t num_timings =
+ static_cast<uint32_t>(swapchain.timing.size());
+ if (num_timings > MAX_TIMING_INFOS) {
+ swapchain.timing.removeAt(0);
+ }
+ if (time->desiredPresentTime) {
+ // Set the desiredPresentTime:
+ ALOGV(
+ "Calling "
+ "native_window_set_buffers_timestamp(%" PRId64 ")",
+ time->desiredPresentTime);
+ native_window_set_buffers_timestamp(
+ window,
+ static_cast<int64_t>(time->desiredPresentTime));
+ }
}
err = window->queueBuffer(window, img.buffer.get(), fence);
// queueBuffer always closes fence, even on error
@@ -1084,8 +1279,8 @@
VkResult result = VK_SUCCESS;
// TODO(ianelliott): FULLY IMPLEMENT THIS FUNCTION!!!
- pDisplayTimingProperties->minRefreshDuration = 16666666666;
- pDisplayTimingProperties->maxRefreshDuration = 16666666666;
+ pDisplayTimingProperties->minRefreshDuration = 16666666;
+ pDisplayTimingProperties->maxRefreshDuration = 16666666;
return result;
}
@@ -1101,15 +1296,16 @@
VkResult result = VK_SUCCESS;
if (!swapchain.frame_timestamps_enabled) {
+ ALOGV("Calling native_window_enable_frame_timestamps(true)");
native_window_enable_frame_timestamps(window, true);
swapchain.frame_timestamps_enabled = true;
}
- // TODO(ianelliott): FULLY IMPLEMENT THIS FUNCTION!!!
if (timings) {
- *count = 0;
+ // TODO(ianelliott): plumb return value (e.g. VK_INCOMPLETE)
+ copy_ready_timings(swapchain, count, timings);
} else {
- *count = 0;
+ *count = get_num_ready_timings(swapchain);
}
return result;