Snap for 5761090 from 72e88a79af497b294787c6ba7e232c3bd4f3c137 to qt-c2f2-release

Change-Id: Ibea92fe8f60ff6049c82d9ada661b9b01129e6bd
diff --git a/resolv/Android.bp b/resolv/Android.bp
index 30d5f5a..6819b7a 100644
--- a/resolv/Android.bp
+++ b/resolv/Android.bp
@@ -205,17 +205,22 @@
     srcs: [
         "dns_tls_test.cpp",
         "libnetd_resolv_test.cpp",
+        "res_cache_test.cpp",
     ],
     shared_libs: [
         "libbase",
         "libcrypto",
+        "libcutils",
         "liblog",
         "libssl",
     ],
     static_libs: [
+        "libgmock",
         "libnetd_resolv",
         "libnetd_test_dnsresponder",
         "libnetdutils",
+        "libprotobuf-cpp-lite",
         "server_configurable_flags",
+        "stats_proto",
     ],
 }
diff --git a/resolv/Dns64Configuration.cpp b/resolv/Dns64Configuration.cpp
index 6cd9c2e..a1dfdca 100644
--- a/resolv/Dns64Configuration.cpp
+++ b/resolv/Dns64Configuration.cpp
@@ -33,9 +33,11 @@
 #include "netdutils/BackoffSequence.h"
 #include "netdutils/DumpWriter.h"
 #include "netid_client.h"
+#include "stats.pb.h"
 
 namespace android {
 
+using android::net::NetworkDnsEventReported;
 using netdutils::DumpWriter;
 using netdutils::IPAddress;
 using netdutils::IPPrefix;
@@ -152,8 +154,9 @@
     // ourselves, which means we also bypass all the special netcontext flag
     // handling and the resolver event logging.
     struct addrinfo* res = nullptr;
-    const int status =
-            android_getaddrinfofornetcontext(kIPv4OnlyHost, nullptr, &hints, &netcontext, &res);
+    NetworkDnsEventReported event;
+    const int status = android_getaddrinfofornetcontext(kIPv4OnlyHost, nullptr, &hints, &netcontext,
+                                                        &res, &event);
     ScopedAddrinfo result(res);
     if (status != 0) {
         ALOGW("(%u, %u) plat_prefix/dns(%s) status = %d/%s", cfg->netId, cfg->discoveryId,
diff --git a/resolv/DnsProxyListener.cpp b/resolv/DnsProxyListener.cpp
index 63aa331..8925bc4 100644
--- a/resolv/DnsProxyListener.cpp
+++ b/resolv/DnsProxyListener.cpp
@@ -32,7 +32,6 @@
 #define LOG_TAG "DnsProxyListener"
 
 #include <algorithm>
-#include <list>
 #include <vector>
 
 #include <android-base/stringprintf.h>
@@ -57,6 +56,7 @@
 #include "gethnamaddr.h"
 #include "netd_resolv/stats.h"  // RCODE_TIMEOUT
 #include "res_send.h"
+#include "resolv_cache.h"
 #include "resolv_private.h"
 #include "stats.pb.h"
 
@@ -299,17 +299,34 @@
     return true;
 }
 
+void initDnsEvent(NetworkDnsEventReported* event) {
+    // The value 0 has the special meaning of unset/unknown in Westworld atoms.
+    event->set_hints_ai_flags(-1);
+    event->set_res_nsend_flags(-1);
+}
+
+// Return 0 if the event should not be logged.
+// Otherwise, return subsampling_denom
+uint32_t getDnsEventSubsamplingRate(int netid, int returnCode) {
+    uint32_t subsampling_denom = resolv_cache_get_subsampling_denom(netid, returnCode);
+    if (subsampling_denom == 0) return 0;
+    // Sample the event with a chance of 1 / denom.
+    return (arc4random_uniform(subsampling_denom) == 0) ? subsampling_denom : 0;
+}
+
 void reportDnsEvent(int eventType, const android_net_context& netContext, int latencyUs,
-                    int returnCode, const NetworkDnsEventReported& dnsEvent,
-                    const std::string& query_name, const std::vector<std::string>& ip_addrs = {},
-                    int total_ip_addr_count = 0) {
-    std::string dnsQueryStats = dnsEvent.dns_query_events().SerializeAsString();
-    char const* dnsQueryStatsBytes = dnsQueryStats.c_str();
-    stats::BytesField dnsQueryBytesField{dnsQueryStatsBytes, dnsQueryStats.size()};
-    android::net::stats::stats_write(android::net::stats::NETWORK_DNS_EVENT_REPORTED, eventType,
-                                     returnCode, latencyUs, dnsEvent.hints_ai_flags(),
-                                     dnsEvent.res_nsend_flags(), dnsEvent.network_type(),
-                                     dnsEvent.private_dns_modes(), dnsQueryBytesField);
+                    int returnCode, NetworkDnsEventReported& event, const std::string& query_name,
+                    const std::vector<std::string>& ip_addrs = {}, int total_ip_addr_count = 0) {
+    if (uint32_t rate = getDnsEventSubsamplingRate(netContext.dns_netid, returnCode)) {
+        const std::string& dnsQueryStats = event.dns_query_events().SerializeAsString();
+        stats::BytesField dnsQueryBytesField{dnsQueryStats.c_str(), dnsQueryStats.size()};
+        event.set_return_code(static_cast<ReturnCode>(returnCode));
+        android::net::stats::stats_write(android::net::stats::NETWORK_DNS_EVENT_REPORTED,
+                                         event.event_type(), event.return_code(),
+                                         event.latency_micros(), event.hints_ai_flags(),
+                                         event.res_nsend_flags(), event.network_type(),
+                                         event.private_dns_modes(), dnsQueryBytesField, rate);
+    }
 
     const auto& listeners = ResolverEventReporter::getInstance().getListeners();
     if (listeners.size() == 0) {
@@ -590,7 +607,8 @@
     return true;
 }
 
-void DnsProxyListener::GetAddrInfoHandler::doDns64Synthesis(int32_t* rv, addrinfo** res) {
+void DnsProxyListener::GetAddrInfoHandler::doDns64Synthesis(int32_t* rv, addrinfo** res,
+                                                            NetworkDnsEventReported* event) {
     if (mHost == nullptr) return;
 
     const bool ipv6WantedButNoData = (mHints && mHints->ai_family == AF_INET6 && *rv == EAI_NODATA);
@@ -613,7 +631,8 @@
             mHints->ai_family = AF_INET;
             // Don't need to do freeaddrinfo(res) before starting new DNS lookup because previous
             // DNS lookup is failed with error EAI_NODATA.
-            *rv = android_getaddrinfofornetcontext(mHost, mService, mHints, &mNetContext, res);
+            *rv = android_getaddrinfofornetcontext(mHost, mService, mHints, &mNetContext, res,
+                                                   event);
             queryLimiter.finish(uid);
             if (*rv) {
                 *rv = EAI_NODATA;  // return original error code
@@ -648,9 +667,11 @@
     maybeFixupNetContext(&mNetContext);
     const uid_t uid = mClient->getUid();
     int32_t rv = 0;
-    NetworkDnsEventReported dnsEvent;
+    NetworkDnsEventReported event;
+    initDnsEvent(&event);
     if (queryLimiter.start(uid)) {
-        rv = android_getaddrinfofornetcontext(mHost, mService, mHints, &mNetContext, &result);
+        rv = android_getaddrinfofornetcontext(mHost, mService, mHints, &mNetContext, &result,
+                                              &event);
         queryLimiter.finish(uid);
     } else {
         // Note that this error code is currently not passed down to the client.
@@ -660,8 +681,11 @@
                    << ", max concurrent queries reached";
     }
 
-    doDns64Synthesis(&rv, &result);
-    const int latencyUs = int(s.timeTakenUs());
+    doDns64Synthesis(&rv, &result, &event);
+    const int32_t latencyUs = saturate_cast<int32_t>(s.timeTakenUs());
+    event.set_latency_micros(latencyUs);
+    event.set_event_type(EVENT_GETADDRINFO);
+    event.set_hints_ai_flags((mHints ? mHints->ai_flags : 0));
 
     if (rv) {
         // getaddrinfo failed
@@ -680,8 +704,8 @@
     }
     std::vector<std::string> ip_addrs;
     const int total_ip_addr_count = extractGetAddrInfoAnswers(result, &ip_addrs);
-    reportDnsEvent(INetdEventListener::EVENT_GETADDRINFO, mNetContext, latencyUs, rv, dnsEvent,
-                   mHost, ip_addrs, total_ip_addr_count);
+    reportDnsEvent(INetdEventListener::EVENT_GETADDRINFO, mNetContext, latencyUs, rv, event, mHost,
+                   ip_addrs, total_ip_addr_count);
     freeaddrinfo(result);
     mClient->decRef();
 }
@@ -854,10 +878,11 @@
     // Send DNS query
     std::vector<uint8_t> ansBuf(MAXPACKET, 0);
     int arcode, nsendAns = -1;
-    NetworkDnsEventReported dnsEvent;
+    NetworkDnsEventReported event;
+    initDnsEvent(&event);
     if (queryLimiter.start(uid)) {
         nsendAns = resolv_res_nsend(&mNetContext, msg.data(), msgLen, ansBuf.data(), MAXPACKET,
-                                    &arcode, static_cast<ResNsendFlags>(mFlags));
+                                    &arcode, static_cast<ResNsendFlags>(mFlags), &event);
         queryLimiter.finish(uid);
     } else {
         LOG(WARNING) << "ResNSendHandler::run: resnsend: from UID " << uid
@@ -865,14 +890,17 @@
         nsendAns = -EBUSY;
     }
 
-    const int latencyUs = int(s.timeTakenUs());
+    const int32_t latencyUs = saturate_cast<int32_t>(s.timeTakenUs());
+    event.set_latency_micros(latencyUs);
+    event.set_event_type(EVENT_RES_NSEND);
+    event.set_res_nsend_flags(static_cast<ResNsendFlags>(mFlags));
 
     // Fail, send -errno
     if (nsendAns < 0) {
         sendBE32(mClient, nsendAns);
         if (rr_type == ns_t_a || rr_type == ns_t_aaaa) {
             reportDnsEvent(INetdEventListener::EVENT_RES_NSEND, mNetContext, latencyUs,
-                           resNSendToAiError(nsendAns, arcode), dnsEvent, rr_name);
+                           resNSendToAiError(nsendAns, arcode), event, rr_name);
         }
         return;
     }
@@ -895,7 +923,7 @@
         const int total_ip_addr_count =
                 extractResNsendAnswers((uint8_t*) ansBuf.data(), nsendAns, rr_type, &ip_addrs);
         reportDnsEvent(INetdEventListener::EVENT_RES_NSEND, mNetContext, latencyUs,
-                       resNSendToAiError(nsendAns, arcode), dnsEvent, rr_name, ip_addrs,
+                       resNSendToAiError(nsendAns, arcode), event, rr_name, ip_addrs,
                        total_ip_addr_count);
     }
 }
@@ -994,7 +1022,8 @@
     free(mName);
 }
 
-void DnsProxyListener::GetHostByNameHandler::doDns64Synthesis(int32_t* rv, struct hostent** hpp) {
+void DnsProxyListener::GetHostByNameHandler::doDns64Synthesis(int32_t* rv, struct hostent** hpp,
+                                                              NetworkDnsEventReported* event) {
     // Don't have to consider family AF_UNSPEC case because gethostbyname{, 2} only supports
     // family AF_INET or AF_INET6.
     const bool ipv6WantedButNoData = (mAf == AF_INET6 && *rv == EAI_NODATA);
@@ -1011,7 +1040,7 @@
     // If caller wants IPv6 answers but no data, try to query IPv4 answers for synthesis
     const uid_t uid = mClient->getUid();
     if (queryLimiter.start(uid)) {
-        *rv = android_gethostbynamefornetcontext(mName, AF_INET, &mNetContext, hpp);
+        *rv = android_gethostbynamefornetcontext(mName, AF_INET, &mNetContext, hpp, event);
         queryLimiter.finish(uid);
         if (*rv) {
             *rv = EAI_NODATA;  // return original error code
@@ -1035,9 +1064,10 @@
     const uid_t uid = mClient->getUid();
     hostent* hp = nullptr;
     int32_t rv = 0;
-    NetworkDnsEventReported dnsEvent;
+    NetworkDnsEventReported event;
+    initDnsEvent(&event);
     if (queryLimiter.start(uid)) {
-        rv = android_gethostbynamefornetcontext(mName, mAf, &mNetContext, &hp);
+        rv = android_gethostbynamefornetcontext(mName, mAf, &mNetContext, &hp, &event);
         queryLimiter.finish(uid);
     } else {
         rv = EAI_MEMORY;
@@ -1045,8 +1075,11 @@
                    << ", max concurrent queries reached";
     }
 
-    doDns64Synthesis(&rv, &hp);
-    const int latencyUs = lround(s.timeTakenUs());
+    doDns64Synthesis(&rv, &hp, &event);
+    const int32_t latencyUs = saturate_cast<int32_t>(s.timeTakenUs());
+    event.set_latency_micros(latencyUs);
+    event.set_event_type(EVENT_GETHOSTBYNAME);
+
     LOG(DEBUG) << "GetHostByNameHandler::run: errno: " << (hp ? "success" : strerror(errno));
 
     bool success = true;
@@ -1064,7 +1097,7 @@
 
     std::vector<std::string> ip_addrs;
     const int total_ip_addr_count = extractGetHostByNameAnswers(hp, &ip_addrs);
-    reportDnsEvent(INetdEventListener::EVENT_GETHOSTBYNAME, mNetContext, latencyUs, rv, dnsEvent,
+    reportDnsEvent(INetdEventListener::EVENT_GETHOSTBYNAME, mNetContext, latencyUs, rv, event,
                    mName, ip_addrs, total_ip_addr_count);
     mClient->decRef();
 }
@@ -1134,7 +1167,8 @@
     free(mAddress);
 }
 
-void DnsProxyListener::GetHostByAddrHandler::doDns64ReverseLookup(struct hostent** hpp) {
+void DnsProxyListener::GetHostByAddrHandler::doDns64ReverseLookup(struct hostent** hpp,
+                                                                  NetworkDnsEventReported* event) {
     if (*hpp != nullptr || mAddressFamily != AF_INET6 || !mAddress) {
         return;
     }
@@ -1162,7 +1196,8 @@
     if (queryLimiter.start(uid)) {
         // Remove NAT64 prefix and do reverse DNS query
         struct in_addr v4addr = {.s_addr = v6addr.s6_addr32[3]};
-        android_gethostbyaddrfornetcontext(&v4addr, sizeof(v4addr), AF_INET, &mNetContext, hpp);
+        android_gethostbyaddrfornetcontext(&v4addr, sizeof(v4addr), AF_INET, &mNetContext, hpp,
+                                           event);
         queryLimiter.finish(uid);
         if (*hpp) {
             // Replace IPv4 address with original queried IPv6 address in place. The space has
@@ -1184,10 +1219,11 @@
     const uid_t uid = mClient->getUid();
     hostent* hp = nullptr;
     int32_t rv = 0;
-    NetworkDnsEventReported dnsEvent;
+    NetworkDnsEventReported event;
+    initDnsEvent(&event);
     if (queryLimiter.start(uid)) {
-        rv = android_gethostbyaddrfornetcontext(mAddress, mAddressLen, mAddressFamily,
-                                                &mNetContext, &hp);
+        rv = android_gethostbyaddrfornetcontext(mAddress, mAddressLen, mAddressFamily, &mNetContext,
+                                                &hp, &event);
         queryLimiter.finish(uid);
     } else {
         rv = EAI_MEMORY;
@@ -1195,8 +1231,10 @@
                    << ", max concurrent queries reached";
     }
 
-    doDns64ReverseLookup(&hp);
-    const int latencyUs = int(s.timeTakenUs());
+    doDns64ReverseLookup(&hp, &event);
+    const int32_t latencyUs = saturate_cast<int32_t>(s.timeTakenUs());
+    event.set_latency_micros(latencyUs);
+    event.set_event_type(EVENT_GETHOSTBYADDR);
 
     LOG(DEBUG) << "GetHostByAddrHandler::run: result: " << (hp ? "success" : gai_strerror(rv));
 
@@ -1212,7 +1250,7 @@
         LOG(WARNING) << "GetHostByAddrHandler::run: Error writing DNS result to client";
     }
 
-    reportDnsEvent(INetdEventListener::EVENT_GETHOSTBYADDR, mNetContext, latencyUs, rv, dnsEvent,
+    reportDnsEvent(INetdEventListener::EVENT_GETHOSTBYADDR, mNetContext, latencyUs, rv, event,
                    (hp && hp->h_name) ? hp->h_name : "null", {}, 0);
     mClient->decRef();
 }
diff --git a/resolv/DnsProxyListener.h b/resolv/DnsProxyListener.h
index 423e77f..9b71bc5 100644
--- a/resolv/DnsProxyListener.h
+++ b/resolv/DnsProxyListener.h
@@ -28,6 +28,8 @@
 namespace android {
 namespace net {
 
+class NetworkDnsEventReported;
+
 class DnsProxyListener : public FrameworkListener {
   public:
     DnsProxyListener();
@@ -54,7 +56,7 @@
         void run();
 
       private:
-        void doDns64Synthesis(int32_t* rv, addrinfo** res);
+        void doDns64Synthesis(int32_t* rv, addrinfo** res, NetworkDnsEventReported* event);
 
         SocketClient* mClient;  // ref counted
         char* mHost;            // owned. TODO: convert to std::string.
@@ -80,7 +82,7 @@
         void run();
 
       private:
-        void doDns64Synthesis(int32_t* rv, hostent** hpp);
+        void doDns64Synthesis(int32_t* rv, hostent** hpp, NetworkDnsEventReported* event);
 
         SocketClient* mClient;  // ref counted
         char* mName;            // owned. TODO: convert to std::string.
@@ -105,7 +107,7 @@
         void run();
 
       private:
-        void doDns64ReverseLookup(hostent** hpp);
+        void doDns64ReverseLookup(hostent** hpp, NetworkDnsEventReported* event);
 
         SocketClient* mClient;  // ref counted
         void* mAddress;         // address to lookup; owned
diff --git a/resolv/DnsTlsDispatcher.cpp b/resolv/DnsTlsDispatcher.cpp
index d9896ad..baafa53 100644
--- a/resolv/DnsTlsDispatcher.cpp
+++ b/resolv/DnsTlsDispatcher.cpp
@@ -18,13 +18,17 @@
 //#define LOG_NDEBUG 0
 
 #include "DnsTlsDispatcher.h"
+#include <netdutils/Stopwatch.h>
 #include "DnsTlsSocketFactory.h"
+#include "resolv_private.h"
+#include "stats.pb.h"
 
 #include "log/log.h"
 
 namespace android {
 namespace net {
 
+using android::netdutils::Stopwatch;
 using netdutils::Slice;
 
 // static
@@ -82,29 +86,45 @@
     return out;
 }
 
-DnsTlsTransport::Response DnsTlsDispatcher::query(
-        const std::list<DnsTlsServer> &tlsServers, unsigned mark,
-        const Slice query, const Slice ans, int *resplen) {
-    const std::list<DnsTlsServer> orderedServers(getOrderedServerList(tlsServers, mark));
+DnsTlsTransport::Response DnsTlsDispatcher::query(const std::list<DnsTlsServer>& tlsServers,
+                                                  res_state statp, const Slice query,
+                                                  const Slice ans, int* resplen) {
+    const std::list<DnsTlsServer> orderedServers(getOrderedServerList(tlsServers, statp->_mark));
 
     if (orderedServers.empty()) ALOGW("Empty DnsTlsServer list");
 
     DnsTlsTransport::Response code = DnsTlsTransport::Response::internal_error;
+    int serverCount = 0;
     for (const auto& server : orderedServers) {
-        code = this->query(server, mark, query, ans, resplen);
+        DnsQueryEvent* dnsQueryEvent =
+                statp->event->mutable_dns_query_events()->add_dns_query_event();
+        dnsQueryEvent->set_rcode(NS_R_INTERNAL_ERROR);
+        Stopwatch query_stopwatch;
+        code = this->query(server, statp->_mark, query, ans, resplen);
+
+        dnsQueryEvent->set_latency_micros(saturate_cast<int32_t>(query_stopwatch.timeTakenUs()));
+        dnsQueryEvent->set_dns_server_index(serverCount++);
+        dnsQueryEvent->set_ip_version(ipFamilyToIPVersion(server.ss.ss_family));
+        dnsQueryEvent->set_protocol(PROTO_DOT);
+        dnsQueryEvent->set_type(getQueryType(query.base(), query.size()));
+
         switch (code) {
             // These response codes are valid responses and not expected to
             // change if another server is queried.
             case DnsTlsTransport::Response::success:
+                dnsQueryEvent->set_rcode(
+                        static_cast<NsRcode>(reinterpret_cast<HEADER*>(ans.base())->rcode));
+                [[fallthrough]];
             case DnsTlsTransport::Response::limit_error:
                 return code;
-                break;
             // These response codes might differ when trying other servers, so
             // keep iterating to see if we can get a different (better) result.
             case DnsTlsTransport::Response::network_error:
+                // Sync from res_tls_send in res_send.cpp
+                dnsQueryEvent->set_rcode(NS_R_TIMEOUT);
+                [[fallthrough]];
             case DnsTlsTransport::Response::internal_error:
                 continue;
-                break;
             // No "default" statement.
         }
     }
diff --git a/resolv/DnsTlsDispatcher.h b/resolv/DnsTlsDispatcher.h
index 7a48089..b8e9968 100644
--- a/resolv/DnsTlsDispatcher.h
+++ b/resolv/DnsTlsDispatcher.h
@@ -28,6 +28,7 @@
 #include "DnsTlsServer.h"
 #include "DnsTlsTransport.h"
 #include "IDnsTlsSocketFactory.h"
+#include "resolv_private.h"
 
 namespace android {
 namespace net {
@@ -48,9 +49,9 @@
     // the count of bytes written in |resplen|. Returns a success or error code.
     // The order in which servers from |tlsServers| are queried may not be the
     // order passed in by the caller.
-    DnsTlsTransport::Response query(const std::list<DnsTlsServer>& tlsServers, unsigned mark,
-                                    const netdutils::Slice query, const netdutils::Slice ans,
-                                    int* _Nonnull resplen);
+    DnsTlsTransport::Response query(const std::list<DnsTlsServer>& tlsServers,
+                                    res_state _Nonnull statp, const netdutils::Slice query,
+                                    const netdutils::Slice ans, int* _Nonnull resplen);
 
     // Given a |query|, sends it to the server on the network indicated by |mark|,
     // and writes the response into |ans|,  and indicates
diff --git a/resolv/ResolverController.cpp b/resolv/ResolverController.cpp
index e16ca69..ac24259 100644
--- a/resolv/ResolverController.cpp
+++ b/resolv/ResolverController.cpp
@@ -317,6 +317,8 @@
         if (servers.empty()) {
             dw.println("No DNS servers defined");
         } else {
+            dw.println("DnsEvent subsampling map: " +
+                       android::base::Join(resolv_cache_dump_subsampling_map(netId), ' '));
             dw.println(
                     "DNS servers: # IP (total, successes, errors, timeouts, internal errors, "
                     "RTT avg, last sample)");
@@ -357,8 +359,7 @@
         mDns64Configuration.dump(dw, netId);
         ExternalPrivateDnsStatus privateDnsStatus = {PrivateDnsMode::OFF, 0, {}};
         gPrivateDnsConfiguration.getStatus(netId, &privateDnsStatus);
-        dw.println("Private DNS mode: %s",
-                   getPrivateDnsModeString(static_cast<PrivateDnsMode>(privateDnsStatus.mode)));
+        dw.println("Private DNS mode: %s", getPrivateDnsModeString(privateDnsStatus.mode));
         if (!privateDnsStatus.numServers) {
             dw.println("No Private DNS servers configured");
         } else {
diff --git a/resolv/getaddrinfo.cpp b/resolv/getaddrinfo.cpp
index 1d5038e..bb9ef07 100644
--- a/resolv/getaddrinfo.cpp
+++ b/resolv/getaddrinfo.cpp
@@ -63,6 +63,8 @@
 
 #define ANY 0
 
+using android::net::NetworkDnsEventReported;
+
 const char in_addrany[] = {0, 0, 0, 0};
 const char in_loopback[] = {127, 0, 0, 1};
 const char in6_addrany[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
@@ -125,7 +127,7 @@
 
 static int str2number(const char*);
 static int explore_fqdn(const struct addrinfo*, const char*, const char*, struct addrinfo**,
-                        const struct android_net_context*);
+                        const struct android_net_context*, NetworkDnsEventReported* event);
 static int explore_null(const struct addrinfo*, const char*, struct addrinfo**);
 static int explore_numeric(const struct addrinfo*, const char*, const char*, struct addrinfo**,
                            const char*);
@@ -141,7 +143,8 @@
 static struct addrinfo* getanswer(const querybuf*, int, const char*, int, const struct addrinfo*,
                                   int* herrno);
 static int dns_getaddrinfo(const char* name, const addrinfo* pai,
-                           const android_net_context* netcontext, addrinfo** rv);
+                           const android_net_context* netcontext, addrinfo** rv,
+                           NetworkDnsEventReported* event);
 static void _sethtent(FILE**);
 static void _endhtent(FILE**);
 static struct addrinfo* _gethtent(FILE**, const char*, const struct addrinfo*);
@@ -265,13 +268,15 @@
             .dns_mark = MARK_UNSET,
             .uid = NET_CONTEXT_INVALID_UID,
     };
-    return android_getaddrinfofornetcontext(hostname, servname, &hints, &netcontext, result);
+    NetworkDnsEventReported event;
+    return android_getaddrinfofornetcontext(hostname, servname, &hints, &netcontext, result,
+                                            &event);
 }
 
 int android_getaddrinfofornetcontext(const char* hostname, const char* servname,
                                      const struct addrinfo* hints,
                                      const struct android_net_context* netcontext,
-                                     struct addrinfo** res) {
+                                     struct addrinfo** res, NetworkDnsEventReported* event) {
     struct addrinfo sentinel = {};
     struct addrinfo* cur = &sentinel;
     int error = 0;
@@ -281,6 +286,7 @@
     // hints is allowed to be nullptr
     assert(res != nullptr);
     assert(netcontext != nullptr);
+    assert(event != nullptr);
 
     struct addrinfo ai = {
             .ai_flags = 0,
@@ -413,7 +419,7 @@
 
             LOG(DEBUG) << __func__ << ": explore_fqdn(): ai_family=" << tmp.ai_family
                        << " ai_socktype=" << tmp.ai_socktype << " ai_protocol=" << tmp.ai_protocol;
-            error = explore_fqdn(&tmp, hostname, servname, &cur->ai_next, netcontext);
+            error = explore_fqdn(&tmp, hostname, servname, &cur->ai_next, netcontext, event);
 
             while (cur->ai_next) cur = cur->ai_next;
         }
@@ -436,7 +442,8 @@
 
 // FQDN hostname, DNS lookup
 static int explore_fqdn(const struct addrinfo* pai, const char* hostname, const char* servname,
-                        struct addrinfo** res, const struct android_net_context* netcontext) {
+                        struct addrinfo** res, const struct android_net_context* netcontext,
+                        NetworkDnsEventReported* event) {
     struct addrinfo* result;
     int error = 0;
 
@@ -451,7 +458,7 @@
     if (get_portmatch(pai, servname) != 0) return 0;
 
     if (!files_getaddrinfo(hostname, pai, &result)) {
-        error = dns_getaddrinfo(hostname, pai, netcontext, &result);
+        error = dns_getaddrinfo(hostname, pai, netcontext, &result, event);
     }
     if (!error) {
         struct addrinfo* cur;
@@ -1379,7 +1386,8 @@
 }
 
 static int dns_getaddrinfo(const char* name, const addrinfo* pai,
-                           const android_net_context* netcontext, addrinfo** rv) {
+                           const android_net_context* netcontext, addrinfo** rv,
+                           NetworkDnsEventReported* event) {
     res_target q = {};
     res_target q2 = {};
 
@@ -1441,7 +1449,7 @@
      * fully populate the thread private data here, but if we get down there
      * and have a cache hit that would be wasted, so we do the rest there on miss
      */
-    res_setnetcontext(res, netcontext);
+    res_setnetcontext(res, netcontext, event);
 
     int he;
     if (res_searchN(name, &q, res, &he) < 0) {
diff --git a/resolv/getaddrinfo.h b/resolv/getaddrinfo.h
index cf54cab..e8ba5dc 100644
--- a/resolv/getaddrinfo.h
+++ b/resolv/getaddrinfo.h
@@ -17,9 +17,11 @@
 #pragma once
 
 #include "netd_resolv/resolv.h"  // struct android_net_context
+#include "stats.pb.h"
 
 struct addrinfo;
 
 // This is the DNS proxy entry point for getaddrinfo().
 int android_getaddrinfofornetcontext(const char*, const char*, const addrinfo*,
-                                     const android_net_context*, addrinfo**);
+                                     const android_net_context*, addrinfo**,
+                                     android::net::NetworkDnsEventReported*);
diff --git a/resolv/gethnamaddr.cpp b/resolv/gethnamaddr.cpp
index 1cf0694..a40a7b7 100644
--- a/resolv/gethnamaddr.cpp
+++ b/resolv/gethnamaddr.cpp
@@ -78,6 +78,9 @@
 #include "netd_resolv/resolv.h"
 #include "resolv_cache.h"
 #include "resolv_private.h"
+#include "stats.pb.h"
+
+using android::net::NetworkDnsEventReported;
 
 // NetBSD uses _DIAGASSERT to null-check arguments and the like,
 // but it's clear from the number of mistakes in their assertions
@@ -116,19 +119,22 @@
 static void addrsort(char**, int, res_state);
 
 static int dns_gethtbyaddr(const unsigned char* uaddr, int len, int af,
-                           const android_net_context* netcontext, getnamaddr* info);
+                           const android_net_context* netcontext, getnamaddr* info,
+                           NetworkDnsEventReported* event);
 static int dns_gethtbyname(const char* name, int af, getnamaddr* info);
 
 static int gethostbyname_internal(const char* name, int af, res_state res, hostent* hp, char* hbuf,
-                                  size_t hbuflen, const android_net_context* netcontext);
+                                  size_t hbuflen, const android_net_context* netcontext,
+                                  NetworkDnsEventReported* event);
 static int gethostbyname_internal_real(const char* name, int af, res_state res, hostent* hp,
                                        char* buf, size_t buflen);
 static int android_gethostbyaddrfornetcontext_proxy_internal(const void*, socklen_t, int,
                                                              struct hostent*, char*, size_t,
-                                                             const struct android_net_context*);
+                                                             const struct android_net_context*,
+                                                             NetworkDnsEventReported* event);
 static int android_gethostbyaddrfornetcontext_proxy(const void* addr, socklen_t len, int af,
                                                     const struct android_net_context* netcontext,
-                                                    hostent** hp);
+                                                    hostent** hp, NetworkDnsEventReported* event);
 
 #define BOUNDED_INCR(x)      \
     do {                     \
@@ -494,14 +500,16 @@
 
 // very similar in proxy-ness to android_getaddrinfo_proxy
 static int gethostbyname_internal(const char* name, int af, res_state res, hostent* hp, char* hbuf,
-                                  size_t hbuflen, const android_net_context* netcontext) {
-    res_setnetcontext(res, netcontext);
+                                  size_t hbuflen, const android_net_context* netcontext,
+                                  NetworkDnsEventReported* event) {
+    res_setnetcontext(res, netcontext, event);
     return gethostbyname_internal_real(name, af, res, hp, hbuf, hbuflen);
 }
 
 static int android_gethostbyaddrfornetcontext_real(const void* addr, socklen_t len, int af,
                                                    struct hostent* hp, char* buf, size_t buflen,
-                                                   const struct android_net_context* netcontext) {
+                                                   const struct android_net_context* netcontext,
+                                                   NetworkDnsEventReported* event) {
     const u_char* uaddr = (const u_char*) addr;
     socklen_t size;
     struct getnamaddr info;
@@ -543,7 +551,7 @@
     info.buf = buf;
     info.buflen = buflen;
     if (_hf_gethtbyaddr(uaddr, len, af, &info)) {
-        int error = dns_gethtbyaddr(uaddr, len, af, netcontext, &info);
+        int error = dns_gethtbyaddr(uaddr, len, af, netcontext, &info, event);
         if (error != 0) return error;
     }
     return 0;
@@ -551,8 +559,9 @@
 
 static int android_gethostbyaddrfornetcontext_proxy_internal(
         const void* addr, socklen_t len, int af, struct hostent* hp, char* hbuf, size_t hbuflen,
-        const struct android_net_context* netcontext) {
-    return android_gethostbyaddrfornetcontext_real(addr, len, af, hp, hbuf, hbuflen, netcontext);
+        const struct android_net_context* netcontext, NetworkDnsEventReported* event) {
+    return android_gethostbyaddrfornetcontext_real(addr, len, af, hp, hbuf, hbuflen, netcontext,
+                                                   event);
 }
 
 // TODO: Consider leaving function without returning error code as _gethtent() does because
@@ -795,7 +804,8 @@
 }
 
 static int dns_gethtbyaddr(const unsigned char* uaddr, int len, int af,
-                           const android_net_context* netcontext, getnamaddr* info) {
+                           const android_net_context* netcontext, getnamaddr* info,
+                           NetworkDnsEventReported* event) {
     char qbuf[MAXDNAME + 1], *qp, *ep;
     int n;
     int advance;
@@ -842,7 +852,7 @@
     res_state res = res_get_state();
     if (!res) return EAI_MEMORY;
 
-    res_setnetcontext(res, netcontext);
+    res_setnetcontext(res, netcontext, event);
     int he;
     n = res_nquery(res, qbuf, C_IN, T_PTR, buf->buf, (int)sizeof(buf->buf), &he);
     if (n < 0) {
@@ -886,13 +896,16 @@
  */
 
 int android_gethostbynamefornetcontext(const char* name, int af,
-                                       const struct android_net_context* netcontext, hostent** hp) {
-    int error;
+                                       const struct android_net_context* netcontext, hostent** hp,
+                                       NetworkDnsEventReported* event) {
+    assert(event != nullptr);
+
     res_state res = res_get_state();
     if (res == NULL) return EAI_MEMORY;
     res_static* rs = res_get_static();  // For thread-safety.
+    int error;
     error = gethostbyname_internal(name, af, res, &rs->host, rs->hostbuf, sizeof(rs->hostbuf),
-                                   netcontext);
+                                   netcontext, event);
     if (error == 0) {
         *hp = &rs->host;
     }
@@ -900,16 +913,19 @@
 }
 
 int android_gethostbyaddrfornetcontext(const void* addr, socklen_t len, int af,
-                                       const struct android_net_context* netcontext, hostent** hp) {
-    return android_gethostbyaddrfornetcontext_proxy(addr, len, af, netcontext, hp);
+                                       const struct android_net_context* netcontext, hostent** hp,
+                                       NetworkDnsEventReported* event) {
+    return android_gethostbyaddrfornetcontext_proxy(addr, len, af, netcontext, hp, event);
 }
 
 static int android_gethostbyaddrfornetcontext_proxy(const void* addr, socklen_t len, int af,
                                                     const struct android_net_context* netcontext,
-                                                    hostent** hp) {
+                                                    hostent** hp, NetworkDnsEventReported* event) {
+    assert(event != nullptr);
+
     struct res_static* rs = res_get_static();  // For thread-safety.
     int error = android_gethostbyaddrfornetcontext_proxy_internal(
-            addr, len, af, &rs->host, rs->hostbuf, sizeof(rs->hostbuf), netcontext);
+            addr, len, af, &rs->host, rs->hostbuf, sizeof(rs->hostbuf), netcontext, event);
     if (error == 0) *hp = &rs->host;
     return error;
 }
diff --git a/resolv/gethnamaddr.h b/resolv/gethnamaddr.h
index 27cc1c2..bfdb14e 100644
--- a/resolv/gethnamaddr.h
+++ b/resolv/gethnamaddr.h
@@ -18,10 +18,12 @@
 
 #include <netdb.h>               // struct hostent
 #include "netd_resolv/resolv.h"  // struct android_net_context
+#include "stats.pb.h"
 
 // This is the entry point for the gethostbyname() family of legacy calls.
-int android_gethostbynamefornetcontext(const char*, int, const android_net_context*, hostent**);
+int android_gethostbynamefornetcontext(const char*, int, const android_net_context*, hostent**,
+                                       android::net::NetworkDnsEventReported*);
 
 // This is the entry point for the gethostbyaddr() family of legacy calls.
 int android_gethostbyaddrfornetcontext(const void*, socklen_t, int, const android_net_context*,
-                                       hostent**);
+                                       hostent**, android::net::NetworkDnsEventReported*);
diff --git a/resolv/libnetd_resolv_test.cpp b/resolv/libnetd_resolv_test.cpp
index 7fc5669..6e4b9ee 100644
--- a/resolv/libnetd_resolv_test.cpp
+++ b/resolv/libnetd_resolv_test.cpp
@@ -26,6 +26,7 @@
 #include "getaddrinfo.h"
 #include "gethnamaddr.h"
 #include "resolv_cache.h"
+#include "stats.pb.h"
 
 // TODO: make this dynamic and stop depending on implementation details.
 constexpr unsigned int TEST_NETID = 30;
@@ -39,6 +40,8 @@
 namespace android {
 namespace net {
 
+using android::net::NetworkDnsEventReported;
+
 // Minimize class ResolverTest to be class TestBase because class TestBase doesn't need all member
 // functions of class ResolverTest and class DnsResponderClient.
 class TestBase : public ::testing::Test {
@@ -112,8 +115,9 @@
     // Invalid hostname and servname.
     // Both hostname and servname are null pointers. Expect error number EAI_NONAME.
     struct addrinfo* result = nullptr;
+    NetworkDnsEventReported event;
     int rv = android_getaddrinfofornetcontext(nullptr /*hostname*/, nullptr /*servname*/,
-                                              nullptr /*hints*/, &mNetcontext, &result);
+                                              nullptr /*hints*/, &mNetcontext, &result, &event);
     EXPECT_EQ(EAI_NONAME, rv);
     if (result) {
         freeaddrinfo(result);
@@ -167,8 +171,9 @@
                 .ai_next = config.ai_next,
         };
 
+        NetworkDnsEventReported event;
         rv = android_getaddrinfofornetcontext("localhost", nullptr /*servname*/, &hints,
-                                              &mNetcontext, &result);
+                                              &mNetcontext, &result, &event);
         EXPECT_EQ(config.expected_errorno, rv);
 
         if (result) {
@@ -190,8 +195,9 @@
                 .ai_family = family,  // unsupported family
         };
 
+        NetworkDnsEventReported event;
         int rv = android_getaddrinfofornetcontext("localhost", nullptr /*servname*/, &hints,
-                                                  &mNetcontext, &result);
+                                                  &mNetcontext, &result, &event);
         EXPECT_EQ(EAI_FAMILY, rv);
 
         if (result) freeaddrinfo(result);
@@ -237,8 +243,9 @@
                         .ai_socktype = socktype,
                 };
 
+                NetworkDnsEventReported event;
                 int rv = android_getaddrinfofornetcontext("localhost", nullptr /*servname*/, &hints,
-                                                          &mNetcontext, &result);
+                                                          &mNetcontext, &result, &event);
                 EXPECT_EQ(EAI_BADHINTS, rv);
 
                 if (result) freeaddrinfo(result);
@@ -320,8 +327,9 @@
         };
 
         struct addrinfo* result = nullptr;
+        NetworkDnsEventReported event;
         int rv = android_getaddrinfofornetcontext("localhost", config.servname, &hints,
-                                                  &mNetcontext, &result);
+                                                  &mNetcontext, &result, &event);
         EXPECT_EQ(config.expected_errorno, rv);
 
         if (result) freeaddrinfo(result);
@@ -344,7 +352,9 @@
     // Want AAAA answer but DNS server has A answer only.
     struct addrinfo* result = nullptr;
     const addrinfo hints = {.ai_family = AF_INET6};
-    int rv = android_getaddrinfofornetcontext("v4only", nullptr, &hints, &mNetcontext, &result);
+    NetworkDnsEventReported event;
+    int rv = android_getaddrinfofornetcontext("v4only", nullptr, &hints, &mNetcontext, &result,
+                                              &event);
     EXPECT_LE(1U, GetNumQueries(dns, v4_host_name));
     EXPECT_TRUE(result == nullptr);
     EXPECT_EQ(EAI_NODATA, rv);
@@ -382,8 +392,9 @@
 
         struct addrinfo* result = nullptr;
         const struct addrinfo hints = {.ai_family = config.ai_family};
-        int rv =
-                android_getaddrinfofornetcontext("sawadee", nullptr, &hints, &mNetcontext, &result);
+        NetworkDnsEventReported event;
+        int rv = android_getaddrinfofornetcontext("sawadee", nullptr, &hints, &mNetcontext, &result,
+                                                  &event);
         EXPECT_EQ(0, rv);
         EXPECT_TRUE(result != nullptr);
         EXPECT_EQ(1U, GetNumQueries(dns, host_name));
@@ -429,8 +440,9 @@
 
         struct addrinfo* result = nullptr;
         const struct addrinfo hints = {.ai_family = AF_UNSPEC};
-        int rv =
-                android_getaddrinfofornetcontext(host_name, nullptr, &hints, &mNetcontext, &result);
+        NetworkDnsEventReported event;
+        int rv = android_getaddrinfofornetcontext(host_name, nullptr, &hints, &mNetcontext, &result,
+                                                  &event);
         EXPECT_EQ(config.expected_errorno, rv);
 
         if (result) freeaddrinfo(result);
@@ -453,7 +465,9 @@
 
     struct addrinfo* result = nullptr;
     const struct addrinfo hints = {.ai_family = AF_UNSPEC};
-    int rv = android_getaddrinfofornetcontext("hello", nullptr, &hints, &mNetcontext, &result);
+    NetworkDnsEventReported event;
+    int rv = android_getaddrinfofornetcontext("hello", nullptr, &hints, &mNetcontext, &result,
+                                              &event);
     EXPECT_EQ(NETD_RESOLV_TIMEOUT, rv);
 
     if (result) freeaddrinfo(result);
@@ -488,8 +502,9 @@
         dns.clearQueries();
 
         struct hostent* hp = nullptr;
+        NetworkDnsEventReported event;
         int rv = android_gethostbynamefornetcontext("jiababuei", config.ai_family, &mNetcontext,
-                                                    &hp);
+                                                    &hp, &event);
         EXPECT_EQ(0, rv);
         EXPECT_TRUE(hp != nullptr);
         EXPECT_EQ(1U, GetNumQueries(dns, host_name));
@@ -512,7 +527,8 @@
 
     // Want AAAA answer but DNS server has A answer only.
     struct hostent* hp = nullptr;
-    int rv = android_gethostbynamefornetcontext("v4only", AF_INET6, &mNetcontext, &hp);
+    NetworkDnsEventReported event;
+    int rv = android_gethostbynamefornetcontext("v4only", AF_INET6, &mNetcontext, &hp, &event);
     EXPECT_LE(1U, GetNumQueries(dns, v4_host_name));
     EXPECT_TRUE(hp == nullptr);
     EXPECT_EQ(EAI_NODATA, rv);
@@ -555,7 +571,8 @@
                                                     mDefaultSearchDomains, &mDefaultParams_Binder));
 
         struct hostent* hp = nullptr;
-        int rv = android_gethostbynamefornetcontext(host_name, AF_INET, &mNetcontext, &hp);
+        NetworkDnsEventReported event;
+        int rv = android_gethostbynamefornetcontext(host_name, AF_INET, &mNetcontext, &hp, &event);
         EXPECT_TRUE(hp == nullptr);
         EXPECT_EQ(config.expected_errorno, rv);
     }
@@ -576,7 +593,8 @@
                                                 mDefaultSearchDomains, &mDefaultParams_Binder));
 
     struct hostent* hp = nullptr;
-    int rv = android_gethostbynamefornetcontext(host_name, AF_INET, &mNetcontext, &hp);
+    NetworkDnsEventReported event;
+    int rv = android_gethostbynamefornetcontext(host_name, AF_INET, &mNetcontext, &hp, &event);
     EXPECT_EQ(NETD_RESOLV_TIMEOUT, rv);
 }
 
diff --git a/resolv/res_cache.cpp b/resolv/res_cache.cpp
index f0ff564..d6f0cd8 100644
--- a/resolv/res_cache.cpp
+++ b/resolv/res_cache.cpp
@@ -36,7 +36,11 @@
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
+
 #include <mutex>
+#include <string>
+#include <unordered_map>
+#include <vector>
 
 #include <arpa/inet.h>
 #include <arpa/nameser.h>
@@ -47,6 +51,8 @@
 
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 #include <android-base/thread_annotations.h>
 #include <android/multinetwork.h>  // ResNsendFlags
 
@@ -1146,6 +1152,8 @@
     char defdname[MAXDNSRCHPATH];
     int dnsrch_offset[MAXDNSRCH + 1];  // offsets into defdname
     int wait_for_pending_req_timeout_count;
+    // Map format: ReturnCode:rate_denom
+    std::unordered_map<int, uint32_t> dns_event_subsampling_map;
 };
 
 // A helper class for the Clang Thread Safety Analysis to deal with
@@ -1604,6 +1612,49 @@
     return (info != nullptr) && (info->nscount > 0);
 }
 
+namespace {
+
+// Map format: ReturnCode:rate_denom
+// if the ReturnCode is not associated with any rate_denom, use default
+// Sampling rate varies by return code; events to log are chosen randomly, with a
+// probability proportional to the sampling rate.
+constexpr const char DEFAULT_SUBSAMPLING_MAP[] = "default:1 0:100 7:10";
+
+std::unordered_map<int, uint32_t> resolv_get_dns_event_subsampling_map() {
+    using android::base::ParseInt;
+    using android::base::ParseUint;
+    using android::base::Split;
+    using server_configurable_flags::GetServerConfigurableFlag;
+    std::unordered_map<int, uint32_t> sampling_rate_map{};
+    std::vector<std::string> subsampling_vector =
+            Split(GetServerConfigurableFlag("netd_native", "dns_event_subsample_map",
+                                            DEFAULT_SUBSAMPLING_MAP),
+                  " ");
+    for (const auto& pair : subsampling_vector) {
+        std::vector<std::string> rate_denom = Split(pair, ":");
+        int return_code;
+        uint32_t denom;
+        if (rate_denom.size() != 2) {
+            LOG(ERROR) << __func__ << ": invalid subsampling_pair = " << pair;
+            continue;
+        }
+        if (rate_denom[0] == "default") {
+            return_code = DNSEVENT_SUBSAMPLING_MAP_DEFAULT_KEY;
+        } else if (!ParseInt(rate_denom[0], &return_code)) {
+            LOG(ERROR) << __func__ << ": parse subsampling_pair failed = " << pair;
+            continue;
+        }
+        if (!ParseUint(rate_denom[1], &denom)) {
+            LOG(ERROR) << __func__ << ": parse subsampling_pair failed = " << pair;
+            continue;
+        }
+        sampling_rate_map[return_code] = denom;
+    }
+    return sampling_rate_map;
+}
+
+}  // namespace
+
 static int resolv_create_cache_for_net_locked(unsigned netid) {
     resolv_cache* cache = find_named_cache_locked(netid);
     // Should not happen
@@ -1621,6 +1672,7 @@
     }
     cache_info->cache = cache;
     cache_info->netid = netid;
+    cache_info->dns_event_subsampling_map = resolv_get_dns_event_subsampling_map();
     insert_cache_info_locked(cache_info);
 
     return 0;
@@ -2000,6 +2052,42 @@
     return revision_id;
 }
 
+std::vector<std::string> resolv_cache_dump_subsampling_map(unsigned netid) {
+    using android::base::StringPrintf;
+    std::lock_guard guard(cache_mutex);
+    resolv_cache_info* cache_info = find_cache_info_locked(netid);
+    if (cache_info == nullptr) return {};
+    std::vector<std::string> result;
+    for (const auto& pair : cache_info->dns_event_subsampling_map) {
+        result.push_back(StringPrintf("%s:%d",
+                                      (pair.first == DNSEVENT_SUBSAMPLING_MAP_DEFAULT_KEY)
+                                              ? "default"
+                                              : std::to_string(pair.first).c_str(),
+                                      pair.second));
+    }
+    return result;
+}
+
+// Decides whether an event should be sampled using a random number generator and
+// a sampling factor derived from the netid and the return code.
+//
+// Returns the subsampling rate if the event should be sampled, or 0 if it should be discarded.
+uint32_t resolv_cache_get_subsampling_denom(unsigned netid, int return_code) {
+    std::lock_guard guard(cache_mutex);
+    resolv_cache_info* cache_info = find_cache_info_locked(netid);
+    if (cache_info == nullptr) return 0;  // Don't log anything at all.
+    const auto& subsampling_map = cache_info->dns_event_subsampling_map;
+    auto search_returnCode = subsampling_map.find(return_code);
+    uint32_t denom;
+    if (search_returnCode != subsampling_map.end()) {
+        denom = search_returnCode->second;
+    } else {
+        auto search_default = subsampling_map.find(DNSEVENT_SUBSAMPLING_MAP_DEFAULT_KEY);
+        denom = (search_default == subsampling_map.end()) ? 0 : search_default->second;
+    }
+    return denom;
+}
+
 int resolv_cache_get_resolver_stats(unsigned netid, res_params* params, res_stats stats[MAXNS]) {
     std::lock_guard guard(cache_mutex);
     resolv_cache_info* info = find_cache_info_locked(netid);
diff --git a/resolv/res_cache_test.cpp b/resolv/res_cache_test.cpp
new file mode 100644
index 0000000..0b09d88
--- /dev/null
+++ b/resolv/res_cache_test.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include <netdb.h>
+
+#include <cutils/properties.h>
+#include <gmock/gmock-matchers.h>
+#include <gtest/gtest.h>
+
+#include "netd_resolv/stats.h"
+#include "resolv_cache.h"
+
+constexpr int TEST_NETID = 30;
+
+namespace {
+
+constexpr int EAI_OK = 0;
+constexpr char DNS_EVENT_SUBSAMPLING_MAP_FLAG[] =
+        "persist.device_config.netd_native.dns_event_subsample_map";
+
+class ScopedCacheCreate {
+  public:
+    explicit ScopedCacheCreate(unsigned netid, const char* subsampling_map,
+                               const char* property = DNS_EVENT_SUBSAMPLING_MAP_FLAG)
+        : mStoredNetId(netid), mStoredProperty(property) {
+        property_get(property, mStoredMap, "");
+        property_set(property, subsampling_map);
+        EXPECT_EQ(0, resolv_create_cache_for_net(netid));
+    }
+    ~ScopedCacheCreate() {
+        resolv_delete_cache_for_net(mStoredNetId);
+        property_set(mStoredProperty, mStoredMap);
+    }
+
+  private:
+    unsigned mStoredNetId;
+    const char* mStoredProperty;
+    char mStoredMap[PROPERTY_VALUE_MAX]{};
+};
+
+}  // namespace
+
+TEST(ResolvCacheTest, DnsEventSubsampling) {
+    // Test defaults, default flag is "default:1 0:100 7:10" if no experiment flag is set
+    {
+        ScopedCacheCreate scopedCacheCreate(TEST_NETID, "");
+        EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_NODATA), 10U);
+        EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_OK), 100U);
+        EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_BADFLAGS),
+                  1U);  // default
+        EXPECT_THAT(resolv_cache_dump_subsampling_map(TEST_NETID),
+                    testing::UnorderedElementsAreArray({"default:1", "0:100", "7:10"}));
+    }
+    // Now change the experiment flag to "0:42 default:666"
+    {
+        ScopedCacheCreate scopedCacheCreate(TEST_NETID, "0:42 default:666");
+        EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_OK), 42U);
+        EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_NODATA),
+                  666U);  // default
+        EXPECT_THAT(resolv_cache_dump_subsampling_map(TEST_NETID),
+                    testing::UnorderedElementsAreArray({"default:666", "0:42"}));
+    }
+    // Now change the experiment flag to something illegal
+    {
+        ScopedCacheCreate scopedCacheCreate(TEST_NETID, "asvaxx");
+        // 0(disable log) is the default value if experiment flag is invalid.
+        EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_OK), 0U);
+        EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_NODATA), 0U);
+        EXPECT_TRUE(resolv_cache_dump_subsampling_map(TEST_NETID).empty());
+    }
+    // Test negative and zero denom
+    {
+        ScopedCacheCreate scopedCacheCreate(TEST_NETID, "0:-42 default:-666 7:10 10:0");
+        // 0(disable log) is the default value if no valid denom is set
+        EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_OK), 0U);
+        EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_BADFLAGS), 0U);
+        EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_NODATA), 10U);
+        EXPECT_EQ(resolv_cache_get_subsampling_denom(TEST_NETID, EAI_SOCKTYPE), 0U);
+        EXPECT_THAT(resolv_cache_dump_subsampling_map(TEST_NETID),
+                    testing::UnorderedElementsAreArray({"7:10", "10:0"}));
+    }
+}
diff --git a/resolv/res_init.cpp b/resolv/res_init.cpp
index ceb3023..bdbe162 100644
--- a/resolv/res_init.cpp
+++ b/resolv/res_init.cpp
@@ -383,7 +383,8 @@
     return (statp->nscount);
 }
 
-void res_setnetcontext(res_state statp, const struct android_net_context* netcontext) {
+void res_setnetcontext(res_state statp, const struct android_net_context* netcontext,
+                       android::net::NetworkDnsEventReported* _Nonnull event) {
     if (statp != NULL) {
         statp->netid = netcontext->dns_netid;
         statp->_mark = netcontext->dns_mark;
@@ -393,5 +394,6 @@
         if (netcontext->flags & NET_CONTEXT_FLAG_USE_LOCAL_NAMESERVERS) {
             statp->use_local_nameserver = true;
         }
+        statp->event = event;
     }
 }
diff --git a/resolv/res_send.cpp b/resolv/res_send.cpp
index d89ad7e..1ccfc74 100644
--- a/resolv/res_send.cpp
+++ b/resolv/res_send.cpp
@@ -101,6 +101,7 @@
 #include <android/multinetwork.h>  // ResNsendFlags
 
 #include <netdutils/Slice.h>
+#include <netdutils/Stopwatch.h>
 #include "DnsTlsDispatcher.h"
 #include "DnsTlsTransport.h"
 #include "PrivateDnsConfiguration.h"
@@ -110,10 +111,13 @@
 #include "res_state_ext.h"
 #include "resolv_cache.h"
 #include "resolv_private.h"
+#include "stats.pb.h"
 
 // TODO: use the namespace something like android::netd_resolv for libnetd_resolv
 using namespace android::net;
+using android::net::NetworkDnsEventReported;
 using android::netdutils::Slice;
+using android::netdutils::Stopwatch;
 
 static DnsTlsDispatcher sDnsTlsDispatcher;
 
@@ -133,11 +137,29 @@
 static int res_tls_send(res_state, const Slice query, const Slice answer, int* rcode,
                         bool* fallback);
 
-/* BIONIC-BEGIN: implement source port randomization */
+NsType getQueryType(const uint8_t* msg, size_t msgLen) {
+    ns_msg handle;
+    ns_rr rr;
+    if (ns_initparse((const uint8_t*)msg, msgLen, &handle) < 0 ||
+        ns_parserr(&handle, ns_s_qd, 0, &rr) < 0) {
+        return NS_T_INVALID;
+    }
+    return static_cast<NsType>(ns_rr_type(rr));
+}
+
+IpVersion ipFamilyToIPVersion(const int ipFamily) {
+    switch (ipFamily) {
+        case AF_INET:
+            return IV_IPV4;
+        case AF_INET6:
+            return IV_IPV6;
+        default:
+            return IV_UNKNOWN;
+    }
+}
 
 // BEGIN: Code copied from ISC eventlib
 // TODO: move away from this code
-
 #define BILLION 1000000000
 
 static struct timespec evConsTime(time_t sec, long nsec) {
@@ -201,6 +223,7 @@
 
 // END: Code copied from ISC eventlib
 
+/* BIONIC-BEGIN: implement source port randomization */
 static int random_bind(int s, int family) {
     sockaddr_union u;
     int j;
@@ -373,6 +396,10 @@
     return (1);
 }
 
+static DnsQueryEvent* addDnsQueryEvent(NetworkDnsEventReported* event) {
+    return event->mutable_dns_query_events()->add_dns_query_event();
+}
+
 int res_nsend(res_state statp, const u_char* buf, int buflen, u_char* ans, int anssiz, int* rcode,
               uint32_t flags) {
     int gotsomewhere, terrno, v_circuit, resplen, n;
@@ -391,11 +418,15 @@
     terrno = ETIMEDOUT;
 
     int anslen = 0;
+    Stopwatch cache_stopwatch;
     cache_status = _resolv_cache_lookup(statp->netid, buf, buflen, ans, anssiz, &anslen, flags);
-
+    const int32_t cacheLatencyUs = saturate_cast<int32_t>(cache_stopwatch.timeTakenUs());
     if (cache_status == RESOLV_CACHE_FOUND) {
         HEADER* hp = (HEADER*)(void*)ans;
         *rcode = hp->rcode;
+        DnsQueryEvent* dnsQueryEvent = addDnsQueryEvent(statp->event);
+        dnsQueryEvent->set_latency_micros(cacheLatencyUs);
+        dnsQueryEvent->set_cache_hit(static_cast<CacheStatus>(cache_status));
         return anslen;
     } else if (cache_status != RESOLV_CACHE_UNSUPPORTED) {
         // had a cache miss for a known network, so populate the thread private
@@ -522,12 +553,11 @@
 
         for (int ns = 0; ns < statp->nscount; ns++) {
             if (!usable_servers[ns]) continue;
-            struct sockaddr* nsap;
             int nsaplen;
             time_t now = 0;
             int delay = 0;
             *rcode = RCODE_INTERNAL_ERROR;
-            nsap = get_nsaddr(statp, (size_t) ns);
+            const sockaddr* nsap = get_nsaddr(statp, ns);
             nsaplen = get_salen(nsap);
 
         same_ns:
@@ -551,13 +581,16 @@
                 }
             }
 
-            [[maybe_unused]] static const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
-            [[maybe_unused]] char abuf[NI_MAXHOST];
+            static const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
+            char abuf[NI_MAXHOST];
+            DnsQueryEvent* dnsQueryEvent = addDnsQueryEvent(statp->event);
+            dnsQueryEvent->set_cache_hit(static_cast<CacheStatus>(cache_status));
 
             if (getnameinfo(nsap, (socklen_t)nsaplen, abuf, sizeof(abuf), NULL, 0, niflags) == 0)
                 LOG(DEBUG) << __func__ << ": Querying server (# " << ns + 1
                            << ") address = " << abuf;
 
+            Stopwatch query_stopwatch;
             if (v_circuit) {
                 /* Use VC; at most one attempt per server. */
                 bool shouldRecordStats = (attempt == 0);
@@ -566,6 +599,15 @@
                 n = send_vc(statp, &params, buf, buflen, ans, anssiz, &terrno, ns, &now, rcode,
                             &delay);
 
+                dnsQueryEvent->set_latency_micros(
+                        saturate_cast<int32_t>(query_stopwatch.timeTakenUs()));
+                dnsQueryEvent->set_dns_server_index(ns);
+                dnsQueryEvent->set_ip_version(ipFamilyToIPVersion(nsap->sa_family));
+                dnsQueryEvent->set_retry_times(attempt);
+                dnsQueryEvent->set_rcode(static_cast<NsRcode>(*rcode));
+                dnsQueryEvent->set_protocol(PROTO_TCP);
+                dnsQueryEvent->set_type(getQueryType(buf, buflen));
+
                 /*
                  * Only record stats the first time we try a query. This ensures that
                  * queries that deterministically fail (e.g., a name that always returns
@@ -594,6 +636,15 @@
                 n = send_dg(statp, &params, buf, buflen, ans, anssiz, &terrno, ns, &v_circuit,
                             &gotsomewhere, &now, rcode, &delay);
 
+                dnsQueryEvent->set_latency_micros(
+                        saturate_cast<int32_t>(query_stopwatch.timeTakenUs()));
+                dnsQueryEvent->set_dns_server_index(ns);
+                dnsQueryEvent->set_ip_version(ipFamilyToIPVersion(nsap->sa_family));
+                dnsQueryEvent->set_retry_times(attempt);
+                dnsQueryEvent->set_rcode(static_cast<NsRcode>(*rcode));
+                dnsQueryEvent->set_protocol(PROTO_UDP);
+                dnsQueryEvent->set_type(getQueryType(buf, buflen));
+
                 /* Only record stats the first time we try a query. See above. */
                 if (attempt == 0) {
                     res_sample sample;
@@ -1211,13 +1262,25 @@
     }
 }
 
+PrivateDnsModes convertEnumType(PrivateDnsMode privateDnsmode) {
+    switch (privateDnsmode) {
+        case PrivateDnsMode::OFF:
+            return PrivateDnsModes::PDM_OFF;
+        case PrivateDnsMode::OPPORTUNISTIC:
+            return PrivateDnsModes::PDM_OPPORTUNISTIC;
+        case PrivateDnsMode::STRICT:
+            return PrivateDnsModes::PDM_STRICT;
+    }
+    return PrivateDnsModes::PDM_UNKNOWN;
+}
+
 static int res_tls_send(res_state statp, const Slice query, const Slice answer, int* rcode,
                         bool* fallback) {
     int resplen = 0;
     const unsigned netId = statp->netid;
-    const unsigned mark = statp->_mark;
 
     PrivateDnsStatus privateDnsStatus = gPrivateDnsConfiguration.getStatus(netId);
+    statp->event->set_private_dns_modes(convertEnumType(privateDnsStatus.mode));
 
     if (privateDnsStatus.mode == PrivateDnsMode::OFF) {
         *fallback = true;
@@ -1256,7 +1319,7 @@
 
     LOG(INFO) << __func__ << ": performing query over TLS";
 
-    const auto response = sDnsTlsDispatcher.query(privateDnsStatus.validatedServers, mark, query,
+    const auto response = sDnsTlsDispatcher.query(privateDnsStatus.validatedServers, statp, query,
                                                   answer, &resplen);
 
     LOG(INFO) << __func__ << ": TLS query result: " << static_cast<int>(response);
@@ -1298,9 +1361,11 @@
 }
 
 int resolv_res_nsend(const android_net_context* netContext, const uint8_t* msg, int msgLen,
-                     uint8_t* ans, int ansLen, int* rcode, uint32_t flags) {
+                     uint8_t* ans, int ansLen, int* rcode, uint32_t flags,
+                     NetworkDnsEventReported* event) {
+    assert(event != nullptr);
     res_state res = res_get_state();
-    res_setnetcontext(res, netContext);
+    res_setnetcontext(res, netContext, event);
     _resolv_populate_res_for_net(res);
     *rcode = NOERROR;
     return res_nsend(res, msg, msgLen, ans, ansLen, rcode, flags);
diff --git a/resolv/res_send.h b/resolv/res_send.h
index 7fc77cb..fb80160 100644
--- a/resolv/res_send.h
+++ b/resolv/res_send.h
@@ -17,7 +17,9 @@
 #pragma once
 
 #include "netd_resolv/resolv.h"  // struct android_net_context
+#include "stats.pb.h"
 
 // Query dns with raw msg
 int resolv_res_nsend(const android_net_context* netContext, const uint8_t* msg, int msgLen,
-                     uint8_t* ans, int ansLen, int* rcode, uint32_t flags);
+                     uint8_t* ans, int ansLen, int* rcode, uint32_t flags,
+                     android::net::NetworkDnsEventReported* event);
diff --git a/resolv/resolv_cache.h b/resolv/resolv_cache.h
index 0056076..df21fb3 100644
--- a/resolv/resolv_cache.h
+++ b/resolv/resolv_cache.h
@@ -32,10 +32,13 @@
 
 #include <stddef.h>
 
+#include <unordered_map>
 #include <vector>
 
 struct __res_state;
 
+constexpr int DNSEVENT_SUBSAMPLING_MAP_DEFAULT_KEY = -1;
+
 /* sets the name server addresses to the provided res_state structure. The
  * name servers are retrieved from the cache which is associated
  * with the network to which the res_state structure is associated */
@@ -43,6 +46,9 @@
 
 std::vector<unsigned> resolv_list_caches();
 
+std::vector<std::string> resolv_cache_dump_subsampling_map(unsigned netid);
+uint32_t resolv_cache_get_subsampling_denom(unsigned netid, int return_code);
+
 typedef enum {
     RESOLV_CACHE_UNSUPPORTED, /* the cache can't handle that kind of queries */
                               /* or the answer buffer is too small */
diff --git a/resolv/resolv_private.h b/resolv/resolv_private.h
index 9fb0d48..3ecb6c1 100644
--- a/resolv/resolv_private.h
+++ b/resolv/resolv_private.h
@@ -64,6 +64,7 @@
 #include "netd_resolv/resolv.h"
 #include "netd_resolv/stats.h"
 #include "resolv_static.h"
+#include "stats.pb.h"
 
 // Linux defines MAXHOSTNAMELEN as 64, while the domain name limit in
 // RFC 1034 and RFC 1035 is 255 octets.
@@ -118,6 +119,7 @@
     } _u;
     struct res_static rstatic[1];
     bool use_local_nameserver; /* DNS-over-TLS bypass */
+    android::net::NetworkDnsEventReported* event;
 };
 
 typedef struct __res_state* res_state;
@@ -226,7 +228,8 @@
 int res_getservers(res_state, sockaddr_union*, int);
 
 struct android_net_context; /* forward */
-void res_setnetcontext(res_state, const struct android_net_context*);
+void res_setnetcontext(res_state, const struct android_net_context*,
+                       android::net::NetworkDnsEventReported* event);
 
 int getaddrinfo_numeric(const char* hostname, const char* servname, addrinfo hints,
                         addrinfo** result);
@@ -237,4 +240,16 @@
 // switch resolver log severity
 android::base::LogSeverity logSeverityStrToEnum(const std::string& logSeverityStr);
 
+template <typename Dest>
+Dest saturate_cast(int64_t x) {
+    using DestLimits = std::numeric_limits<Dest>;
+    if (x > DestLimits::max()) return DestLimits::max();
+    if (x < DestLimits::min()) return DestLimits::min();
+    return static_cast<Dest>(x);
+}
+
+android::net::NsType getQueryType(const uint8_t* msg, size_t msgLen);
+
+android::net::IpVersion ipFamilyToIPVersion(int ipFamily);
+
 #endif  // NETD_RESOLV_PRIVATE_H
diff --git a/resolv/stats.proto b/resolv/stats.proto
index dd9992c..60e2071 100644
--- a/resolv/stats.proto
+++ b/resolv/stats.proto
@@ -68,6 +68,8 @@
     // NS_R_BADSIG  = 16,
     NS_R_BADKEY = 17;
     NS_R_BADTIME = 18;
+    NS_R_INTERNAL_ERROR = 254;
+    NS_R_TIMEOUT = 255;
 }
 
 // Currently defined type values for resources and queries.
@@ -144,11 +146,11 @@
     IV_IPV6 = 2;
 }
 
-enum TransportType {
-    TT_UNKNOWN = 0;
-    TT_UDP = 1;
-    TT_TCP = 2;
-    TT_DOT = 3;
+enum Protocol {
+    PROTO_UNKNOWN = 0;
+    PROTO_UDP = 1;
+    PROTO_TCP = 2;
+    PROTO_DOT = 3;
 }
 
 enum PrivateDnsModes {
@@ -158,21 +160,22 @@
     PDM_STRICT = 3;
 }
 
-enum Transport {
+enum NetworkType {
+    NT_UNKNOWN = 0;
     // Indicates this network uses a Cellular transport.
-    TRANSPORT_DEFAULT = 0;  // TRANSPORT_CELLULAR
+    NT_CELLULAR = 1;
     // Indicates this network uses a Wi-Fi transport.
-    TRANSPORT_WIFI = 1;
+    NT_WIFI = 2;
     // Indicates this network uses a Bluetooth transport.
-    TRANSPORT_BLUETOOTH = 2;
+    NT_BLUETOOTH = 3;
     // Indicates this network uses an Ethernet transport.
-    TRANSPORT_ETHERNET = 3;
+    NT_ETHERNET = 4;
     // Indicates this network uses a VPN transport.
-    TRANSPORT_VPN = 4;
+    NT_VPN = 5;
     // Indicates this network uses a Wi-Fi Aware transport.
-    TRANSPORT_WIFI_AWARE = 5;
+    NT_WIFI_AWARE = 6;
     // Indicates this network uses a LoWPAN transport.
-    TRANSPORT_LOWPAN = 6;
+    NT_LOWPAN = 7;
 }
 
 enum CacheStatus{
@@ -196,13 +199,13 @@
 
     optional IpVersion ip_version = 4;
 
-    optional TransportType transport = 5;
+    optional Protocol protocol = 5;
 
     // Number of DNS query retry times
     optional int32 retry_times = 6;
 
     // Ordinal number of name server.
-    optional int32 dns_server_count = 7;
+    optional int32 dns_server_index = 7;
 
     // Used only by TCP and DOT. True for new connections.
     optional bool connected = 8;
@@ -238,7 +241,7 @@
     // Only valid for event_type = EVENT_RESNSEND.
     optional int32 res_nsend_flags = 5;
 
-    optional Transport network_type = 6;
+    optional NetworkType network_type = 6;
 
     // The DNS over TLS mode on a specific netId.
     optional PrivateDnsModes private_dns_modes = 7;
@@ -246,4 +249,7 @@
     // Additional pass-through fields opaque to statsd.
     // The DNS resolver Mainline module can add new fields here without requiring an OS update.
     optional DnsQueryEvents dns_query_events = 8;
-}
\ No newline at end of file
+
+    // The sample rate of DNS stats (to statsd) is 1/sampling_rate_denom.
+    optional int32 sampling_rate_denom = 9;
+}