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

Commit 380a0638 authored by Hugo Benichi's avatar Hugo Benichi
Browse files

DefaultNetworkEvent metrics: rehaul

This patch changes instrumentation of default networks and default
network events:
- stop logging events for default network transitions,
  but instead consistently log one event per continuous segment
  when one given network was the default, including logging an
  event for when there is no default network.
- keep a separate rolling buffer of DefaultNetworkEvent for
  dumpsys and bug reports.

These changes allow to simplify post aggregation of default network
event metrics by removing any need to do time series processing.
Instead, metrics and counters can be implemented withouth any ambiguity
by following the recipe:

% of x = sum(duration | x = true) / sum (all durations)

where x can be various conditions such as:
- the default network was validated
- the default network was WiFi
- the default network was IPv6
- there was no default network
- ...

Most importantly, this new logging scheme allows to measure much more
reliably:
- the % of the time that a device had Internet, in the sense that the
default network was validated.
- the time transitions between default networks, keyed by previous and
new transports/link layer, which allows to derive wakelock durations
and wakelock power costs from default network switches.

This patch also simplifies the dumpsys interface of the connmetrics
service and reduces the commands to three:
- "flush" for metrics upload.
- "proto" for printing buffered event in text proto format.
- "list" for listing all events and statistics.

Bug: 34901696
Bug: 65700460
Test: runtest frameworks-net
Change-Id: I0521f1681a60cca07ac3bfd5741d64ce44de4cdd
parent 9009eabb
Loading
Loading
Loading
Loading
+47 −19
Original line number Diff line number Diff line
@@ -20,44 +20,72 @@ import static android.net.ConnectivityManager.NETID_UNSET;

import android.net.NetworkCapabilities;

import com.android.internal.util.BitUtils;

import java.util.StringJoiner;

/**
 * An event recorded by ConnectivityService when there is a change in the default network.
 * {@hide}
 */
public class DefaultNetworkEvent {

    // The ID of the network that has become the new default or NETID_UNSET if none.
    // The creation time in milliseconds of this DefaultNetworkEvent.
    public final long creationTimeMs;
    // The network ID of the network or NETID_UNSET if none.
    public int netId = NETID_UNSET;
    // The list of transport types of the new default network, for example TRANSPORT_WIFI, as
    // defined in NetworkCapabilities.java.
    public int[] transportTypes = new int[0];
    // The ID of the network that was the default before or NETID_UNSET if none.
    public int prevNetId = NETID_UNSET;
    // Whether the previous network had IPv4/IPv6 connectivity.
    public boolean prevIPv4;
    public boolean prevIPv6;
    // The list of transport types, as defined in NetworkCapabilities.java.
    public int transports;
    // The list of transport types of the last previous default network.
    public int previousTransports;
    // Whether the network has IPv4/IPv6 connectivity.
    public boolean ipv4;
    public boolean ipv6;
    // The initial network score when this network became the default network.
    public int initialScore;
    // The initial network score when this network stopped being the default network.
    public int finalScore;
    // The total duration in milliseconds this network was the default network.
    public long durationMs;
    // The total duration in milliseconds this network was the default network and was validated.
    public long validatedMs;

    public DefaultNetworkEvent(long timeMs) {
        creationTimeMs = timeMs;
    }

    /** Update the durationMs of this DefaultNetworkEvent for the given current time. */
    public void updateDuration(long timeMs) {
        durationMs = timeMs - creationTimeMs;
    }

    @Override
    public String toString() {
        String prevNetwork = String.valueOf(prevNetId);
        String newNetwork = String.valueOf(netId);
        if (prevNetId != 0) {
            prevNetwork += ":" + ipSupport();
        StringJoiner j = new StringJoiner(", ", "DefaultNetworkEvent(", ")");
        j.add("netId=" + netId);
        for (int t : BitUtils.unpackBits(transports)) {
            j.add(NetworkCapabilities.transportNameOf(t));
        }
        j.add("ip=" + ipSupport());
        if (initialScore > 0) {
            j.add("initial_score=" + initialScore);
        }
        if (netId != 0) {
            newNetwork += ":" + NetworkCapabilities.transportNamesOf(transportTypes);
        if (finalScore > 0) {
            j.add("final_score=" + finalScore);
        }
        return String.format("DefaultNetworkEvent(%s -> %s)", prevNetwork, newNetwork);
        j.add(String.format("duration=%.0fs", durationMs / 1000.0));
        j.add(String.format("validation=%4.1f%%", (validatedMs * 100.0) / durationMs));
        return j.toString();
    }

    private String ipSupport() {
        if (prevIPv4 && prevIPv6) {
        if (ipv4 && ipv6) {
            return "IPv4v6";
        }
        if (prevIPv6) {
        if (ipv6) {
            return "IPv6";
        }
        if (prevIPv4) {
        if (ipv4) {
            return "IPv4";
        }
        return "NONE";
+25 −11
Original line number Diff line number Diff line
@@ -50,9 +50,10 @@ message Pair {
  optional int32 value = 2;
};

// An event record when the system default network disconnects or the system
// switches to a new default network.
// Next tag: 10.
// An event recorded when the system default network disconnects or the system
// switches to a new default network. An event is also recorded to cover gaps
// without a default network.
// Next tag: 12
message DefaultNetworkEvent {

  // Reason why this network stopped being the default.
@@ -72,26 +73,34 @@ message DefaultNetworkEvent {
  };

  // Duration in milliseconds when this network was the default.
  // Since version 4
  // Since P.
  optional int64 default_network_duration_ms = 5;

  // Duration in milliseconds without a default network before this network
  // became the default.
  // Since version 4
  optional int64 no_default_network_duration_ms = 6;
  // Duration in milliseconds when this default network Internet access was
  // validated. This field is equal to 0 for DefaultNetworkEvents representing
  // lack of a default network.
  // Since P.
  optional int64 validation_duration_ms = 11;

  // Network score of this network when it became the default network.
  // Since version 4
  // Or 0 if this event represents a period without a default network.
  // Since P.
  optional int64 initial_score = 7;

  // Network score of this network when it stopped being the default network.
  // Since version 4
  // Or 0 if this event represents a period without a default network.
  // Since P.
  optional int64 final_score = 8;

  // Best available information about IP support of this default network.
  // Since version 4
  // Or NONE if this event represents a period without a default network.
  // Since P.
  optional IPSupport ip_support = 9;

  // LinkLayer of the previous default network. Ignores any previous period
  // without a default network.
  // Since P
  optional LinkLayer previous_default_network_link_layer = 10;

  // Deprecated fields

@@ -112,6 +121,11 @@ message DefaultNetworkEvent {
  // TRANSPORT_* constants as defined in NetworkCapabilities.
  // Deprecated since version 3. Replaced by top-level transports field.
  repeated int32 transport_types = 4 [deprecated = true];

  // Duration in milliseconds without a default network. This field is non-zero
  // only for DefaultNetworkEvents representing lack of a default network.
  // Since P.
  optional int64 no_default_network_duration_ms = 6 [deprecated = true];
};

// Logs IpReachabilityMonitor probe events and NUD_FAILED events.
+8 −2
Original line number Diff line number Diff line
@@ -2109,9 +2109,14 @@ public class ConnectivityService extends IConnectivityManager.Stub
                        final boolean valid =
                                (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
                        final boolean wasValidated = nai.lastValidated;
                        final boolean wasDefault = isDefaultNetwork(nai);
                        if (DBG) log(nai.name() + " validation " + (valid ? "passed" : "failed") +
                                (msg.obj == null ? "" : " with redirect to " + (String)msg.obj));
                        if (valid != nai.lastValidated) {
                            if (wasDefault) {
                                metricsLogger().defaultNetworkMetrics().logDefaultNetworkValidity(
                                        SystemClock.elapsedRealtime(), valid);
                            }
                            final int oldScore = nai.getCurrentScore();
                            nai.lastValidated = valid;
                            nai.everValidated |= valid;
@@ -2283,7 +2288,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
                // Let rematchAllNetworksAndRequests() below record a new default network event
                // if there is a fallback. Taken together, the two form a X -> 0, 0 -> Y sequence
                // whose timestamps tell how long it takes to recover a default network.
                metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(null, nai);
                long now = SystemClock.elapsedRealtime();
                metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(now, null, nai);
            }
            notifyIfacesChangedForNetworkStats();
            // TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied
@@ -5013,7 +5019,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
            makeDefault(newNetwork);
            // Log 0 -> X and Y -> X default network transitions, where X is the new default.
            metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(
                    newNetwork, oldDefaultNetwork);
                    now, newNetwork, oldDefaultNetwork);
            // Have a new default network, release the transition wakelock in
            scheduleReleaseNetworkTransitionWakelock();
        }
+102 −15
Original line number Diff line number Diff line
@@ -18,9 +18,11 @@ package com.android.server.connectivity;

import android.net.LinkProperties;
import android.net.metrics.DefaultNetworkEvent;
import android.net.metrics.IpConnectivityLog;
import android.os.SystemClock;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.BitUtils;
import com.android.internal.util.RingBuffer;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;

import java.io.PrintWriter;
@@ -35,19 +37,49 @@ public class DefaultNetworkMetrics {

    private static final int ROLLING_LOG_SIZE = 64;

    public final long creationTimeMs = SystemClock.elapsedRealtime();

    // Event buffer used for metrics upload. The buffer is cleared when events are collected.
    @GuardedBy("this")
    private final List<DefaultNetworkEvent> mEvents = new ArrayList<>();

    // Rolling event buffer used for dumpsys and bugreports.
    @GuardedBy("this")
    private final RingBuffer<DefaultNetworkEvent> mEventsLog =
            new RingBuffer(DefaultNetworkEvent.class, ROLLING_LOG_SIZE);

    // Information about the current status of the default network.
    @GuardedBy("this")
    private DefaultNetworkEvent mCurrentDefaultNetwork;
    @GuardedBy("this")
    private boolean mIsCurrentlyValid;
    @GuardedBy("this")
    private long mLastValidationTimeMs;
    // Transport information about the last default network.
    @GuardedBy("this")
    private int mLastTransports;

    public DefaultNetworkMetrics() {
        newDefaultNetwork(creationTimeMs, null);
    }

    public synchronized void listEvents(PrintWriter pw) {
        pw.println("default network events:");
        long localTimeMs = System.currentTimeMillis();
        for (DefaultNetworkEvent ev : mEvents) {
            pw.println(ev);
        long timeMs = SystemClock.elapsedRealtime();
        for (DefaultNetworkEvent ev : mEventsLog.toArray()) {
            printEvent(localTimeMs, pw, ev);
        }
        mCurrentDefaultNetwork.updateDuration(timeMs);
        if (mIsCurrentlyValid) {
            updateValidationTime(timeMs);
            mLastValidationTimeMs = timeMs;
        }
        printEvent(localTimeMs, pw, mCurrentDefaultNetwork);
    }

    public synchronized void listEventsAsProto(PrintWriter pw) {
        for (DefaultNetworkEvent ev : mEvents) {
        for (DefaultNetworkEvent ev : mEventsLog.toArray()) {
            pw.print(IpConnectivityEventBuilder.toProto(ev));
        }
    }
@@ -59,20 +91,75 @@ public class DefaultNetworkMetrics {
        mEvents.clear();
    }

    public synchronized void logDefaultNetworkValidity(long timeMs, boolean isValid) {
        if (!isValid && mIsCurrentlyValid) {
            mIsCurrentlyValid = false;
            updateValidationTime(timeMs);
        }

        if (isValid && !mIsCurrentlyValid) {
            mIsCurrentlyValid = true;
            mLastValidationTimeMs = timeMs;
        }
    }

    private void updateValidationTime(long timeMs) {
        mCurrentDefaultNetwork.validatedMs += timeMs - mLastValidationTimeMs;
    }

    public synchronized void logDefaultNetworkEvent(
            NetworkAgentInfo newNai, NetworkAgentInfo prevNai) {
        DefaultNetworkEvent ev = new DefaultNetworkEvent();
            long timeMs, NetworkAgentInfo newNai, NetworkAgentInfo oldNai) {
        logCurrentDefaultNetwork(timeMs, oldNai);
        newDefaultNetwork(timeMs, newNai);
    }

    private void logCurrentDefaultNetwork(long timeMs, NetworkAgentInfo oldNai) {
        DefaultNetworkEvent ev = mCurrentDefaultNetwork;
        ev.updateDuration(timeMs);
        ev.previousTransports = mLastTransports;
        // oldNai is null if the system had no default network before the transition.
        if (oldNai != null) {
            // The system acquired a new default network.
            fillLinkInfo(ev, oldNai);
            ev.finalScore = oldNai.getCurrentScore();
            ev.validatedMs = ev.durationMs;
        }
        // Only change transport of the previous default network if the event currently logged
        // corresponds to an existing default network, and not to the absence of a default network.
        // This allows to log pairs of transports for successive default networks regardless of
        // whether or not the system experienced a period without any default network.
        if (ev.transports != 0) {
            mLastTransports = ev.transports;
        }
        mEvents.add(ev);
        mEventsLog.append(ev);
    }

    private void newDefaultNetwork(long timeMs, NetworkAgentInfo newNai) {
        DefaultNetworkEvent ev = new DefaultNetworkEvent(timeMs);
        ev.durationMs = timeMs;
        // newNai is null if the system has no default network after the transition.
        if (newNai != null) {
            ev.netId = newNai.network().netId;
            ev.transportTypes = newNai.networkCapabilities.getTransportTypes();
            fillLinkInfo(ev, newNai);
            ev.initialScore = newNai.getCurrentScore();
            if (newNai.lastValidated) {
                mIsCurrentlyValid = true;
                mLastValidationTimeMs = timeMs;
            }
        }
        if (prevNai != null) {
            ev.prevNetId = prevNai.network().netId;
            final LinkProperties lp = prevNai.linkProperties;
            ev.prevIPv4 = lp.hasIPv4Address() && lp.hasIPv4DefaultRoute();
            ev.prevIPv6 = lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute();
        mCurrentDefaultNetwork = ev;
    }

        mEvents.add(ev);
    private static void fillLinkInfo(DefaultNetworkEvent ev, NetworkAgentInfo nai) {
        LinkProperties lp = nai.linkProperties;
        ev.netId = nai.network().netId;
        ev.transports |= BitUtils.packBits(nai.networkCapabilities.getTransportTypes());
        ev.ipv4 |= lp.hasIPv4Address() && lp.hasIPv4DefaultRoute();
        ev.ipv6 |= lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute();
    }

    private static void printEvent(long localTimeMs, PrintWriter pw, DefaultNetworkEvent ev) {
        long localCreationTimeMs = localTimeMs - ev.durationMs;
        pw.println(String.format("%tT.%tL: %s", localCreationTimeMs, localCreationTimeMs, ev));
    }
}
+15 −8
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;

import android.net.ConnectivityManager;
import android.net.ConnectivityMetricsEvent;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.ApfStats;
@@ -135,11 +136,17 @@ final public class IpConnectivityEventBuilder {
    public static IpConnectivityEvent toProto(DefaultNetworkEvent in) {
        IpConnectivityLogClass.DefaultNetworkEvent ev =
                new IpConnectivityLogClass.DefaultNetworkEvent();
        ev.networkId = netIdOf(in.netId);
        ev.previousNetworkId = netIdOf(in.prevNetId);
        ev.transportTypes = in.transportTypes;
        ev.previousNetworkIpSupport = ipSupportOf(in);
        final IpConnectivityEvent out = buildEvent(in.netId, 0, null);
        ev.finalScore = in.finalScore;
        ev.initialScore = in.initialScore;
        ev.ipSupport = ipSupportOf(in);
        ev.defaultNetworkDurationMs = in.durationMs;
        ev.validationDurationMs = in.validatedMs;
        ev.previousDefaultNetworkLinkLayer = transportsToLinkLayer(in.previousTransports);
        final IpConnectivityEvent out = buildEvent(in.netId, in.transports, null);
        if (in.transports == 0) {
            // Set link layer to NONE for events representing the absence of a default network.
            out.linkLayer = IpConnectivityLogClass.NONE;
        }
        out.setDefaultNetworkEvent(ev);
        return out;
    }
@@ -321,13 +328,13 @@ final public class IpConnectivityEventBuilder {
    }

    private static int ipSupportOf(DefaultNetworkEvent in) {
        if (in.prevIPv4 && in.prevIPv6) {
        if (in.ipv4 && in.ipv6) {
            return IpConnectivityLogClass.DefaultNetworkEvent.DUAL;
        }
        if (in.prevIPv6) {
        if (in.ipv6) {
            return IpConnectivityLogClass.DefaultNetworkEvent.IPV6;
        }
        if (in.prevIPv4) {
        if (in.ipv4) {
            return IpConnectivityLogClass.DefaultNetworkEvent.IPV4;
        }
        return IpConnectivityLogClass.DefaultNetworkEvent.NONE;
Loading