tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

WebrtcGlobalStatsHistory.cpp (9097B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "WebrtcGlobalStatsHistory.h"
      6 
      7 #include "domstubs.h"
      8 #include "mozilla/ClearOnShutdown.h"
      9 #include "mozilla/LinkedList.h"
     10 #include "mozilla/StaticPrefs_media.h"
     11 #include "mozilla/dom/RTCStatsReportBinding.h"  // for RTCStatsReportInternal
     12 #include "mozilla/fallible.h"
     13 #include "mozilla/mozalloc_oom.h"
     14 #include "nsDOMNavigationTiming.h"
     15 
     16 namespace mozilla::dom {
     17 
     18 constexpr auto SEC_TO_MS(const DOMHighResTimeStamp sec) -> DOMHighResTimeStamp {
     19  return sec * 1000.0;
     20 }
     21 
     22 constexpr auto MIN_TO_MS(const DOMHighResTimeStamp min) -> DOMHighResTimeStamp {
     23  return SEC_TO_MS(min * 60.0);
     24 }
     25 
     26 // Prefs
     27 auto WebrtcGlobalStatsHistory::Pref::Enabled() -> bool {
     28  return mozilla::StaticPrefs::media_aboutwebrtc_hist_enabled();
     29 }
     30 
     31 auto WebrtcGlobalStatsHistory::Pref::PollIntervalMs() -> uint32_t {
     32  return mozilla::StaticPrefs::media_aboutwebrtc_hist_poll_interval_ms();
     33 }
     34 
     35 auto WebrtcGlobalStatsHistory::Pref::StorageWindowS() -> uint32_t {
     36  return mozilla::StaticPrefs::media_aboutwebrtc_hist_storage_window_s();
     37 }
     38 
     39 auto WebrtcGlobalStatsHistory::Pref::PruneAfterM() -> uint32_t {
     40  return mozilla::StaticPrefs::media_aboutwebrtc_hist_prune_after_m();
     41 }
     42 
     43 auto WebrtcGlobalStatsHistory::Pref::ClosedStatsToRetain() -> uint32_t {
     44  return mozilla::StaticPrefs::media_aboutwebrtc_hist_closed_stats_to_retain();
     45 }
     46 
     47 auto WebrtcGlobalStatsHistory::Get() -> WebrtcGlobalStatsHistory::StatsMap& {
     48  static StaticAutoPtr<StatsMap> sHist;
     49  if (!sHist) {
     50    sHist = new StatsMap();
     51    ClearOnShutdown(&sHist);
     52  }
     53  return *sHist;
     54 }
     55 
     56 auto WebrtcGlobalStatsHistory::Entry::ReportElement::Timestamp() const
     57    -> DOMHighResTimeStamp {
     58  return report->mTimestamp;
     59 }
     60 
     61 auto WebrtcGlobalStatsHistory::Entry::SdpElement::Timestamp() const
     62    -> DOMHighResTimeStamp {
     63  return sdp.mTimestamp;
     64 }
     65 
     66 auto WebrtcGlobalStatsHistory::Entry::MakeReportElement(
     67    UniquePtr<RTCStatsReportInternal> aReport)
     68    -> WebrtcGlobalStatsHistory::Entry::ReportElement* {
     69  auto* elem = new ReportElement();
     70  elem->report = std::move(aReport);
     71  // We don't want to store a copy of the SDP history with each stats entry.
     72  // SDP History is stored seperately, see MakeSdpElements.
     73  elem->report->mSdpHistory.Clear();
     74  return elem;
     75 }
     76 
     77 auto WebrtcGlobalStatsHistory::Entry::MakeSdpElementsSince(
     78    Sequence<RTCSdpHistoryEntryInternal>&& aSdpHistory,
     79    const Maybe<DOMHighResTimeStamp>& aSdpAfter)
     80    -> AutoCleanLinkedList<WebrtcGlobalStatsHistory::Entry::SdpElement> {
     81  AutoCleanLinkedList<WebrtcGlobalStatsHistory::Entry::SdpElement> result;
     82  for (auto& sdpHist : aSdpHistory) {
     83    if (!aSdpAfter || aSdpAfter.value() < sdpHist.mTimestamp) {
     84      auto* element = new SdpElement();
     85      element->sdp = sdpHist;
     86      result.insertBack(element);
     87    }
     88  }
     89  return result;
     90 }
     91 
     92 template <typename T>
     93 auto FindFirstEntryAfter(const T* first,
     94                         const Maybe<DOMHighResTimeStamp>& aAfter) -> const T* {
     95  const auto* current = first;
     96  while (aAfter && current && current->Timestamp() <= aAfter.value()) {
     97    current = current->getNext();
     98  }
     99  return current;
    100 }
    101 
    102 template <typename T>
    103 auto CountElementsToEndInclusive(const LinkedListElement<T>* elem) -> size_t {
    104  size_t count = 0;
    105  const auto* cursor = elem;
    106  while (cursor && cursor->isInList()) {
    107    count++;
    108    cursor = cursor->getNext();
    109  }
    110  return count;
    111 }
    112 
    113 auto WebrtcGlobalStatsHistory::Entry::Since(
    114    const Maybe<DOMHighResTimeStamp>& aAfter) const
    115    -> nsTArray<RTCStatsReportInternal> {
    116  nsTArray<RTCStatsReportInternal> results;
    117  const auto* cursor = FindFirstEntryAfter(mReports.getFirst(), aAfter);
    118  const auto count = CountElementsToEndInclusive(cursor);
    119  if (!results.SetCapacity(count, fallible)) {
    120    mozalloc_handle_oom(0);
    121  }
    122  while (cursor) {
    123    results.AppendElement(RTCStatsReportInternal(*cursor->report));
    124    cursor = cursor->getNext();
    125  }
    126  return results;
    127 }
    128 
    129 auto WebrtcGlobalStatsHistory::Entry::SdpSince(
    130    const Maybe<DOMHighResTimeStamp>& aAfter) const -> RTCSdpHistoryInternal {
    131  RTCSdpHistoryInternal results;
    132  results.mPcid = mPcid;
    133  // If no timestamp was passed copy the entire history
    134  const auto* cursor = FindFirstEntryAfter(mSdp.getFirst(), aAfter);
    135  const auto count = CountElementsToEndInclusive(cursor);
    136  if (!results.mSdpHistory.SetCapacity(count, fallible)) {
    137    mozalloc_handle_oom(0);
    138  }
    139  while (cursor) {
    140    if (!results.mSdpHistory.AppendElement(
    141            RTCSdpHistoryEntryInternal(cursor->sdp), fallible)) {
    142      mozalloc_handle_oom(0);
    143    }
    144    cursor = cursor->getNext();
    145  }
    146  return results;
    147 }
    148 
    149 auto WebrtcGlobalStatsHistory::Entry::Prune(const DOMHighResTimeStamp aBefore)
    150    -> void {
    151  // Clear everything in the case that we don't keep stats
    152  if (mIsLongTermStatsDisabled) {
    153    mReports.clear();
    154  }
    155  // Clear everything before the cutoff
    156  for (auto* element = mReports.getFirst();
    157       element && element->report->mTimestamp < aBefore;
    158       element = mReports.getFirst()) {
    159    delete mReports.popFirst();
    160  }
    161  // I don't think we should prune SDPs but if we did it would look like this:
    162  // Note: we always keep the most recent SDP
    163 }
    164 
    165 auto WebrtcGlobalStatsHistory::InitHistory(const nsAString& aPcId,
    166                                           const bool aIsLongTermStatsDisabled)
    167    -> void {
    168  MOZ_ASSERT(XRE_IsParentProcess());
    169  if (WebrtcGlobalStatsHistory::Get().MaybeGet(aPcId)) {
    170    return;
    171  }
    172  WebrtcGlobalStatsHistory::Get().GetOrInsertNew(aPcId, nsString(aPcId),
    173                                                 aIsLongTermStatsDisabled);
    174 };
    175 
    176 auto WebrtcGlobalStatsHistory::Record(UniquePtr<RTCStatsReportInternal> aReport)
    177    -> void {
    178  MOZ_ASSERT(XRE_IsParentProcess());
    179  // Use the report timestamp as "now" for determining time depth
    180  // based pruning.
    181  const auto now = aReport->mTimestamp;
    182  const auto earliest = now - SEC_TO_MS(Pref::StorageWindowS());
    183 
    184  // Store closed state before moving the report
    185  const auto closed = aReport->mClosed;
    186  const auto pcId = aReport->mPcid;
    187 
    188  auto history = WebrtcGlobalStatsHistory::GetHistory(aReport->mPcid);
    189  if (history && Pref::Enabled()) {
    190    auto entry = history.value();
    191    // Remove expired entries
    192    entry->Prune(earliest);
    193    // Find new SDP entries
    194    auto sdpAfter = Maybe<DOMHighResTimeStamp>(Nothing());
    195    if (auto* lastSdp = entry->mSdp.getLast(); lastSdp) {
    196      sdpAfter = Some(lastSdp->Timestamp());
    197    }
    198    entry->mSdp.extendBack(
    199        Entry::MakeSdpElementsSince(std::move(aReport->mSdpHistory), sdpAfter));
    200    // Reports must be in ascending order by mTimestamp
    201    const auto* latest = entry->mReports.getLast();
    202    // Maintain sorted order
    203    if (!latest || latest->report->mTimestamp < aReport->mTimestamp) {
    204      entry->mReports.insertBack(Entry::MakeReportElement(std::move(aReport)));
    205    }
    206  }
    207  // Close the history if needed
    208  if (closed) {
    209    CloseHistory(pcId);
    210  }
    211 }
    212 
    213 auto WebrtcGlobalStatsHistory::CloseHistory(const nsAString& aPcId) -> void {
    214  MOZ_ASSERT(XRE_IsParentProcess());
    215  auto maybeHist = WebrtcGlobalStatsHistory::Get().MaybeGet(aPcId);
    216  if (!maybeHist) {
    217    return;
    218  }
    219  {
    220    auto&& hist = maybeHist.value();
    221    hist->mIsClosed = true;
    222 
    223    if (hist->mIsLongTermStatsDisabled) {
    224      WebrtcGlobalStatsHistory::Get().Remove(aPcId);
    225      return;
    226    }
    227  }
    228  size_t remainingClosedStatsToRetain =
    229      WebrtcGlobalStatsHistory::Pref::ClosedStatsToRetain();
    230  WebrtcGlobalStatsHistory::Get().RemoveIf([&](auto& iter) {
    231    auto& entry = iter.Data();
    232    if (!entry->mIsClosed) {
    233      return false;
    234    }
    235    if (entry->mIsLongTermStatsDisabled) {
    236      return true;
    237    }
    238    if (remainingClosedStatsToRetain > 0) {
    239      remainingClosedStatsToRetain -= 1;
    240      return false;
    241    }
    242    return true;
    243  });
    244 }
    245 
    246 auto WebrtcGlobalStatsHistory::Clear() -> void {
    247  MOZ_ASSERT(XRE_IsParentProcess());
    248  WebrtcGlobalStatsHistory::Get().RemoveIf([](auto& aIter) {
    249    // First clear all the closed histories.
    250    if (aIter.Data()->mIsClosed) {
    251      return true;
    252    }
    253    // For all remaining histories clear their stored reports
    254    aIter.Data()->mReports.clear();
    255    // As an optimization we don't clear the SDP, because that would
    256    // be reconstitued in the very next stats gathering polling period.
    257    // Those are potentially large allocations which we can skip.
    258    return false;
    259  });
    260 }
    261 
    262 auto WebrtcGlobalStatsHistory::PcIds() -> dom::Sequence<nsString> {
    263  MOZ_ASSERT(XRE_IsParentProcess());
    264  dom::Sequence<nsString> pcIds;
    265  for (const auto& pcId : WebrtcGlobalStatsHistory::Get().Keys()) {
    266    if (!pcIds.AppendElement(pcId, fallible)) {
    267      mozalloc_handle_oom(0);
    268    }
    269  }
    270  return pcIds;
    271 }
    272 
    273 auto WebrtcGlobalStatsHistory::GetHistory(const nsAString& aPcId)
    274    -> Maybe<RefPtr<Entry> > {
    275  MOZ_ASSERT(XRE_IsParentProcess());
    276  const auto pcid = NS_ConvertUTF16toUTF8(aPcId);
    277 
    278  return WebrtcGlobalStatsHistory::Get().MaybeGet(aPcId);
    279 }
    280 }  // namespace mozilla::dom