Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 53071d6d authored by Michael Chan's avatar Michael Chan
Browse files

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
parent 4eebf590
Loading
Loading
Loading
Loading
+94 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+72 −1
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ package android.view;
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 @@ public final class MotionEvent implements Parcelable {
    
    private long mDownTime;
    private long mEventTime;
    private long mEventTimeNano;
    private int mAction;
    private float mX;
    private float mY;
@@ -124,6 +124,62 @@ public final class MotionEvent implements Parcelable {
        }
    }

    /**
     * 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
     * define the motion.
@@ -163,6 +219,7 @@ public final class MotionEvent implements Parcelable {
        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 @@ public final class MotionEvent implements Parcelable {
        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 @@ public final class MotionEvent implements Parcelable {
        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;
@@ -316,6 +375,16 @@ public final class MotionEvent implements Parcelable {
        return mEventTime;
    }

    /**
     * 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 final class MotionEvent implements Parcelable {
    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 @@ public final class MotionEvent implements Parcelable {
    private void readFromParcel(Parcel in) {
        mDownTime = in.readLong();
        mEventTime = in.readLong();
        mEventTimeNano = in.readLong();
        mAction = in.readInt();
        mX = in.readFloat();
        mY = in.readFloat();
+28 −1
Original line number Diff line number Diff line
@@ -77,6 +77,9 @@ public final class ViewRoot extends Handler implements ViewParent,
    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 final class ViewRoot extends Handler implements ViewParent,
    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 @@ public final class ViewRoot extends Handler implements ViewParent,
            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 @@ public final class ViewRoot extends Handler implements ViewParent,
                        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();

@@ -2686,6 +2709,10 @@ public final class ViewRoot extends Handler implements ViewParent,
        public void dispatchPointer(MotionEvent event, long eventTime) {
            final ViewRoot viewRoot = mViewRoot.get();
            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);
+3 −3
Original line number Diff line number Diff line
@@ -63,7 +63,7 @@ public class InputDevice {
            yMoveScale = my != 0 ? (1.0f/my) : 1.0f;
        }
        
        MotionEvent generateMotion(InputDevice device, long curTime,
        MotionEvent generateMotion(InputDevice device, long curTime, long curTimeNano,
                boolean isAbs, Display display, int orientation,
                int metaState) {
            if (!changed) {
@@ -167,7 +167,7 @@ public class InputDevice {
                if (!isAbs) {
                    x = y = 0;
                }
                return MotionEvent.obtain(downTime, curTime, action,
                return MotionEvent.obtainNano(downTime, curTime, curTimeNano, action,
                        scaledX, scaledY, scaledPressure, scaledSize, metaState,
                        xPrecision, yPrecision, device.id, edgeFlags);
            } else {
@@ -181,7 +181,7 @@ public class InputDevice {
                    }
                    return null;
                }
                MotionEvent me = MotionEvent.obtain(downTime, curTime,
                MotionEvent me = MotionEvent.obtainNano(downTime, curTime, curTimeNano,
                        MotionEvent.ACTION_MOVE, scaledX, scaledY,
                        scaledPressure, scaledSize, metaState,
                        xPrecision, yPrecision, device.id, edgeFlags);
+31 −15
Original line number Diff line number Diff line
@@ -18,8 +18,9 @@ package com.android.server;

import android.content.Context;
import android.content.res.Configuration;
import android.os.SystemClock;
import android.os.LatencyTimer;
import android.os.PowerManager;
import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
import android.view.Display;
@@ -74,13 +75,16 @@ public abstract class KeyInputQueue {
    public static final int FILTER_KEEP = 1;
    public static final int FILTER_ABORT = -1;

    private static final boolean MEASURE_LATENCY = false;
    private LatencyTimer lt;

    public interface FilterCallback {
        int filterEvent(QueuedEvent ev);
    }
    
    static class QueuedEvent {
        InputDevice inputDevice;
        long when;
        long whenNano;
        int flags; // From the raw event
        int classType; // One of the class constants in InputEvent
        Object event;
@@ -88,7 +92,7 @@ public abstract class KeyInputQueue {

        void copyFrom(QueuedEvent that) {
            this.inputDevice = that.inputDevice;
            this.when = that.when;
            this.whenNano = that.whenNano;
            this.flags = that.flags;
            this.classType = that.classType;
            this.event = that.event;
@@ -107,6 +111,10 @@ public abstract class KeyInputQueue {
    }

    KeyInputQueue(Context context) {
        if (MEASURE_LATENCY) {
            lt = new LatencyTimer(100, 1000);
        }

        PowerManager pm = (PowerManager)context.getSystemService(
                                                        Context.POWER_SERVICE);
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
@@ -241,7 +249,7 @@ public abstract class KeyInputQueue {
                    
                    if (configChanged) {
                        synchronized (mFirst) {
                            addLocked(di, SystemClock.uptimeMillis(), 0,
                            addLocked(di, System.nanoTime(), 0,
                                    RawInputEvent.CLASS_CONFIGURATION_CHANGED,
                                    null);
                        }
@@ -256,6 +264,7 @@ public abstract class KeyInputQueue {
                        // timebase as SystemClock.uptimeMillis().
                        //curTime = gotOne ? ev.when : SystemClock.uptimeMillis();
                        final long curTime = SystemClock.uptimeMillis();
                        final long curTimeNano = System.nanoTime();
                        //Log.i(TAG, "curTime=" + curTime + ", systemClock=" + SystemClock.uptimeMillis());
                        
                        final int classes = di.classes;
@@ -276,7 +285,7 @@ public abstract class KeyInputQueue {
                                down = false;
                            }
                            int keycode = rotateKeyCodeLocked(ev.keycode);
                            addLocked(di, curTime, ev.flags,
                            addLocked(di, curTimeNano, ev.flags,
                                    RawInputEvent.CLASS_KEYBOARD,
                                    newKeyEvent(di, di.mDownTime, curTime, down,
                                            keycode, 0, scancode,
@@ -330,7 +339,7 @@ public abstract class KeyInputQueue {
                                }
                                
                                MotionEvent me;
                                me = di.mAbs.generateMotion(di, curTime, true,
                                me = di.mAbs.generateMotion(di, curTime, curTimeNano, true,
                                        mDisplay, mOrientation, mGlobalMetaState);
                                if (false) Log.v(TAG, "Absolute: x=" + di.mAbs.x
                                        + " y=" + di.mAbs.y + " ev=" + me);
@@ -338,15 +347,15 @@ public abstract class KeyInputQueue {
                                    if (WindowManagerPolicy.WATCH_POINTER) {
                                        Log.i(TAG, "Enqueueing: " + me);
                                    }
                                    addLocked(di, curTime, ev.flags,
                                    addLocked(di, curTimeNano, ev.flags,
                                            RawInputEvent.CLASS_TOUCHSCREEN, me);
                                }
                                me = di.mRel.generateMotion(di, curTime, false,
                                me = di.mRel.generateMotion(di, curTime, curTimeNano, false,
                                        mDisplay, mOrientation, mGlobalMetaState);
                                if (false) Log.v(TAG, "Relative: x=" + di.mRel.x
                                        + " y=" + di.mRel.y + " ev=" + me);
                                if (me != null) {
                                    addLocked(di, curTime, ev.flags,
                                    addLocked(di, curTimeNano, ev.flags,
                                            RawInputEvent.CLASS_TRACKBALL, me);
                                }
                            }
@@ -530,7 +539,7 @@ public abstract class KeyInputQueue {
        }
    }
    
    private QueuedEvent obtainLocked(InputDevice device, long when,
    private QueuedEvent obtainLocked(InputDevice device, long whenNano,
            int flags, int classType, Object event) {
        QueuedEvent ev;
        if (mCacheCount == 0) {
@@ -542,7 +551,7 @@ public abstract class KeyInputQueue {
            mCacheCount--;
        }
        ev.inputDevice = device;
        ev.when = when;
        ev.whenNano = whenNano;
        ev.flags = flags;
        ev.classType = classType;
        ev.event = event;
@@ -561,13 +570,13 @@ public abstract class KeyInputQueue {
        }
    }

    private void addLocked(InputDevice device, long when, int flags,
    private void addLocked(InputDevice device, long whenNano, int flags,
            int classType, Object event) {
        boolean poke = mFirst.next == mLast;

        QueuedEvent ev = obtainLocked(device, when, flags, classType, event);
        QueuedEvent ev = obtainLocked(device, whenNano, flags, classType, event);
        QueuedEvent p = mLast.prev;
        while (p != mFirst && ev.when < p.when) {
        while (p != mFirst && ev.whenNano < p.whenNano) {
            p = p.prev;
        }

@@ -578,8 +587,15 @@ public abstract class KeyInputQueue {
        ev.inQueue = true;

        if (poke) {
            long time;
            if (MEASURE_LATENCY) {
                time = System.nanoTime();
            }
            mFirst.notify();
            mWakeLock.acquire();
            if (MEASURE_LATENCY) {
                lt.sample("1 addLocked-queued event ", System.nanoTime() - time);
            }
        }
    }

Loading