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:
126
services/core/java/com/android/server/pm/AbstractStatsBase.java
Normal file
126
services/core/java/com/android/server/pm/AbstractStatsBase.java
Normal 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);
|
||||
}
|
||||
294
services/core/java/com/android/server/pm/CompilerStats.java
Normal file
294
services/core/java/com/android/server/pm/CompilerStats.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
199
services/core/java/com/android/server/pm/PackageUsage.java
Normal file
199
services/core/java/com/android/server/pm/PackageUsage.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user