Created
December 7, 2015 03:20
-
-
Save hereisderek/35c3b9bdc07d076e0823 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.support.annotation.IntDef; | |
import android.support.v4.util.SimpleArrayMap; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.util.WeakHashMap; | |
/** | |
* Only used for internal debug timing | |
* Created by derek on 5/12/15. | |
*/ | |
public class Timer { | |
private static final String TAG = Timer.class.getSimpleName(); | |
private static final WeakHashMap<Object, SimpleArrayMap<Long, Integer>> timers = new WeakHashMap<>(); | |
public static boolean defaultShowLog = true; | |
public static final class OPERATIONS { | |
private static final int INVALID = -1; | |
private static final int UNDEFINED = 0; | |
private static final int START = 1; | |
private static final int PAUSE = 2; | |
private static final int RESUME = 3; | |
private static final int END = 4; | |
private static final int CLEAR = 5; | |
} | |
@Retention(RetentionPolicy.SOURCE) | |
@IntDef({OPERATIONS.INVALID, OPERATIONS.UNDEFINED, OPERATIONS.START, OPERATIONS.PAUSE, OPERATIONS.RESUME, OPERATIONS.END, OPERATIONS.CLEAR}) | |
private @interface Operation{} | |
/* public caller */ | |
public static long startTimer(Object object) { | |
return startTimer(object, defaultShowLog); | |
} | |
public static long pauseTimer(Object object) { | |
return pauseTimer(object, defaultShowLog); | |
} | |
public static long resumeTimer(Object object) { | |
return resumeTimer(object, defaultShowLog); | |
} | |
public static long endTimer(Object object) { | |
return endTimer(object, defaultShowLog); | |
} | |
public static long clearTimer(Object object) { | |
return clearTimer(object, defaultShowLog); | |
} | |
public static long startTimer(Object object, boolean showLog) { | |
return addOperationOnList(object, OPERATIONS.START, false, showLog); | |
} | |
public static long pauseTimer(Object object, boolean showLog) { | |
return addOperationOnList(object, OPERATIONS.PAUSE, false, showLog); | |
} | |
public static long resumeTimer(Object object, boolean showLog) { | |
return addOperationOnList(object, OPERATIONS.RESUME, false, showLog); | |
} | |
public static long endTimer(Object object, boolean showLog) { | |
return addOperationOnList(object, OPERATIONS.END, false, showLog); | |
} | |
public static long clearTimer(Object object, boolean showLog) { | |
return addOperationOnList(object, OPERATIONS.CLEAR, false, showLog); | |
} | |
/* Util */ | |
private static long getCurrentMillisTime(){ | |
return System.nanoTime(); | |
} | |
private static long addOperationOnList(Object object, @Operation int operation){ | |
return addOperationOnList(object, operation, false, defaultShowLog); | |
} | |
private static long addOperationOnList(Object object, @Operation int operation, boolean skipCheck, boolean showLog){ | |
long currentTime = getCurrentMillisTime(); | |
if (!skipCheck) checkAndFixConflictStatusPreOperate(object, operation, true); | |
SimpleArrayMap<Long, Integer> map = getOperationMapForObject(object); | |
map.put(currentTime, operation); | |
if (showLog) DLog.i(TAG, "currentTime:" + currentTime, " operation:" + getNameForOperation(operation) | |
, getElapsedTimeStringWhenNeeded(object, currentTime) | |
); | |
return currentTime; | |
} | |
//TODO: not finished | |
private static String getElapsedTimeStringWhenNeeded(Object object, long currentTime){ | |
String result = ""; | |
SimpleArrayMap<Long, Integer> map = getOperationMapForObject(object); | |
boolean foundValidResult = false; | |
long elapsedTime; | |
long pausedTime = -1; | |
long resumedTime = -1; | |
long startedTime = -1; | |
OperationMapPair current = lastOperationMap(map, OPERATIONS.END, OPERATIONS.PAUSE, OPERATIONS.RESUME, OPERATIONS.START); | |
int startIndex = map.size() - 1; | |
for (int i = startIndex; i >= 0; i--){ | |
@Operation int operation = map.valueAt(i); | |
switch (operation) { | |
case OPERATIONS.RESUME: | |
resumedTime = map.keyAt(i); | |
break; | |
case OPERATIONS.PAUSE: | |
break; | |
case OPERATIONS.END: | |
break; | |
case OPERATIONS.START: | |
case OPERATIONS.UNDEFINED: | |
break; | |
default: | |
break; | |
} | |
} | |
return result; | |
} | |
private static SimpleArrayMap<Long, Integer> getOperationMapForObject(Object object) { | |
if (object == null) return null; | |
SimpleArrayMap<Long, Integer> map; | |
if (timers.containsKey(object)) { | |
map = timers.get(object); | |
} else { | |
if(defaultShowLog) DLog.i(TAG, "map for object is null, could've been GCed"); | |
map = new SimpleArrayMap<>(); | |
timers.put(object, map); | |
} | |
return map; | |
} | |
private static String getNameForOperation(@Operation int operation){ | |
switch (operation) { | |
case OPERATIONS.INVALID: | |
return "INVALID"; | |
case OPERATIONS.UNDEFINED: | |
return "UNDEFINED"; | |
case OPERATIONS.START: | |
return "START"; | |
case OPERATIONS.PAUSE: | |
return "PAUSE"; | |
case OPERATIONS.RESUME: | |
return "RESUME"; | |
case OPERATIONS.END: | |
return "END"; | |
case OPERATIONS.CLEAR: | |
return "CLEAR"; | |
} | |
return "ERROR"; | |
} | |
/** | |
* for example, you shouldn't pause a timer when it's already paused | |
* the if {@param fix} is set to true, it will just create a resume right before the pause, and print a log | |
* @param object | |
* @param fix | |
* @return conflict exists, could apply fix | |
*/ | |
private static boolean checkAndFixConflictStatusPreOperate(Object object, @Operation int operation, boolean fix) { | |
SimpleArrayMap<Long, Integer> map = getOperationMapForObject(object); | |
Long currentTime = getCurrentMillisTime(); | |
if (defaultShowLog) DLog.d(TAG, "pre-operate check for operation:", getNameForOperation(operation)); | |
if (/*map == null ||*/ map.size() == 0) { | |
if (operation != OPERATIONS.START) { | |
DLog.i(TAG, "current map size is zero before operation ", getNameForOperation(operation), ", could've been GCed"); | |
if (fix) { | |
if (defaultShowLog) DLog.i(TAG, "Fix is on, will append UNDEFINED operation at the end (which is also the beginning of current map)"); | |
map.put(currentTime, OPERATIONS.UNDEFINED); | |
} | |
} | |
//map = new SimpleArrayMap<>(); | |
return true; | |
} else { | |
//@Operation int operateToBeAdded = OPERATIONS.UNDEFINED; | |
@Operation int lastOperation = OPERATIONS.INVALID; | |
switch (operation) { | |
case OPERATIONS.START: | |
case OPERATIONS.END: | |
lastOperation = lastOperation(map, OPERATIONS.START, OPERATIONS.END); | |
if (lastOperation == operation) { | |
if (fix) { | |
if (defaultShowLog) | |
DLog.i(TAG, "Fix is on, will try to fix error: \"" + (operation == OPERATIONS.START ? "start" : "end") + " when it is already\" by: adding " + (operation == OPERATIONS.START ? "end" : "start") + " beforehand"); | |
map.put(currentTime, operation == OPERATIONS.START ? OPERATIONS.END : OPERATIONS.START); | |
} | |
return true; | |
} else if (lastOperation == OPERATIONS.UNDEFINED) { | |
if (fix) { | |
if (defaultShowLog){ | |
DLog.i(TAG, "Fix is on, will try to fix error: \"unable to find the starting point\" by appending UNDEFINED operation at the end"); | |
} | |
map.put(currentTime, OPERATIONS.UNDEFINED); | |
} | |
return false; | |
} | |
break; | |
case OPERATIONS.PAUSE: | |
lastOperation = lastOperation(map, OPERATIONS.PAUSE, OPERATIONS.RESUME, OPERATIONS.START, OPERATIONS.END); | |
if (lastOperation == OPERATIONS.PAUSE) { | |
if (fix) { | |
if (defaultShowLog) DLog.i(TAG, "Fix is on, will try to fix error: \"pause when already paused\" by: adding resume beforehand"); | |
map.put(currentTime, OPERATIONS.RESUME); | |
lastOperation = OPERATIONS.INVALID; | |
} | |
} else if (lastOperation == OPERATIONS.END){ | |
if (defaultShowLog) DLog.i(TAG, "Fix is on, will try to fix error: \"pause when already ended\" by: adding start beforehand"); | |
map.put(currentTime, OPERATIONS.START); | |
lastOperation = OPERATIONS.INVALID; | |
} | |
break; | |
case OPERATIONS.RESUME: | |
lastOperation = lastOperation(map, OPERATIONS.PAUSE, OPERATIONS.RESUME, OPERATIONS.END); | |
if (lastOperation == OPERATIONS.RESUME) { | |
if (fix) { | |
if (defaultShowLog) DLog.i(TAG, "Fix is on, will try to fix error: \"resume when already resumed\" by: adding pause beforehand"); | |
map.put(currentTime, OPERATIONS.PAUSE); | |
lastOperation = OPERATIONS.INVALID; | |
} | |
} else if (lastOperation == OPERATIONS.END){ | |
if (defaultShowLog) DLog.i(TAG, "Fix is on, will try to fix error: \"resume when already ended\" by: adding start beforehand"); | |
map.put(currentTime, OPERATIONS.START); | |
lastOperation = OPERATIONS.INVALID; | |
} | |
break; | |
} | |
return true; | |
} | |
} | |
@Timer.Operation | |
private static int lastOperation(SimpleArrayMap<Long, Integer> map, @Operation int... operations){ | |
int size = map.size(); | |
if (size == 0) return OPERATIONS.UNDEFINED; | |
OperationMapPair pair = lastOperationMap(map, operations); | |
return pair != null ? pair.operation : OPERATIONS.UNDEFINED; | |
} | |
/*@Timer.Operation | |
private static int lastOperation(SimpleArrayMap<Long, Integer> map, @Operation int... operations){ | |
int size = map.size(); | |
if (size == 0) return OPERATIONS.UNDEFINED; | |
for (int i = size - 1; i >= 0; i--){ | |
for (int operation : operations){ | |
if (operation == map.valueAt(i)) { | |
return operation; | |
} | |
} | |
} | |
return OPERATIONS.UNDEFINED; | |
}*/ | |
private static class OperationMapPair{ | |
public long time; | |
public @Operation int operation; | |
public int index; | |
public OperationMapPair(long time, int operation) { | |
this.time = time; | |
this.operation = operation; | |
} | |
public OperationMapPair(long time, int operation, int index) { | |
this.time = time; | |
this.operation = operation; | |
this.index = index; | |
} | |
public static OperationMapPair getOperationMapPairByIndex(SimpleArrayMap<Long, Integer> map, int index){ | |
return new OperationMapPair(map.keyAt(index), map.valueAt(index), index); | |
} | |
} | |
private static OperationMapPair lastOperationMap(SimpleArrayMap<Long, Integer> map, @Operation int... operations){ | |
return lastOperationMap(-1, map, operations); | |
} | |
private static OperationMapPair lastOperationMap(int startIndex, SimpleArrayMap<Long, Integer> map, @Operation int... operations){ | |
int size = map.size(); | |
if (size == 0) return null; | |
if (startIndex == -1 || startIndex < 0 || startIndex > size - 1) startIndex = size - 1; | |
for (int i = startIndex; i >= 0; i--){ | |
for (int operation : operations){ | |
if (operation == map.valueAt(i)) { | |
return new OperationMapPair(map.keyAt(i), map.valueAt(i), i); | |
} | |
} | |
} | |
return null; | |
} | |
@Override | |
public String toString() { | |
return Timer.allToString(); | |
} | |
public static String allToString(){ | |
return allToString(defaultShowLog); | |
} | |
public static String allToString(boolean showLog) { | |
StringBuilder stringBuilder = new StringBuilder("showing all timer lists:\n"); | |
/* Iterator it = timers.entrySet().iterator(); | |
while (it.hasNext()) { | |
Map.Entry pair = (Map.Entry)it.next(); | |
Object object = pair.getValue(); | |
//System.out.println(pair.getKey() + " = " + pair.getValue()); | |
stringBuilder.append(toString(object)); | |
//it.remove(); // avoids a ConcurrentModificationException | |
}*/ | |
synchronized (timers) { | |
for (Object object : timers.values()) { | |
if (object != null) { | |
stringBuilder.append(toString(object, false)); | |
} | |
} | |
} | |
//for (int i = 0; i < timers.size(); i++) { | |
// Object object = timers.; | |
// stringBuilder.append(toString(object)); | |
//} | |
if (showLog) DLog.i(TAG, stringBuilder); | |
return stringBuilder.toString(); | |
} | |
public static String toString(Object object) { | |
return toString(object, defaultShowLog); | |
} | |
public static String toString(Object object, boolean showLog) { | |
if (object == null) return "object is null, might has been GCed"; | |
SimpleArrayMap<Long, Integer> map = getOperationMapForObject(object); | |
StringBuilder stringBuilder = new StringBuilder("showing timer list for " + object.getClass().getSimpleName() + "\n"); | |
for (int i = 0; i < map.size(); i++) { | |
@Operation int operation = map.valueAt(i); | |
stringBuilder.append("Time:" + map.keyAt(i) + " Operation:" + getNameForOperation(operation) + "\n"); | |
} | |
if (showLog) DLog.i(TAG, stringBuilder); | |
return stringBuilder.toString(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment