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

Commit 26ca65d4 authored by Chris Wren's avatar Chris Wren
Browse files

create a metrics log reader for tests

Add parallel logs using the new format for the old MetricsLogger
calls. Deduplicate these after the new reader implementation is
complete.

Test: ./vendor/google/tools/systemui/systemui_test_cookbook.sh
Change-Id: If12e1b66b0ec5ac23bd65e44df4f3c6d6931e722
parent c45df0d7
Loading
Loading
Loading
Loading
+157 −9
Original line number Diff line number Diff line
/*
 * 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;

import android.util.EventLog;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;

@@ -14,16 +30,16 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 */

public class LogBuilder {

    private static final String TAG = "LogBuilder";
    private SparseArray<Object> entries = new SparseArray();

    public LogBuilder(int mainCategory) {
        setCategory(mainCategory);
    }

    public LogBuilder setView(View view) {
        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_VIEW, view.getId());
        return this;
    /* Deserialize from the eventlog */
    public LogBuilder(Object[] items) {
      deserialize(items);
    }

    public LogBuilder setCategory(int category) {
@@ -36,16 +52,48 @@ public class LogBuilder {
        return this;
    }

    public LogBuilder setSubtype(int subtype) {
        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_SUBTYPE, subtype);
        return this;
    }

    public LogBuilder setTimestamp(long timestamp) {
        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_TIMESTAMP, timestamp);
        return this;
    }

    public LogBuilder setPackageName(String packageName) {
        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME, packageName);
        return this;
    }

    public LogBuilder setCounterName(String name) {
        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_NAME, name);
        return this;
    }

    public LogBuilder setCounterBucket(int bucket) {
        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET, bucket);
        return this;
    }

    public LogBuilder setCounterBucket(long bucket) {
        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET, bucket);
        return this;
    }

    public LogBuilder setCounterValue(int value) {
        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_VALUE, value);
        return this;
    }

    /**
     * @param tag From your MetricsEvent enum.
     * @param value One of Integer, Long, Float, String
     * @return
     */
    public LogBuilder addTaggedData(int tag, Object value) {
        if (!(value instanceof Integer ||
            value instanceof String ||
            value instanceof Long ||
            value instanceof Float)) {
        if (isValidValue(value)) {
            throw new IllegalArgumentException(
                    "Value must be loggable type - int, long, float, String");
        }
@@ -53,6 +101,94 @@ public class LogBuilder {
        return this;
    }

    public boolean isValidValue(Object value) {
        return !(value instanceof Integer ||
            value instanceof String ||
            value instanceof Long ||
            value instanceof Float);
    }

    public Object getTaggedData(int tag) {
        return entries.get(tag);
    }

    public int getCategory() {
        Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_CATEGORY);
        if (obj instanceof Integer) {
            return (Integer) obj;
        } else {
            return MetricsEvent.VIEW_UNKNOWN;
        }
    }

    public int getType() {
        Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_TYPE);
        if (obj instanceof Integer) {
            return (Integer) obj;
        } else {
            return MetricsEvent.TYPE_UNKNOWN;
        }
    }

    public int getSubtype() {
        Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_SUBTYPE);
        if (obj instanceof Integer) {
            return (Integer) obj;
        } else {
            return 0;
        }
    }

    public long getTimestamp() {
        Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_TIMESTAMP);
        if (obj instanceof Long) {
            return (Long) obj;
        } else {
            return 0;
        }
    }

    public String getPackageName() {
        Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME);
        if (obj instanceof String) {
            return (String) obj;
        } else {
            return null;
        }
    }

    public String getCounterName() {
        Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_NAME);
        if (obj instanceof String) {
            return (String) obj;
        } else {
            return null;
        }
    }

    public long getCounterBucket() {
        Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET);
        if (obj instanceof Number) {
            return ((Number) obj).longValue();
        } else {
            return 0L;
        }
    }

    public boolean isLongCounterBucket() {
        Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET);
        return obj instanceof Long;
    }

    public int getCounterValue() {
        Object obj = entries.get(MetricsEvent.RESERVED_FOR_LOGBUILDER_VALUE);
        if (obj instanceof Integer) {
            return (Integer) obj;
        } else {
            return 0;
        }
    }

    /**
     * Assemble logs into structure suitable for EventLog.
     */
@@ -64,5 +200,17 @@ public class LogBuilder {
        }
        return out;
    }
}

    public void deserialize(Object[] items) {
        int i = 0;
        while(i < items.length) {
            Object key = items[i++];
            Object value = i < items.length ? items[i++] : null;
            if (key instanceof Integer) {
                entries.put((Integer) key, value);
            } else {
              Log.i(TAG, "Invalid key " + key.toString());
            }
        }
    }
}
+43 −5
Original line number Diff line number Diff line
@@ -17,12 +17,10 @@ package com.android.internal.logging;

import android.content.Context;
import android.os.Build;
import android.util.EventLog;
import android.view.View;

import com.android.internal.logging.nano.MetricsProto.MetricsEvent;


/**
 * Log all the things.
 *
@@ -38,6 +36,10 @@ public class MetricsLogger {
            throw new IllegalArgumentException("Must define metric category");
        }
        EventLogTags.writeSysuiViewVisibility(category, 100);
        EventLogTags.writeSysuiMultiAction(
                new LogBuilder(category)
                        .setType(MetricsEvent.TYPE_OPEN)
                        .serialize());
    }

    public static void hidden(Context context, int category) throws IllegalArgumentException {
@@ -45,6 +47,10 @@ public class MetricsLogger {
            throw new IllegalArgumentException("Must define metric category");
        }
        EventLogTags.writeSysuiViewVisibility(category, 0);
        EventLogTags.writeSysuiMultiAction(
                new LogBuilder(category)
                        .setType(MetricsEvent.TYPE_CLOSE)
                        .serialize());
    }

    public static void visibility(Context context, int category, boolean visibile)
@@ -62,21 +68,38 @@ public class MetricsLogger {
    }

    public static void action(Context context, int category) {
        action(context, category, "");
        EventLogTags.writeSysuiAction(category, "");
        EventLogTags.writeSysuiMultiAction(
                new LogBuilder(category)
                        .setType(MetricsEvent.TYPE_ACTION)
                        .serialize());
    }

    public static void action(Context context, int category, int value) {
        action(context, category, Integer.toString(value));
        EventLogTags.writeSysuiAction(category, Integer.toString(value));
        EventLogTags.writeSysuiMultiAction(
                new LogBuilder(category)
                        .setType(MetricsEvent.TYPE_ACTION)
                        .setSubtype(value)
                        .serialize());
    }

    public static void action(Context context, int category, boolean value) {
        action(context, category, Boolean.toString(value));
        EventLogTags.writeSysuiAction(category, Boolean.toString(value));
        EventLogTags.writeSysuiMultiAction(
                new LogBuilder(category)
                        .setType(MetricsEvent.TYPE_ACTION)
                        .setSubtype(value ? 1 : 0)
                        .serialize());
    }

    public static void action(LogBuilder content) {
        //EventLog.writeEvent(524292, content.serialize());
        // Below would be the *right* way to do this, using the generated
        // EventLogTags method, but that doesn't work.
        if (content.getType() == MetricsEvent.TYPE_UNKNOWN) {
            content.setType(MetricsEvent.TYPE_ACTION);
        }
        EventLogTags.writeSysuiMultiAction(content.serialize());
    }

@@ -86,15 +109,30 @@ public class MetricsLogger {
            throw new IllegalArgumentException("Must define metric category");
        }
        EventLogTags.writeSysuiAction(category, pkg);
        EventLogTags.writeSysuiMultiAction(new LogBuilder(category)
                .setType(MetricsEvent.TYPE_ACTION)
                .setPackageName(pkg)
                .serialize());
    }

    /** Add an integer value to the monotonically increasing counter with the given name. */
    public static void count(Context context, String name, int value) {
        EventLogTags.writeSysuiCount(name, value);
        EventLogTags.writeSysuiMultiAction(
                new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
                        .setCounterName(name)
                        .setCounterValue(value)
                        .serialize());
    }

    /** Increment the bucket with the integer label on the histogram with the given name. */
    public static void histogram(Context context, String name, int bucket) {
        EventLogTags.writeSysuiHistogram(name, bucket);
        EventLogTags.writeSysuiMultiAction(
                new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
                        .setCounterName(name)
                        .setCounterBucket(bucket)
                        .setCounterValue(1)
                        .serialize());
    }
}
+64 −0
Original line number Diff line number Diff line
/*
 * 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;

import com.android.internal.logging.legacy.LegacyConversionLogger;
import com.android.internal.logging.legacy.EventLogCollector;

import java.util.Queue;

/**
 * Read platform logs.
 */
public class MetricsReader {
    private EventLogCollector mReader;
    private Queue<LogBuilder> mEventQueue;
    private long mLastEventMs;
    private long mCheckpointMs;

    /** Open a new session and start reading logs.
     *
     * 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.
     */
    public void read(long startMs) {
        EventLogCollector reader = EventLogCollector.getInstance();
        LegacyConversionLogger logger = new LegacyConversionLogger();
        mLastEventMs = reader.collect(logger, startMs);
        mEventQueue = logger.getEvents();
    }

    public void checkpoint() {
        read(0L);
        mCheckpointMs = mLastEventMs;
        mEventQueue = null;
    }

    public void reset() {
        read(mCheckpointMs);
    }

    /* Does the current log session have another entry? */
    public boolean hasNext() {
        return mEventQueue == null ? false : !mEventQueue.isEmpty();
    }

    /* Next entry in the current log session. */
    public LogBuilder next() {
        return mEventQueue == null ? null : mEventQueue.remove();
    }

}
+54 −0
Original line number Diff line number Diff line
/*
 * 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;

/**
 * Parse the Android counter event logs.
 * @hide
 */
public class CounterParser extends TagParser {
    private static final String TAG = "CounterParser";
    private static final int EVENTLOG_TAG = 524290;

    @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 {
                String name = ((String) operands[0]);
                int value = (Integer) operands[1];
                logCount(logger, name, value);
            } catch (ClassCastException e) {
                if (debug) {
                    Log.d(TAG, "unexpected operand type", e);
                }
            }
        } else if (debug) {
            Log.d(TAG, "wrong number of operands: " + operands.length);
        }
    }

    protected void logCount(TronLogger logger, String name, int value) {
        logger.incrementBy(TronCounters.TRON_AOSP_PREFIX + name, value);
    }
}
+188 −0
Original line number Diff line number Diff line
/*
 * 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 SysuiViewVisibilityParser());
        addParser(new SysuiActionParser());
        addParser(new SysuiQueryParser());
        addParser(new NotificationPanelRevealedParser());
        addParser(new NotificationPanelHiddenParser());
        addParser(new NotificationClickedParser());
        addParser(new NotificationActionClickedParser());
        addParser(new NotificationCanceledParser());
        addParser(new NotificationVisibilityParser());
        addParser(new NotificationAlertParser());
        addParser(new NotificationExpansionParser());
        addParser(new CounterParser());
        addParser(new HistogramParser());
        addParser(new LockscreenGestureParser());
        addParser(new StatusBarStateParser());
        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.readEvents(tags, nativeEvents);
            for (EventLog.Event nativeEvent : nativeEvents) {
                Event event = new Event(nativeEvent);
                events.add(event);
            }
        }
    }
}
Loading