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