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
(cherry picked from commit 37e5fdc6b4)
This commit is contained in:
Andreas Gampe
2016-07-12 22:42:41 -07:00
parent 08313b0d95
commit 7e619a912c
6 changed files with 704 additions and 215 deletions

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -225,7 +225,8 @@ public class OtaDexoptService extends IOtaDexopt.Stub {
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 @@ public class OtaDexoptService extends IOtaDexopt.Stub {
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) {

View File

@@ -90,7 +90,8 @@ class PackageDexOptimizer {
* 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 @@ class PackageDexOptimizer {
}
try {
return performDexOptLI(pkg, sharedLibraries, instructionSets, checkProfiles,
targetCompilationFilter);
targetCompilationFilter, packageStats);
} finally {
if (useLock) {
mDexoptWakeLock.release();
@@ -150,7 +151,8 @@ class PackageDexOptimizer {
}
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 @@ class PackageDexOptimizer {
| 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;

View File

@@ -76,8 +76,6 @@ import static android.content.pm.PackageManager.PERMISSION_DENIED;
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;
@@ -206,7 +204,6 @@ import android.text.TextUtils;
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;
@@ -265,7 +262,6 @@ import org.xmlpull.v1.XmlPullParser;
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;
@@ -278,7 +274,6 @@ import java.io.FileOutputStream;
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;
@@ -305,7 +300,6 @@ import java.util.concurrent.CountDownLatch;
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.
@@ -1126,204 +1120,7 @@ public class PackageManagerService extends IPackageManager.Stub {
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;
@@ -2702,7 +2499,8 @@ public class PackageManagerService extends IPackageManager.Stub {
// 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());
@@ -7453,7 +7251,8 @@ public class PackageManagerService extends IPackageManager.Stub {
// 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 {
@@ -7498,11 +7297,12 @@ public class PackageManagerService extends IPackageManager.Stub {
// 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) {
@@ -7556,7 +7356,8 @@ public class PackageManagerService extends IPackageManager.Stub {
}
public void shutdown() {
mPackageUsage.write(true);
mPackageUsage.writeNow(mPackages);
mCompilerStats.writeNow();
}
@Override
@@ -15188,7 +14989,8 @@ public class PackageManagerService extends IPackageManager.Stub {
// 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.
@@ -18147,6 +17949,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
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;
@@ -18264,6 +18067,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
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)) {
@@ -18385,6 +18189,8 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
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();
@@ -18747,6 +18553,11 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
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);
@@ -18811,6 +18622,38 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
}
}
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();
@@ -20978,4 +20821,20 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
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);
}
}

View File

@@ -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);
}
}
}