add more debug info to SQL section in bugreport
after this CL, adb bugreport will the following info (under SQL section
of each app's meminfo dump)
SQL
heap: 344 memoryUsed: 344
pageCacheOverflo: 67 largestMemAlloc: 50
DATABASES
Pagesize Dbsize Lookaside Dbname
1024 7 24 googlesettings.db
1024 26 110 talk.db
1024 11 0 (attached) transient_talk_db
1024 11 32 subscribedfeeds.db
1024 20 27 gservices.db
Change-Id: Iabd13be9793d9794137c60a045b84fa632f13498
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7c49bb7..b0a59c7 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -39,6 +39,7 @@
import android.content.res.Resources;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDebug;
+import android.database.sqlite.SQLiteDebug.DbStats;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.os.Bundle;
@@ -1441,6 +1442,7 @@
private static final String HEAP_COLUMN = "%17s %8s %8s %8s %8s";
private static final String ONE_COUNT_COLUMN = "%17s %8d";
private static final String TWO_COUNT_COLUMNS = "%17s %8d %17s %8d";
+ private static final String DB_INFO_FORMAT = " %8d %8d %10d %s";
// Formatting for checkin service - update version if row format changes
private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 1;
@@ -1760,8 +1762,7 @@
int binderDeathObjectCount = Debug.getBinderDeathObjectCount();
int openSslSocketCount = OpenSSLSocketImpl.getInstanceCount();
long sqliteAllocated = SQLiteDebug.getHeapAllocatedSize() / 1024;
- SQLiteDebug.PagerStats stats = new SQLiteDebug.PagerStats();
- SQLiteDebug.getPagerStats(stats);
+ SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo();
// Check to see if we were called by checkin server. If so, print terse format.
boolean doCheckinFormat = false;
@@ -1835,10 +1836,15 @@
// SQL
pw.print(sqliteAllocated); pw.print(',');
- pw.print(stats.databaseBytes / 1024); pw.print(',');
- pw.print(stats.numPagers); pw.print(',');
- pw.print((stats.totalBytes - stats.referencedBytes) / 1024); pw.print(',');
- pw.print(stats.referencedBytes / 1024); pw.print('\n');
+ pw.print(stats.memoryUsed / 1024); pw.print(',');
+ pw.print(stats.pageCacheOverflo / 1024); pw.print(',');
+ pw.print(stats.largestMemAlloc / 1024); pw.print(',');
+ for (int i = 0; i < stats.dbStats.size(); i++) {
+ DbStats dbStats = stats.dbStats.get(i);
+ printRow(pw, DB_INFO_FORMAT, dbStats.pageSize, dbStats.dbSize,
+ dbStats.lookaside, dbStats.dbName);
+ pw.print(',');
+ }
return;
}
@@ -1879,11 +1885,21 @@
// SQLite mem info
pw.println(" ");
pw.println(" SQL");
- printRow(pw, TWO_COUNT_COLUMNS, "heap:", sqliteAllocated, "dbFiles:",
- stats.databaseBytes / 1024);
- printRow(pw, TWO_COUNT_COLUMNS, "numPagers:", stats.numPagers, "inactivePageKB:",
- (stats.totalBytes - stats.referencedBytes) / 1024);
- printRow(pw, ONE_COUNT_COLUMN, "activePageKB:", stats.referencedBytes / 1024);
+ printRow(pw, TWO_COUNT_COLUMNS, "heap:", sqliteAllocated, "memoryUsed:",
+ stats.memoryUsed / 1024);
+ printRow(pw, TWO_COUNT_COLUMNS, "pageCacheOverflo:", stats.pageCacheOverflo / 1024,
+ "largestMemAlloc:", stats.largestMemAlloc / 1024);
+ pw.println(" ");
+ int N = stats.dbStats.size();
+ if (N > 0) {
+ pw.println(" DATABASES");
+ printRow(pw, " %8s %8s %10s %s", "Pagesize", "Dbsize", "Lookaside", "Dbname");
+ for (int i = 0; i < N; i++) {
+ DbStats dbStats = stats.dbStats.get(i);
+ printRow(pw, DB_INFO_FORMAT, dbStats.pageSize, dbStats.dbSize,
+ dbStats.lookaside, dbStats.dbName);
+ }
+ }
// Asset details.
String assetAlloc = AssetManager.getAssetAllocations();
diff --git a/core/java/android/database/sqlite/SQLiteClosable.java b/core/java/android/database/sqlite/SQLiteClosable.java
index 71fa2c2..1830f6c 100644
--- a/core/java/android/database/sqlite/SQLiteClosable.java
+++ b/core/java/android/database/sqlite/SQLiteClosable.java
@@ -32,7 +32,7 @@
synchronized(mLock) {
if (mReferenceCount <= 0) {
throw new IllegalStateException(
- "attempt to acquire a reference on an already-closed " + getObjInfo());
+ "attempt to re-open an already-closed object: " + getObjInfo());
}
mReferenceCount++;
}
@@ -59,7 +59,6 @@
private String getObjInfo() {
StringBuilder buff = new StringBuilder();
buff.append(this.getClass().getName());
- buff.append(" Obj");
buff.append(" (");
if (this instanceof SQLiteDatabase) {
buff.append("database = ");
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index dfd4024..b232ff9 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -23,6 +23,7 @@
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.SQLException;
+import android.database.sqlite.SQLiteDebug.DbStats;
import android.os.Debug;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -30,10 +31,14 @@
import android.util.Config;
import android.util.EventLog;
import android.util.Log;
+import android.util.Pair;
import java.io.File;
+import java.lang.ref.WeakReference;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
@@ -787,25 +792,27 @@
* @throws SQLiteException if the database cannot be opened
*/
public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags) {
- SQLiteDatabase db = null;
+ SQLiteDatabase sqliteDatabase = null;
try {
// Open the database.
- SQLiteDatabase sqliteDatabase = new SQLiteDatabase(path, factory, flags);
+ sqliteDatabase = new SQLiteDatabase(path, factory, flags);
if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
sqliteDatabase.enableSqlTracing(path);
}
if (SQLiteDebug.DEBUG_SQL_TIME) {
sqliteDatabase.enableSqlProfiling(path);
}
- return sqliteDatabase;
} catch (SQLiteDatabaseCorruptException e) {
// Try to recover from this, if we can.
// TODO: should we do this for other open failures?
Log.e(TAG, "Deleting and re-creating corrupt database " + path, e);
EventLog.writeEvent(EVENT_DB_CORRUPT, path);
new File(path).delete();
- return new SQLiteDatabase(path, factory, flags);
+ sqliteDatabase = new SQLiteDatabase(path, factory, flags);
}
+ ActiveDatabases.getInstance().mActiveDatabases.add(
+ new WeakReference<SQLiteDatabase>(sqliteDatabase));
+ return sqliteDatabase;
}
/**
@@ -2040,6 +2047,88 @@
mMaxSqlCacheSize = cacheSize;
}
+ static class ActiveDatabases {
+ private static final ActiveDatabases activeDatabases = new ActiveDatabases();
+ private HashSet<WeakReference<SQLiteDatabase>> mActiveDatabases =
+ new HashSet<WeakReference<SQLiteDatabase>>();
+ private ActiveDatabases() {} // disable instantiation of this class
+ static ActiveDatabases getInstance() {return activeDatabases;}
+ }
+
+ /* package */ static ArrayList<DbStats> getDbStats() {
+ ArrayList<DbStats> dbStatsList = new ArrayList<DbStats>();
+ for (WeakReference<SQLiteDatabase> w : ActiveDatabases.getInstance().mActiveDatabases) {
+ SQLiteDatabase db = w.get();
+ if (db == null || !db.isOpen()) {
+ continue;
+ }
+ // get SQLITE_DBSTATUS_LOOKASIDE_USED for the db
+ int lookasideUsed = db.native_getDbLookaside();
+
+ // get the lastnode of the dbname
+ String path = db.getPath();
+ int indx = path.lastIndexOf("/");
+ String lastnode = path.substring((indx != -1) ? ++indx : 0);
+
+ // get list of attached dbs and for each db, get its size and pagesize
+ ArrayList<Pair<String, String>> attachedDbs = getAttachedDbs(db);
+ for (int i = 0; i < attachedDbs.size(); i++) {
+ Pair<String, String> p = attachedDbs.get(i);
+ long pageCount = getPragmaVal(db, p.first + ".page_count;");
+
+ // first entry in the attached db list is always the main database
+ // don't worry about prefixing the dbname with "main"
+ String dbName;
+ if (i == 0) {
+ dbName = lastnode;
+ } else {
+ // lookaside is only relevant for the main db
+ lookasideUsed = 0;
+ dbName = " (attached) " + p.first;
+ // if the attached db has a path, attach the lastnode from the path to above
+ if (p.second.trim().length() > 0) {
+ int idx = p.second.lastIndexOf("/");
+ dbName += " : " + p.second.substring((idx != -1) ? ++idx : 0);
+ }
+ }
+ dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(), lookasideUsed));
+ }
+ }
+ return dbStatsList;
+ }
+
+ /**
+ * get the specified pragma value from sqlite for the specified database.
+ * only handles pragma's that return int/long.
+ * NO JAVA locks are held in this method.
+ * TODO: use this to do all pragma's in this class
+ */
+ private static long getPragmaVal(SQLiteDatabase db, String pragma) {
+ SQLiteStatement prog = null;
+ try {
+ prog = new SQLiteStatement(db, "PRAGMA " + pragma);
+ long val = prog.simpleQueryForLong();
+ return val;
+ } finally {
+ if (prog != null) prog.close();
+ }
+ }
+
+ /**
+ * returns list of full pathnames of all attached databases
+ * including the main database
+ * TODO: move this to {@link DatabaseUtils}
+ */
+ private static ArrayList<Pair<String, String>> getAttachedDbs(SQLiteDatabase dbObj) {
+ ArrayList<Pair<String, String>> attachedDbs = new ArrayList<Pair<String, String>>();
+ Cursor c = dbObj.rawQuery("pragma database_list;", null);
+ while (c.moveToNext()) {
+ attachedDbs.add(new Pair<String, String>(c.getString(1), c.getString(2)));
+ }
+ c.close();
+ return attachedDbs;
+ }
+
/**
* Native call to open the database.
*
@@ -2093,4 +2182,11 @@
* @return the number of changes made in the last statement executed.
*/
/* package */ native int lastChangeCount();
+
+ /**
+ * return the SQLITE_DBSTATUS_LOOKASIDE_USED documented here
+ * http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html
+ * @return int value of SQLITE_DBSTATUS_LOOKASIDE_USED
+ */
+ private native int native_getDbLookaside();
}
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index 4ea680e..a4db6d9 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -16,6 +16,8 @@
package android.database.sqlite;
+import java.util.ArrayList;
+
import android.util.Log;
/**
@@ -68,14 +70,87 @@
* @see #getPagerStats(PagerStats)
*/
public static class PagerStats {
- /** The total number of bytes in all pagers in the current process */
+ /** The total number of bytes in all pagers in the current process
+ * @deprecated not used any longer
+ */
+ @Deprecated
public long totalBytes;
- /** The number of bytes in referenced pages in all pagers in the current process */
+ /** The number of bytes in referenced pages in all pagers in the current process
+ * @deprecated not used any longer
+ * */
+ @Deprecated
public long referencedBytes;
- /** The number of bytes in all database files opened in the current process */
+ /** The number of bytes in all database files opened in the current process
+ * @deprecated not used any longer
+ */
+ @Deprecated
public long databaseBytes;
- /** The number of pagers opened in the current process */
+ /** The number of pagers opened in the current process
+ * @deprecated not used any longer
+ */
+ @Deprecated
public int numPagers;
+
+ /** the current amount of memory checked out by sqlite using sqlite3_malloc().
+ * documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html
+ */
+ public int memoryUsed;
+
+ /** the number of bytes of page cache allocation which could not be sattisfied by the
+ * SQLITE_CONFIG_PAGECACHE buffer and where forced to overflow to sqlite3_malloc().
+ * The returned value includes allocations that overflowed because they where too large
+ * (they were larger than the "sz" parameter to SQLITE_CONFIG_PAGECACHE) and allocations
+ * that overflowed because no space was left in the page cache.
+ * documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html
+ */
+ public int pageCacheOverflo;
+
+ /** records the largest memory allocation request handed to sqlite3.
+ * documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html
+ */
+ public int largestMemAlloc;
+
+ /** a list of {@link DbStats} - one for each main database opened by the applications
+ * running on the android device
+ */
+ public ArrayList<DbStats> dbStats;
+ }
+
+ /**
+ * contains statistics about a database
+ * @author vnori@google.com (Your Name Here)
+ *
+ */
+ public static class DbStats {
+ /** name of the database */
+ public String dbName;
+
+ /** the page size for the database */
+ public long pageSize;
+
+ /** the database size */
+ public long dbSize;
+
+ /** documented here http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html */
+ public int lookaside;
+
+ public DbStats(String dbName, long pageCount, long pageSize, int lookaside) {
+ this.dbName = dbName;
+ this.pageSize = pageSize;
+ dbSize = (pageCount * pageSize) / 1024;
+ this.lookaside = lookaside;
+ }
+ }
+
+ /**
+ * return all pager and database stats for the current process.
+ * @return {@link PagerStats}
+ */
+ public static PagerStats getDatabaseInfo() {
+ PagerStats stats = new PagerStats();
+ getPagerStats(stats);
+ stats.dbStats = SQLiteDatabase.getDbStats();
+ return stats;
}
/**
diff --git a/core/jni/android_database_SQLiteDatabase.cpp b/core/jni/android_database_SQLiteDatabase.cpp
index 44e091d..fb93014 100644
--- a/core/jni/android_database_SQLiteDatabase.cpp
+++ b/core/jni/android_database_SQLiteDatabase.cpp
@@ -306,6 +306,16 @@
return sqlite3_changes(handle);
}
+/* native int native_getDbLookaside(); */
+static jint native_getDbLookaside(JNIEnv* env, jobject object)
+{
+ sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
+ int pCur = -1;
+ int unused;
+ sqlite3_db_status(handle, SQLITE_DBSTATUS_LOOKASIDE_USED, &pCur, &unused, 0);
+ return pCur;
+}
+
/* set locale in the android_metadata table, install localized collators, and rebuild indexes */
static void native_setLocale(JNIEnv* env, jobject object, jstring localeString, jint flags)
{
@@ -442,6 +452,7 @@
{"lastInsertRow", "()J", (void *)lastInsertRow},
{"lastChangeCount", "()I", (void *)lastChangeCount},
{"native_setLocale", "(Ljava/lang/String;I)V", (void *)native_setLocale},
+ {"native_getDbLookaside", "()I", (void *)native_getDbLookaside},
{"releaseMemory", "()I", (void *)native_releaseMemory},
};
diff --git a/core/jni/android_database_SQLiteDebug.cpp b/core/jni/android_database_SQLiteDebug.cpp
index 916df35..873b2a1 100644
--- a/core/jni/android_database_SQLiteDebug.cpp
+++ b/core/jni/android_database_SQLiteDebug.cpp
@@ -29,36 +29,28 @@
// From mem_mspace.c in libsqlite
extern "C" mspace sqlite3_get_mspace();
-// From sqlite.c, hacked in for Android
-extern "C" void sqlite3_get_pager_stats(sqlite3_int64 * totalBytesOut,
- sqlite3_int64 * referencedBytesOut,
- sqlite3_int64 * dbBytesOut,
- int * numPagersOut);
-
namespace android {
-static jfieldID gTotalBytesField;
-static jfieldID gReferencedBytesField;
-static jfieldID gDbBytesField;
-static jfieldID gNumPagersField;
+static jfieldID gMemoryUsedField;
+static jfieldID gPageCacheOverfloField;
+static jfieldID gLargestMemAllocField;
#define USE_MSPACE 0
static void getPagerStats(JNIEnv *env, jobject clazz, jobject statsObj)
{
- sqlite3_int64 totalBytes;
- sqlite3_int64 referencedBytes;
- sqlite3_int64 dbBytes;
- int numPagers;
+ int memoryUsed;
+ int pageCacheOverflo;
+ int largestMemAlloc;
+ int unused;
- sqlite3_get_pager_stats(&totalBytes, &referencedBytes, &dbBytes,
- &numPagers);
-
- env->SetLongField(statsObj, gTotalBytesField, totalBytes);
- env->SetLongField(statsObj, gReferencedBytesField, referencedBytes);
- env->SetLongField(statsObj, gDbBytesField, dbBytes);
- env->SetIntField(statsObj, gNumPagersField, numPagers);
+ sqlite3_status(SQLITE_STATUS_MEMORY_USED, &memoryUsed, &unused, 0);
+ sqlite3_status(SQLITE_STATUS_MALLOC_SIZE, &unused, &largestMemAlloc, 0);
+ sqlite3_status(SQLITE_STATUS_PAGECACHE_OVERFLOW, &pageCacheOverflo, &unused, 0);
+ env->SetIntField(statsObj, gMemoryUsedField, memoryUsed);
+ env->SetIntField(statsObj, gPageCacheOverfloField, pageCacheOverflo);
+ env->SetIntField(statsObj, gLargestMemAllocField, largestMemAlloc);
}
static jlong getHeapSize(JNIEnv *env, jobject clazz)
@@ -213,27 +205,21 @@
return -1;
}
- gTotalBytesField = env->GetFieldID(clazz, "totalBytes", "J");
- if (gTotalBytesField == NULL) {
- LOGE("Can't find totalBytes");
+ gMemoryUsedField = env->GetFieldID(clazz, "memoryUsed", "I");
+ if (gMemoryUsedField == NULL) {
+ LOGE("Can't find memoryUsed");
return -1;
}
- gReferencedBytesField = env->GetFieldID(clazz, "referencedBytes", "J");
- if (gReferencedBytesField == NULL) {
- LOGE("Can't find referencedBytes");
+ gLargestMemAllocField = env->GetFieldID(clazz, "largestMemAlloc", "I");
+ if (gLargestMemAllocField == NULL) {
+ LOGE("Can't find largestMemAlloc");
return -1;
}
- gDbBytesField = env->GetFieldID(clazz, "databaseBytes", "J");
- if (gDbBytesField == NULL) {
- LOGE("Can't find databaseBytes");
- return -1;
- }
-
- gNumPagersField = env->GetFieldID(clazz, "numPagers", "I");
- if (gNumPagersField == NULL) {
- LOGE("Can't find numPagers");
+ gPageCacheOverfloField = env->GetFieldID(clazz, "pageCacheOverflo", "I");
+ if (gPageCacheOverfloField == NULL) {
+ LOGE("Can't find pageCacheOverflo");
return -1;
}