Frameworks/base: Add compiler stats to Package Manager
Add a simple class for storing compiler statistics. Capture compile
times for code paths from a package.
Bug: 29223204
Change-Id: I1b066de6a83a739470a42480eee0bfef88423eea
diff --git a/services/core/java/com/android/server/pm/AbstractStatsBase.java b/services/core/java/com/android/server/pm/AbstractStatsBase.java
new file mode 100644
index 0000000..612c476
--- /dev/null
+++ b/services/core/java/com/android/server/pm/AbstractStatsBase.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package com.android.server.pm;
+
+import android.os.Environment;
+import android.os.SystemClock;
+import android.util.AtomicFile;
+
+import java.io.File;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * A simple base class for statistics that need to be saved/restored from a dedicated file. This
+ * class provides a base implementation that:
+ * <ul>
+ * <li>Provide an AtomicFile to the actual read/write code
+ * <li>A background-thread write and a synchronous write
+ * <li>Write-limiting for the background-thread (by default writes are at least 30 minutes apart)
+ * <li>Can lock on the provided data object before writing
+ * </ul>
+ * For completion, a subclass needs to implement actual {@link #writeInternal(Object) writing} and
+ * {@link #readInternal(Object) reading}.
+ */
+public abstract class AbstractStatsBase<T> {
+
+ private static final int WRITE_INTERVAL_MS =
+ (PackageManagerService.DEBUG_DEXOPT) ? 0 : 30*60*1000;
+ private final Object mFileLock = new Object();
+ private final AtomicLong mLastTimeWritten = new AtomicLong(0);
+ private final AtomicBoolean mBackgroundWriteRunning = new AtomicBoolean(false);
+ private final String mFileName;
+ private final String mBackgroundThreadName;
+ private final boolean mLock;
+
+ protected AbstractStatsBase(String fileName, String threadName, boolean lock) {
+ mFileName = fileName;
+ mBackgroundThreadName = threadName;
+ mLock = lock;
+ }
+
+ protected AtomicFile getFile() {
+ File dataDir = Environment.getDataDirectory();
+ File systemDir = new File(dataDir, "system");
+ File fname = new File(systemDir, mFileName);
+ return new AtomicFile(fname);
+ }
+
+ void writeNow(final T data) {
+ writeImpl(data);
+ mLastTimeWritten.set(SystemClock.elapsedRealtime());
+ }
+
+ boolean maybeWriteAsync(final T data) {
+ if (SystemClock.elapsedRealtime() - mLastTimeWritten.get() < WRITE_INTERVAL_MS
+ && !PackageManagerService.DEBUG_DEXOPT) {
+ return false;
+ }
+
+ if (mBackgroundWriteRunning.compareAndSet(false, true)) {
+ new Thread(mBackgroundThreadName) {
+ @Override
+ public void run() {
+ try {
+ writeImpl(data);
+ mLastTimeWritten.set(SystemClock.elapsedRealtime());
+ } finally {
+ mBackgroundWriteRunning.set(false);
+ }
+ }
+ }.start();
+ return true;
+ }
+
+ return false;
+ }
+
+ private void writeImpl(T data) {
+ if (mLock) {
+ synchronized (data) {
+ synchronized (mFileLock) {
+ writeInternal(data);
+ }
+ }
+ } else {
+ synchronized (mFileLock) {
+ writeInternal(data);
+ }
+ }
+ }
+
+ protected abstract void writeInternal(T data);
+
+ void read(T data) {
+ if (mLock) {
+ synchronized (data) {
+ synchronized (mFileLock) {
+ readInternal(data);
+ }
+ }
+ } else {
+ synchronized (mFileLock) {
+ readInternal(data);
+ }
+ }
+ // We use the current time as last-written. read() is called on system server startup
+ // (current situation), and we want to postpone I/O at boot.
+ mLastTimeWritten.set(SystemClock.elapsedRealtime());
+ }
+
+ protected abstract void readInternal(T data);
+}
diff --git a/services/core/java/com/android/server/pm/CompilerStats.java b/services/core/java/com/android/server/pm/CompilerStats.java
new file mode 100644
index 0000000..46efd98
--- /dev/null
+++ b/services/core/java/com/android/server/pm/CompilerStats.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package com.android.server.pm;
+
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Log;
+
+import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.IndentingPrintWriter;
+
+import libcore.io.IoUtils;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A class that collects, serializes and deserializes compiler-related statistics on a
+ * per-package per-code-path basis.
+ *
+ * Currently used to track compile times.
+ */
+class CompilerStats extends AbstractStatsBase<Void> {
+
+ private final static String COMPILER_STATS_VERSION_HEADER = "PACKAGE_MANAGER__COMPILER_STATS__";
+ private final static int COMPILER_STATS_VERSION = 1;
+
+ /**
+ * Class to collect all stats pertaining to one package.
+ */
+ static class PackageStats {
+
+ private final String packageName;
+
+ /**
+ * This map stores compile-times for all code paths in the package. The value
+ * is in milliseconds.
+ */
+ private final Map<String, Long> compileTimePerCodePath;
+
+ /**
+ * @param packageName
+ */
+ public PackageStats(String packageName) {
+ this.packageName = packageName;
+ // We expect at least one element in here, but let's make it minimal.
+ compileTimePerCodePath = new ArrayMap<>(2);
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ /**
+ * Return the recorded compile time for a given code path. Returns
+ * 0 if there is no recorded time.
+ */
+ public long getCompileTime(String codePath) {
+ String storagePath = getStoredPathFromCodePath(codePath);
+ synchronized (compileTimePerCodePath) {
+ Long l = compileTimePerCodePath.get(storagePath);
+ if (l == null) {
+ return 0;
+ }
+ return l;
+ }
+ }
+
+ public void setCompileTime(String codePath, long compileTimeInMs) {
+ String storagePath = getStoredPathFromCodePath(codePath);
+ synchronized (compileTimePerCodePath) {
+ if (compileTimeInMs <= 0) {
+ compileTimePerCodePath.remove(storagePath);
+ } else {
+ compileTimePerCodePath.put(storagePath, compileTimeInMs);
+ }
+ }
+ }
+
+ private static String getStoredPathFromCodePath(String codePath) {
+ int lastSlash = codePath.lastIndexOf(File.separatorChar);
+ return codePath.substring(lastSlash + 1);
+ }
+
+ public void dump(IndentingPrintWriter ipw) {
+ synchronized (compileTimePerCodePath) {
+ if (compileTimePerCodePath.size() == 0) {
+ ipw.println("(No recorded stats)");
+ } else {
+ for (Map.Entry<String, Long> e : compileTimePerCodePath.entrySet()) {
+ ipw.println(" " + e.getKey() + " - " + e.getValue());
+ }
+ }
+ }
+ }
+ }
+
+ private final Map<String, PackageStats> packageStats;
+
+ public CompilerStats() {
+ super("package-cstats.list", "CompilerStats_DiskWriter", /* lock */ false);
+ packageStats = new HashMap<>();
+ }
+
+ public PackageStats getPackageStats(String packageName) {
+ synchronized (packageStats) {
+ return packageStats.get(packageName);
+ }
+ }
+
+ public void setPackageStats(String packageName, PackageStats stats) {
+ synchronized (packageStats) {
+ packageStats.put(packageName, stats);
+ }
+ }
+
+ public PackageStats createPackageStats(String packageName) {
+ synchronized (packageStats) {
+ PackageStats newStats = new PackageStats(packageName);
+ packageStats.put(packageName, newStats);
+ return newStats;
+ }
+ }
+
+ public PackageStats getOrCreatePackageStats(String packageName) {
+ synchronized (packageStats) {
+ PackageStats existingStats = packageStats.get(packageName);
+ if (existingStats != null) {
+ return existingStats;
+ }
+
+ return createPackageStats(packageName);
+ }
+ }
+
+ public void deletePackageStats(String packageName) {
+ synchronized (packageStats) {
+ packageStats.remove(packageName);
+ }
+ }
+
+ // I/O
+
+ // The encoding is simple:
+ //
+ // 1) The first line is a line consisting of the version header and the version number.
+ //
+ // 2) The rest of the file is package data.
+ // 2.1) A package is started by any line not starting with "-";
+ // 2.2) Any line starting with "-" is code path data. The format is:
+ // '-'{code-path}':'{compile-time}
+
+ public void write(Writer out) {
+ @SuppressWarnings("resource")
+ FastPrintWriter fpw = new FastPrintWriter(out);
+
+ fpw.print(COMPILER_STATS_VERSION_HEADER);
+ fpw.println(COMPILER_STATS_VERSION);
+
+ synchronized (packageStats) {
+ for (PackageStats pkg : packageStats.values()) {
+ synchronized (pkg.compileTimePerCodePath) {
+ if (!pkg.compileTimePerCodePath.isEmpty()) {
+ fpw.println(pkg.getPackageName());
+
+ for (Map.Entry<String, Long> e : pkg.compileTimePerCodePath.entrySet()) {
+ fpw.println("-" + e.getKey() + ":" + e.getValue());
+ }
+ }
+ }
+ }
+ }
+
+ fpw.flush();
+ }
+
+ public boolean read(Reader r) {
+ synchronized (packageStats) {
+ // TODO: Could make this a final switch, then we wouldn't have to synchronize over
+ // the whole reading.
+ packageStats.clear();
+
+ try {
+ BufferedReader in = new BufferedReader(r);
+
+ // Read header, do version check.
+ String versionLine = in.readLine();
+ if (versionLine == null) {
+ throw new IllegalArgumentException("No version line found.");
+ } else {
+ if (!versionLine.startsWith(COMPILER_STATS_VERSION_HEADER)) {
+ throw new IllegalArgumentException("Invalid version line: " + versionLine);
+ }
+ int version = Integer.parseInt(
+ versionLine.substring(COMPILER_STATS_VERSION_HEADER.length()));
+ if (version != COMPILER_STATS_VERSION) {
+ // TODO: Upgrade older formats? For now, just reject and regenerate.
+ throw new IllegalArgumentException("Unexpected version: " + version);
+ }
+ }
+
+ // For simpler code, we ignore any data lines before the first package. We
+ // collect it in a fake package.
+ PackageStats currentPackage = new PackageStats("fake package");
+
+ String s = null;
+ while ((s = in.readLine()) != null) {
+ if (s.startsWith("-")) {
+ int colonIndex = s.indexOf(':');
+ if (colonIndex == -1 || colonIndex == 1) {
+ throw new IllegalArgumentException("Could not parse data " + s);
+ }
+ String codePath = s.substring(1, colonIndex);
+ long time = Long.parseLong(s.substring(colonIndex + 1));
+ currentPackage.setCompileTime(codePath, time);
+ } else {
+ currentPackage = getOrCreatePackageStats(s);
+ }
+ }
+ } catch (Exception e) {
+ Log.e(PackageManagerService.TAG, "Error parsing compiler stats", e);
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ void writeNow() {
+ writeNow(null);
+ }
+
+ boolean maybeWriteAsync() {
+ return maybeWriteAsync(null);
+ }
+
+ @Override
+ protected void writeInternal(Void data) {
+ AtomicFile file = getFile();
+ FileOutputStream f = null;
+
+ try {
+ f = file.startWrite();
+ OutputStreamWriter osw = new OutputStreamWriter(f);
+ osw.flush();
+ file.finishWrite(f);
+ } catch (IOException e) {
+ if (f != null) {
+ file.failWrite(f);
+ }
+ Log.e(PackageManagerService.TAG, "Failed to write compiler stats", e);
+ }
+ }
+
+ void read() {
+ read((Void)null);
+ }
+
+ @Override
+ protected void readInternal(Void data) {
+ AtomicFile file = getFile();
+ BufferedReader in = null;
+ try {
+ in = new BufferedReader(new InputStreamReader(file.openRead()));
+ read(in);
+ } catch (FileNotFoundException expected) {
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 02c6472..77c69c9 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -225,7 +225,8 @@
optimizer.performDexOpt(nextPackage, nextPackage.usesLibraryFiles,
null /* ISAs */, false /* checkProfiles */,
- getCompilerFilterForReason(compilationReason));
+ getCompilerFilterForReason(compilationReason),
+ null /* CompilerStats.PackageStats */);
mCommandsForCurrentPackage = collectingConnection.commands;
if (mCommandsForCurrentPackage.isEmpty()) {
@@ -271,7 +272,8 @@
mPackageManagerService.mInstaller, mPackageManagerService.mInstallLock, mContext);
optimizer.performDexOpt(nextPackage, nextPackage.usesLibraryFiles, null /* ISAs */,
false /* checkProfiles */,
- getCompilerFilterForReason(PackageManagerService.REASON_AB_OTA));
+ getCompilerFilterForReason(PackageManagerService.REASON_AB_OTA),
+ mPackageManagerService.getOrCreateCompilerPackageStats(nextPackage));
}
private void moveAbArtifacts(Installer installer) {
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 26a840d..19b1201 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -90,7 +90,8 @@
* synchronized on {@link #mInstallLock}.
*/
int performDexOpt(PackageParser.Package pkg, String[] sharedLibraries,
- String[] instructionSets, boolean checkProfiles, String targetCompilationFilter) {
+ String[] instructionSets, boolean checkProfiles, String targetCompilationFilter,
+ CompilerStats.PackageStats packageStats) {
synchronized (mInstallLock) {
final boolean useLock = mSystemReady;
if (useLock) {
@@ -99,7 +100,7 @@
}
try {
return performDexOptLI(pkg, sharedLibraries, instructionSets, checkProfiles,
- targetCompilationFilter);
+ targetCompilationFilter, packageStats);
} finally {
if (useLock) {
mDexoptWakeLock.release();
@@ -150,7 +151,8 @@
}
private int performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries,
- String[] targetInstructionSets, boolean checkProfiles, String targetCompilerFilter) {
+ String[] targetInstructionSets, boolean checkProfiles, String targetCompilerFilter,
+ CompilerStats.PackageStats packageStats) {
final String[] instructionSets = targetInstructionSets != null ?
targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
@@ -254,10 +256,17 @@
| DEXOPT_BOOTCOMPLETE);
try {
+ long startTime = System.currentTimeMillis();
+
mInstaller.dexopt(path, sharedGid, pkg.packageName, dexCodeInstructionSet,
dexoptNeeded, oatDir, dexFlags, targetCompilerFilter, pkg.volumeUuid,
sharedLibrariesPath);
performedDexOpt = true;
+
+ if (packageStats != null) {
+ long endTime = System.currentTimeMillis();
+ packageStats.setCompileTime(path, (int)(endTime - startTime));
+ }
} catch (InstallerException e) {
Slog.w(TAG, "Failed to dexopt", e);
successfulDexOpt = false;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 78fa3a3..833dca4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -76,8 +76,6 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.pm.PackageParser.PARSE_IS_PRIVILEGED;
import static android.content.pm.PackageParser.isApkFile;
-import static android.os.Process.PACKAGE_INFO_GID;
-import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDWR;
@@ -208,7 +206,6 @@
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.AtomicFile;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.ExceptionUtils;
@@ -267,7 +264,6 @@
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
-import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
@@ -280,7 +276,6 @@
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
-import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.security.DigestInputStream;
@@ -307,7 +302,6 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
/**
* Keep track of all those APKs everywhere.
@@ -1131,204 +1125,7 @@
final @NonNull String mSharedSystemSharedLibraryPackageName;
private final PackageUsage mPackageUsage = new PackageUsage();
-
- private class PackageUsage {
- private static final int WRITE_INTERVAL
- = (DEBUG_DEXOPT) ? 0 : 30*60*1000; // 30m in ms
-
- private final Object mFileLock = new Object();
- private final AtomicLong mLastWritten = new AtomicLong(0);
- private final AtomicBoolean mBackgroundWriteRunning = new AtomicBoolean(false);
-
- private boolean mIsHistoricalPackageUsageAvailable = true;
-
- boolean isHistoricalPackageUsageAvailable() {
- return mIsHistoricalPackageUsageAvailable;
- }
-
- void write(boolean force) {
- if (force) {
- writeInternal();
- return;
- }
- if (SystemClock.elapsedRealtime() - mLastWritten.get() < WRITE_INTERVAL
- && !DEBUG_DEXOPT) {
- return;
- }
- if (mBackgroundWriteRunning.compareAndSet(false, true)) {
- new Thread("PackageUsage_DiskWriter") {
- @Override
- public void run() {
- try {
- writeInternal();
- } finally {
- mBackgroundWriteRunning.set(false);
- }
- }
- }.start();
- }
- }
-
- private void writeInternal() {
- synchronized (mPackages) {
- synchronized (mFileLock) {
- AtomicFile file = getFile();
- FileOutputStream f = null;
- try {
- f = file.startWrite();
- BufferedOutputStream out = new BufferedOutputStream(f);
- FileUtils.setPermissions(file.getBaseFile().getPath(),
- 0640, SYSTEM_UID, PACKAGE_INFO_GID);
- StringBuilder sb = new StringBuilder();
-
- sb.append(USAGE_FILE_MAGIC_VERSION_1);
- sb.append('\n');
- out.write(sb.toString().getBytes(StandardCharsets.US_ASCII));
-
- for (PackageParser.Package pkg : mPackages.values()) {
- if (pkg.getLatestPackageUseTimeInMills() == 0L) {
- continue;
- }
- sb.setLength(0);
- sb.append(pkg.packageName);
- for (long usageTimeInMillis : pkg.mLastPackageUsageTimeInMills) {
- sb.append(' ');
- sb.append(usageTimeInMillis);
- }
- sb.append('\n');
- out.write(sb.toString().getBytes(StandardCharsets.US_ASCII));
- }
- out.flush();
- file.finishWrite(f);
- } catch (IOException e) {
- if (f != null) {
- file.failWrite(f);
- }
- Log.e(TAG, "Failed to write package usage times", e);
- }
- }
- }
- mLastWritten.set(SystemClock.elapsedRealtime());
- }
-
- void readLP() {
- synchronized (mFileLock) {
- AtomicFile file = getFile();
- BufferedInputStream in = null;
- try {
- in = new BufferedInputStream(file.openRead());
- StringBuffer sb = new StringBuffer();
-
- String firstLine = readLine(in, sb);
- if (firstLine == null) {
- // Empty file. Do nothing.
- } else if (USAGE_FILE_MAGIC_VERSION_1.equals(firstLine)) {
- readVersion1LP(in, sb);
- } else {
- readVersion0LP(in, sb, firstLine);
- }
- } catch (FileNotFoundException expected) {
- mIsHistoricalPackageUsageAvailable = false;
- } catch (IOException e) {
- Log.w(TAG, "Failed to read package usage times", e);
- } finally {
- IoUtils.closeQuietly(in);
- }
- }
- mLastWritten.set(SystemClock.elapsedRealtime());
- }
-
- private void readVersion0LP(InputStream in, StringBuffer sb, String firstLine)
- throws IOException {
- // Initial version of the file had no version number and stored one
- // package-timestamp pair per line.
- // Note that the first line has already been read from the InputStream.
- for (String line = firstLine; line != null; line = readLine(in, sb)) {
- String[] tokens = line.split(" ");
- if (tokens.length != 2) {
- throw new IOException("Failed to parse " + line +
- " as package-timestamp pair.");
- }
-
- String packageName = tokens[0];
- PackageParser.Package pkg = mPackages.get(packageName);
- if (pkg == null) {
- continue;
- }
-
- long timestamp = parseAsLong(tokens[1]);
- for (int reason = 0;
- reason < PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT;
- reason++) {
- pkg.mLastPackageUsageTimeInMills[reason] = timestamp;
- }
- }
- }
-
- private void readVersion1LP(InputStream in, StringBuffer sb) throws IOException {
- // Version 1 of the file started with the corresponding version
- // number and then stored a package name and eight timestamps per line.
- String line;
- while ((line = readLine(in, sb)) != null) {
- String[] tokens = line.split(" ");
- if (tokens.length != PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT + 1) {
- throw new IOException("Failed to parse " + line + " as a timestamp array.");
- }
-
- String packageName = tokens[0];
- PackageParser.Package pkg = mPackages.get(packageName);
- if (pkg == null) {
- continue;
- }
-
- for (int reason = 0;
- reason < PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT;
- reason++) {
- pkg.mLastPackageUsageTimeInMills[reason] = parseAsLong(tokens[reason + 1]);
- }
- }
- }
-
- private long parseAsLong(String token) throws IOException {
- try {
- return Long.parseLong(token);
- } catch (NumberFormatException e) {
- throw new IOException("Failed to parse " + token + " as a long.", e);
- }
- }
-
- private String readLine(InputStream in, StringBuffer sb) throws IOException {
- return readToken(in, sb, '\n');
- }
-
- private String readToken(InputStream in, StringBuffer sb, char endOfToken)
- throws IOException {
- sb.setLength(0);
- while (true) {
- int ch = in.read();
- if (ch == -1) {
- if (sb.length() == 0) {
- return null;
- }
- throw new IOException("Unexpected EOF");
- }
- if (ch == endOfToken) {
- return sb.toString();
- }
- sb.append((char)ch);
- }
- }
-
- private AtomicFile getFile() {
- File dataDir = Environment.getDataDirectory();
- File systemDir = new File(dataDir, "system");
- File fname = new File(systemDir, "package-usage.list");
- return new AtomicFile(fname);
- }
-
- private static final String USAGE_FILE_MAGIC = "PACKAGE_USAGE__VERSION_";
- private static final String USAGE_FILE_MAGIC_VERSION_1 = USAGE_FILE_MAGIC + "1";
- }
+ private final CompilerStats mCompilerStats = new CompilerStats();
class PackageHandler extends Handler {
private boolean mBound = false;
@@ -2709,7 +2506,8 @@
// Now that we know all the packages we are keeping,
// read and update their last usage times.
- mPackageUsage.readLP();
+ mPackageUsage.read(mPackages);
+ mCompilerStats.read();
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
SystemClock.uptimeMillis());
@@ -7477,7 +7275,8 @@
// Package could not be found. Report failure.
return PackageDexOptimizer.DEX_OPT_FAILED;
}
- mPackageUsage.write(false);
+ mPackageUsage.maybeWriteAsync(mPackages);
+ mCompilerStats.maybeWriteAsync();
}
long callingId = Binder.clearCallingIdentity();
try {
@@ -7522,11 +7321,12 @@
// Currently this will do a full compilation of the library by default.
pdo.performDexOpt(depPackage, null /* sharedLibraries */, instructionSets,
false /* checkProfiles */,
- getCompilerFilterForReason(REASON_NON_SYSTEM_LIBRARY));
+ getCompilerFilterForReason(REASON_NON_SYSTEM_LIBRARY),
+ getOrCreateCompilerPackageStats(depPackage));
}
}
return pdo.performDexOpt(p, p.usesLibraryFiles, instructionSets, checkProfiles,
- targetCompilerFilter);
+ targetCompilerFilter, getOrCreateCompilerPackageStats(p));
}
Collection<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) {
@@ -7580,7 +7380,8 @@
}
public void shutdown() {
- mPackageUsage.write(true);
+ mPackageUsage.writeNow(mPackages);
+ mCompilerStats.writeNow();
}
@Override
@@ -15226,7 +15027,8 @@
// Also, don't fail application installs if the dexopt step fails.
mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles,
null /* instructionSets */, false /* checkProfiles */,
- getCompilerFilterForReason(REASON_INSTALL));
+ getCompilerFilterForReason(REASON_INSTALL),
+ getOrCreateCompilerPackageStats(pkg));
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
// Notify BackgroundDexOptService that the package has been changed.
@@ -18187,6 +17989,7 @@
public static final int DUMP_DOMAIN_PREFERRED = 1 << 18;
public static final int DUMP_FROZEN = 1 << 19;
public static final int DUMP_DEXOPT = 1 << 20;
+ public static final int DUMP_COMPILER_STATS = 1 << 21;
public static final int OPTION_SHOW_FILTERS = 1 << 0;
@@ -18304,6 +18107,7 @@
pw.println(" installs: details about install sessions");
pw.println(" check-permission <permission> <package> [<user>]: does pkg hold perm?");
pw.println(" dexopt: dump dexopt state");
+ pw.println(" compiler-stats: dump compiler statistics");
pw.println(" <package.name>: info about given package");
return;
} else if ("--checkin".equals(opt)) {
@@ -18425,6 +18229,8 @@
dumpState.setDump(DumpState.DUMP_FROZEN);
} else if ("dexopt".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_DEXOPT);
+ } else if ("compiler-stats".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_COMPILER_STATS);
} else if ("write".equals(cmd)) {
synchronized (mPackages) {
mSettings.writeLPr();
@@ -18787,6 +18593,11 @@
dumpDexoptStateLPr(pw, packageName);
}
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_COMPILER_STATS)) {
+ if (dumpState.onTitlePrinted()) pw.println();
+ dumpCompilerStatsLPr(pw, packageName);
+ }
+
if (!checkin && dumpState.isDumping(DumpState.DUMP_MESSAGES) && packageName == null) {
if (dumpState.onTitlePrinted()) pw.println();
mSettings.dumpReadMessagesLPr(pw, dumpState);
@@ -18851,6 +18662,38 @@
}
}
+ private void dumpCompilerStatsLPr(PrintWriter pw, String packageName) {
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
+ ipw.println();
+ ipw.println("Compiler stats:");
+ ipw.increaseIndent();
+ Collection<PackageParser.Package> packages = null;
+ if (packageName != null) {
+ PackageParser.Package targetPackage = mPackages.get(packageName);
+ if (targetPackage != null) {
+ packages = Collections.singletonList(targetPackage);
+ } else {
+ ipw.println("Unable to find package: " + packageName);
+ return;
+ }
+ } else {
+ packages = mPackages.values();
+ }
+
+ for (PackageParser.Package pkg : packages) {
+ ipw.println("[" + pkg.packageName + "]");
+ ipw.increaseIndent();
+
+ CompilerStats.PackageStats stats = getCompilerPackageStats(pkg.packageName);
+ if (stats == null) {
+ ipw.println("(No recorded stats)");
+ } else {
+ stats.dump(ipw);
+ }
+ ipw.decreaseIndent();
+ }
+ }
+
private String dumpDomainString(String packageName) {
List<IntentFilterVerificationInfo> iviList = getIntentFilterVerifications(packageName)
.getList();
@@ -21012,4 +20855,20 @@
msg.setData(data);
mProcessLoggingHandler.sendMessage(msg);
}
+
+ public CompilerStats.PackageStats getCompilerPackageStats(String pkgName) {
+ return mCompilerStats.getPackageStats(pkgName);
+ }
+
+ public CompilerStats.PackageStats getOrCreateCompilerPackageStats(PackageParser.Package pkg) {
+ return getOrCreateCompilerPackageStats(pkg.packageName);
+ }
+
+ public CompilerStats.PackageStats getOrCreateCompilerPackageStats(String pkgName) {
+ return mCompilerStats.getOrCreatePackageStats(pkgName);
+ }
+
+ public void deleteCompilerPackageStats(String pkgName) {
+ mCompilerStats.deletePackageStats(pkgName);
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageUsage.java b/services/core/java/com/android/server/pm/PackageUsage.java
new file mode 100644
index 0000000..ac1f739
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageUsage.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package com.android.server.pm;
+
+import static android.os.Process.PACKAGE_INFO_GID;
+import static android.os.Process.SYSTEM_UID;
+
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.os.FileUtils;
+import android.util.AtomicFile;
+import android.util.Log;
+
+import libcore.io.IoUtils;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+class PackageUsage extends AbstractStatsBase<Map<String, PackageParser.Package>> {
+
+ private static final String USAGE_FILE_MAGIC = "PACKAGE_USAGE__VERSION_";
+ private static final String USAGE_FILE_MAGIC_VERSION_1 = USAGE_FILE_MAGIC + "1";
+
+ private boolean mIsHistoricalPackageUsageAvailable = true;
+
+ PackageUsage() {
+ super("package-usage.list", "PackageUsage_DiskWriter", /* lock */ true);
+ }
+
+ boolean isHistoricalPackageUsageAvailable() {
+ return mIsHistoricalPackageUsageAvailable;
+ }
+
+ @Override
+ protected void writeInternal(Map<String, PackageParser.Package> packages) {
+ AtomicFile file = getFile();
+ FileOutputStream f = null;
+ try {
+ f = file.startWrite();
+ BufferedOutputStream out = new BufferedOutputStream(f);
+ FileUtils.setPermissions(file.getBaseFile().getPath(),
+ 0640, SYSTEM_UID, PACKAGE_INFO_GID);
+ StringBuilder sb = new StringBuilder();
+
+ sb.append(USAGE_FILE_MAGIC_VERSION_1);
+ sb.append('\n');
+ out.write(sb.toString().getBytes(StandardCharsets.US_ASCII));
+
+ for (PackageParser.Package pkg : packages.values()) {
+ if (pkg.getLatestPackageUseTimeInMills() == 0L) {
+ continue;
+ }
+ sb.setLength(0);
+ sb.append(pkg.packageName);
+ for (long usageTimeInMillis : pkg.mLastPackageUsageTimeInMills) {
+ sb.append(' ');
+ sb.append(usageTimeInMillis);
+ }
+ sb.append('\n');
+ out.write(sb.toString().getBytes(StandardCharsets.US_ASCII));
+ }
+ out.flush();
+ file.finishWrite(f);
+ } catch (IOException e) {
+ if (f != null) {
+ file.failWrite(f);
+ }
+ Log.e(PackageManagerService.TAG, "Failed to write package usage times", e);
+ }
+ }
+
+ @Override
+ protected void readInternal(Map<String, PackageParser.Package> packages) {
+ AtomicFile file = getFile();
+ BufferedInputStream in = null;
+ try {
+ in = new BufferedInputStream(file.openRead());
+ StringBuffer sb = new StringBuffer();
+
+ String firstLine = readLine(in, sb);
+ if (firstLine == null) {
+ // Empty file. Do nothing.
+ } else if (USAGE_FILE_MAGIC_VERSION_1.equals(firstLine)) {
+ readVersion1LP(packages, in, sb);
+ } else {
+ readVersion0LP(packages, in, sb, firstLine);
+ }
+ } catch (FileNotFoundException expected) {
+ mIsHistoricalPackageUsageAvailable = false;
+ } catch (IOException e) {
+ Log.w(PackageManagerService.TAG, "Failed to read package usage times", e);
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ }
+
+ private void readVersion0LP(Map<String, PackageParser.Package> packages, InputStream in,
+ StringBuffer sb, String firstLine)
+ throws IOException {
+ // Initial version of the file had no version number and stored one
+ // package-timestamp pair per line.
+ // Note that the first line has already been read from the InputStream.
+ for (String line = firstLine; line != null; line = readLine(in, sb)) {
+ String[] tokens = line.split(" ");
+ if (tokens.length != 2) {
+ throw new IOException("Failed to parse " + line +
+ " as package-timestamp pair.");
+ }
+
+ String packageName = tokens[0];
+ PackageParser.Package pkg = packages.get(packageName);
+ if (pkg == null) {
+ continue;
+ }
+
+ long timestamp = parseAsLong(tokens[1]);
+ for (int reason = 0;
+ reason < PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT;
+ reason++) {
+ pkg.mLastPackageUsageTimeInMills[reason] = timestamp;
+ }
+ }
+ }
+
+ private void readVersion1LP(Map<String, PackageParser.Package> packages, InputStream in,
+ StringBuffer sb) throws IOException {
+ // Version 1 of the file started with the corresponding version
+ // number and then stored a package name and eight timestamps per line.
+ String line;
+ while ((line = readLine(in, sb)) != null) {
+ String[] tokens = line.split(" ");
+ if (tokens.length != PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT + 1) {
+ throw new IOException("Failed to parse " + line + " as a timestamp array.");
+ }
+
+ String packageName = tokens[0];
+ PackageParser.Package pkg = packages.get(packageName);
+ if (pkg == null) {
+ continue;
+ }
+
+ for (int reason = 0;
+ reason < PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT;
+ reason++) {
+ pkg.mLastPackageUsageTimeInMills[reason] = parseAsLong(tokens[reason + 1]);
+ }
+ }
+ }
+
+ private long parseAsLong(String token) throws IOException {
+ try {
+ return Long.parseLong(token);
+ } catch (NumberFormatException e) {
+ throw new IOException("Failed to parse " + token + " as a long.", e);
+ }
+ }
+
+ private String readLine(InputStream in, StringBuffer sb) throws IOException {
+ return readToken(in, sb, '\n');
+ }
+
+ private String readToken(InputStream in, StringBuffer sb, char endOfToken)
+ throws IOException {
+ sb.setLength(0);
+ while (true) {
+ int ch = in.read();
+ if (ch == -1) {
+ if (sb.length() == 0) {
+ return null;
+ }
+ throw new IOException("Unexpected EOF");
+ }
+ if (ch == endOfToken) {
+ return sb.toString();
+ }
+ sb.append((char)ch);
+ }
+ }
+}
\ No newline at end of file