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,
|
optimizer.performDexOpt(nextPackage, nextPackage.usesLibraryFiles,
|
||||||
null /* ISAs */, false /* checkProfiles */,
|
null /* ISAs */, false /* checkProfiles */,
|
||||||
getCompilerFilterForReason(compilationReason));
|
getCompilerFilterForReason(compilationReason),
|
||||||
|
null /* CompilerStats.PackageStats */);
|
||||||
|
|
||||||
mCommandsForCurrentPackage = collectingConnection.commands;
|
mCommandsForCurrentPackage = collectingConnection.commands;
|
||||||
if (mCommandsForCurrentPackage.isEmpty()) {
|
if (mCommandsForCurrentPackage.isEmpty()) {
|
||||||
@@ -271,7 +272,8 @@ public class OtaDexoptService extends IOtaDexopt.Stub {
|
|||||||
mPackageManagerService.mInstaller, mPackageManagerService.mInstallLock, mContext);
|
mPackageManagerService.mInstaller, mPackageManagerService.mInstallLock, mContext);
|
||||||
optimizer.performDexOpt(nextPackage, nextPackage.usesLibraryFiles, null /* ISAs */,
|
optimizer.performDexOpt(nextPackage, nextPackage.usesLibraryFiles, null /* ISAs */,
|
||||||
false /* checkProfiles */,
|
false /* checkProfiles */,
|
||||||
getCompilerFilterForReason(PackageManagerService.REASON_AB_OTA));
|
getCompilerFilterForReason(PackageManagerService.REASON_AB_OTA),
|
||||||
|
mPackageManagerService.getOrCreateCompilerPackageStats(nextPackage));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void moveAbArtifacts(Installer installer) {
|
private void moveAbArtifacts(Installer installer) {
|
||||||
|
|||||||
@@ -90,7 +90,8 @@ class PackageDexOptimizer {
|
|||||||
* synchronized on {@link #mInstallLock}.
|
* synchronized on {@link #mInstallLock}.
|
||||||
*/
|
*/
|
||||||
int performDexOpt(PackageParser.Package pkg, String[] sharedLibraries,
|
int performDexOpt(PackageParser.Package pkg, String[] sharedLibraries,
|
||||||
String[] instructionSets, boolean checkProfiles, String targetCompilationFilter) {
|
String[] instructionSets, boolean checkProfiles, String targetCompilationFilter,
|
||||||
|
CompilerStats.PackageStats packageStats) {
|
||||||
synchronized (mInstallLock) {
|
synchronized (mInstallLock) {
|
||||||
final boolean useLock = mSystemReady;
|
final boolean useLock = mSystemReady;
|
||||||
if (useLock) {
|
if (useLock) {
|
||||||
@@ -99,7 +100,7 @@ class PackageDexOptimizer {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return performDexOptLI(pkg, sharedLibraries, instructionSets, checkProfiles,
|
return performDexOptLI(pkg, sharedLibraries, instructionSets, checkProfiles,
|
||||||
targetCompilationFilter);
|
targetCompilationFilter, packageStats);
|
||||||
} finally {
|
} finally {
|
||||||
if (useLock) {
|
if (useLock) {
|
||||||
mDexoptWakeLock.release();
|
mDexoptWakeLock.release();
|
||||||
@@ -150,7 +151,8 @@ class PackageDexOptimizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries,
|
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 ?
|
final String[] instructionSets = targetInstructionSets != null ?
|
||||||
targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
|
targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
|
||||||
|
|
||||||
@@ -254,10 +256,17 @@ class PackageDexOptimizer {
|
|||||||
| DEXOPT_BOOTCOMPLETE);
|
| DEXOPT_BOOTCOMPLETE);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
mInstaller.dexopt(path, sharedGid, pkg.packageName, dexCodeInstructionSet,
|
mInstaller.dexopt(path, sharedGid, pkg.packageName, dexCodeInstructionSet,
|
||||||
dexoptNeeded, oatDir, dexFlags, targetCompilerFilter, pkg.volumeUuid,
|
dexoptNeeded, oatDir, dexFlags, targetCompilerFilter, pkg.volumeUuid,
|
||||||
sharedLibrariesPath);
|
sharedLibrariesPath);
|
||||||
performedDexOpt = true;
|
performedDexOpt = true;
|
||||||
|
|
||||||
|
if (packageStats != null) {
|
||||||
|
long endTime = System.currentTimeMillis();
|
||||||
|
packageStats.setCompileTime(path, (int)(endTime - startTime));
|
||||||
|
}
|
||||||
} catch (InstallerException e) {
|
} catch (InstallerException e) {
|
||||||
Slog.w(TAG, "Failed to dexopt", e);
|
Slog.w(TAG, "Failed to dexopt", e);
|
||||||
successfulDexOpt = false;
|
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.PackageManager.PERMISSION_GRANTED;
|
||||||
import static android.content.pm.PackageParser.PARSE_IS_PRIVILEGED;
|
import static android.content.pm.PackageParser.PARSE_IS_PRIVILEGED;
|
||||||
import static android.content.pm.PackageParser.isApkFile;
|
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.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
|
||||||
import static android.system.OsConstants.O_CREAT;
|
import static android.system.OsConstants.O_CREAT;
|
||||||
import static android.system.OsConstants.O_RDWR;
|
import static android.system.OsConstants.O_RDWR;
|
||||||
@@ -206,7 +204,6 @@ import android.text.TextUtils;
|
|||||||
import android.text.format.DateUtils;
|
import android.text.format.DateUtils;
|
||||||
import android.util.ArrayMap;
|
import android.util.ArrayMap;
|
||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
import android.util.AtomicFile;
|
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.EventLog;
|
import android.util.EventLog;
|
||||||
import android.util.ExceptionUtils;
|
import android.util.ExceptionUtils;
|
||||||
@@ -265,7 +262,6 @@ import org.xmlpull.v1.XmlPullParser;
|
|||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
import org.xmlpull.v1.XmlSerializer;
|
import org.xmlpull.v1.XmlSerializer;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@@ -278,7 +274,6 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
import java.io.FilenameFilter;
|
import java.io.FilenameFilter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.DigestInputStream;
|
import java.security.DigestInputStream;
|
||||||
@@ -305,7 +300,6 @@ import java.util.concurrent.CountDownLatch;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keep track of all those APKs everywhere.
|
* Keep track of all those APKs everywhere.
|
||||||
@@ -1126,204 +1120,7 @@ public class PackageManagerService extends IPackageManager.Stub {
|
|||||||
final @NonNull String mSharedSystemSharedLibraryPackageName;
|
final @NonNull String mSharedSystemSharedLibraryPackageName;
|
||||||
|
|
||||||
private final PackageUsage mPackageUsage = new PackageUsage();
|
private final PackageUsage mPackageUsage = new PackageUsage();
|
||||||
|
private final CompilerStats mCompilerStats = new CompilerStats();
|
||||||
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";
|
|
||||||
}
|
|
||||||
|
|
||||||
class PackageHandler extends Handler {
|
class PackageHandler extends Handler {
|
||||||
private boolean mBound = false;
|
private boolean mBound = false;
|
||||||
@@ -2702,7 +2499,8 @@ public class PackageManagerService extends IPackageManager.Stub {
|
|||||||
|
|
||||||
// Now that we know all the packages we are keeping,
|
// Now that we know all the packages we are keeping,
|
||||||
// read and update their last usage times.
|
// read and update their last usage times.
|
||||||
mPackageUsage.readLP();
|
mPackageUsage.read(mPackages);
|
||||||
|
mCompilerStats.read();
|
||||||
|
|
||||||
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
|
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
|
||||||
SystemClock.uptimeMillis());
|
SystemClock.uptimeMillis());
|
||||||
@@ -7453,7 +7251,8 @@ public class PackageManagerService extends IPackageManager.Stub {
|
|||||||
// Package could not be found. Report failure.
|
// Package could not be found. Report failure.
|
||||||
return PackageDexOptimizer.DEX_OPT_FAILED;
|
return PackageDexOptimizer.DEX_OPT_FAILED;
|
||||||
}
|
}
|
||||||
mPackageUsage.write(false);
|
mPackageUsage.maybeWriteAsync(mPackages);
|
||||||
|
mCompilerStats.maybeWriteAsync();
|
||||||
}
|
}
|
||||||
long callingId = Binder.clearCallingIdentity();
|
long callingId = Binder.clearCallingIdentity();
|
||||||
try {
|
try {
|
||||||
@@ -7498,11 +7297,12 @@ public class PackageManagerService extends IPackageManager.Stub {
|
|||||||
// Currently this will do a full compilation of the library by default.
|
// Currently this will do a full compilation of the library by default.
|
||||||
pdo.performDexOpt(depPackage, null /* sharedLibraries */, instructionSets,
|
pdo.performDexOpt(depPackage, null /* sharedLibraries */, instructionSets,
|
||||||
false /* checkProfiles */,
|
false /* checkProfiles */,
|
||||||
getCompilerFilterForReason(REASON_NON_SYSTEM_LIBRARY));
|
getCompilerFilterForReason(REASON_NON_SYSTEM_LIBRARY),
|
||||||
|
getOrCreateCompilerPackageStats(depPackage));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pdo.performDexOpt(p, p.usesLibraryFiles, instructionSets, checkProfiles,
|
return pdo.performDexOpt(p, p.usesLibraryFiles, instructionSets, checkProfiles,
|
||||||
targetCompilerFilter);
|
targetCompilerFilter, getOrCreateCompilerPackageStats(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
Collection<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) {
|
Collection<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) {
|
||||||
@@ -7556,7 +7356,8 @@ public class PackageManagerService extends IPackageManager.Stub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
mPackageUsage.write(true);
|
mPackageUsage.writeNow(mPackages);
|
||||||
|
mCompilerStats.writeNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -15188,7 +14989,8 @@ public class PackageManagerService extends IPackageManager.Stub {
|
|||||||
// Also, don't fail application installs if the dexopt step fails.
|
// Also, don't fail application installs if the dexopt step fails.
|
||||||
mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles,
|
mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles,
|
||||||
null /* instructionSets */, false /* checkProfiles */,
|
null /* instructionSets */, false /* checkProfiles */,
|
||||||
getCompilerFilterForReason(REASON_INSTALL));
|
getCompilerFilterForReason(REASON_INSTALL),
|
||||||
|
getOrCreateCompilerPackageStats(pkg));
|
||||||
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
|
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
|
||||||
|
|
||||||
// Notify BackgroundDexOptService that the package has been changed.
|
// 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_DOMAIN_PREFERRED = 1 << 18;
|
||||||
public static final int DUMP_FROZEN = 1 << 19;
|
public static final int DUMP_FROZEN = 1 << 19;
|
||||||
public static final int DUMP_DEXOPT = 1 << 20;
|
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;
|
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(" installs: details about install sessions");
|
||||||
pw.println(" check-permission <permission> <package> [<user>]: does pkg hold perm?");
|
pw.println(" check-permission <permission> <package> [<user>]: does pkg hold perm?");
|
||||||
pw.println(" dexopt: dump dexopt state");
|
pw.println(" dexopt: dump dexopt state");
|
||||||
|
pw.println(" compiler-stats: dump compiler statistics");
|
||||||
pw.println(" <package.name>: info about given package");
|
pw.println(" <package.name>: info about given package");
|
||||||
return;
|
return;
|
||||||
} else if ("--checkin".equals(opt)) {
|
} 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);
|
dumpState.setDump(DumpState.DUMP_FROZEN);
|
||||||
} else if ("dexopt".equals(cmd)) {
|
} else if ("dexopt".equals(cmd)) {
|
||||||
dumpState.setDump(DumpState.DUMP_DEXOPT);
|
dumpState.setDump(DumpState.DUMP_DEXOPT);
|
||||||
|
} else if ("compiler-stats".equals(cmd)) {
|
||||||
|
dumpState.setDump(DumpState.DUMP_COMPILER_STATS);
|
||||||
} else if ("write".equals(cmd)) {
|
} else if ("write".equals(cmd)) {
|
||||||
synchronized (mPackages) {
|
synchronized (mPackages) {
|
||||||
mSettings.writeLPr();
|
mSettings.writeLPr();
|
||||||
@@ -18747,6 +18553,11 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
|
|||||||
dumpDexoptStateLPr(pw, packageName);
|
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 (!checkin && dumpState.isDumping(DumpState.DUMP_MESSAGES) && packageName == null) {
|
||||||
if (dumpState.onTitlePrinted()) pw.println();
|
if (dumpState.onTitlePrinted()) pw.println();
|
||||||
mSettings.dumpReadMessagesLPr(pw, dumpState);
|
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) {
|
private String dumpDomainString(String packageName) {
|
||||||
List<IntentFilterVerificationInfo> iviList = getIntentFilterVerifications(packageName)
|
List<IntentFilterVerificationInfo> iviList = getIntentFilterVerifications(packageName)
|
||||||
.getList();
|
.getList();
|
||||||
@@ -20978,4 +20821,20 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
|
|||||||
msg.setData(data);
|
msg.setData(data);
|
||||||
mProcessLoggingHandler.sendMessage(msg);
|
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