Skip to content

Commit 89101cd

Browse files
author
Jeff Brown
committed
Improve the slow query instrumentation.
On user-debug and eng builds, you can set the "db.log.slow_query_threshold" system property to queries that take longer than the specified number of milliseconds. Set it to 0 to log all queries. This property has been around for a while but it was implemented poorly. In particular, it *changed* the behavior of the query by calling getCount() while holding the Db connection. In normal operation, the query will not actually run until later. By putting the timing logic into fillWindow() instead, we ensure that we only measure queries that actually ran. We also capture cases where the cursor window gets filled multiple times. Bug: 5520301 Change-Id: I174f5e1ea15831a1d22a36e9a804d7755f230b38
1 parent c43b9b6 commit 89101cd

File tree

7 files changed

+76
-30
lines changed

7 files changed

+76
-30
lines changed

core/java/android/database/CursorWindow.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
5555
public int mWindowPtr;
5656

5757
private int mStartPos;
58+
private final String mName;
5859

5960
private final CloseGuard mCloseGuard = CloseGuard.get();
6061

@@ -85,6 +86,8 @@ private static native void nativeCopyStringToBuffer(int windowPtr, int row, int
8586
private static native boolean nativePutDouble(int windowPtr, double value, int row, int column);
8687
private static native boolean nativePutNull(int windowPtr, int row, int column);
8788

89+
private static native String nativeGetName(int windowPtr);
90+
8891
/**
8992
* Creates a new empty cursor window and gives it a name.
9093
* <p>
@@ -100,6 +103,7 @@ private static native void nativeCopyStringToBuffer(int windowPtr, int row, int
100103
*/
101104
public CursorWindow(String name, boolean localWindow) {
102105
mStartPos = 0;
106+
mName = name;
103107
mWindowPtr = nativeCreate(name, sCursorWindowSize, localWindow);
104108
if (mWindowPtr == 0) {
105109
throw new CursorWindowAllocationException("Cursor window allocation of " +
@@ -130,6 +134,7 @@ private CursorWindow(Parcel source) {
130134
throw new CursorWindowAllocationException("Cursor window could not be "
131135
+ "created from binder.");
132136
}
137+
mName = nativeGetName(mWindowPtr);
133138
mCloseGuard.open("close");
134139
}
135140

@@ -156,6 +161,14 @@ private void dispose() {
156161
}
157162
}
158163

164+
/**
165+
* Gets the name of this cursor window.
166+
* @hide
167+
*/
168+
public String getName() {
169+
return mName;
170+
}
171+
159172
/**
160173
* Closes the cursor window and frees its underlying resources when all other
161174
* remaining references have been released.
@@ -778,4 +791,9 @@ private String printStats() {
778791
String s = (buff.length() > 980) ? buff.substring(0, 980) : buff.toString();
779792
return "# Open Cursors=" + total + s;
780793
}
794+
795+
@Override
796+
public String toString() {
797+
return getName() + " {" + Integer.toHexString(mWindowPtr) + "}";
798+
}
781799
}

core/java/android/database/sqlite/SQLiteDatabase.java

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -306,10 +306,6 @@ synchronized void setLastSqlStatement(String sql) {
306306
/** Used to find out where this object was created in case it never got closed. */
307307
private final Throwable mStackTrace;
308308

309-
// System property that enables logging of slow queries. Specify the threshold in ms.
310-
private static final String LOG_SLOW_QUERIES_PROPERTY = "db.log.slow_query_threshold";
311-
private final int mSlowQueryThreshold;
312-
313309
/** stores the list of statement ids that need to be finalized by sqlite */
314310
private final ArrayList<Integer> mClosedStatementIds = new ArrayList<Integer>();
315311

@@ -1559,11 +1555,6 @@ public Cursor rawQueryWithFactory(
15591555
String editTable) {
15601556
verifyDbIsOpen();
15611557
BlockGuard.getThreadPolicy().onReadFromDisk();
1562-
long timeStart = 0;
1563-
1564-
if (false || mSlowQueryThreshold != -1) {
1565-
timeStart = System.currentTimeMillis();
1566-
}
15671558

15681559
SQLiteDatabase db = getDbConnection(sql);
15691560
SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(db, sql, editTable);
@@ -1574,24 +1565,6 @@ public Cursor rawQueryWithFactory(
15741565
cursorFactory != null ? cursorFactory : mFactory,
15751566
selectionArgs);
15761567
} finally {
1577-
if (false || mSlowQueryThreshold != -1) {
1578-
1579-
// Force query execution
1580-
int count = -1;
1581-
if (cursor != null) {
1582-
count = cursor.getCount();
1583-
}
1584-
1585-
long duration = System.currentTimeMillis() - timeStart;
1586-
1587-
if (false || duration >= mSlowQueryThreshold) {
1588-
Log.v(SQLiteCursor.TAG,
1589-
"query (" + duration + " ms): " + driver.toString() + ", args are "
1590-
+ (selectionArgs != null
1591-
? TextUtils.join(",", selectionArgs)
1592-
: "<null>") + ", count is " + count);
1593-
}
1594-
}
15951568
releaseDbConnection(db);
15961569
}
15971570
return cursor;
@@ -1967,7 +1940,6 @@ private SQLiteDatabase(String path, CursorFactory factory, int flags,
19671940
setMaxSqlCacheSize(DEFAULT_SQL_CACHE_SIZE);
19681941
mFlags = flags;
19691942
mPath = path;
1970-
mSlowQueryThreshold = SystemProperties.getInt(LOG_SLOW_QUERIES_PROPERTY, -1);
19711943
mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
19721944
mFactory = factory;
19731945
mPrograms = new WeakHashMap<SQLiteClosable,Object>();

core/java/android/database/sqlite/SQLiteDebug.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.util.ArrayList;
2020

21+
import android.os.Build;
22+
import android.os.SystemProperties;
2123
import android.util.Log;
2224

2325
/**
@@ -64,6 +66,28 @@ public final class SQLiteDebug {
6466
public static final boolean DEBUG_LOCK_TIME_TRACKING_STACK_TRACE =
6567
Log.isLoggable("SQLiteLockStackTrace", Log.VERBOSE);
6668

69+
/**
70+
* True to enable database performance testing instrumentation.
71+
* @hide
72+
*/
73+
public static final boolean DEBUG_LOG_SLOW_QUERIES = Build.IS_DEBUGGABLE;
74+
75+
/**
76+
* Determines whether a query should be logged.
77+
*
78+
* Reads the "db.log.slow_query_threshold" system property, which can be changed
79+
* by the user at any time. If the value is zero, then all queries will
80+
* be considered slow. If the value does not exist, then no queries will
81+
* be considered slow.
82+
*
83+
* This value can be changed dynamically while the system is running.
84+
* @hide
85+
*/
86+
public static final boolean shouldLogSlowQuery(long elapsedTimeMillis) {
87+
int slowQueryMillis = SystemProperties.getInt("db.log.slow_query_threshold", -1);
88+
return slowQueryMillis >= 0 && elapsedTimeMillis > slowQueryMillis;
89+
}
90+
6791
/**
6892
* Contains statistics about the active pagers in the current process.
6993
*

core/java/android/database/sqlite/SQLiteQuery.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import android.database.CursorWindow;
2020
import android.os.SystemClock;
21+
import android.text.TextUtils;
2122
import android.util.Log;
2223

2324
/**
@@ -32,6 +33,7 @@ public class SQLiteQuery extends SQLiteProgram {
3233

3334
private static native int nativeFillWindow(int databasePtr, int statementPtr, int windowPtr,
3435
int startPos, int offsetParam);
36+
3537
private static native int nativeColumnCount(int statementPtr);
3638
private static native String nativeColumnName(int statementPtr, int columnIndex);
3739

@@ -80,8 +82,24 @@ private static native int nativeFillWindow(int databasePtr, int statementPtr, in
8082
acquireReference();
8183
try {
8284
window.acquireReference();
85+
int startPos = window.getStartPosition();
8386
int numRows = nativeFillWindow(nHandle, nStatement, window.mWindowPtr,
84-
window.getStartPosition(), mOffsetIndex);
87+
startPos, mOffsetIndex);
88+
if (SQLiteDebug.DEBUG_LOG_SLOW_QUERIES) {
89+
long elapsed = SystemClock.uptimeMillis() - timeStart;
90+
if (SQLiteDebug.shouldLogSlowQuery(elapsed)) {
91+
Log.d(TAG, "fillWindow took " + elapsed
92+
+ " ms: window=\"" + window
93+
+ "\", startPos=" + startPos
94+
+ ", offset=" + mOffsetIndex
95+
+ ", filledRows=" + window.getNumRows()
96+
+ ", countedRows=" + numRows
97+
+ ", query=\"" + mSql + "\""
98+
+ ", args=[" + (mBindArgs != null ?
99+
TextUtils.join(", ", mBindArgs.values()) : "")
100+
+ "]");
101+
}
102+
}
85103
mDatabase.logTimeStat(mSql, timeStart);
86104
return numRows;
87105
} catch (IllegalStateException e){

core/java/android/os/Build.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,13 @@ public static class VERSION_CODES {
325325
public static final String USER = getString("ro.build.user");
326326
public static final String HOST = getString("ro.build.host");
327327

328+
/**
329+
* Returns true if we are running a debug build such as "user-debug" or "eng".
330+
* @hide
331+
*/
332+
public static final boolean IS_DEBUGGABLE =
333+
SystemProperties.getInt("ro.debuggable", 0) == 1;
334+
328335
/**
329336
* Returns the version string for the radio firmware. May return
330337
* null (if, for instance, the radio is not currently on).

core/jni/android_database_CursorWindow.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ static void nativeDispose(JNIEnv* env, jclass clazz, jint windowPtr) {
104104
}
105105
}
106106

107+
static jstring nativeGetName(JNIEnv* env, jclass clazz, jint windowPtr) {
108+
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
109+
return env->NewStringUTF(window->name().string());
110+
}
111+
107112
static void nativeWriteToParcel(JNIEnv * env, jclass clazz, jint windowPtr,
108113
jobject parcelObj) {
109114
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
@@ -485,6 +490,8 @@ static JNINativeMethod sMethods[] =
485490
(void*)nativeDispose },
486491
{ "nativeWriteToParcel", "(ILandroid/os/Parcel;)V",
487492
(void*)nativeWriteToParcel },
493+
{ "nativeGetName", "(I)Ljava/lang/String;",
494+
(void*)nativeGetName },
488495
{ "nativeClear", "(I)V",
489496
(void*)nativeClear },
490497
{ "nativeGetNumRows", "(I)I",

libs/binder/CursorWindow.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ uint32_t CursorWindow::alloc(size_t size, bool aligned) {
211211
uint32_t offset = mHeader->freeOffset + padding;
212212
uint32_t nextFreeOffset = offset + size;
213213
if (nextFreeOffset > mSize) {
214-
LOGE("Window is full: requested allocation %d bytes, "
214+
LOGW("Window is full: requested allocation %d bytes, "
215215
"free space %d bytes, window size %d bytes",
216216
size, freeSpace(), mSize);
217217
return 0;

0 commit comments

Comments
 (0)