Commit 0d37cc58 authored by lizeb's avatar lizeb Committed by Commit bot

android: Record the memory size of the GSA process.

Chrome connects to a bound service exposed by GSA, elevating its
importance to the system (see the linked bug). This requirement is going
away, and this CL is assessing the amount of memory wasted / saved this
way.

On a Nexus 5X, the cost of gathering this data has been
measured (through Trace events) to be ~60ms of wall clock time (on a
low-priority background thread), with ~2ms of CPU time on a little
core. The data is gathered at most once per startup.

BUG=614388

Review-Url: https://codereview.chromium.org/2477513004
Cr-Commit-Position: refs/heads/master@{#430288}
parent 20316028
......@@ -218,6 +218,19 @@ public class RecordHistogram {
timeUnit.toMillis(min), timeUnit.toMillis(max), numBuckets);
}
/**
* Records a sample in a histogram of sizes in KB. This is the Java equivalent of the
* UMA_HISTOGRAM_MEMORY_KB C++ macro.
*
* Good for sizes up to about 500MB.
*
* @param name name of the histogram.
* @param sizeInkB Sample to record in KB.
*/
public static void recordMemoryKBHistogram(String name, int sizeInKB) {
recordCustomCountHistogram(name, sizeInKB, 1000, 500000, 50);
}
private static int clampToInt(long value) {
if (value > Integer.MAX_VALUE) return Integer.MAX_VALUE;
// Note: Clamping to MIN_VALUE rather than 0, to let base/ histograms code
......
......@@ -5,11 +5,14 @@
package org.chromium.chrome.browser.gsa;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
......@@ -18,9 +21,15 @@ import android.os.RemoteException;
import android.util.Log;
import org.chromium.base.Callback;
import org.chromium.base.ContextUtils;
import org.chromium.base.TraceEvent;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.SuppressFBWarnings;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.ChromeApplication;
import java.util.List;
/**
* A simple client that connects and talks to the GSAService using Messages.
*/
......@@ -31,8 +40,8 @@ public class GSAServiceClient {
* Constants for gsa communication. These should not change without corresponding changes on the
* service side in GSA.
*/
private static final String GSA_SERVICE =
"com.google.android.ssb.action.SSB_SERVICE";
@VisibleForTesting
static final String GSA_SERVICE = "com.google.android.ssb.action.SSB_SERVICE";
public static final int REQUEST_REGISTER_CLIENT = 2;
public static final int RESPONSE_UPDATE_SSB = 3;
......@@ -40,6 +49,10 @@ public class GSAServiceClient {
public static final String KEY_GSA_CONTEXT = "ssb_service:ssb_context";
public static final String KEY_GSA_PACKAGE_NAME = "ssb_service:ssb_package_name";
@VisibleForTesting
static final int INVALID_PSS = -1;
private static boolean sHasRecordedPss;
/** Messenger to handle incoming messages from the service */
private final Messenger mMessenger;
private final IncomingHandler mHandler;
......@@ -50,7 +63,7 @@ public class GSAServiceClient {
/** Messenger for communicating with service. */
private Messenger mService;
private ComponentName mComponentName;
/**
* Handler of incoming messages from service.
......@@ -60,17 +73,85 @@ public class GSAServiceClient {
private class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
if (msg.what == RESPONSE_UPDATE_SSB) {
if (mService == null) return;
Bundle bundle = (Bundle) msg.obj;
String account =
mGsaHelper.getGSAAccountFromState(bundle.getByteArray(KEY_GSA_STATE));
GSAState.getInstance(mContext.getApplicationContext()).setGsaAccount(account);
if (mOnMessageReceived != null) mOnMessageReceived.onResult(bundle);
} else {
if (msg.what != RESPONSE_UPDATE_SSB) {
super.handleMessage(msg);
return;
}
if (mService == null) return;
final Bundle bundle = (Bundle) msg.obj;
String account = mGsaHelper.getGSAAccountFromState(bundle.getByteArray(KEY_GSA_STATE));
GSAState.getInstance(mContext.getApplicationContext()).setGsaAccount(account);
if (sHasRecordedPss) {
if (mOnMessageReceived != null) mOnMessageReceived.onResult(bundle);
return;
}
// Getting the PSS for the GSA service process can be long, don't block the UI thread on
// that. Also, don't process the callback before the PSS is known, since the callback
// can lead to a service disconnect, which can lead to the framework killing the
// process. Hence an AsyncTask (long operation), and processing the callback in
// onPostExecute() (don't disconnect before).
sHasRecordedPss = true;
new AsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackground(Void... params) {
TraceEvent.begin("GSAServiceClient.getPssForservice");
try {
// Looking for the service process is done by component name, which is
// inefficient. We really want the PID, which is only accessible from within
// a Binder transaction. Since the service connection is Messenger-based,
// the calls are not processed from a Binder thread. The alternatives are:
// 1. Override methods in the framework to append the calling PID to the
// Message.
// 2. Usse msg.callingUid to narrow down the search.
//
// (1) is dirty (and brittle), and (2) only works on L+, and still requires
// to get the full list of services from ActivityManager.
return getPssForService(mComponentName);
} finally {
TraceEvent.end("GSAServiceClient.getPssForservice");
}
}
@Override
protected void onPostExecute(Integer pssInKB) {
if (pssInKB != INVALID_PSS) {
RecordHistogram.recordMemoryKBHistogram(
"Search.GsaProcessMemoryPss", pssInKB);
}
if (mOnMessageReceived != null) mOnMessageReceived.onResult(bundle);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
/**
* Get the PSS used by the process hosting a service.
*
* @param packageName Package name of the service to search for.
* @return the PSS in kB of the process hosting a service, or INVALID_PSS.
*/
@VisibleForTesting
static int getPssForService(ComponentName componentName) {
if (componentName == null) return INVALID_PSS;
Context context = ContextUtils.getApplicationContext();
ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningServiceInfo> services =
activityManager.getRunningServices(1000);
if (services == null) return INVALID_PSS;
int pid = -1;
for (ActivityManager.RunningServiceInfo info : services) {
if (componentName.equals(info.service)) {
pid = info.pid;
break;
}
}
if (pid == -1) return INVALID_PSS;
Debug.MemoryInfo infos[] = activityManager.getProcessMemoryInfo(new int[] {pid});
if (infos == null || infos.length == 0) return INVALID_PSS;
return infos[0].getTotalPss();
}
/**
......@@ -137,6 +218,7 @@ public class GSAServiceClient {
if (mContext == null) return;
mService = new Messenger(service);
mComponentName = name;
try {
Message registerClientMessage = Message.obtain(
null, REQUEST_REGISTER_CLIENT);
......
......@@ -1223,6 +1223,7 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTest.java",
"javatests/src/org/chromium/chrome/browser/gcore/MockConnectedTask.java",
"javatests/src/org/chromium/chrome/browser/gcore/MockConnectedTaskTest.java",
"javatests/src/org/chromium/chrome/browser/gsa/GSAServiceClientTest.java",
"javatests/src/org/chromium/chrome/browser/hardware_acceleration/ChromeTabbedActivityHWATest.java",
"javatests/src/org/chromium/chrome/browser/hardware_acceleration/CustomTabActivityHWATest.java",
"javatests/src/org/chromium/chrome/browser/hardware_acceleration/EmbedContentViewActivityHWATest.java",
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.gsa;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import org.chromium.base.Log;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/** Tests for GSAServiceClient. */
public class GSAServiceClientTest extends InstrumentationTestCase {
private static final String TAG = "GSAServiceClientTest";
@SmallTest
public void testGetPssForService() throws Exception {
Context context = getInstrumentation().getTargetContext();
if (!GSAState.getInstance(context).isGsaAvailable()) {
Log.w(TAG, "GSA is not available");
return;
}
final AtomicReference<ComponentName> componentNameRef = new AtomicReference<>();
final Semaphore semaphore = new Semaphore(0);
Intent intent =
new Intent(GSAServiceClient.GSA_SERVICE).setPackage(GSAState.SEARCH_INTENT_PACKAGE);
ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
componentNameRef.set(name);
semaphore.release();
}
@Override
public void onServiceDisconnected(ComponentName name) {}
};
context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
try {
assertTrue("Timeout", semaphore.tryAcquire(10, TimeUnit.SECONDS));
int pss = GSAServiceClient.getPssForService(componentNameRef.get());
assertTrue(pss != GSAServiceClient.INVALID_PSS);
assertTrue(pss > 0);
} finally {
context.unbindService(connection);
}
}
@SmallTest
public void testGetPssForServiceServiceNotFound() throws Exception {
ComponentName componentName = new ComponentName("unknown.package.name", "UnknownClass");
int pss = GSAServiceClient.getPssForService(componentName);
assertTrue(pss == GSAServiceClient.INVALID_PSS);
}
}
......@@ -56587,6 +56587,15 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries.
</summary>
</histogram>
<histogram name="Search.GsaProcessMemoryPss" units="KB">
<owner>lizeb@chromium.org</owner>
<summary>
On Android, when Chrome connects to a bound service exposed by GSA, the
memory footprint of the GSA process in KB, as measured by PSS. Reported at
most once per Chrome startup.
</summary>
</histogram>
<histogram name="Search.MigratedPrefToDictionaryValue" enum="BooleanHit">
<owner>caitkp@chromium.org</owner>
<summary>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment