Skip to content

Instantly share code, notes, and snippets.

@hereisderek
Created December 7, 2015 03:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hereisderek/35c3b9bdc07d076e0823 to your computer and use it in GitHub Desktop.
Save hereisderek/35c3b9bdc07d076e0823 to your computer and use it in GitHub Desktop.
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