blob: 7318bc15bcd2c52dac25b34a9c05d3a6c1b68bba [file] [log] [blame]
/*
* Copyright (C) 2019 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.helpers;
import static com.android.helpers.MetricUtility.constructKey;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.content.Context;
import android.os.Debug.MemoryInfo;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Helper to collect totalpss memory usage per process tracked by the ActivityManager
* memoryinfo.
*/
public class TotalPssHelper implements ICollectorHelper<Long> {
private static final String TAG = TotalPssHelper.class.getSimpleName();
private static final int DEFAULT_THRESHOLD = 1024;
private static final int DEFAULT_MIN_ITERATIONS = 6;
private static final int DEFAULT_MAX_ITERATIONS = 20;
private static final int DEFAULT_SLEEP_TIME = 1000;
private static final String PSS_METRIC_PREFIX = "am_totalpss_bytes";
private String[] mProcessNames;
// Minimum number of iterations needed before deciding on the memory usage.
private int mMinIterations;
// Maximum number of iterations needed waiting for memory usage to be stabilized.
private int mMaxIterations;
// Sleep time in between the iterations.
private int mSleepTime;
// Threshold in kb to use whether the data is stabilized.
private int mThreshold;
// Map to maintain the pss memory size.
private Map<String, Long> mPssFinalMap = new HashMap<>();
public void setUp(String... processNames) {
mProcessNames = processNames;
// Minimum iterations should be atleast 3 to check for the
// stabilization of the memory usage.
mMinIterations = DEFAULT_MIN_ITERATIONS;
mMaxIterations = DEFAULT_MAX_ITERATIONS;
mSleepTime = DEFAULT_SLEEP_TIME;
mThreshold = DEFAULT_THRESHOLD;
}
@Override
public boolean startCollecting() {
return true;
}
@Override
public Map<String, Long> getMetrics() {
if (mMinIterations < 3) {
Log.w(TAG, "Need atleast 3 iterations to check memory usage stabilization.");
return mPssFinalMap;
}
if (mProcessNames != null) {
for (String processName : mProcessNames) {
if (!processName.isEmpty()) {
measureMemory(processName);
}
}
}
return mPssFinalMap;
}
@Override
public boolean stopCollecting() {
return true;
}
/**
* Measure memory info of the given process name tracked by the activity manager
* MemoryInfo(i.e getTotalPss).
*
* @param processName to calculate the memory info.
*/
private void measureMemory(String processName) {
Log.i(TAG, "Tracking memory usage of the process - " + processName);
List<Long> pssData = new ArrayList<Long>();
long pss = 0;
int iteration = 0;
while (iteration < mMaxIterations) {
sleep(mSleepTime);
pss = getPss(processName);
pssData.add(pss);
if (iteration >= mMinIterations && stabilized(pssData)) {
Log.i(TAG, "Memory usage stabilized at iteration count = " + iteration);
// Final metric reported in bytes.
mPssFinalMap.put(constructKey(PSS_METRIC_PREFIX, processName), pss * 1024);
return;
}
iteration++;
}
Log.i(TAG, processName + " memory usage did not stabilize."
+ " Returning the average of the pss data collected.");
// Final metric reported in bytes.
mPssFinalMap.put(constructKey(PSS_METRIC_PREFIX, processName), average(pssData) * 1024);
}
/**
* Time to sleep in between the iterations.
*
* @param time in ms to sleep.
*/
private void sleep(int time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
// ignore
}
}
/**
* Get the total pss memory of the given process name.
*
* @param processName of the process to measure the memory.
* @return the memory in KB.
*/
private long getPss(String processName) {
ActivityManager am = (ActivityManager) InstrumentationRegistry.getInstrumentation()
.getContext().getSystemService(Context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> apps = am.getRunningAppProcesses();
for (RunningAppProcessInfo proc : apps) {
if (!proc.processName.equals(processName)) {
continue;
}
MemoryInfo meminfo = am.getProcessMemoryInfo(new int[] {
proc.pid
})[0];
Log.i(TAG,
String.format("Memory usage of process - %s is %d", processName,
meminfo.getTotalPss()));
return meminfo.getTotalPss();
}
Log.w(TAG, "Not able to find the process id for the process = " + processName);
return 0;
}
/**
* Checks whether the memory usage is stabilized by calculating the sum of the difference
* between the last 3 values and comparing that to the threshold.
*
* @param pssData list of pssData of the given process name.
* @return true if the memory is stabilized.
*/
private boolean stabilized(List<Long> pssData) {
long diff1 = Math.abs(pssData.get(pssData.size() - 1) - pssData.get(pssData.size() - 2));
long diff2 = Math.abs(pssData.get(pssData.size() - 2) - pssData.get(pssData.size() - 3));
Log.i(TAG, "diff1=" + diff1 + " diff2=" + diff2);
return (diff1 + diff2) < mThreshold;
}
/**
* Returns the average of the pssData collected for the maxIterations.
*
* @param pssData list of pssData.
* @return
*/
private long average(List<Long> pssData) {
long sum = 0;
for (long sample : pssData) {
sum += sample;
}
return sum / pssData.size();
}
/**
* @param minIterations before starting to check for memory is stabilized.
*/
public void setMinIterations(int minIterations) {
mMinIterations = minIterations;
}
/**
* @param maxIterations to wait for memory to be stabilized.
*/
public void setMaxIterations(int maxIterations) {
mMaxIterations = maxIterations;
}
/**
* @param sleepTime in between the iterations.
*/
public void setSleepTime(int sleepTime) {
mSleepTime = sleepTime;
}
/**
* @param threshold for difference in memory usage between two successive iterations in kb
*/
public void setThreshold(int threshold) {
mThreshold = threshold;
}
}