diff options
| author | 2016-07-15 22:20:37 +0000 | |
|---|---|---|
| committer | 2016-07-15 22:20:38 +0000 | |
| commit | f52ce76d1fdb83ce839adc87847c2486b3fb5975 (patch) | |
| tree | 1f54b9303633b6fd955f262f19317a60826bc6f5 | |
| parent | 095979b46366fcb176215e448bcb59074fa97c0e (diff) | |
| parent | 37e5fdc6b4963f3533caecdd92b129f79da69dd8 (diff) | |
Merge "Frameworks/base: Add compiler stats to Package Manager" into nyc-mr1-dev
6 files changed, 704 insertions, 215 deletions
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 000000000000..612c4767ccaa --- /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 000000000000..46efd98fdc6b --- /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 02c6472b2f88..77c69c905036 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 @@ 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) { diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 26a840da094b..19b120123b26 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 @@ 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; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 78fa3a37dcf5..833dca4c12f3 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_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; @@ -208,7 +206,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; @@ -267,7 +264,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; @@ -280,7 +276,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; @@ -307,7 +302,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. @@ -1131,204 +1125,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; @@ -2709,7 +2506,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()); @@ -7477,7 +7275,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 { @@ -7522,11 +7321,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) { @@ -7580,7 +7380,8 @@ public class PackageManagerService extends IPackageManager.Stub { } public void shutdown() { - mPackageUsage.write(true); + mPackageUsage.writeNow(mPackages); + mCompilerStats.writeNow(); } @Override @@ -15226,7 +15027,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. @@ -18187,6 +17989,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; @@ -18304,6 +18107,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)) { @@ -18425,6 +18229,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(); @@ -18787,6 +18593,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); @@ -18851,6 +18662,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(); @@ -21012,4 +20855,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); + } } 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 000000000000..ac1f739cd9f8 --- /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 |