Source code

Revision control

Copy as Markdown

Other Tools

/* 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 "domstubs.h"
#include "mozilla/LinkedList.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