Bug 1533363 - Part 1: Add HttpTrafficAnalyzer service; r=dragana
authorLiang-Heng Chen <xeonchen@gmail.com>
Thu, 04 Apr 2019 21:38:28 +0000
changeset 526882 13bff8b50313c4e3600a8637b2baabd68d2d6893
parent 526881 a13bf582f195439790257dcd375838f2aced5da3
child 526883 7c3de15af00bcd3ba3e8fd8d89d6385dd64f9f85
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdragana
bugs1533363
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1533363 - Part 1: Add HttpTrafficAnalyzer service; r=dragana Differential Revision: https://phabricator.services.mozilla.com/D24517
modules/libpref/init/StaticPrefList.h
netwerk/protocol/http/HttpTrafficAnalyzer.cpp
netwerk/protocol/http/HttpTrafficAnalyzer.h
netwerk/protocol/http/HttpTrafficAnalyzer.inc
netwerk/protocol/http/moz.build
netwerk/protocol/http/nsAHttpConnection.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/protocol/http/nsHttpConnection.h
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/protocol/http/nsHttpHandler.h
netwerk/protocol/http/nsHttpTransaction.cpp
netwerk/protocol/http/nsHttpTransaction.h
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -2031,16 +2031,23 @@ PREF("network.predictor.cleaned-up", boo
 
 // A testing flag.
 VARCACHE_PREF(
   "network.predictor.doing-tests",
    network_predictor_doing_tests,
   bool, false
 )
 
+// Telemetry of traffic categories
+VARCACHE_PREF(
+  "network.traffic_analyzer.enabled",
+  network_traffic_analyzer_enabled,
+  RelaxedAtomicBool, false
+)
+
 //---------------------------------------------------------------------------
 // ContentSessionStore prefs
 //---------------------------------------------------------------------------
 // Maximum number of bytes of DOMSessionStorage data we collect per origin.
 VARCACHE_PREF(
   "browser.sessionstore.dom_storage_limit",
   browser_sessionstore_dom_storage_limit,
   uint32_t, 2048
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/HttpTrafficAnalyzer.cpp
@@ -0,0 +1,162 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HttpTrafficAnalyzer.h"
+#include "HttpLog.h"
+
+#include "mozilla/StaticPrefs.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace net {
+
+#define DEFINE_CATEGORY(_name, _idx) NS_LITERAL_CSTRING("Y=" #_idx "|" #_name),
+static const nsCString gKeyName[] = {
+#include "HttpTrafficAnalyzer.inc"
+};
+#undef DEFINE_CATEGORY
+
+// ----------------------------------------------------
+// | Flags                           |   Load Type    |
+// ----------------------------------------------------
+// | nsIClassOfService::Leader       |       A        |
+// | w/o nsIRequest::LOAD_BACKGROUND |       B        |
+// | w/ nsIRequest::LOAD_BACKGROUND  |       C        |
+// ----------------------------------------------------
+// | Category                        | List Category  |
+// ----------------------------------------------------
+// | Content                         |       I        |
+// | Basic Disconnected List         |      II        |
+// | Fingerprinting                  |     III        |
+// ----------------------------------------------------
+// ====================================================
+// | Normal Mode                                      |
+// ----------------------------------------------------
+// | Y = 0 for first party                            |
+// | Y = 1 for non-listed third party type            |
+// ----------------------------------------------------
+// |          \Y\          | Type A | Type B | Type C |
+// ----------------------------------------------------
+// | Category I            |    2   |    3   |    4   |
+// | Category II           |    5   |    6   |    7   |
+// | Category III          |    8   |    9   |   10   |
+// ====================================================
+// | Private Mode                                     |
+// ----------------------------------------------------
+// | Y = 11 for first party                           |
+// | Y = 12 for non-listed third party type           |
+// ----------------------------------------------------
+// |          \Y\          | Type A | Type B | Type C |
+// ----------------------------------------------------
+// | Category I            |   13   |   14   |   15   |
+// | Category II           |   16   |   17   |   18   |
+// | Category III          |   19   |   20   |   21   |
+// ====================================================
+
+HttpTrafficCategory HttpTrafficAnalyzer::CreateTrafficCategory(
+    bool aIsPrivateMode, bool aIsThirdParty, ClassOfService aClassOfService,
+    TrackingClassification aClassification) {
+  uint8_t category = aIsPrivateMode ? 11 : 0;
+  if (!aIsThirdParty) {
+    return static_cast<HttpTrafficCategory>(category);
+  }
+
+  switch (aClassification) {
+    case TrackingClassification::eNone:
+      return static_cast<HttpTrafficCategory>(category + 1);
+    case TrackingClassification::eBasic:
+      category += 2;
+      break;
+    case TrackingClassification::eContent:
+      category += 5;
+      break;
+    case TrackingClassification::eFingerprinting:
+      category += 8;
+      break;
+    default:
+      MOZ_ASSERT(false, "incorrect classification");
+      return HttpTrafficCategory::eInvalid;
+  }
+
+  switch (aClassOfService) {
+    case ClassOfService::eLeader:
+      return static_cast<HttpTrafficCategory>(category);
+    case ClassOfService::eBackground:
+      return static_cast<HttpTrafficCategory>(category + 1);
+    case ClassOfService::eOther:
+      return static_cast<HttpTrafficCategory>(category + 2);
+  }
+
+  MOZ_ASSERT(false, "incorrect class of service");
+  return HttpTrafficCategory::eInvalid;
+}
+
+nsresult HttpTrafficAnalyzer::IncrementHttpTransaction(
+    HttpTrafficCategory aCategory) {
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+  MOZ_ASSERT(StaticPrefs::network_traffic_analyzer_enabled());
+  MOZ_ASSERT(aCategory != HttpTrafficCategory::eInvalid, "invalid category");
+
+  LOG(("HttpTrafficAnalyzer::IncrementHttpTransaction [%s] [this=%p]\n",
+       gKeyName[aCategory].get(), this));
+
+  return NS_OK;
+}
+
+nsresult HttpTrafficAnalyzer::IncrementHttpConnection(
+    HttpTrafficCategory aCategory) {
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+  MOZ_ASSERT(StaticPrefs::network_traffic_analyzer_enabled());
+  MOZ_ASSERT(aCategory != HttpTrafficCategory::eInvalid, "invalid category");
+
+  LOG(("HttpTrafficAnalyzer::IncrementHttpConnection [%s] [this=%p]\n",
+       gKeyName[aCategory].get(), this));
+
+  return NS_OK;
+}
+
+nsresult HttpTrafficAnalyzer::IncrementHttpConnection(
+    nsTArray<HttpTrafficCategory> &&aCategories) {
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+  MOZ_ASSERT(StaticPrefs::network_traffic_analyzer_enabled());
+  MOZ_ASSERT(!aCategories.IsEmpty(), "empty category");
+
+  nsTArray<HttpTrafficCategory> categories(std::move(aCategories));
+
+  LOG(("HttpTrafficAnalyzer::IncrementHttpConnection size=%" PRIuPTR
+       " [this=%p]\n",
+       categories.Length(), this));
+
+  // divide categories into 4 parts:
+  //   1) normal 1st-party (Y = 0)
+  //   2) normal 3rd-party (0 < Y < 11)
+  //   3) private 1st-party (Y = 11)
+  //   4) private 3rd-party (11 < Y < 22)
+  // Normal and private transaction should not share the same connection,
+  // and we choose 3rd-party prior than 1st-party.
+  HttpTrafficCategory best = categories[0];
+  if ((best == 0 || best == 11) && categories.Length() > 1) {
+    best = categories[1];
+  }
+  Unused << IncrementHttpConnection(best);
+
+  return NS_OK;
+}
+
+nsresult HttpTrafficAnalyzer::AccumulateHttpTransferredSize(
+    HttpTrafficCategory aCategory, uint64_t aBytesRead, uint64_t aBytesSent) {
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+  MOZ_ASSERT(StaticPrefs::network_traffic_analyzer_enabled());
+  MOZ_ASSERT(aCategory != HttpTrafficCategory::eInvalid, "invalid category");
+
+  LOG(("HttpTrafficAnalyzer::AccumulateHttpTransferredSize [%s] rb=%" PRIu64 " "
+       "sb=%" PRIu64 " [this=%p]\n",
+       gKeyName[aCategory].get(), aBytesRead, aBytesSent, this));
+
+  return NS_OK;
+}
+
+}  // namespace net
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/HttpTrafficAnalyzer.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_netwerk_protocol_http_HttpTrafficAnalyzer_h
+#define mozilla_netwerk_protocol_http_HttpTrafficAnalyzer_h
+
+namespace mozilla {
+namespace net {
+
+#define DEFINE_CATEGORY(_name, _idx) e##_name = _idx##u,
+enum HttpTrafficCategory : uint8_t {
+#include "HttpTrafficAnalyzer.inc"
+  eInvalid = 255,
+};
+#undef DEFINE_CATEGORY
+
+class HttpTrafficAnalyzer final {
+ public:
+  enum ClassOfService : uint8_t {
+    eLeader = 0,
+    eBackground = 1,
+    eOther = 255,
+  };
+
+  enum TrackingClassification : uint8_t {
+    eNone = 0,
+    eBasic = 1,
+    eContent = 2,
+    eFingerprinting = 3,
+  };
+
+  static HttpTrafficCategory CreateTrafficCategory(
+      bool aIsPrivateMode, bool aIsThirdParty, ClassOfService aClassOfService,
+      TrackingClassification aClassification);
+
+  nsresult IncrementHttpTransaction(HttpTrafficCategory aCategory);
+  nsresult IncrementHttpConnection(HttpTrafficCategory aCategory);
+  nsresult IncrementHttpConnection(nsTArray<HttpTrafficCategory> &&aCategories);
+  nsresult AccumulateHttpTransferredSize(HttpTrafficCategory aCategory,
+                                         uint64_t aBytesRead,
+                                         uint64_t aBytesSent);
+};
+
+}  // namespace net
+}  // namespace mozilla
+
+#endif  // mozilla_netwerk_protocol_http_HttpTrafficAnalyzer_h
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/HttpTrafficAnalyzer.inc
@@ -0,0 +1,100 @@
+// Type A) Parser blocking script loads will have an mContentPolicyType
+//         indicating a script load and a nsIClassOfService::Leader class of service.
+// Type B) I couldn’t find a class flag that corresponds to synchronous loads
+//         that block the load event, but I think a simple way to determine
+//         which ones they are is to examine whether the channel belongs to a
+//         load group and do not have a LOAD_BACKGROUND load flag.
+//         If that condition holds, then it is blocking the load event of some document.
+// Type C) I think a simple way to find channels that don’t block document loads
+//         (e.g. XHR and fetch) is to look for those which are in a load group
+//         and have a LOAD_BACKGROUND load flag.
+
+
+// Y=0 - all requests/connections/bytes that are first party.
+DEFINE_CATEGORY(NormalFirstParty, 0)
+
+// Y=1 - all requests/connections/bytes that are third party
+// but don’t fall into other categories
+DEFINE_CATEGORY(NormalThirdPartyOther, 1)
+
+// Y=2 - all requests/connections/bytes associated with third party loads that
+// match the Content Category and have a load of type (A)
+DEFINE_CATEGORY(NormalThirdPartyContentLeader, 2)
+
+// Y=3 - all requests/connections/bytes associated with third party loads that
+// match the Content Category and have a load of type (B)
+DEFINE_CATEGORY(NormalThirdPartyContentBackground, 3)
+
+// Y=4 - all requests/connections/bytes associated with third party loads that
+// match the Content Category and have a load of type (C)
+DEFINE_CATEGORY(NormalThirdPartyContentOther, 4)
+
+// Y=5 - all requests/connections/bytes associated with third party loads that
+// match the Analytics/Social/Advertising (Basic) Category and have a load of type (A)
+DEFINE_CATEGORY(NormalThirdPartyBasicLeader, 5)
+
+// Y=6 - all requests/connections/bytes associated with third party loads that
+// match the Analytics/Social/Advertising (Basic) Category and have a load of type (B)
+DEFINE_CATEGORY(NormalThirdPartyBasicBackground, 6)
+
+// Y=7 - all requests/connections/bytes associated with third party loads that
+// match the Analytics/Social/Advertising (Basic) Category and have a load of type (C)
+DEFINE_CATEGORY(NormalThirdPartyBasicOther, 7)
+
+// Y=8 - all requests/connections/bytes associated with third party loads that
+// match the Fingerprinting Category and have a load of type (A)
+DEFINE_CATEGORY(NormalThirdPartyFingerprintingLeader, 8)
+
+// Y=9 - all requests/connections/bytes associated with third party loads that
+// match the Fingerprinting Category and have a load of type (B)
+DEFINE_CATEGORY(NormalThirdPartyFingerprintingBackground, 9)
+
+// Y=10 - all requests/connections/bytes associated with third party loads that
+// match the Fingerprinting Category and have a load of type (C)
+DEFINE_CATEGORY(NormalThirdPartyFingerprintingOther, 10)
+
+// Y=11 - private mode and all requests/connections/bytes that are first party.
+DEFINE_CATEGORY(PrivateFirstParty, 11)
+
+// Y=12 - private mode and all requests/connections/bytes that are third party
+// but don’t fall into other categories
+DEFINE_CATEGORY(PrivateThirdPartyOther, 12)
+
+// Y=13 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Content Category and have a load of type (A)
+DEFINE_CATEGORY(PrivateThirdPartyContentLeader, 13)
+
+// Y=14 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Content Category and have a load of type (B)
+DEFINE_CATEGORY(PrivateThirdPartyContentBackground, 14)
+
+// Y=15 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Content Category and have a load of type (C)
+DEFINE_CATEGORY(PrivateThirdPartyContentOther, 15)
+
+// Y=16 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Analytics/Social/Advertising (Basic) Category
+// and have a load of type (A)
+DEFINE_CATEGORY(PrivateThirdPartyBasicLeader, 16)
+
+// Y=17 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Analytics/Social/Advertising (Basic) Category
+// and have a load of type (B)
+DEFINE_CATEGORY(PrivateThirdPartyBasicBackground, 17)
+
+// Y=18 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Analytics/Social/Advertising (Basic) Category
+// and have a load of type (C)
+DEFINE_CATEGORY(PrivateThirdPartyBasicOther, 18)
+
+// Y=19 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Fingerprinting Category and have a load of type (A)
+DEFINE_CATEGORY(PrivateThirdPartyFingerprintingLeader, 19)
+
+// Y=20 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Fingerprinting Category and have a load of type (B)
+DEFINE_CATEGORY(PrivateThirdPartyFingerprintingBackground, 20)
+
+// Y=21 - private mode and all requests/connections/bytes associated with
+// third party loads that match the Fingerprinting Category and have a load of type (C)
+DEFINE_CATEGORY(PrivateThirdPartyFingerprintingOther, 21)
--- a/netwerk/protocol/http/moz.build
+++ b/netwerk/protocol/http/moz.build
@@ -79,16 +79,17 @@ UNIFIED_SOURCES += [
     'HttpAuthUtils.cpp',
     'HttpBackgroundChannelChild.cpp',
     'HttpBackgroundChannelParent.cpp',
     'HttpBaseChannel.cpp',
     'HttpChannelChild.cpp',
     'HttpChannelParent.cpp',
     'HttpChannelParentListener.cpp',
     'HttpInfo.cpp',
+    'HttpTrafficAnalyzer.cpp',
     'InterceptedChannel.cpp',
     'InterceptedHttpChannel.cpp',
     'nsCORSListenerProxy.cpp',
     'nsHttp.cpp',
     'nsHttpActivityDistributor.cpp',
     'nsHttpAuthCache.cpp',
     'nsHttpAuthManager.cpp',
     'nsHttpBasicAuth.cpp',
--- a/netwerk/protocol/http/nsAHttpConnection.h
+++ b/netwerk/protocol/http/nsAHttpConnection.h
@@ -144,16 +144,20 @@ class nsAHttpConnection : public nsISupp
   // any thread.
   virtual void SetSecurityCallbacks(nsIInterfaceRequestor *aCallbacks) = 0;
 
   // nsHttp.h version
   virtual HttpVersion Version() = 0;
 
   // A notification of the current active tab id change.
   virtual void TopLevelOuterContentWindowIdChanged(uint64_t windowId) = 0;
+
+  // categories set by nsHttpTransaction to identify how this connection is
+  // being used.
+  virtual void SetTrafficCategory(HttpTrafficCategory) = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpConnection, NS_AHTTPCONNECTION_IID)
 
 #define NS_DECL_NSAHTTPCONNECTION(fwdObject)                                  \
   MOZ_MUST_USE nsresult OnHeadersAvailable(                                   \
       nsAHttpTransaction *, nsHttpRequestHead *, nsHttpResponseHead *,        \
       bool *reset) override;                                                  \
@@ -220,16 +224,19 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpCon
   void SetLastTransactionExpectedNoContent(bool val) override {               \
     if (fwdObject) (fwdObject)->SetLastTransactionExpectedNoContent(val);     \
   }                                                                           \
   int64_t BytesWritten() override {                                           \
     return fwdObject ? (fwdObject)->BytesWritten() : 0;                       \
   }                                                                           \
   void SetSecurityCallbacks(nsIInterfaceRequestor *aCallbacks) override {     \
     if (fwdObject) (fwdObject)->SetSecurityCallbacks(aCallbacks);             \
+  }                                                                           \
+  void SetTrafficCategory(HttpTrafficCategory aCategory) override {           \
+    if (fwdObject) (fwdObject)->SetTrafficCategory(aCategory);                \
   }
 
 // ThrottleResponse deliberately ommited since we want different implementation
 // for h1 and h2 connections.
 
 }  // namespace net
 }  // namespace mozilla
 
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -120,16 +120,17 @@
 #include "nsINetworkLinkService.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ServiceWorkerUtils.h"
 #include "mozilla/net/AsyncUrlChannelClassifier.h"
 #include "mozilla/net/NeckoChannelParams.h"
 #include "mozilla/net/UrlClassifierFeatureFactory.h"
 #include "nsIWebNavigation.h"
+#include "HttpTrafficAnalyzer.h"
 
 #ifdef MOZ_TASK_TRACER
 #  include "GeckoTaskTracer.h"
 #endif
 
 #ifdef MOZ_GECKO_PROFILER
 #  include "ProfilerMarkerPayload.h"
 #endif
@@ -1277,36 +1278,83 @@ nsresult nsHttpChannel::SetupTransaction
                                 NS_GET_IID(nsIHttpPushListener),
                                 getter_AddRefs(pushListener));
   if (pushListener) {
     mCaps |= NS_HTTP_ONPUSH_LISTENER;
   }
 
   EnsureTopLevelOuterContentWindowId();
 
+  HttpTrafficCategory category = CreateTrafficCategory();
+
   nsCOMPtr<nsIAsyncInputStream> responseStream;
   rv = mTransaction->Init(
       mCaps, mConnectionInfo, &mRequestHead, mUploadStream, mReqContentLength,
       mUploadStreamHasHeaders, GetCurrentThreadEventTarget(), callbacks, this,
-      mTopLevelOuterContentWindowId, getter_AddRefs(responseStream));
+      mTopLevelOuterContentWindowId, category, getter_AddRefs(responseStream));
   if (NS_FAILED(rv)) {
     mTransaction = nullptr;
     return rv;
   }
 
   mTransaction->SetClassOfService(mClassOfService);
   if (EnsureRequestContext()) {
     mTransaction->SetRequestContext(mRequestContext);
   }
 
   rv = nsInputStreamPump::Create(getter_AddRefs(mTransactionPump),
                                  responseStream);
   return rv;
 }
 
+HttpTrafficCategory nsHttpChannel::CreateTrafficCategory() {
+  MOZ_ASSERT(!mFirstPartyClassificationFlags ||
+             !mThirdPartyClassificationFlags);
+
+  if (!StaticPrefs::network_traffic_analyzer_enabled()) {
+    return HttpTrafficCategory::eInvalid;
+  }
+
+  HttpTrafficAnalyzer::ClassOfService cos;
+  {
+    if (mClassOfService & nsIClassOfService::Leader &&
+        mLoadInfo->GetExternalContentPolicyType() ==
+            nsIContentPolicy::TYPE_SCRIPT) {
+      cos = HttpTrafficAnalyzer::ClassOfService::eLeader;
+    } else if (mLoadFlags & nsIRequest::LOAD_BACKGROUND) {
+      cos = HttpTrafficAnalyzer::ClassOfService::eBackground;
+    } else {
+      cos = HttpTrafficAnalyzer::ClassOfService::eOther;
+    }
+  }
+
+  bool isThirdParty = !!mThirdPartyClassificationFlags;
+  HttpTrafficAnalyzer::TrackingClassification tc;
+  {
+    uint32_t flags = isThirdParty ? mThirdPartyClassificationFlags
+                                  : mFirstPartyClassificationFlags;
+
+    using CF = nsIHttpChannel::ClassificationFlags;
+    using TC = HttpTrafficAnalyzer::TrackingClassification;
+
+    if (flags & CF::CLASSIFIED_TRACKING_CONTENT) {
+      tc = TC::eContent;
+    } else if (flags & CF::CLASSIFIED_FINGERPRINTING) {
+      tc = TC::eFingerprinting;
+    } else if (flags & CF::CLASSIFIED_ANY_BASIC_TRACKING) {
+      tc = TC::eBasic;
+    } else {
+      tc = TC::eNone;
+    }
+  }
+
+  return HttpTrafficAnalyzer::CreateTrafficCategory(NS_UsePrivateBrowsing(this),
+                                                    isThirdParty, cos, tc);
+}
+
 enum class Report { Error, Warning };
 
 // Helper Function to report messages to the console when the loaded
 // script had a wrong MIME type.
 void ReportMimeTypeMismatch(nsHttpChannel *aChannel, const char *aMessageName,
                             nsIURI *aURI, const nsACString &aContentType,
                             Report report) {
   NS_ConvertUTF8toUTF16 spec(aURI->GetSpecOrDefault());
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -447,16 +447,18 @@ class nsHttpChannel final : public HttpB
   MOZ_MUST_USE nsresult ContinueOnStopRequest(nsresult status, bool aIsFromNet,
                                               bool aContentComplete);
 
   void HandleAsyncRedirectChannelToHttps();
   MOZ_MUST_USE nsresult StartRedirectChannelToHttps();
   MOZ_MUST_USE nsresult ContinueAsyncRedirectChannelToURI(nsresult rv);
   MOZ_MUST_USE nsresult OpenRedirectChannel(nsresult rv);
 
+  HttpTrafficCategory CreateTrafficCategory();
+
   /**
    * A function that takes care of reading STS and PKP headers and enforcing
    * STS and PKP load rules. After a secure channel is erected, STS and PKP
    * requires the channel to be trusted or any STS or PKP header data on
    * the channel is ignored. This is called from ProcessResponse.
    */
   MOZ_MUST_USE nsresult ProcessSecurityHeaders();
 
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -967,16 +967,24 @@ void nsHttpConnection::Close(nsresult re
     mTCPKeepaliveTransitionTimer->Cancel();
     mTCPKeepaliveTransitionTimer = nullptr;
   }
   if (mForceSendTimer) {
     mForceSendTimer->Cancel();
     mForceSendTimer = nullptr;
   }
 
+  if (!mTrafficCategory.IsEmpty()) {
+    HttpTrafficAnalyzer *hta = gHttpHandler->GetHttpTrafficAnalyzer();
+    if (hta) {
+      hta->IncrementHttpConnection(std::move(mTrafficCategory));
+      MOZ_ASSERT(mTrafficCategory.IsEmpty());
+    }
+  }
+
   if (NS_FAILED(reason)) {
     if (mIdleMonitoring) EndIdleMonitoring();
 
     mTLSFilter = nullptr;
 
     // The connection and security errors clear out alt-svc mappings
     // in case any previously validated ones are now invalid
     if (((reason == NS_ERROR_NET_RESET) ||
@@ -2651,10 +2659,19 @@ bool nsHttpConnection::NoClientCertAuth(
 bool nsHttpConnection::CanAcceptWebsocket() {
   if (!UsingSpdy()) {
     return true;
   }
 
   return mSpdySession->CanAcceptWebsocket();
 }
 
+void nsHttpConnection::SetTrafficCategory(HttpTrafficCategory aCategory) {
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+  if (aCategory == HttpTrafficCategory::eInvalid ||
+      mTrafficCategory.Contains(aCategory)) {
+    return;
+  }
+  Unused << mTrafficCategory.AppendElement(aCategory);
+}
+
 }  // namespace net
 }  // namespace mozilla
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -12,16 +12,17 @@
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsProxyRelease.h"
 #include "prinrval.h"
 #include "TunnelUtils.h"
 #include "mozilla/Mutex.h"
 #include "ARefBase.h"
 #include "TimingStruct.h"
+#include "HttpTrafficAnalyzer.h"
 
 #include "nsIAsyncInputStream.h"
 #include "nsIAsyncOutputStream.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsITimer.h"
 
 class nsISocketTransport;
 class nsISSLSocketControl;
@@ -246,16 +247,18 @@ class nsHttpConnection final : public ns
   // Return true when the socket this connection is using has not been
   // authenticated using a client certificate.  Before SSL negotiation
   // has finished this returns false.
   bool NoClientCertAuth() const;
 
   // HTTP/2 websocket support
   bool CanAcceptWebsocket();
 
+  void SetTrafficCategory(HttpTrafficCategory aCategory);
+
  private:
   // Value (set in mTCPKeepaliveConfig) indicates which set of prefs to use.
   enum TCPKeepaliveConfig {
     kTCPKeepaliveDisabled = 0,
     kTCPKeepaliveShortLivedConfig,
     kTCPKeepaliveLongLivedConfig
   };
 
@@ -431,16 +434,18 @@ class nsHttpConnection final : public ns
   PRIntervalTime mLastRequestBytesSentTime;
 
  public:
   void BootstrapTimings(TimingStruct times);
 
  private:
   TimingStruct mBootstrappedTimings;
   bool mBootstrappedTimingsSet;
+
+  nsTArray<HttpTrafficCategory> mTrafficCategory;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsHttpConnection, NS_HTTPCONNECTION_IID)
 
 }  // namespace net
 }  // namespace mozilla
 
 #endif  // nsHttpConnection_h__
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -2712,10 +2712,20 @@ void nsHttpHandler::BlacklistSpdy(const 
   mConnMgr->BlacklistSpdy(ci);
   mBlacklistedSpdyOrigins.PutEntry(ci->GetOrigin());
 }
 
 bool nsHttpHandler::IsSpdyBlacklisted(const nsHttpConnectionInfo *ci) {
   return mBlacklistedSpdyOrigins.Contains(ci->GetOrigin());
 }
 
+HttpTrafficAnalyzer *nsHttpHandler::GetHttpTrafficAnalyzer() {
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+  if (!StaticPrefs::network_traffic_analyzer_enabled()) {
+    return nullptr;
+  }
+
+  return &mHttpTrafficAnalyzer;
+}
+
 }  // namespace net
 }  // namespace mozilla
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -7,16 +7,17 @@
 #define nsHttpHandler_h__
 
 #include <functional>
 
 #include "nsHttp.h"
 #include "nsHttpAuthCache.h"
 #include "nsHttpConnectionMgr.h"
 #include "ASpdySession.h"
+#include "HttpTrafficAnalyzer.h"
 
 #include "mozilla/Mutex.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TimeStamp.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsWeakReference.h"
 
@@ -409,16 +410,18 @@ class nsHttpHandler final : public nsIHt
   // mLastActiveTabLoadOptimizationHit timestamp to now.
   void NotifyActiveTabLoadOptimization();
   TimeStamp const GetLastActiveTabLoadOptimizationHit();
   void SetLastActiveTabLoadOptimizationHit(TimeStamp const &when);
   bool IsBeforeLastActiveTabLoadOptimization(TimeStamp const &when);
 
   bool DumpHpackTables() { return mDumpHpackTables; }
 
+  HttpTrafficAnalyzer *GetHttpTrafficAnalyzer();
+
  private:
   nsHttpHandler();
 
   virtual ~nsHttpHandler();
 
   MOZ_MUST_USE nsresult Init();
 
   //
@@ -658,16 +661,18 @@ class nsHttpHandler final : public nsIHt
   uint32_t mFastOpenStallsLimit;
   uint32_t mFastOpenStallsCounter;
   uint32_t mFastOpenStallsIdleTime;
   uint32_t mFastOpenStallsTimeout;
 
   // If true, the transactions from active tab will be dispatched first.
   bool mActiveTabPriority;
 
+  HttpTrafficAnalyzer mHttpTrafficAnalyzer;
+
  private:
   // For Rate Pacing Certain Network Events. Only assign this pointer on
   // socket thread.
   void MakeNewRequestTokenBucket();
   RefPtr<EventTokenBucket> mRequestTokenBucket;
 
  public:
   // Socket thread only
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -141,17 +141,18 @@ nsHttpTransaction::nsHttpTransaction()
       mTopLevelOuterContentWindowId(0),
       mSubmittedRatePacing(false),
       mPassedRatePacing(false),
       mSynchronousRatePaceRequest(false),
       mClassOfService(0),
       m0RTTInProgress(false),
       mDoNotTryEarlyData(false),
       mEarlyDataDisposition(EARLY_NONE),
-      mFastOpenStatus(TFO_NOT_TRIED) {
+      mFastOpenStatus(TFO_NOT_TRIED),
+      mTrafficCategory(HttpTrafficCategory::eInvalid) {
   this->mSelfAddr.inet = {};
   this->mPeerAddr.inet = {};
   LOG(("Creating nsHttpTransaction @%p\n", this));
 
 #ifdef MOZ_VALGRIND
   memset(&mSelfAddr, 0, sizeof(NetAddr));
   memset(&mPeerAddr, 0, sizeof(NetAddr));
 #endif
@@ -256,29 +257,32 @@ nsHttpTransaction::~nsHttpTransaction() 
   }
 }
 
 nsresult nsHttpTransaction::Init(
     uint32_t caps, nsHttpConnectionInfo *cinfo, nsHttpRequestHead *requestHead,
     nsIInputStream *requestBody, uint64_t requestContentLength,
     bool requestBodyHasHeaders, nsIEventTarget *target,
     nsIInterfaceRequestor *callbacks, nsITransportEventSink *eventsink,
-    uint64_t topLevelOuterContentWindowId, nsIAsyncInputStream **responseBody) {
+    uint64_t topLevelOuterContentWindowId, HttpTrafficCategory trafficCategory,
+    nsIAsyncInputStream **responseBody) {
   nsresult rv;
 
   LOG1(("nsHttpTransaction::Init [this=%p caps=%x]\n", this, caps));
 
   MOZ_ASSERT(cinfo);
   MOZ_ASSERT(requestHead);
   MOZ_ASSERT(target);
   MOZ_ASSERT(NS_IsMainThread());
 
   mTopLevelOuterContentWindowId = topLevelOuterContentWindowId;
   LOG(("  window-id = %" PRIx64, mTopLevelOuterContentWindowId));
 
+  mTrafficCategory = trafficCategory;
+
   mActivityDistributor = services::GetActivityDistributor();
   if (!mActivityDistributor) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   bool activityDistributorActive;
   rv = mActivityDistributor->GetIsActive(&activityDistributorActive);
   if (NS_SUCCEEDED(rv) && activityDistributorActive) {
@@ -520,16 +524,26 @@ void nsHttpTransaction::SetConnection(ns
 
 void nsHttpTransaction::OnActivated() {
   MOZ_ASSERT(OnSocketThread());
 
   if (mActivated) {
     return;
   }
 
+  if (mTrafficCategory != HttpTrafficCategory::eInvalid) {
+    HttpTrafficAnalyzer *hta = gHttpHandler->GetHttpTrafficAnalyzer();
+    if (hta) {
+      hta->IncrementHttpTransaction(mTrafficCategory);
+    }
+    if (mConnection) {
+      mConnection->SetTrafficCategory(mTrafficCategory);
+    }
+  }
+
   if (mConnection && mRequestHead &&
       mConnection->Version() >= HttpVersion::v2_0) {
     // So this is fun. On http/2, we want to send TE: Trailers, to be
     // spec-compliant. So we add it to the request head here. The fun part
     // is that adding a header to the request head at this point has no
     // effect on what we send on the wire, as the headers are already
     // flattened (in Init()) by the time we get here. So the *real* adding
     // of the header happens in the h2 compression code. We still have to
@@ -1169,16 +1183,24 @@ void nsHttpTransaction::Close(nsresult r
   // EOF or an error still require an end time be recorded.
   if (TimingEnabled()) {
     const TimingStruct timings = Timings();
     if (timings.responseEnd.IsNull() && !timings.responseStart.IsNull()) {
       SetResponseEnd(TimeStamp::Now());
     }
   }
 
+  if (mTrafficCategory != HttpTrafficCategory::eInvalid) {
+    HttpTrafficAnalyzer *hta = gHttpHandler->GetHttpTrafficAnalyzer();
+    if (hta) {
+      hta->AccumulateHttpTransferredSize(mTrafficCategory, mTransferSize,
+                                         mContentRead);
+    }
+  }
+
   if (relConn && mConnection) {
     MutexAutoLock lock(mLock);
     mConnection = nullptr;
   }
 
   mStatus = reason;
   mTransactionDone = true;  // forcibly flag the transaction as complete
   mClosed = true;
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -75,25 +75,23 @@ class nsHttpTransaction final : public n
   // @param topLevelOuterContentWindowId
   //        indicate the top level outer content window in which
   //        this transaction is being loaded.
   // @param responseBody
   //        the input stream that will contain the response data.  async
   //        wait on this input stream for data.  on first notification,
   //        headers should be available (check transaction status).
   //
-  MOZ_MUST_USE nsresult Init(uint32_t caps, nsHttpConnectionInfo *connInfo,
-                             nsHttpRequestHead *reqHeaders,
-                             nsIInputStream *reqBody, uint64_t reqContentLength,
-                             bool reqBodyIncludesHeaders,
-                             nsIEventTarget *consumerTarget,
-                             nsIInterfaceRequestor *callbacks,
-                             nsITransportEventSink *eventsink,
-                             uint64_t topLevelOuterContentWindowId,
-                             nsIAsyncInputStream **responseBody);
+  MOZ_MUST_USE nsresult
+  Init(uint32_t caps, nsHttpConnectionInfo *connInfo,
+       nsHttpRequestHead *reqHeaders, nsIInputStream *reqBody,
+       uint64_t reqContentLength, bool reqBodyIncludesHeaders,
+       nsIEventTarget *consumerTarget, nsIInterfaceRequestor *callbacks,
+       nsITransportEventSink *eventsink, uint64_t topLevelOuterContentWindowId,
+       HttpTrafficCategory trafficCategory, nsIAsyncInputStream **responseBody);
 
   void OnActivated() override;
 
   // attributes
   nsHttpResponseHead *ResponseHead() {
     return mHaveAllHeaders ? mResponseHead : nullptr;
   }
   nsISupports *SecurityInfo() { return mSecurityInfo; }
@@ -479,14 +477,16 @@ class nsHttpTransaction final : public n
     EARLY_ACCEPTED,
     EARLY_425
   } mEarlyDataDisposition;
 
   uint8_t mFastOpenStatus;
 
   // H2 websocket support
   RefPtr<SpdyConnectTransaction> mH2WSTransaction;
+
+  HttpTrafficCategory mTrafficCategory;
 };
 
 }  // namespace net
 }  // namespace mozilla
 
 #endif  // nsHttpTransaction_h__