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

Commit 647c86d7 authored by Hugo Benichi's avatar Hugo Benichi
Browse files

Log RA listening statistics

This patch adds a new ApfStats event class that counts RA packet
reception statistics on the RA listener thread of ApfFilter and reports
the maximum program size advertised by hardware.

Statistics are gathered for the lifetime of a network with APF
capabilities and uploaded at network teardown when the listener thread
exits.

Example event:
ConnectivityMetricsEvent(15:44:23.741, 0, 0): ApfStats(284945ms 2048B RA: 2 received, 0 matching, 0 ignored, 0 expired, 0 parse errors, 2 program updates)

Bug: 28204408
Change-Id: Id2eaafdca97f61152a4b66d06061c971bc0aba4c
parent 4fc3ee5b
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -26031,6 +26031,20 @@ package android.net.metrics {
    field public final int programLength;
  }
  public final class ApfStats implements android.os.Parcelable {
    method public int describeContents();
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.net.metrics.ApfStats> CREATOR;
    field public final int droppedRas;
    field public final long durationMs;
    field public final int matchingRas;
    field public final int maxProgramSize;
    field public final int parseErrors;
    field public final int programUpdates;
    field public final int receivedRas;
    field public final int zeroLifetimeRas;
  }
  public final class DefaultNetworkEvent implements android.os.Parcelable {
    method public int describeContents();
    method public static void logEvent(int, int[], int, boolean, boolean);
+103 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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 android.net.metrics;

import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;

/**
 * An event logged for an interface with APF capabilities when its IpManager state machine exits.
 * {@hide}
 */
@SystemApi
public final class ApfStats implements Parcelable {

    public final long durationMs;     // time interval in milliseconds these stastistics covers
    public final int receivedRas;     // number of received RAs
    public final int matchingRas;     // number of received RAs matching a known RA
    public final int droppedRas;      // number of received RAs ignored due to the MAX_RAS limit
    public final int zeroLifetimeRas; // number of received RAs with a minimum lifetime of 0
    public final int parseErrors;     // number of received RAs that could not be parsed
    public final int programUpdates;  // number of APF program updates
    public final int maxProgramSize;  // maximum APF program size advertised by hardware

    /** {@hide} */
    public ApfStats(long durationMs, int receivedRas, int matchingRas, int droppedRas,
            int zeroLifetimeRas, int parseErrors, int programUpdates, int maxProgramSize) {
        this.durationMs = durationMs;
        this.receivedRas = receivedRas;
        this.matchingRas = matchingRas;
        this.droppedRas = droppedRas;
        this.zeroLifetimeRas = zeroLifetimeRas;
        this.parseErrors = parseErrors;
        this.programUpdates = programUpdates;
        this.maxProgramSize = maxProgramSize;
    }

    private ApfStats(Parcel in) {
        this.durationMs = in.readLong();
        this.receivedRas = in.readInt();
        this.matchingRas = in.readInt();
        this.droppedRas = in.readInt();
        this.zeroLifetimeRas = in.readInt();
        this.parseErrors = in.readInt();
        this.programUpdates = in.readInt();
        this.maxProgramSize = in.readInt();
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeLong(durationMs);
        out.writeInt(receivedRas);
        out.writeInt(matchingRas);
        out.writeInt(droppedRas);
        out.writeInt(zeroLifetimeRas);
        out.writeInt(parseErrors);
        out.writeInt(programUpdates);
        out.writeInt(maxProgramSize);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public String toString() {
        return new StringBuilder("ApfStats(")
                .append(String.format("%dms ", durationMs))
                .append(String.format("%dB RA: {", maxProgramSize))
                .append(String.format("%d received, ", receivedRas))
                .append(String.format("%d matching, ", matchingRas))
                .append(String.format("%d dropped, ", droppedRas))
                .append(String.format("%d zero lifetime, ", zeroLifetimeRas))
                .append(String.format("%d parse errors, ", parseErrors))
                .append(String.format("%d program updates})", programUpdates))
                .toString();
    }

    public static final Parcelable.Creator<ApfStats> CREATOR = new Parcelable.Creator<ApfStats>() {
        public ApfStats createFromParcel(Parcel in) {
            return new ApfStats(in);
        }

        public ApfStats[] newArray(int size) {
            return new ApfStats[size];
        }
    };
}
+83 −15
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.net.apf;

import static android.system.OsConstants.*;

import android.os.SystemClock;
import android.net.LinkProperties;
import android.net.NetworkUtils;
import android.net.apf.ApfGenerator;
@@ -25,6 +26,7 @@ import android.net.apf.ApfGenerator.IllegalInstructionException;
import android.net.apf.ApfGenerator.Register;
import android.net.ip.IpManager;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.ApfStats;
import android.net.metrics.IpConnectivityLog;
import android.system.ErrnoException;
import android.system.Os;
@@ -72,6 +74,17 @@ import libcore.io.IoBridge;
 * @hide
 */
public class ApfFilter {

    // Enums describing the outcome of receiving an RA packet.
    private static enum ProcessRaResult {
        MATCH,          // Received RA matched a known RA
        DROPPED,        // Received RA ignored due to MAX_RAS
        PARSE_ERROR,    // Received RA could not be parsed
        ZERO_LIFETIME,  // Received RA had 0 lifetime
        UPDATE_NEW_RA,  // APF program updated for new RA
        UPDATE_EXPIRY   // APF program updated for expiry
    }

    // Thread to listen for RAs.
    @VisibleForTesting
    class ReceiveThread extends Thread {
@@ -79,6 +92,16 @@ public class ApfFilter {
        private final FileDescriptor mSocket;
        private volatile boolean mStopped;

        // Starting time of the RA receiver thread.
        private final long mStart = SystemClock.elapsedRealtime();

        private int mReceivedRas;     // Number of received RAs
        private int mMatchingRas;     // Number of received RAs matching a known RA
        private int mDroppedRas;      // Number of received RAs ignored due to the MAX_RAS limit
        private int mParseErrors;     // Number of received RAs that could not be parsed
        private int mZeroLifetimeRas; // Number of received RAs with a 0 lifetime
        private int mProgramUpdates;  // Number of APF program updates triggered by receiving a RA

        public ReceiveThread(FileDescriptor socket) {
            mSocket = socket;
        }
@@ -97,13 +120,46 @@ public class ApfFilter {
            while (!mStopped) {
                try {
                    int length = Os.read(mSocket, mPacket, 0, mPacket.length);
                    processRa(mPacket, length);
                    updateStats(processRa(mPacket, length));
                } catch (IOException|ErrnoException e) {
                    if (!mStopped) {
                        Log.e(TAG, "Read error", e);
                    }
                }
            }
            logStats();
        }

        private void updateStats(ProcessRaResult result) {
            mReceivedRas++;
            switch(result) {
                case MATCH:
                    mMatchingRas++;
                    return;
                case DROPPED:
                    mDroppedRas++;
                    return;
                case PARSE_ERROR:
                    mParseErrors++;
                    return;
                case ZERO_LIFETIME:
                    mZeroLifetimeRas++;
                    return;
                case UPDATE_EXPIRY:
                    mMatchingRas++;
                    mProgramUpdates++;
                    return;
                case UPDATE_NEW_RA:
                    mProgramUpdates++;
                    return;
            }
        }

        private void logStats() {
            long durationMs = SystemClock.elapsedRealtime() - mStart;
            int maxSize = mApfCapabilities.maximumApfProgramSize;
            mMetricsLog.log(new ApfStats(durationMs, mReceivedRas, mMatchingRas, mDroppedRas,
                     mZeroLifetimeRas, mParseErrors, mProgramUpdates, maxSize));
        }
    }

@@ -216,6 +272,7 @@ public class ApfFilter {
    }

    // Returns seconds since Unix Epoch.
    // TODO: use SystemClock.elapsedRealtime() instead
    private static long curTime() {
        return System.currentTimeMillis() / DateUtils.SECOND_IN_MILLIS;
    }
@@ -809,15 +866,12 @@ public class ApfFilter {
                programMinLifetime, rasToFilter.size(), mRas.size(), program.length, flags));
    }

    // Install a new filter program if the last installed one will die soon.
    @GuardedBy("this")
    private void maybeInstallNewProgramLocked() {
        if (mRas.size() == 0) return;
        // If the current program doesn't expire for a while, don't bother updating.
    /**
     * Returns {@code true} if a new program should be installed because the current one dies soon.
     */
    private boolean shouldInstallnewProgram() {
        long expiry = mLastTimeInstalledProgram + mLastInstalledProgramMinLifetime;
        if (expiry < curTime() + MAX_PROGRAM_LIFETIME_WORTH_REFRESHING) {
            installNewProgramLocked();
        }
        return expiry < curTime() + MAX_PROGRAM_LIFETIME_WORTH_REFRESHING;
    }

    private void hexDump(String msg, byte[] packet, int length) {
@@ -836,7 +890,12 @@ public class ApfFilter {
        }
    }

    private synchronized void processRa(byte[] packet, int length) {
    /**
     * Process an RA packet, updating the list of known RAs and installing a new APF program
     * if the current APF program should be updated.
     * @return a ProcessRaResult enum describing what action was performed.
     */
    private synchronized ProcessRaResult processRa(byte[] packet, int length) {
        if (VDBG) hexDump("Read packet = ", packet, length);

        // Have we seen this RA before?
@@ -858,25 +917,34 @@ public class ApfFilter {
                // Swap to front of array.
                mRas.add(0, mRas.remove(i));

                maybeInstallNewProgramLocked();
                return;
                // If the current program doesn't expire for a while, don't update.
                if (shouldInstallnewProgram()) {
                    installNewProgramLocked();
                    return ProcessRaResult.UPDATE_EXPIRY;
                }
                return ProcessRaResult.MATCH;
            }
        }
        purgeExpiredRasLocked();
        // TODO: figure out how to proceed when we've received more then MAX_RAS RAs.
        if (mRas.size() >= MAX_RAS) return;
        if (mRas.size() >= MAX_RAS) {
            return ProcessRaResult.DROPPED;
        }
        final Ra ra;
        try {
            ra = new Ra(packet, length);
        } catch (Exception e) {
            Log.e(TAG, "Error parsing RA: " + e);
            return;
            return ProcessRaResult.PARSE_ERROR;
        }
        // Ignore 0 lifetime RAs.
        if (ra.isExpired()) return;
        if (ra.isExpired()) {
            return ProcessRaResult.ZERO_LIFETIME;
        }
        log("Adding " + ra);
        mRas.add(ra);
        installNewProgramLocked();
        return ProcessRaResult.UPDATE_NEW_RA;
    }

    /**