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
This commit is contained in:
Hugo Benichi
2016-06-07 15:35:16 +09:00
parent 4fc3ee5be2
commit 647c86d70a
3 changed files with 200 additions and 15 deletions

View File

@@ -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);

View File

@@ -0,0 +1,103 @@
/*
* 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];
}
};
}

View File

@@ -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;
}
/**