package android.metrics;
import android.annotation.SystemApi;
+import android.util.EventLog;
+import android.util.EventLog.Event;
+import android.util.Log;
-import com.android.internal.logging.legacy.LegacyConversionLogger;
-import com.android.internal.logging.legacy.EventLogCollector;
+import com.android.internal.logging.MetricsLogger;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.Queue;
/**
*/
@SystemApi
public class MetricsReader {
- private EventLogCollector mReader;
- private Queue<LogMaker> mEventQueue;
+ private Queue<LogMaker> mEventQueue = new LinkedList<>();
private long mLastEventMs;
private long mCheckpointMs;
+ private int[] LOGTAGS = { MetricsLogger.LOGTAG };
- /** Open a new session and start reading logs.
+ /**
+ * Read the available logs into a new session.
*
- * Starts reading from the oldest log not already read by this reader object.
- * On first invocation starts from the oldest available log ion the system.
+ * The session will contain events starting from the oldest available
+ * log on the system up to the most recent at the time of this call.
+ *
+ * A call to {@link #checkpoint()} will cause the session to contain
+ * only events that occured after that call.
+ *
+ * This call will not return until the system buffer overflows the
+ * specified timestamp. If the specified timestamp is 0, then the
+ * call will return immediately since any logs 1970 have already been
+ * overwritten (n.b. if the underlying system has the capability to
+ * store many decades of system logs, this call may fail in
+ * interesting ways.)
+ *
+ * @param horizonMs block until this timestamp is overwritten, 0 for non-blocking read.
*/
- public void read(long startMs) {
- EventLogCollector reader = EventLogCollector.getInstance();
- LegacyConversionLogger logger = new LegacyConversionLogger();
- mLastEventMs = reader.collect(logger, startMs);
- mEventQueue = logger.getEvents();
+ public void read(long horizonMs) {
+ ArrayList<Event> nativeEvents = new ArrayList<>();
+ try {
+ EventLog.readEventsOnWrapping(LOGTAGS, horizonMs, nativeEvents);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ mEventQueue.clear();
+ for (EventLog.Event event : nativeEvents) {
+ final long eventTimestampMs = event.getTimeNanos() / 1000000;
+ if (eventTimestampMs > mCheckpointMs) {
+ Object data = event.getData();
+ Object[] objects;
+ if (data instanceof Object[]) {
+ objects = (Object[]) data;
+ } else {
+ // wrap scalar objects
+ objects = new Object[1];
+ objects[0] = data;
+ }
+ mEventQueue.add(new LogMaker(objects)
+ .setTimestamp(eventTimestampMs));
+ mLastEventMs = eventTimestampMs;
+ }
+ }
}
+ /** Cause this session to only contain events that occur after this call. */
public void checkpoint() {
+ // read the log to find the most recent event.
read(0L);
+ // any queued event is now too old, so drop them.
+ mEventQueue.clear();
mCheckpointMs = mLastEventMs;
- mEventQueue = null;
}
+ /**
+ * Rewind the session to the beginning of time and read all available logs.
+ *
+ * A prior call to {@link #checkpoint()} will cause the reader to ignore
+ * any event with a timestamp before the time of that call.
+ *
+ * The underlying log buffer is live: between calls to {@link #reset()}, older
+ * events may be lost from the beginning of the session, and new events may
+ * appear at the end.
+ */
public void reset() {
- read(mCheckpointMs);
+ read(0l);
}
/* Does the current log session have another entry? */
public boolean hasNext() {
- return mEventQueue == null ? false : !mEventQueue.isEmpty();
+ return !mEventQueue.isEmpty();
}
- /* Next entry in the current log session. */
+ /* Return the next entry in the current log session. */
public LogMaker next() {
- return mEventQueue == null ? null : mEventQueue.remove();
+ return mEventQueue.poll();
}
}
*/
public class MetricsLogger {
// define metric categories in frameworks/base/proto/src/metrics_constants.proto.
+ // mirror changes in native version at system/core/libmetricslogger/metrics_logger.cpp
public static final int VIEW_UNKNOWN = MetricsEvent.VIEW_UNKNOWN;
+ public static final int LOGTAG = EventLogTags.SYSUI_MULTI_ACTION;
public static void visible(Context context, int category) throws IllegalArgumentException {
if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
/** Increment the bucket with the integer label on the histogram with the given name. */
public static void histogram(Context context, String name, int bucket) {
+ // see LogHistogram in system/core/libmetricslogger/metrics_logger.cpp
EventLogTags.writeSysuiHistogram(name, bucket);
EventLogTags.writeSysuiMultiAction(
new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
+++ /dev/null
-/*
- * Copyright (C) 2017 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 com.android.internal.logging.legacy;
-
-import android.util.ArrayMap;
-import android.util.EventLog;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-
-/**
- * Scan the event log for interaction metrics events.
- * @hide
- */
-public class EventLogCollector {
- private static final String TAG = "EventLogCollector";
-
- // TODO replace this with GoogleLogTags.TRON_HEARTBEAT
- @VisibleForTesting
- static final int TRON_HEARTBEAT = 208000;
-
- private static EventLogCollector sInstance;
-
- private final ArrayMap<Integer, TagParser> mTagParsers;
- private int[] mInterestingTags;
-
- private LogReader mLogReader;
-
- private EventLogCollector() {
- mTagParsers = new ArrayMap<>();
- addParser(new PowerScreenStateParser());
- addParser(new SysuiMultiActionParser());
-
- mLogReader = new LogReader();
- }
-
- public static EventLogCollector getInstance() {
- if (sInstance == null) {
- sInstance = new EventLogCollector();
- }
- return sInstance;
- }
-
- @VisibleForTesting
- public void setLogReader(LogReader logReader) {
- mLogReader = logReader;
- }
-
- private int[] getInterestingTags() {
- if (mInterestingTags == null) {
- mInterestingTags = new int[mTagParsers.size()];
- for (int i = 0; i < mTagParsers.size(); i++) {
- mInterestingTags[i] = mTagParsers.valueAt(i).getTag();
- }
- }
- return mInterestingTags;
- }
-
- // I would customize ArrayMap to add put(TagParser), but ArrayMap is final.
- @VisibleForTesting
- void addParser(TagParser parser) {
- mTagParsers.put(parser.getTag(), parser);
- mInterestingTags = null;
- }
-
- public void collect(LegacyConversionLogger logger) {
- collect(logger, 0L);
- }
-
- public long collect(TronLogger logger, long lastSeenEventMs) {
- long lastEventMs = 0L;
- final boolean debug = Util.debug();
-
- if (debug) {
- Log.d(TAG, "Eventlog Collection");
- }
- ArrayList<Event> events = new ArrayList<>();
- try {
- mLogReader.readEvents(getInterestingTags(), events);
- } catch (IOException e) {
- e.printStackTrace();
- }
- if (debug) {
- Log.d(TAG, "read this many events: " + events.size());
- }
-
- for (Event event : events) {
- final long millis = event.getTimeNanos() / 1000000;
- if (millis > lastSeenEventMs) {
- final int tag = event.getTag();
- TagParser parser = mTagParsers.get(tag);
- if (parser == null) {
- if (debug) {
- Log.d(TAG, "unknown tag: " + tag);
- }
- continue;
- }
- if (debug) {
- Log.d(TAG, "parsing tag: " + tag);
- }
- parser.parseEvent(logger, event);
- lastEventMs = Math.max(lastEventMs, millis);
- } else {
- if (debug) {
- Log.d(TAG, "old event: " + millis + " < " + lastSeenEventMs);
- }
- }
- }
- return lastEventMs;
- }
-
- @VisibleForTesting
- static class Event {
- long mTimeNanos;
- int mTag;
- Object mData;
-
- Event(long timeNanos, int tag, Object data) {
- super();
- mTimeNanos = timeNanos;
- mTag = tag;
- mData = data;
- }
-
- Event(EventLog.Event event) {
- mTimeNanos = event.getTimeNanos();
- mTag = event.getTag();
- mData = event.getData();
- }
-
- public long getTimeNanos() {
- return mTimeNanos;
- }
-
- public int getTag() {
- return mTag;
- }
-
- public Object getData() {
- return mData;
- }
- }
-
- @VisibleForTesting
- static class LogReader {
- public void readEvents(int[] tags, Collection<Event> events) throws IOException {
- // Testing in Android: the Static Final Class Strikes Back!
- ArrayList<EventLog.Event> nativeEvents = new ArrayList<>();
- EventLog.readEventsOnWrapping(tags, 0L, nativeEvents);
- for (EventLog.Event nativeEvent : nativeEvents) {
- Event event = new Event(nativeEvent);
- events.add(event);
- }
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2017 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 com.android.internal.logging.legacy;
-
-import android.metrics.LogMaker;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.Queue;
-
-/** @hide */
-public class LegacyConversionLogger implements TronLogger {
- private final Queue<LogMaker> mQueue;
- private HashMap<String, Boolean> mConfig;
-
- public LegacyConversionLogger() {
- mQueue = new LinkedList<>();
- }
-
- public Queue<LogMaker> getEvents() {
- return mQueue;
- }
-
- @Override
- public void increment(String counterName) {
- LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
- .setCounterName(counterName)
- .setCounterValue(1);
- mQueue.add(b);
- }
-
- @Override
- public void incrementBy(String counterName, int value) {
- LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
- .setCounterName(counterName)
- .setCounterValue(value);
- mQueue.add(b);
- }
-
- @Override
- public void incrementIntHistogram(String counterName, int bucket) {
- LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
- .setCounterName(counterName)
- .setCounterBucket(bucket)
- .setCounterValue(1);
- mQueue.add(b);
- }
-
- @Override
- public void incrementLongHistogram(String counterName, long bucket) {
- LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
- .setCounterName(counterName)
- .setCounterBucket(bucket)
- .setCounterValue(1);
- mQueue.add(b);
- }
-
- @Override
- public LogMaker obtain() {
- return new LogMaker(MetricsEvent.VIEW_UNKNOWN);
- }
-
- @Override
- public void dispose(LogMaker proto) {
- }
-
- @Override
- public void addEvent(LogMaker proto) {
- mQueue.add(proto);
- }
-
- @Override
- public boolean getConfig(String configName) {
- if (mConfig != null && mConfig.containsKey(configName)) {
- return mConfig.get(configName);
- }
- return false;
- }
-
- @Override
- public void setConfig(String configName, boolean newValue) {
- if (mConfig == null) {
- mConfig = new HashMap<>();
- }
- mConfig.put(configName, newValue);
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2017 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 com.android.internal.logging.legacy;
-
-import android.util.Log;
-
-import android.metrics.LogMaker;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-/**
- * Parse the Android lockscreen gesture logs.
- * @hide
- */
-public class PowerScreenStateParser extends TagParser {
- private static final String TAG = "PowerScreenStateParser";
- private static final int EVENTLOG_TAG = 2728;
-
- // source of truth is android.view.WindowManagerPolicy, why:
- // 0: on
- // 1: OFF_BECAUSE_OF_ADMIN
- // 2: OFF_BECAUSE_OF_USER
- // 3: OFF_BECAUSE_OF_TIMEOUT
-
- @Override
- public int getTag() {
- return EVENTLOG_TAG;
- }
-
- @Override
- public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) {
- final boolean debug = Util.debug();
- if (operands.length >= 2) {
- try {
- // (offOrOn|1|5),(becauseOfUser|1|5),(totalTouchDownTime|2|3),(touchCycles|1|1)
- boolean state = (((Integer) operands[0]).intValue()) == 1;
- int why = ((Integer) operands[1]).intValue();
-
- LogMaker proto = logger.obtain();
- proto.setCategory(MetricsEvent.SCREEN);
- proto.setType(state ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE);
- proto.setTimestamp(eventTimeMs);
- proto.setSubtype(why);
- logger.addEvent(proto);
- } catch (ClassCastException e) {
- if (debug) {
- Log.e(TAG, "unexpected operand type: ", e);
- }
- }
- } else if (debug) {
- Log.w(TAG, "wrong number of operands: " + operands.length);
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2017 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 com.android.internal.logging.legacy;
-
-import android.util.Log;
-
-import android.metrics.LogMaker;
-
-/**
- * ...and one parser to rule them all.
- *
- * This should, at some point in the future, be the only parser.
- * @hide
- */
-public class SysuiMultiActionParser extends TagParser {
- private static final String TAG = "SysuiMultiActionParser";
- private static final int EVENTLOG_TAG = 524292;
-
- @Override
- public int getTag() {
- return EVENTLOG_TAG;
- }
-
- @Override
- public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) {
- final boolean debug = Util.debug();
- try {
- logger.addEvent(new LogMaker(operands).setTimestamp(eventTimeMs));
- } catch (ClassCastException e) {
- if (debug) {
- Log.e(TAG, "unexpected operand type: ", e);
- }
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2017 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 com.android.internal.logging.legacy;
-
-import android.util.Log;
-
-import android.metrics.LogMaker;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-/**
- * Abstraction layer between EventLog static classes and the actual TagParsers.
- * @hide
- */
-public abstract class TagParser {
- private static final String TAG = "TagParser";
-
- protected int mSinceCreationMillis;
- protected int mSinceUpdateMillis;
- protected int mSinceVisibleMillis;
-
- abstract int getTag();
-
- @VisibleForTesting
- abstract public void parseEvent(TronLogger logger, long eventTimeMs, Object[] objects);
-
- /**
- * Parse the event into the proto: return true if proto was modified.
- */
- public void parseEvent(TronLogger logger, EventLogCollector.Event event) {
- final boolean debug = Util.debug();
- Object data = event.getData();
- Object[] objects;
- if (data instanceof Object[]) {
- objects = (Object[]) data;
- for (int i = 0; i < objects.length; i++) {
- if (objects[i] == null) {
- if (debug) {
- Log.d(TAG, "unexpected null value:" + event.getTag());
- }
- return;
- }
- }
- } else {
- // wrap scalar objects
- objects = new Object[1];
- objects[0] = data;
- }
-
- parseEvent(logger, event.getTimeNanos() / 1000000, objects);
- }
-
- protected void resetTimes() {
- mSinceCreationMillis = 0;
- mSinceUpdateMillis = 0;
- mSinceVisibleMillis = 0;
- }
-
- public void parseTimes(Object[] operands, int index) {
- resetTimes();
-
- if (operands.length > index && operands[index] instanceof Integer) {
- mSinceCreationMillis = (Integer) operands[index];
- }
-
- index++;
- if (operands.length > index && operands[index] instanceof Integer) {
- mSinceUpdateMillis = (Integer) operands[index];
- }
-
- index++;
- if (operands.length > index && operands[index] instanceof Integer) {
- mSinceVisibleMillis = (Integer) operands[index];
- }
- }
-
- public void filltimes(LogMaker proto) {
- if (mSinceCreationMillis != 0) {
- proto.addTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS,
- mSinceCreationMillis);
- }
- if (mSinceUpdateMillis != 0) {
- proto.addTaggedData(MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS,
- mSinceUpdateMillis);
- }
- if (mSinceVisibleMillis != 0) {
- proto.addTaggedData(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS,
- mSinceVisibleMillis);
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2017 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 com.android.internal.logging.legacy;
-
-/**
- * Names of the counters that the Tron package defines.
- *
- * Other counter names may be generated by AOSP code.
- * @hide
- */
-public class TronCounters {
-
- static final String TRON_COLLECTIONS = "tron_collections";
-
- static final String TRON_COLLECTION_PERIOD = "tron_collection_period_minutes";
-
- static final String TRON_EVENTLOG_LENGTH = "tron_eventlog_horizon";
-
- static final String TRON_NOTE_DISMISS = "tron_note_dismiss";
-
- static final String TRON_NOTE_DISMISS_BY_USER = "tron_note_dismiss_user";
-
- static final String TRON_NOTE_DISMISS_BY_CLICK = "tron_note_dismiss_click";
-
- static final String TRON_NOTE_DISMISS_BY_LISTENER = "tron_note_dismiss_listener";
-
- static final String TRON_NOTE_DISMISS_BY_BAN = "tron_note_dismiss_ban";
-
- static final String TRON_NOTE_REVEALED = "tron_note_revealed";
-
- static final String TRON_NOTIFICATION_LOAD = "tron_notification_load";
-
- static final String TRON_VIEW = "tron_view";
-
- static final String TRON_ACTION = "tron_action";
-
- static final String TRON_DETAIL = "tron_detail";
-
- static final String TRON_NOTE_LIFETIME = "tron_note_lifetime";
-
- /** Append the AOSP-generated name */
- static final String TRON_AOSP_PREFIX = "tron_varz_";
-
- static final String TRON_ACTION_BAD_INT = "tron_action_bad_int";
-
- static final String TRON_NOTE_FRESHNESS = "tron_note_freshness";
-
- static final String TRON_NOTE_BUZZ = "tron_note_buzz";
-
- static final String TRON_NOTE_BEEP = "tron_note_beep";
-
- static final String TRON_NOTE_BLINK = "tron_note_blink";
-
- static final String TRON_DISABLE = "tron_disable";
-
- static final String TRON_LAST_HEART_AGE = "tron_last_heart_minutes";
-
- static final String TRON_HEARTS_SEEN = "tron_hearts_seen";
-}
+++ /dev/null
-/*
- * Copyright (C) 2017 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 com.android.internal.logging.legacy;
-
-import android.metrics.LogMaker;
-
-/**
- * An entity that knows how to log events and counters.
- */
-public interface TronLogger {
- /** Add one to the named counter. */
- void increment(String counterName);
-
- /** Add an arbitrary value to the named counter. */
- void incrementBy(String counterName, int value);
-
- /** Increment a specified bucket on the named histogram by one. */
- void incrementIntHistogram(String counterName, int bucket);
-
- /** Increment the specified bucket on the named histogram by one. */
- void incrementLongHistogram(String counterName, long bucket);
-
- /** Obtain a SystemUiEvent proto, must release this with dispose() or addEvent(). */
- LogMaker obtain();
-
- void dispose(LogMaker proto);
-
- /** Submit an event to be logged. Logger will dispose of proto. */
- void addEvent(LogMaker proto);
-
- /** Get a config flag. */
- boolean getConfig(String configName);
-
- /** Set a config flag. */
- void setConfig(String configName, boolean newValue);
-}
+++ /dev/null
-/*
- * Copyright (C) 2017 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 com.android.internal.logging.legacy;
-
-/**
- * Created by cwren on 11/21/16.
- */
-public class Util {
- public static boolean debug() {
- return false;
- }
-}