summaryrefslogtreecommitdiffstats
path: root/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.cpp')
-rw-r--r--dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.cpp282
1 files changed, 282 insertions, 0 deletions
diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.cpp b/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.cpp
new file mode 100644
index 0000000000..1d576b5dca
--- /dev/null
+++ b/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.cpp
@@ -0,0 +1,282 @@
+/* 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 "WebrtcGlobalStatsHistory.h"
+#include <memory>
+
+#include "domstubs.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/RTCStatsReportBinding.h" // for RTCStatsReportInternal
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/fallible.h"
+#include "mozilla/mozalloc_oom.h"
+#include "nsDOMNavigationTiming.h"
+
+namespace mozilla::dom {
+
+constexpr auto SEC_TO_MS(const DOMHighResTimeStamp sec) -> DOMHighResTimeStamp {
+ return sec * 1000.0;
+}
+
+constexpr auto MIN_TO_MS(const DOMHighResTimeStamp min) -> DOMHighResTimeStamp {
+ return SEC_TO_MS(min * 60.0);
+}
+
+// Prefs
+auto WebrtcGlobalStatsHistory::Pref::Enabled() -> bool {
+ return mozilla::StaticPrefs::media_aboutwebrtc_hist_enabled();
+}
+
+auto WebrtcGlobalStatsHistory::Pref::PollIntervalMs() -> uint32_t {
+ return mozilla::StaticPrefs::media_aboutwebrtc_hist_poll_interval_ms();
+}
+
+auto WebrtcGlobalStatsHistory::Pref::StorageWindowS() -> uint32_t {
+ return mozilla::StaticPrefs::media_aboutwebrtc_hist_storage_window_s();
+}
+
+auto WebrtcGlobalStatsHistory::Pref::PruneAfterM() -> uint32_t {
+ return mozilla::StaticPrefs::media_aboutwebrtc_hist_prune_after_m();
+}
+
+auto WebrtcGlobalStatsHistory::Pref::ClosedStatsToRetain() -> uint32_t {
+ return mozilla::StaticPrefs::media_aboutwebrtc_hist_closed_stats_to_retain();
+}
+
+auto WebrtcGlobalStatsHistory::Get() -> WebrtcGlobalStatsHistory::StatsMap& {
+ static StaticAutoPtr<StatsMap> sHist;
+ if (!sHist) {
+ sHist = new StatsMap();
+ ClearOnShutdown(&sHist);
+ }
+ return *sHist;
+}
+
+auto WebrtcGlobalStatsHistory::Entry::ReportElement::Timestamp() const
+ -> DOMHighResTimeStamp {
+ return report->mTimestamp;
+}
+
+auto WebrtcGlobalStatsHistory::Entry::SdpElement::Timestamp() const
+ -> DOMHighResTimeStamp {
+ return sdp.mTimestamp;
+}
+
+auto WebrtcGlobalStatsHistory::Entry::MakeReportElement(
+ UniquePtr<RTCStatsReportInternal> aReport)
+ -> WebrtcGlobalStatsHistory::Entry::ReportElement* {
+ auto* elem = new ReportElement();
+ elem->report = std::move(aReport);
+ // We don't want to store a copy of the SDP history with each stats entry.
+ // SDP History is stored seperately, see MakeSdpElements.
+ elem->report->mSdpHistory.Clear();
+ return elem;
+}
+
+auto WebrtcGlobalStatsHistory::Entry::MakeSdpElementsSince(
+ Sequence<RTCSdpHistoryEntryInternal>&& aSdpHistory,
+ const Maybe<DOMHighResTimeStamp>& aSdpAfter)
+ -> AutoCleanLinkedList<WebrtcGlobalStatsHistory::Entry::SdpElement> {
+ AutoCleanLinkedList<WebrtcGlobalStatsHistory::Entry::SdpElement> result;
+ for (auto& sdpHist : aSdpHistory) {
+ if (!aSdpAfter || aSdpAfter.value() < sdpHist.mTimestamp) {
+ auto* element = new SdpElement();
+ element->sdp = sdpHist;
+ result.insertBack(element);
+ }
+ }
+ return result;
+}
+
+template <typename T>
+auto FindFirstEntryAfter(const T* first,
+ const Maybe<DOMHighResTimeStamp>& aAfter) -> const T* {
+ const auto* current = first;
+ while (aAfter && current && current->Timestamp() <= aAfter.value()) {
+ current = current->getNext();
+ }
+ return current;
+}
+
+template <typename T>
+auto CountElementsToEndInclusive(const LinkedListElement<T>* elem) -> size_t {
+ size_t count = 0;
+ const auto* cursor = elem;
+ while (cursor && cursor->isInList()) {
+ count++;
+ cursor = cursor->getNext();
+ }
+ return count;
+}
+
+auto WebrtcGlobalStatsHistory::Entry::Since(
+ const Maybe<DOMHighResTimeStamp>& aAfter) const
+ -> nsTArray<RTCStatsReportInternal> {
+ nsTArray<RTCStatsReportInternal> results;
+ const auto* cursor = FindFirstEntryAfter(mReports.getFirst(), aAfter);
+ const auto count = CountElementsToEndInclusive(cursor);
+ if (!results.SetCapacity(count, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ while (cursor) {
+ results.AppendElement(RTCStatsReportInternal(*cursor->report));
+ cursor = cursor->getNext();
+ }
+ return results;
+}
+
+auto WebrtcGlobalStatsHistory::Entry::SdpSince(
+ const Maybe<DOMHighResTimeStamp>& aAfter) const -> RTCSdpHistoryInternal {
+ RTCSdpHistoryInternal results;
+ results.mPcid = mPcid;
+ // If no timestamp was passed copy the entire history
+ const auto* cursor = FindFirstEntryAfter(mSdp.getFirst(), aAfter);
+ const auto count = CountElementsToEndInclusive(cursor);
+ if (!results.mSdpHistory.SetCapacity(count, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ while (cursor) {
+ if (!results.mSdpHistory.AppendElement(
+ RTCSdpHistoryEntryInternal(cursor->sdp), fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ cursor = cursor->getNext();
+ }
+ return results;
+}
+
+auto WebrtcGlobalStatsHistory::Entry::Prune(const DOMHighResTimeStamp aBefore)
+ -> void {
+ // Clear everything in the case that we don't keep stats
+ if (mIsLongTermStatsDisabled) {
+ mReports.clear();
+ }
+ // Clear everything before the cutoff
+ for (auto* element = mReports.getFirst();
+ element && element->report->mTimestamp < aBefore;
+ element = mReports.getFirst()) {
+ delete mReports.popFirst();
+ }
+ // I don't think we should prune SDPs but if we did it would look like this:
+ // Note: we always keep the most recent SDP
+}
+
+auto WebrtcGlobalStatsHistory::InitHistory(const nsAString& aPcId,
+ const bool aIsLongTermStatsDisabled)
+ -> void {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (WebrtcGlobalStatsHistory::Get().MaybeGet(aPcId)) {
+ return;
+ }
+ WebrtcGlobalStatsHistory::Get().GetOrInsertNew(aPcId, nsString(aPcId),
+ aIsLongTermStatsDisabled);
+};
+
+auto WebrtcGlobalStatsHistory::Record(UniquePtr<RTCStatsReportInternal> aReport)
+ -> void {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ // Use the report timestamp as "now" for determining time depth
+ // based pruning.
+ const auto now = aReport->mTimestamp;
+ const auto earliest = now - SEC_TO_MS(Pref::StorageWindowS());
+
+ // Store closed state before moving the report
+ const auto closed = aReport->mClosed;
+ const auto pcId = aReport->mPcid;
+
+ auto history = WebrtcGlobalStatsHistory::GetHistory(aReport->mPcid);
+ if (history && Pref::Enabled()) {
+ auto entry = history.value();
+ // Remove expired entries
+ entry->Prune(earliest);
+ // Find new SDP entries
+ auto sdpAfter = Maybe<DOMHighResTimeStamp>(Nothing());
+ if (auto* lastSdp = entry->mSdp.getLast(); lastSdp) {
+ sdpAfter = Some(lastSdp->Timestamp());
+ }
+ entry->mSdp.extendBack(
+ Entry::MakeSdpElementsSince(std::move(aReport->mSdpHistory), sdpAfter));
+ // Reports must be in ascending order by mTimestamp
+ const auto* latest = entry->mReports.getLast();
+ // Maintain sorted order
+ if (!latest || latest->report->mTimestamp < aReport->mTimestamp) {
+ entry->mReports.insertBack(Entry::MakeReportElement(std::move(aReport)));
+ }
+ }
+ // Close the history if needed
+ if (closed) {
+ CloseHistory(pcId);
+ }
+}
+
+auto WebrtcGlobalStatsHistory::CloseHistory(const nsAString& aPcId) -> void {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ auto maybeHist = WebrtcGlobalStatsHistory::Get().MaybeGet(aPcId);
+ if (!maybeHist) {
+ return;
+ }
+ {
+ auto&& hist = maybeHist.value();
+ hist->mIsClosed = true;
+
+ if (hist->mIsLongTermStatsDisabled) {
+ WebrtcGlobalStatsHistory::Get().Remove(aPcId);
+ return;
+ }
+ }
+ size_t remainingClosedStatsToRetain =
+ WebrtcGlobalStatsHistory::Pref::ClosedStatsToRetain();
+ WebrtcGlobalStatsHistory::Get().RemoveIf([&](auto& iter) {
+ auto& entry = iter.Data();
+ if (!entry->mIsClosed) {
+ return false;
+ }
+ if (entry->mIsLongTermStatsDisabled) {
+ return true;
+ }
+ if (remainingClosedStatsToRetain > 0) {
+ remainingClosedStatsToRetain -= 1;
+ return false;
+ }
+ return true;
+ });
+}
+
+auto WebrtcGlobalStatsHistory::Clear() -> void {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ WebrtcGlobalStatsHistory::Get().RemoveIf([](auto& aIter) {
+ // First clear all the closed histories.
+ if (aIter.Data()->mIsClosed) {
+ return true;
+ }
+ // For all remaining histories clear their stored reports
+ aIter.Data()->mReports.clear();
+ // As an optimization we don't clear the SDP, because that would
+ // be reconstitued in the very next stats gathering polling period.
+ // Those are potentially large allocations which we can skip.
+ return false;
+ });
+}
+
+auto WebrtcGlobalStatsHistory::PcIds() -> dom::Sequence<nsString> {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ dom::Sequence<nsString> pcIds;
+ for (const auto& pcId : WebrtcGlobalStatsHistory::Get().Keys()) {
+ if (!pcIds.AppendElement(pcId, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ return pcIds;
+}
+
+auto WebrtcGlobalStatsHistory::GetHistory(const nsAString& aPcId)
+ -> Maybe<RefPtr<Entry> > {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ const auto pcid = NS_ConvertUTF16toUTF8(aPcId);
+
+ return WebrtcGlobalStatsHistory::Get().MaybeGet(aPcId);
+}
+} // namespace mozilla::dom