Skip to content

Calculate total ram using MemoryInfo instead of /proc/meminfo #5299

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions firebase-crashlytics/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Unreleased
* [feature] Include Firebase sessions with NDK crashes and ANRs.
* [changed] Improved reliability when reporting memory usage.

# 18.4.1
* [changed] Updated `firebase-sessions` dependency to v1.0.2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,24 +49,6 @@ protected void tearDown() throws Exception {

private static final String ABC_EXPECTED_HASH = "a9993e364706816aba3e25717850c26c9cd0d89d";

public void testConvertMemInfoToBytesFromKb() {
assertEquals(
1055760384,
CommonUtils.convertMemInfoToBytes("1031016 KB", "KB", CommonUtils.BYTES_IN_A_KILOBYTE));
}

public void testConvertMemInfoToBytesFromMb() {
assertEquals(
1081081856,
CommonUtils.convertMemInfoToBytes("1031 MB", "MB", CommonUtils.BYTES_IN_A_MEGABYTE));
}

public void testConvertMemInfoToBytesFromGb() {
assertEquals(
10737418240L,
CommonUtils.convertMemInfoToBytes("10 GB", "GB", CommonUtils.BYTES_IN_A_GIGABYTE));
}

public void testCreateInstanceIdFromNullInput() {
assertNull(CommonUtils.createInstanceIdFrom(((String[]) null)));
}
Expand Down Expand Up @@ -151,7 +133,7 @@ public void testGetCpuArchitecture() {
}

public void testGetTotalRamInBytes() {
final long bytes = CommonUtils.getTotalRamInBytes();
final long bytes = CommonUtils.calculateTotalRamInBytes(getContext());
// can't check complete string because emulators & devices may be different.
assertTrue(bytes > 0);
Log.d(Logger.TAG, "testGetTotalRam: " + bytes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,8 @@
import android.text.TextUtils;
import androidx.annotation.Nullable;
import com.google.firebase.crashlytics.internal.Logger;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
Expand All @@ -47,7 +45,6 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;

public class CommonUtils {

Expand All @@ -73,15 +70,6 @@ public class CommonUtils {
static final String BUILD_IDS_BUILD_ID_RESOURCE_NAME =
"com.google.firebase.crashlytics.build_ids_build_id";

private static final long UNCALCULATED_TOTAL_RAM = -1;
static final int BYTES_IN_A_GIGABYTE = 1073741824;
static final int BYTES_IN_A_MEGABYTE = 1048576;
static final int BYTES_IN_A_KILOBYTE = 1024;

// Caches the result of the total ram calculation, which is expensive, so we only want to
// perform it once. The value won't change over time, so it's safe to cache.
private static long totalRamInBytes = UNCALCULATED_TOTAL_RAM;

// TODO: Maybe move this method into a more appropriate class.
public static SharedPreferences getSharedPrefs(Context context) {
return context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
Expand All @@ -92,37 +80,6 @@ public static SharedPreferences getLegacySharedPrefs(Context context) {
return context.getSharedPreferences(LEGACY_SHARED_PREFS_NAME, Context.MODE_PRIVATE);
}

/**
* Utility method to open the given file and extract the field matching fieldname. Assumes each
* line of the file is formatted "fieldID:value" with an arbitrary amount of whitespace on both
* sides of the colon. Will return null if the file can't be opened or the field was not found.
*/
public static String extractFieldFromSystemFile(File file, String fieldname) {
String toReturn = null;
if (file.exists()) {

BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(file), 1024);
String line;
while ((line = br.readLine()) != null) {
final Pattern pattern = Pattern.compile("\\s*:\\s*");
final String[] pieces = pattern.split(line, 2);
if (pieces.length > 1 && pieces[0].equals(fieldname)) {
toReturn = pieces[1];

break;
}
}
} catch (Exception e) {
Logger.getLogger().e("Error parsing " + file, e);
} finally {
CommonUtils.closeOrLog(br, "Failed to close system file reader.");
}
}
return toReturn;
}

/**
* Get Architecture based on Integer order in Protobuf enum
*
Expand Down Expand Up @@ -175,49 +132,6 @@ static Architecture getValue() {
}
}

/**
* Returns the total ram of the device, in bytes, as read from the /proc/meminfo file. No API call
* exists to get this value in API level 7.
*/
public static synchronized long getTotalRamInBytes() {
if (totalRamInBytes == UNCALCULATED_TOTAL_RAM) {
long bytes = 0;
String result = extractFieldFromSystemFile(new File("/proc/meminfo"), "MemTotal");

if (!TextUtils.isEmpty(result)) {
result = result.toUpperCase(Locale.US);

try {
if (result.endsWith("KB")) {
bytes = convertMemInfoToBytes(result, "KB", BYTES_IN_A_KILOBYTE);
} else if (result.endsWith("MB")) {
// It is uncertain that this notation would ever be returned, but we'll
// leave in this handling since we don't know for certain it isn't.
bytes = convertMemInfoToBytes(result, "MB", BYTES_IN_A_MEGABYTE);
} else if (result.endsWith("GB")) {
// It is uncertain that this notation would ever be returned, but we'll
// leave in this handling since we don't know for certain it isn't.
bytes = convertMemInfoToBytes(result, "GB", BYTES_IN_A_GIGABYTE);
} else {
Logger.getLogger().w("Unexpected meminfo format while computing RAM: " + result);
}
} catch (NumberFormatException e) {
Logger.getLogger().e("Unexpected meminfo format while computing RAM: " + result, e);
}
}
totalRamInBytes = bytes;
}
return totalRamInBytes;
}

/**
* Converts a meminfo value String with associated size notation into bytes by stripping the
* provided unit notation and applying the provided multiplier.
*/
static long convertMemInfoToBytes(String memInfo, String notation, int notationMultiplier) {
return Long.parseLong(memInfo.split(notation)[0].trim()) * notationMultiplier;
}

/**
* Returns the RunningAppProcessInfo object for the given package, or null if it cannot be found.
*/
Expand Down Expand Up @@ -311,6 +225,13 @@ public static String createInstanceIdFrom(String... sliceIds) {
return (concatValue.length() > 0) ? sha1(concatValue) : null;
}

/** Calculates the total ram of the device, in bytes. */
public static synchronized long calculateTotalRamInBytes(Context context) {
MemoryInfo mi = new MemoryInfo();
((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo(mi);
return mi.totalMem;
}

/**
* Calculates and returns the amount of free RAM in bytes.
*
Expand Down Expand Up @@ -492,25 +413,6 @@ public static boolean isAppDebuggable(Context context) {
return (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}

/**
* Gets values for string properties in the strings.xml file by its name. If a key is not present,
* an empty String is returned.
*
* @param context {@link Context} to use when accessing resources
* @param key {@link String} name of the string value to look up
* @return {@link String} value of the specified property, or an empty string if it could not be
* found.
*/
public static String getStringsFileValue(Context context, String key) {
final int id = getResourcesIdentifier(context, key, "string");

if (id > 0) {
return context.getString(id);
}

return "";
}

/**
* Closes a {@link Closeable}, ignoring any {@link IOException}s raised in the process. Does
* nothing if the {@link Closeable} is <code>null</code>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,7 @@ private void doOpenSession(String sessionIdentifier) {

StaticSessionData.AppData appData = createAppData(idManager, this.appData);
StaticSessionData.OsData osData = createOsData();
StaticSessionData.DeviceData deviceData = createDeviceData();
StaticSessionData.DeviceData deviceData = createDeviceData(context);

nativeComponent.prepareNativeSession(
sessionIdentifier,
Expand Down Expand Up @@ -770,15 +770,15 @@ private static StaticSessionData.OsData createOsData() {
VERSION.RELEASE, VERSION.CODENAME, CommonUtils.isRooted());
}

private static StaticSessionData.DeviceData createDeviceData() {
private static StaticSessionData.DeviceData createDeviceData(Context context) {
final StatFs statFs = new StatFs(Environment.getDataDirectory().getPath());
final long diskSpace = (long) statFs.getBlockCount() * (long) statFs.getBlockSize();

return StaticSessionData.DeviceData.create(
CommonUtils.getCpuArchitectureInt(),
Build.MODEL,
Runtime.getRuntime().availableProcessors(),
CommonUtils.getTotalRamInBytes(),
CommonUtils.calculateTotalRamInBytes(context),
diskSpace,
CommonUtils.isEmulator(),
CommonUtils.getDeviceState(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ private CrashlyticsReport.Session.Device populateSessionDeviceData() {
final StatFs statFs = new StatFs(Environment.getDataDirectory().getPath());
final int arch = getDeviceArchitecture();
final int availableProcessors = Runtime.getRuntime().availableProcessors();
final long totalRam = CommonUtils.getTotalRamInBytes();
final long totalRam = CommonUtils.calculateTotalRamInBytes(context);
final long diskSpace = (long) statFs.getBlockCount() * (long) statFs.getBlockSize();
final boolean isEmulator = CommonUtils.isEmulator();
final int state = CommonUtils.getDeviceState();
Expand Down Expand Up @@ -278,7 +278,9 @@ private Event.Device populateEventDeviceData(int orientation) {
final int batteryVelocity = battery.getBatteryVelocity();
final boolean proximityEnabled = CommonUtils.getProximitySensorEnabled(context);
final long usedRamBytes =
CommonUtils.getTotalRamInBytes() - CommonUtils.calculateFreeRamInBytes(context);
ensureNonNegative(
CommonUtils.calculateTotalRamInBytes(context)
- CommonUtils.calculateFreeRamInBytes(context));
final long diskUsedBytes =
CommonUtils.calculateUsedDiskSpaceInBytes(Environment.getDataDirectory().getPath());

Expand Down Expand Up @@ -466,4 +468,9 @@ private static int getDeviceArchitecture() {

return arch;
}

/** Returns the given value, or zero is the value is negative. */
private static long ensureNonNegative(long value) {
return value > 0 ? value : 0;
}
}