public class PageEvent { public static final String TAG = Constants.getTag(PageEvent.class); private final EventType mEventType; private final String mViewSession; private final int[] mPages; private final Orientation mOrientation; private final Clock mClock; private boolean mCollected = false; private boolean mStarted = false; private boolean mStopped = false; private long mStart = 0; private long mStop = 0; private ArrayList<PageEvent> mSubEvents = new ArrayList<PageEvent>(); public static PageEvent view(String viewSession, int[] pages, Orientation orientation, Clock c) { return new PageEvent(EventType.VIEW, viewSession, orientation, pages, c); } public static PageEvent zoom(String viewSession, int[] pages, Orientation orientation, Clock c) { return new PageEvent(EventType.ZOOM, viewSession, orientation, pages, c); } public PageEvent( EventType type, String viewSession, Orientation orientation, int[] pages, Clock c) { mEventType = type; mViewSession = viewSession; mPages = pages; mOrientation = orientation; mClock = c; } /** * Start the timer for this event. This method can be called multiple times, only the first time * will set the start time. */ public void start() { if (!isStarted()) { mStart = mClock.now(); } mStarted = true; } /** * Stop the time for this event. If the event haven't been started yet, the start and stop time * will be the same. */ public void stop() { if (isActive()) { mStop = mClock.now(); } else if (!isStarted()) { long now = mClock.now(); mStart = now; mStop = now; } mStopped = true; } /** * Get the relative duration of this event, this will subtract the duration of sub-events. If the * event is still active, the duration up till this point in time is returned, else it's the * duration from {@link #start()} till {@link #stop()}' * * @return The relative duration of this event */ public long getDuration() { long delta = 0; for (PageEvent e : getSubEvents()) { delta += e.getDuration(); } return getDurationAbsolute() - delta; } /** * Get the absolute duration of this event, this does not subtract duration of sub-events. If the * event is still active, the duration up till this point in time is returned, else it's the * duration from {@link #start()} till {@link #stop()}' * * @return The absolute duration of this event */ public long getDurationAbsolute() { return (isActive() ? mClock.now() : mStop) - mStart; } /** @return {@code true} if the event has been started, but not yet stopped, else {@code false} */ public boolean isActive() { return mStarted && !mStopped; } /** @return {@code true} if the event has been started, else {@code false} */ public boolean isStarted() { return mStarted; } /** @return {@code true} if the event has been stopped, else {@code false} */ public boolean isStopped() { return mStopped; } public void reset() { mStarted = false; mStopped = false; mStart = 0; mStop = 0; for (PageEvent e : mSubEvents) { e.reset(); } mSubEvents.clear(); } public EventType getType() { return mEventType; } public String getViewSession() { return mViewSession; } public int[] getPages() { return mPages; } public long getStart() { return mStart; } public long getStop() { return mStop; } public Clock getClock() { return mClock; } private Orientation getOrientation() { return mOrientation; } public boolean isCollected() { return mCollected; } public void setCollected(boolean collected) { mCollected = collected; } public void addSubEvent(PageEvent e) { mSubEvents.add(e); } public List<PageEvent> getSubEvents() { return mSubEvents; } public List<PageEvent> getSubEventsRecursive() { if (mSubEvents.isEmpty()) { return mSubEvents; } ArrayList<PageEvent> tmp = new ArrayList<PageEvent>(mSubEvents); for (PageEvent e : mSubEvents) { tmp.addAll(e.getSubEventsRecursive()); } return tmp; } @Override public String toString() { return ""; } public JSONObject toDebugJSON() { JSONObject o = toJSON(); try { o.put("start", mStart); o.put("stop", mStop); o.put("clock", mClock.getClass().getSimpleName()); o.put("sub-event-count", mSubEvents.size()); o.put("durationAbs", getDurationAbsolute()); } catch (JSONException e) { SgnLog.d(TAG, e.getMessage(), e); } return o; } public JSONObject toJSON() { try { JSONObject o = new JSONObject(); o.put("type", mEventType.toString()); o.put("ms", getDuration()); o.put("orientation", mOrientation.toString()); o.put("pages", PageflipUtils.join(",", mPages)); o.put("view_session", mViewSession); return o; } catch (JSONException e) { SgnLog.d(TAG, e.getMessage(), e); } return new JSONObject(); } }
public class MemoryCache implements Cache { public static final String TAG = Constants.getTag(MemoryCache.class); /** On average we've measured a Cache.Item from the ETA API to be around 4kb */ private static final int AVG_ITEM_SIZE = 4096; /** Max cache size - init to 1mb */ private int mMaxItems = 256; /** Perceent of cache to remove on cleanup */ private int mPercentToClean = 20; private Map<String, Item> mCache; public MemoryCache() { setLimit(Runtime.getRuntime().maxMemory() / 8); float loadFactor = 1.5f; // Quicker lookup times boolean accessOrder = true; // Enable LRU ordering mCache = Collections.synchronizedMap( new LinkedHashMap<String, Item>(mMaxItems, loadFactor, accessOrder)); } /** * Set the percentage of cache to clean out when memory limit is hit * * @param percentToClean A percentage between 0 and 100 (default is 20) */ public void setCleanLimit(int percentToClean) { if (percentToClean <= 0 || 100 <= percentToClean) { throw new IllegalArgumentException("Percent a number between 0-100"); } mPercentToClean = percentToClean; } /** * Set the limit on memory this Cache may use. * * @param maxMemLimit The limit in bytes */ public void setLimit(long maxMemLimit) { if (maxMemLimit > Runtime.getRuntime().maxMemory()) { throw new IllegalArgumentException("maxMemLimit cannot be more than max heap size"); } mMaxItems = (int) (maxMemLimit / AVG_ITEM_SIZE); SgnLog.v( TAG, "New memory limit: " + maxMemLimit / 1024 + "kb (approx " + mMaxItems + " items)"); } public void put(Request<?> request, Response<?> response) { // If the request is cacheable if (request.getMethod() == Method.GET && request.isCacheable() && !request.isCacheHit() && response.cache != null) { request.addEvent("add-response-to-cache"); synchronized (MemoryCache.class) { mCache.putAll(response.cache); checkSize(); } } } private void checkSize() { int size = mCache.size(); if (size > mMaxItems) { float percentToRemove = (float) mPercentToClean / (float) 100; int itemsToRemove = (int) (size * percentToRemove); // least recently accessed item will be the first one iterated Iterator<Entry<String, Cache.Item>> it = mCache.entrySet().iterator(); while (it.hasNext()) { it.next(); it.remove(); if (itemsToRemove-- == 0) { break; } } SgnLog.d(TAG, "Cleaned " + TAG + " new size: " + mCache.size()); } } public Cache.Item get(String key) { synchronized (MemoryCache.class) { Cache.Item c = mCache.get(key); if (c == null) { return null; } else if (c.isExpired()) { mCache.remove(key); return null; } return c; } } public void clear() { synchronized (MemoryCache.class) { mCache.clear(); } } }