/*
|
* 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;
|
|
private long lastCompiledTime; //record last compiled time for limit launch-pgo-compile;jiangbin;200605
|
/**
|
* @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;
|
}
|
}
|
|
/*AW_CODE:record last compiled time for limit launch-pgo-compile;jiangbin;200605*/
|
public long getLastCompiledTime() {
|
synchronized (compileTimePerCodePath) {
|
return lastCompiledTime;
|
}
|
}
|
|
/*end*/
|
|
public void setCompileTime(String codePath, long compileTimeInMs) {
|
String storagePath = getStoredPathFromCodePath(codePath);
|
synchronized (compileTimePerCodePath) {
|
if (compileTimeInMs <= 0) {
|
compileTimePerCodePath.remove(storagePath);
|
} else {
|
compileTimePerCodePath.put(storagePath, compileTimeInMs);
|
}
|
|
lastCompiledTime = System.currentTimeMillis();//jiangbin;200605
|
}
|
}
|
|
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);
|
write(osw);
|
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);
|
}
|
}
|
}
|