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
diff --git a/core/java/android/net/metrics/DefaultNetworkEvent.java b/core/java/android/net/metrics/DefaultNetworkEvent.java
index eb61c15..8ff8e4f 100644
--- a/core/java/android/net/metrics/DefaultNetworkEvent.java
+++ b/core/java/android/net/metrics/DefaultNetworkEvent.java
@@ -20,44 +20,72 @@
 
 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));
         }
-        if (netId != 0) {
-            newNetwork += ":" + NetworkCapabilities.transportNamesOf(transportTypes);
+        j.add("ip=" + ipSupport());
+        if (initialScore > 0) {
+            j.add("initial_score=" + initialScore);
         }
-        return String.format("DefaultNetworkEvent(%s -> %s)", prevNetwork, newNetwork);
+        if (finalScore > 0) {
+            j.add("final_score=" + finalScore);
+        }
+        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";
diff --git a/proto/src/ipconnectivity.proto b/proto/src/ipconnectivity.proto
index 8dd35af..82a3415 100644
--- a/proto/src/ipconnectivity.proto
+++ b/proto/src/ipconnectivity.proto
@@ -50,9 +50,10 @@
   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 @@
   };
 
   // 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 @@
   // 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.
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 25c96d1..a05e1cc 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2109,9 +2109,14 @@
                         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 @@
                 // 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 @@
             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();
         }
diff --git a/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java
index 8981db1..28c3585 100644
--- a/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java
+++ b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java
@@ -18,9 +18,11 @@
 
 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 @@
 
     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 @@
         mEvents.clear();
     }
 
-    public synchronized void logDefaultNetworkEvent(
-            NetworkAgentInfo newNai, NetworkAgentInfo prevNai) {
-        DefaultNetworkEvent ev = new DefaultNetworkEvent();
-        if (newNai != null) {
-            ev.netId = newNai.network().netId;
-            ev.transportTypes = newNai.networkCapabilities.getTransportTypes();
-        }
-        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();
+    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(
+            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) {
+            fillLinkInfo(ev, newNai);
+            ev.initialScore = newNai.getCurrentScore();
+            if (newNai.lastValidated) {
+                mIsCurrentlyValid = true;
+                mLastValidationTimeMs = timeMs;
+            }
+        }
+        mCurrentDefaultNetwork = 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));
     }
 }
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
index 3d71ecb..a011692b 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -25,6 +25,7 @@
 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 @@
     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 @@
     }
 
     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;
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
index 24217e6..5cc390a 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -45,6 +45,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.function.ToIntFunction;
 
@@ -214,86 +215,66 @@
     }
 
     /**
-     * Clears the event buffer and prints its content as a protobuf serialized byte array
+     * Clear the event buffer and prints its content as a protobuf serialized byte array
      * inside a base64 encoded string.
      */
-    private void cmdFlush(FileDescriptor fd, PrintWriter pw, String[] args) {
+    private void cmdFlush(PrintWriter pw) {
         pw.print(flushEncodedOutput());
     }
 
     /**
-     * Prints the content of the event buffer, either using the events ASCII representation
-     * or using protobuf text format.
+     * Print the content of the rolling event buffer in human readable format.
+     * Also print network dns/connect statistics and recent default network events.
      */
-    private void cmdList(FileDescriptor fd, PrintWriter pw, String[] args) {
-        final ArrayList<ConnectivityMetricsEvent> events;
-        synchronized (mLock) {
-            events = new ArrayList(mBuffer);
-        }
-
-        if (args.length > 1 && args[1].equals("proto")) {
-            for (IpConnectivityEvent ev : IpConnectivityEventBuilder.toProto(events)) {
-                pw.print(ev.toString());
-            }
-            if (mNetdListener != null) {
-                mNetdListener.listAsProtos(pw);
-            }
-            mDefaultNetworkMetrics.listEventsAsProto(pw);
-            return;
-        }
-
+    private void cmdList(PrintWriter pw) {
+        pw.println("metrics events:");
+        final List<ConnectivityMetricsEvent> events = getEvents();
         for (ConnectivityMetricsEvent ev : events) {
             pw.println(ev.toString());
         }
+        pw.println("");
         if (mNetdListener != null) {
             mNetdListener.list(pw);
         }
+        pw.println("");
         mDefaultNetworkMetrics.listEvents(pw);
     }
 
-    /**
-     * Prints for bug reports the content of the rolling event log and the
-     * content of Netd event listener.
+    /*
+     * Print the content of the rolling event buffer in text proto format.
      */
-    private void cmdDumpsys(FileDescriptor fd, PrintWriter pw, String[] args) {
-        final ConnectivityMetricsEvent[] events;
-        synchronized (mLock) {
-            events = mEventLog.toArray();
-        }
-        for (ConnectivityMetricsEvent ev : events) {
-            pw.println(ev.toString());
+    private void cmdListAsProto(PrintWriter pw) {
+        final List<ConnectivityMetricsEvent> events = getEvents();
+        for (IpConnectivityEvent ev : IpConnectivityEventBuilder.toProto(events)) {
+            pw.print(ev.toString());
         }
         if (mNetdListener != null) {
-            mNetdListener.list(pw);
+            mNetdListener.listAsProtos(pw);
         }
-        mDefaultNetworkMetrics.listEvents(pw);
+        mDefaultNetworkMetrics.listEventsAsProto(pw);
     }
 
-    private void cmdStats(FileDescriptor fd, PrintWriter pw, String[] args) {
+    /*
+     * Return a copy of metrics events stored in buffer for metrics uploading.
+     */
+    private List<ConnectivityMetricsEvent> getEvents() {
         synchronized (mLock) {
-            pw.println("Buffered events: " + mBuffer.size());
-            pw.println("Buffer capacity: " + mCapacity);
-            pw.println("Dropped events: " + mDropped);
+            return Arrays.asList(mEventLog.toArray());
         }
-        if (mNetdListener != null) {
-            mNetdListener.dump(pw);
-        }
-    }
-
-    private void cmdDefault(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (args.length == 0) {
-            pw.println("No command");
-            return;
-        }
-        pw.println("Unknown command " + TextUtils.join(" ", args));
     }
 
     public final class Impl extends IIpConnectivityMetrics.Stub {
-        static final String CMD_FLUSH   = "flush";
-        static final String CMD_LIST    = "list";
-        static final String CMD_STATS   = "stats";
-        static final String CMD_DUMPSYS = "-a"; // dumpsys.cpp dumps services with "-a" as arguments
-        static final String CMD_DEFAULT = CMD_STATS;
+        // Dump and flushes the metrics event buffer in base64 encoded serialized proto output.
+        static final String CMD_FLUSH = "flush";
+        // Dump the rolling buffer of metrics event in human readable proto text format.
+        static final String CMD_PROTO = "proto";
+        // Dump the rolling buffer of metrics event and pretty print events using a human readable
+        // format. Also print network dns/connect statistics and default network event time series.
+        static final String CMD_LIST = "list";
+        // By default any other argument will fall into the default case which is remapped to the
+        // "list" command. This includes most notably bug reports collected by dumpsys.cpp with
+        // the "-a" argument.
+        static final String CMD_DEFAULT = CMD_LIST;
 
         @Override
         public int logEvent(ConnectivityMetricsEvent event) {
@@ -308,19 +289,15 @@
             final String cmd = (args.length > 0) ? args[0] : CMD_DEFAULT;
             switch (cmd) {
                 case CMD_FLUSH:
-                    cmdFlush(fd, pw, args);
+                    cmdFlush(pw);
                     return;
-                case CMD_DUMPSYS:
-                    cmdDumpsys(fd, pw, args);
+                case CMD_PROTO:
+                    cmdListAsProto(pw);
                     return;
-                case CMD_LIST:
-                    cmdList(fd, pw, args);
-                    return;
-                case CMD_STATS:
-                    cmdStats(fd, pw, args);
-                    return;
+                case CMD_LIST: // fallthrough
                 default:
-                    cmdDefault(fd, pw, args);
+                    cmdList(pw);
+                    return;
             }
         }
 
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index 05c6e69..61b11e1 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -243,24 +243,21 @@
         mWakeupStats.clear();
     }
 
-    public synchronized void dump(PrintWriter writer) {
-        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
-        pw.println(TAG + ":");
-        pw.increaseIndent();
-        list(pw);
-        pw.decreaseIndent();
-    }
-
     public synchronized void list(PrintWriter pw) {
+        pw.println("dns/connect events:");
         for (int i = 0; i < mNetworkMetrics.size(); i++) {
             pw.println(mNetworkMetrics.valueAt(i).connectMetrics);
         }
         for (int i = 0; i < mNetworkMetrics.size(); i++) {
             pw.println(mNetworkMetrics.valueAt(i).dnsMetrics);
         }
+        pw.println("");
+        pw.println("network statistics:");
         for (NetworkMetricsSnapshot s : getNetworkMetricsSnapshots()) {
             pw.println(s);
         }
+        pw.println("");
+        pw.println("packet wakeup events:");
         for (int i = 0; i < mWakeupStats.size(); i++) {
             pw.println(mWakeupStats.valueAt(i));
         }
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
index ad6ebf9..a65bb24 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -198,37 +198,33 @@
 
     @Test
     public void testDefaultNetworkEventSerialization() {
-        DefaultNetworkEvent ev = new DefaultNetworkEvent();
+        DefaultNetworkEvent ev = new DefaultNetworkEvent(1001);
         ev.netId = 102;
-        ev.prevNetId = 101;
-        ev.transportTypes = new int[]{1, 2, 3};
-        ev.prevIPv4 = true;
-        ev.prevIPv6 = true;
+        ev.transports = 2;
+        ev.previousTransports = 4;
+        ev.ipv4 = true;
+        ev.initialScore = 20;
+        ev.finalScore = 60;
+        ev.durationMs = 54;
+        ev.validatedMs = 27;
 
         String want = String.join("\n",
                 "dropped_events: 0",
                 "events <",
                 "  if_name: \"\"",
-                "  link_layer: 0",
+                "  link_layer: 4",
                 "  network_id: 102",
                 "  time_ms: 0",
-                "  transports: 0",
+                "  transports: 2",
                 "  default_network_event <",
-                "    default_network_duration_ms: 0",
-                "    final_score: 0",
-                "    initial_score: 0",
-                "    ip_support: 0",
-                "    network_id <",
-                "      network_id: 102",
-                "    >",
+                "    default_network_duration_ms: 54",
+                "    final_score: 60",
+                "    initial_score: 20",
+                "    ip_support: 1",
                 "    no_default_network_duration_ms: 0",
-                "    previous_network_id <",
-                "      network_id: 101",
-                "    >",
-                "    previous_network_ip_support: 3",
-                "    transport_types: 1",
-                "    transport_types: 2",
-                "    transport_types: 3",
+                "    previous_default_network_link_layer: 1",
+                "    previous_network_ip_support: 0",
+                "    validation_duration_ms: 27",
                 "  >",
                 ">",
                 "version: 2\n");
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 6c1decc..b48ff8d 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -188,119 +188,99 @@
             {makeNai(102, 50, true, true, cell), makeNai(103, 20, true, false, wifi)},
         };
 
+        long timeMs = mService.mDefaultNetworkMetrics.creationTimeMs;
+        long durationMs = 1001;
         for (NetworkAgentInfo[] pair : defaultNetworks) {
-            mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(pair[1], pair[0]);
+            timeMs += durationMs;
+            durationMs += durationMs;
+            mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs, pair[1], pair[0]);
         }
 
         String want = String.join("\n",
                 "dropped_events: 0",
                 "events <",
                 "  if_name: \"\"",
-                "  link_layer: 0",
-                "  network_id: 100",
-                "  time_ms: 0",
-                "  transports: 0",
-                "  default_network_event <",
-                "    default_network_duration_ms: 0",
-                "    final_score: 0",
-                "    initial_score: 0",
-                "    ip_support: 0",
-                "    network_id <",
-                "      network_id: 100",
-                "    >",
-                "    no_default_network_duration_ms: 0",
-                "    previous_network_id <",
-                "      network_id: 0",
-                "    >",
-                "    previous_network_ip_support: 0",
-                "    transport_types: 0",
-                "  >",
-                ">",
-                "events <",
-                "  if_name: \"\"",
-                "  link_layer: 0",
-                "  network_id: 101",
-                "  time_ms: 0",
-                "  transports: 0",
-                "  default_network_event <",
-                "    default_network_duration_ms: 0",
-                "    final_score: 0",
-                "    initial_score: 0",
-                "    ip_support: 0",
-                "    network_id <",
-                "      network_id: 101",
-                "    >",
-                "    no_default_network_duration_ms: 0",
-                "    previous_network_id <",
-                "      network_id: 100",
-                "    >",
-                "    previous_network_ip_support: 3",
-                "    transport_types: 1",
-                "  >",
-                ">",
-                "events <",
-                "  if_name: \"\"",
-                "  link_layer: 0",
+                "  link_layer: 5",
                 "  network_id: 0",
                 "  time_ms: 0",
                 "  transports: 0",
                 "  default_network_event <",
-                "    default_network_duration_ms: 0",
+                "    default_network_duration_ms: 1001",
                 "    final_score: 0",
                 "    initial_score: 0",
                 "    ip_support: 0",
-                "    network_id <",
-                "      network_id: 0",
-                "    >",
                 "    no_default_network_duration_ms: 0",
-                "    previous_network_id <",
-                "      network_id: 101",
-                "    >",
-                "    previous_network_ip_support: 1",
+                "    previous_default_network_link_layer: 0",
+                "    previous_network_ip_support: 0",
+                "    validation_duration_ms: 0",
                 "  >",
                 ">",
                 "events <",
                 "  if_name: \"\"",
-                "  link_layer: 0",
+                "  link_layer: 2",
+                "  network_id: 100",
+                "  time_ms: 0",
+                "  transports: 1",
+                "  default_network_event <",
+                "    default_network_duration_ms: 2002",
+                "    final_score: 50",
+                "    initial_score: 10",
+                "    ip_support: 3",
+                "    no_default_network_duration_ms: 0",
+                "    previous_default_network_link_layer: 0",
+                "    previous_network_ip_support: 0",
+                "    validation_duration_ms: 2002",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 4",
+                "  network_id: 101",
+                "  time_ms: 0",
+                "  transports: 2",
+                "  default_network_event <",
+                "    default_network_duration_ms: 4004",
+                "    final_score: 60",
+                "    initial_score: 20",
+                "    ip_support: 1",
+                "    no_default_network_duration_ms: 0",
+                "    previous_default_network_link_layer: 2",
+                "    previous_network_ip_support: 0",
+                "    validation_duration_ms: 4004",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 5",
+                "  network_id: 0",
+                "  time_ms: 0",
+                "  transports: 0",
+                "  default_network_event <",
+                "    default_network_duration_ms: 8008",
+                "    final_score: 0",
+                "    initial_score: 0",
+                "    ip_support: 0",
+                "    no_default_network_duration_ms: 0",
+                "    previous_default_network_link_layer: 4",
+                "    previous_network_ip_support: 0",
+                "    validation_duration_ms: 0",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 2",
                 "  network_id: 102",
                 "  time_ms: 0",
-                "  transports: 0",
+                "  transports: 1",
                 "  default_network_event <",
-                "    default_network_duration_ms: 0",
-                "    final_score: 0",
-                "    initial_score: 0",
-                "    ip_support: 0",
-                "    network_id <",
-                "      network_id: 102",
-                "    >",
+                "    default_network_duration_ms: 16016",
+                "    final_score: 50",
+                "    initial_score: 10",
+                "    ip_support: 3",
                 "    no_default_network_duration_ms: 0",
-                "    previous_network_id <",
-                "      network_id: 0",
-                "    >",
+                "    previous_default_network_link_layer: 4",
                 "    previous_network_ip_support: 0",
-                "    transport_types: 0",
-                "  >",
-                ">",
-                "events <",
-                "  if_name: \"\"",
-                "  link_layer: 0",
-                "  network_id: 103",
-                "  time_ms: 0",
-                "  transports: 0",
-                "  default_network_event <",
-                "    default_network_duration_ms: 0",
-                "    final_score: 0",
-                "    initial_score: 0",
-                "    ip_support: 0",
-                "    network_id <",
-                "      network_id: 103",
-                "    >",
-                "    no_default_network_duration_ms: 0",
-                "    previous_network_id <",
-                "      network_id: 102",
-                "    >",
-                "    previous_network_ip_support: 3",
-                "    transport_types: 1",
+                "    validation_duration_ms: 16016",
                 "  >",
                 ">",
                 "version: 2\n");
@@ -379,12 +359,13 @@
         wakeupEvent("wlan0", 10008);
         wakeupEvent("rmnet0", 1000);
 
+        long timeMs = mService.mDefaultNetworkMetrics.creationTimeMs;
         final long cell = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
         final long wifi = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_WIFI});
         NetworkAgentInfo cellNai = makeNai(100, 50, false, true, cell);
         NetworkAgentInfo wifiNai = makeNai(101, 60, true, false, wifi);
-        mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(cellNai, null);
-        mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(wifiNai, cellNai);
+        mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs + 200, cellNai, null);
+        mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs + 300, wifiNai, cellNai);
 
         String want = String.join("\n",
                 "dropped_events: 0",
@@ -473,46 +454,36 @@
                 ">",
                 "events <",
                 "  if_name: \"\"",
-                "  link_layer: 0",
-                "  network_id: 100",
+                "  link_layer: 5",
+                "  network_id: 0",
                 "  time_ms: 0",
                 "  transports: 0",
                 "  default_network_event <",
-                "    default_network_duration_ms: 0",
+                "    default_network_duration_ms: 200",
                 "    final_score: 0",
                 "    initial_score: 0",
                 "    ip_support: 0",
-                "    network_id <",
-                "      network_id: 100",
-                "    >",
                 "    no_default_network_duration_ms: 0",
-                "    previous_network_id <",
-                "      network_id: 0",
-                "    >",
+                "    previous_default_network_link_layer: 0",
                 "    previous_network_ip_support: 0",
-                "    transport_types: 0",
+                "    validation_duration_ms: 0",
                 "  >",
                 ">",
                 "events <",
                 "  if_name: \"\"",
-                "  link_layer: 0",
-                "  network_id: 101",
+                "  link_layer: 2",
+                "  network_id: 100",
                 "  time_ms: 0",
-                "  transports: 0",
+                "  transports: 1",
                 "  default_network_event <",
-                "    default_network_duration_ms: 0",
-                "    final_score: 0",
-                "    initial_score: 0",
-                "    ip_support: 0",
-                "    network_id <",
-                "      network_id: 101",
-                "    >",
+                "    default_network_duration_ms: 100",
+                "    final_score: 50",
+                "    initial_score: 50",
+                "    ip_support: 2",
                 "    no_default_network_duration_ms: 0",
-                "    previous_network_id <",
-                "      network_id: 100",
-                "    >",
-                "    previous_network_ip_support: 2",
-                "    transport_types: 1",
+                "    previous_default_network_link_layer: 0",
+                "    previous_network_ip_support: 0",
+                "    validation_duration_ms: 100",
                 "  >",
                 ">",
                 "events <",
diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 6723601..83194d9 100644
--- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -82,9 +82,8 @@
     public void testWakeupEventLogging() throws Exception {
         final int BUFFER_LENGTH = NetdEventListenerService.WAKEUP_EVENT_BUFFER_LENGTH;
 
-        // Assert no events
-        String[] events1 = listNetdEvent();
-        assertEquals(new String[]{""}, events1);
+        // Baseline without any event
+        String[] baseline = listNetdEvent();
 
         long now = System.currentTimeMillis();
         String prefix = "iface:wlan0";
@@ -93,7 +92,7 @@
             mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, now);
         }
 
-        String[] events2 = listNetdEvent();
+        String[] events2 = remove(listNetdEvent(), baseline);
         int expectedLength2 = uids.length + 1; // +1 for the WakeupStats line
         assertEquals(expectedLength2, events2.length);
         assertContains(events2[0], "WakeupStats");
@@ -111,7 +110,7 @@
             mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, ts);
         }
 
-        String[] events3 = listNetdEvent();
+        String[] events3 = remove(listNetdEvent(), baseline);
         int expectedLength3 = BUFFER_LENGTH + 1; // +1 for the WakeupStats line
         assertEquals(expectedLength3, events3.length);
         assertContains(events2[0], "WakeupStats");
@@ -126,7 +125,7 @@
         uid = 45678;
         mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, now);
 
-        String[] events4 = listNetdEvent();
+        String[] events4 = remove(listNetdEvent(), baseline);
         String lastEvent = events4[events4.length - 1];
         assertContains(lastEvent, "WakeupEvent");
         assertContains(lastEvent, "wlan0");
@@ -423,7 +422,7 @@
         final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null"));
         new Thread(() -> {
             while (System.currentTimeMillis() < stop) {
-                mNetdEventListenerService.dump(pw);
+                mNetdEventListenerService.list(pw);
             }
         }).start();
     }
@@ -461,4 +460,16 @@
     static void assertContains(String got, String want) {
         assertTrue(got + " did not contain \"" + want + "\"", got.contains(want));
     }
+
+    static <T> T[] remove(T[] array, T[] filtered) {
+        List<T> c = Arrays.asList(filtered);
+        int next = 0;
+        for (int i = 0; i < array.length; i++) {
+            if (c.contains(array[i])) {
+                continue;
+            }
+            array[next++] = array[i];
+        }
+        return Arrays.copyOf(array, next);
+    }
 }