Added LatencyTimer to ease latency measurements
new file: core/java/android/os/LatencyTimer.java
modified: core/java/android/view/MotionEvent.java
modified: core/java/android/view/ViewRoot.java
modified: services/java/com/android/server/InputDevice.java
modified: services/java/com/android/server/KeyInputQueue.java
modified: services/java/com/android/server/WindowManagerService.java
diff --git a/core/java/android/os/LatencyTimer.java b/core/java/android/os/LatencyTimer.java
new file mode 100644
index 0000000..ed2f0f9
--- /dev/null
+++ b/core/java/android/os/LatencyTimer.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2009 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 android.os;
+
+import android.util.Log;
+
+import java.util.HashMap;
+
+/**
+ * A class to help with measuring latency in your code.
+ *
+ * Suggested usage:
+ * 1) Instanciate a LatencyTimer as a class field.
+ * private [static] LatencyTimer mLt = new LatencyTimer(100, 1000);
+ * 2) At various points in the code call sample with a string and the time delta to some fixed time.
+ * The string should be unique at each point of the code you are measuring.
+ * mLt.sample("before processing event", System.nanoTime() - event.getEventTimeNano());
+ * processEvent(event);
+ * mLt.sample("after processing event ", System.nanoTime() - event.getEventTimeNano());
+ *
+ * @hide
+ */
+public final class LatencyTimer
+{
+ final String TAG = "LatencyTimer";
+ final int mSampleSize;
+ final int mScaleFactor;
+ volatile HashMap<String, long[]> store = new HashMap<String, long[]>();
+
+ /**
+ * Creates a LatencyTimer object
+ * @param sampleSize number of samples to collect before printing out the average
+ * @param scaleFactor divisor used to make each sample smaller to prevent overflow when
+ * (sampleSize * average sample value)/scaleFactor > Long.MAX_VALUE
+ */
+ public LatencyTimer(int sampleSize, int scaleFactor) {
+ if (scaleFactor == 0) {
+ scaleFactor = 1;
+ }
+ mScaleFactor = scaleFactor;
+ mSampleSize = sampleSize;
+ }
+
+ /**
+ * Add a sample delay for averaging.
+ * @param tag string used for printing out the result. This should be unique at each point of
+ * this called.
+ * @param delta time difference from an unique point of reference for a particular iteration
+ */
+ public void sample(String tag, long delta) {
+ long[] array = getArray(tag);
+
+ // array[mSampleSize] holds the number of used entries
+ final int index = (int) array[mSampleSize]++;
+ array[index] = delta;
+ if (array[mSampleSize] == mSampleSize) {
+ long totalDelta = 0;
+ for (long d : array) {
+ totalDelta += d/mScaleFactor;
+ }
+ array[mSampleSize] = 0;
+ Log.i(TAG, tag + " average = " + totalDelta / mSampleSize);
+ }
+ }
+
+ private long[] getArray(String tag) {
+ long[] data = store.get(tag);
+ if (data == null) {
+ synchronized(store) {
+ data = store.get(tag);
+ if (data == null) {
+ data = new long[mSampleSize + 1];
+ store.put(tag, data);
+ data[mSampleSize] = 0;
+ }
+ }
+ }
+ return data;
+ }
+}
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 86261c4..f1bf0f4 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -19,7 +19,6 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
-import android.util.Config;
/**
* Object used to report movement (mouse, pen, finger, trackball) events. This
@@ -87,6 +86,7 @@
private long mDownTime;
private long mEventTime;
+ private long mEventTimeNano;
private int mAction;
private float mX;
private float mY;
@@ -123,6 +123,62 @@
return ev;
}
}
+
+ /**
+ * Create a new MotionEvent, filling in all of the basic values that
+ * define the motion.
+ *
+ * @param downTime The time (in ms) when the user originally pressed down to start
+ * a stream of position events. This must be obtained from {@link SystemClock#uptimeMillis()}.
+ * @param eventTime The the time (in ms) when this specific event was generated. This
+ * must be obtained from {@link SystemClock#uptimeMillis()}.
+ * @param eventTimeNano The the time (in ns) when this specific event was generated. This
+ * must be obtained from {@link System#nanoTime()}.
+ * @param action The kind of action being performed -- one of either
+ * {@link #ACTION_DOWN}, {@link #ACTION_MOVE}, {@link #ACTION_UP}, or
+ * {@link #ACTION_CANCEL}.
+ * @param x The X coordinate of this event.
+ * @param y The Y coordinate of this event.
+ * @param pressure The current pressure of this event. The pressure generally
+ * ranges from 0 (no pressure at all) to 1 (normal pressure), however
+ * values higher than 1 may be generated depending on the calibration of
+ * the input device.
+ * @param size A scaled value of the approximate size of the area being pressed when
+ * touched with the finger. The actual value in pixels corresponding to the finger
+ * touch is normalized with a device specific range of values
+ * and scaled to a value between 0 and 1.
+ * @param metaState The state of any meta / modifier keys that were in effect when
+ * the event was generated.
+ * @param xPrecision The precision of the X coordinate being reported.
+ * @param yPrecision The precision of the Y coordinate being reported.
+ * @param deviceId The id for the device that this event came from. An id of
+ * zero indicates that the event didn't come from a physical device; other
+ * numbers are arbitrary and you shouldn't depend on the values.
+ * @param edgeFlags A bitfield indicating which edges, if any, where touched by this
+ * MotionEvent.
+ *
+ * @hide
+ */
+ static public MotionEvent obtainNano(long downTime, long eventTime, long eventTimeNano,
+ int action, float x, float y, float pressure, float size, int metaState,
+ float xPrecision, float yPrecision, int deviceId, int edgeFlags) {
+ MotionEvent ev = obtain();
+ ev.mDeviceId = deviceId;
+ ev.mEdgeFlags = edgeFlags;
+ ev.mDownTime = downTime;
+ ev.mEventTime = eventTime;
+ ev.mEventTimeNano = eventTimeNano;
+ ev.mAction = action;
+ ev.mX = ev.mRawX = x;
+ ev.mY = ev.mRawY = y;
+ ev.mPressure = pressure;
+ ev.mSize = size;
+ ev.mMetaState = metaState;
+ ev.mXPrecision = xPrecision;
+ ev.mYPrecision = yPrecision;
+
+ return ev;
+ }
/**
* Create a new MotionEvent, filling in all of the basic values that
@@ -163,6 +219,7 @@
ev.mEdgeFlags = edgeFlags;
ev.mDownTime = downTime;
ev.mEventTime = eventTime;
+ ev.mEventTimeNano = eventTime * 1000000;
ev.mAction = action;
ev.mX = ev.mRawX = x;
ev.mY = ev.mRawY = y;
@@ -199,6 +256,7 @@
ev.mEdgeFlags = 0;
ev.mDownTime = downTime;
ev.mEventTime = eventTime;
+ ev.mEventTimeNano = eventTime * 1000000;
ev.mAction = action;
ev.mX = ev.mRawX = x;
ev.mY = ev.mRawY = y;
@@ -246,6 +304,7 @@
ev.mEdgeFlags = o.mEdgeFlags;
ev.mDownTime = o.mDownTime;
ev.mEventTime = o.mEventTime;
+ ev.mEventTimeNano = o.mEventTimeNano;
ev.mAction = o.mAction;
ev.mX = o.mX;
ev.mRawX = o.mRawX;
@@ -317,6 +376,16 @@
}
/**
+ * Returns the time (in ns) when this specific event was generated.
+ * The value is in nanosecond precision but it may not have nanosecond accuracy.
+ *
+ * @hide
+ */
+ public final long getEventTimeNano() {
+ return mEventTimeNano;
+ }
+
+ /**
* Returns the X coordinate of this event. Whole numbers are pixels; the
* value may have a fraction for input devices that are sub-pixel precise.
*/
@@ -644,6 +713,7 @@
public void writeToParcel(Parcel out, int flags) {
out.writeLong(mDownTime);
out.writeLong(mEventTime);
+ out.writeLong(mEventTimeNano);
out.writeInt(mAction);
out.writeFloat(mX);
out.writeFloat(mY);
@@ -675,6 +745,7 @@
private void readFromParcel(Parcel in) {
mDownTime = in.readLong();
mEventTime = in.readLong();
+ mEventTimeNano = in.readLong();
mAction = in.readInt();
mX = in.readFloat();
mY = in.readFloat();
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 7cd65e2..d999119 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -77,6 +77,9 @@
private static final boolean DEBUG_IMF = false || LOCAL_LOGV;
private static final boolean WATCH_POINTER = false;
+ private static final boolean MEASURE_LATENCY = false;
+ private static LatencyTimer lt;
+
/**
* Maximum time we allow the user to roll the trackball enough to generate
* a key event, before resetting the counters.
@@ -192,6 +195,10 @@
public ViewRoot(Context context) {
super();
+ if (MEASURE_LATENCY && lt == null) {
+ lt = new LatencyTimer(100, 1000);
+ }
+
++sInstanceCount;
// Initialize the statics when this class is first instantiated. This is
@@ -1579,7 +1586,17 @@
boolean didFinish;
if (event == null) {
try {
+ long timeBeforeGettingEvents;
+ if (MEASURE_LATENCY) {
+ timeBeforeGettingEvents = System.nanoTime();
+ }
+
event = sWindowSession.getPendingPointerMove(mWindow);
+
+ if (MEASURE_LATENCY && event != null) {
+ lt.sample("9 Client got events ", System.nanoTime() - event.getEventTimeNano());
+ lt.sample("8 Client getting events ", timeBeforeGettingEvents - event.getEventTimeNano());
+ }
} catch (RemoteException e) {
}
didFinish = true;
@@ -1603,7 +1620,13 @@
captureMotionLog("captureDispatchPointer", event);
}
event.offsetLocation(0, mCurScrollY);
+ if (MEASURE_LATENCY) {
+ lt.sample("A Dispatching TouchEvents", System.nanoTime() - event.getEventTimeNano());
+ }
handled = mView.dispatchTouchEvent(event);
+ if (MEASURE_LATENCY) {
+ lt.sample("B Dispatched TouchEvents ", System.nanoTime() - event.getEventTimeNano());
+ }
if (!handled && isDown) {
int edgeSlop = mViewConfiguration.getScaledEdgeSlop();
@@ -2685,7 +2708,11 @@
public void dispatchPointer(MotionEvent event, long eventTime) {
final ViewRoot viewRoot = mViewRoot.get();
- if (viewRoot != null) {
+ if (viewRoot != null) {
+ if (MEASURE_LATENCY) {
+ // Note: eventTime is in milliseconds
+ ViewRoot.lt.sample("* ViewRoot b4 dispatchPtr", System.nanoTime() - eventTime * 1000000);
+ }
viewRoot.dispatchPointer(event, eventTime);
} else {
new EventCompletion(mMainLooper, this, null, true, event);