michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "WebrtcGlobalInformation.h" michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #include "CSFLog.h" michael@0: michael@0: #include "mozilla/dom/WebrtcGlobalInformationBinding.h" michael@0: michael@0: #include "nsAutoPtr.h" michael@0: #include "nsNetCID.h" // NS_SOCKETTRANSPORTSERVICE_CONTRACTID michael@0: #include "nsServiceManagerUtils.h" // do_GetService michael@0: #include "mozilla/ErrorResult.h" michael@0: #include "mozilla/Vector.h" michael@0: #include "nsProxyRelease.h" michael@0: #include "mozilla/Telemetry.h" michael@0: michael@0: #include "rlogringbuffer.h" michael@0: #include "runnable_utils.h" michael@0: #include "PeerConnectionCtx.h" michael@0: #include "PeerConnectionImpl.h" michael@0: michael@0: using sipcc::PeerConnectionImpl; michael@0: using sipcc::PeerConnectionCtx; michael@0: using sipcc::RTCStatsQuery; michael@0: michael@0: static const char* logTag = "WebrtcGlobalInformation"; michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: typedef Vector> RTCStatsQueries; michael@0: michael@0: static void OnStatsReport_m( michael@0: nsMainThreadPtrHandle aStatsCallback, michael@0: nsAutoPtr aQueryList) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(aQueryList); michael@0: michael@0: WebrtcGlobalStatisticsReport report; michael@0: report.mReports.Construct(); michael@0: for (auto q = aQueryList->begin(); q != aQueryList->end(); ++q) { michael@0: MOZ_ASSERT(*q); michael@0: report.mReports.Value().AppendElement((*q)->report); michael@0: } michael@0: michael@0: ErrorResult rv; michael@0: aStatsCallback.get()->Call(report, rv); michael@0: michael@0: if (rv.Failed()) { michael@0: CSFLogError(logTag, "Error firing stats observer callback"); michael@0: } michael@0: } michael@0: michael@0: static void GetAllStats_s( michael@0: nsMainThreadPtrHandle aStatsCallback, michael@0: nsAutoPtr aQueryList) michael@0: { michael@0: MOZ_ASSERT(aQueryList); michael@0: michael@0: for (auto q = aQueryList->begin(); q != aQueryList->end(); ++q) { michael@0: MOZ_ASSERT(*q); michael@0: PeerConnectionImpl::ExecuteStatsQuery_s(*q); michael@0: } michael@0: michael@0: NS_DispatchToMainThread(WrapRunnableNM(&OnStatsReport_m, michael@0: aStatsCallback, michael@0: aQueryList), michael@0: NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: static void OnGetLogging_m( michael@0: nsMainThreadPtrHandle aLoggingCallback, michael@0: const std::string& aPattern, michael@0: nsAutoPtr> aLogList) michael@0: { michael@0: ErrorResult rv; michael@0: if (!aLogList->empty()) { michael@0: Sequence nsLogs; michael@0: for (auto l = aLogList->begin(); l != aLogList->end(); ++l) { michael@0: nsLogs.AppendElement(NS_ConvertUTF8toUTF16(l->c_str())); michael@0: } michael@0: aLoggingCallback.get()->Call(nsLogs, rv); michael@0: } michael@0: michael@0: if (rv.Failed()) { michael@0: CSFLogError(logTag, "Error firing logging observer callback"); michael@0: } michael@0: } michael@0: michael@0: static void GetLogging_s( michael@0: nsMainThreadPtrHandle aLoggingCallback, michael@0: const std::string& aPattern) michael@0: { michael@0: RLogRingBuffer* logs = RLogRingBuffer::GetInstance(); michael@0: nsAutoPtr> result(new std::deque); michael@0: // Might not exist yet. michael@0: if (logs) { michael@0: logs->Filter(aPattern, 0, result); michael@0: } michael@0: NS_DispatchToMainThread(WrapRunnableNM(&OnGetLogging_m, michael@0: aLoggingCallback, michael@0: aPattern, michael@0: result), michael@0: NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: michael@0: void michael@0: WebrtcGlobalInformation::GetAllStats( michael@0: const GlobalObject& aGlobal, michael@0: WebrtcGlobalStatisticsCallback& aStatsCallback, michael@0: ErrorResult& aRv) michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: aRv.Throw(NS_ERROR_NOT_SAME_THREAD); michael@0: return; michael@0: } michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr stsThread = michael@0: do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: return; michael@0: } michael@0: michael@0: if (!stsThread) { michael@0: aRv.Throw(NS_ERROR_UNEXPECTED); michael@0: return; michael@0: } michael@0: michael@0: nsAutoPtr queries(new RTCStatsQueries); michael@0: michael@0: // If there is no PeerConnectionCtx, go through the same motions, since michael@0: // the API consumer doesn't care why there are no PeerConnectionImpl. michael@0: if (PeerConnectionCtx::isActive()) { michael@0: PeerConnectionCtx *ctx = PeerConnectionCtx::GetInstance(); michael@0: MOZ_ASSERT(ctx); michael@0: for (auto p = ctx->mPeerConnections.begin(); michael@0: p != ctx->mPeerConnections.end(); michael@0: ++p) { michael@0: MOZ_ASSERT(p->second); michael@0: michael@0: if (p->second->HasMedia()) { michael@0: queries->append(nsAutoPtr(new RTCStatsQuery(true))); michael@0: p->second->BuildStatsQuery_m(nullptr, // all tracks michael@0: queries->back()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // CallbackObject does not support threadsafe refcounting, and must be michael@0: // destroyed on main. michael@0: nsMainThreadPtrHandle callbackHandle( michael@0: new nsMainThreadPtrHolder(&aStatsCallback)); michael@0: michael@0: rv = RUN_ON_THREAD(stsThread, michael@0: WrapRunnableNM(&GetAllStats_s, callbackHandle, queries), michael@0: NS_DISPATCH_NORMAL); michael@0: michael@0: aRv = rv; michael@0: } michael@0: michael@0: void michael@0: WebrtcGlobalInformation::GetLogging( michael@0: const GlobalObject& aGlobal, michael@0: const nsAString& aPattern, michael@0: WebrtcGlobalLoggingCallback& aLoggingCallback, michael@0: ErrorResult& aRv) michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: aRv.Throw(NS_ERROR_NOT_SAME_THREAD); michael@0: return; michael@0: } michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr stsThread = michael@0: do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: return; michael@0: } michael@0: michael@0: if (!stsThread) { michael@0: aRv.Throw(NS_ERROR_UNEXPECTED); michael@0: return; michael@0: } michael@0: michael@0: std::string pattern(NS_ConvertUTF16toUTF8(aPattern).get()); michael@0: michael@0: // CallbackObject does not support threadsafe refcounting, and must be michael@0: // destroyed on main. michael@0: nsMainThreadPtrHandle callbackHandle( michael@0: new nsMainThreadPtrHolder(&aLoggingCallback)); michael@0: michael@0: rv = RUN_ON_THREAD(stsThread, michael@0: WrapRunnableNM(&GetLogging_s, callbackHandle, pattern), michael@0: NS_DISPATCH_NORMAL); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: aLoggingCallback.Release(); michael@0: } michael@0: michael@0: aRv = rv; michael@0: } michael@0: michael@0: struct StreamResult { michael@0: StreamResult() : candidateTypeBitpattern(0), streamSucceeded(false) {} michael@0: uint8_t candidateTypeBitpattern; michael@0: bool streamSucceeded; michael@0: }; michael@0: michael@0: static void StoreLongTermICEStatisticsImpl_m( michael@0: nsresult result, michael@0: nsAutoPtr query) { michael@0: michael@0: if (NS_FAILED(result) || michael@0: !query->error.empty() || michael@0: !query->report.mIceCandidateStats.WasPassed()) { michael@0: return; michael@0: } michael@0: michael@0: // First, store stuff in telemetry michael@0: enum { michael@0: REMOTE_GATHERED_SERVER_REFLEXIVE = 1, michael@0: REMOTE_GATHERED_TURN = 1 << 1, michael@0: LOCAL_GATHERED_SERVER_REFLEXIVE = 1 << 2, michael@0: LOCAL_GATHERED_TURN_UDP = 1 << 3, michael@0: LOCAL_GATHERED_TURN_TCP = 1 << 4, michael@0: LOCAL_GATHERED_TURN_TLS = 1 << 5, michael@0: LOCAL_GATHERED_TURN_HTTPS = 1 << 6, michael@0: }; michael@0: michael@0: // TODO(bcampen@mozilla.com): Do we need to watch out for cases where the michael@0: // components within a stream didn't have the same types of relayed michael@0: // candidates? I have a feeling that late trickle could cause this, but right michael@0: // now we don't have enough information to detect it (we would need to know michael@0: // the ICE component id for each candidate pair and candidate) michael@0: michael@0: std::map streamResults; michael@0: michael@0: // Build list of streams, and whether or not they failed. michael@0: for (size_t i = 0; michael@0: i < query->report.mIceCandidatePairStats.Value().Length(); michael@0: ++i) { michael@0: const RTCIceCandidatePairStats &pair = michael@0: query->report.mIceCandidatePairStats.Value()[i]; michael@0: michael@0: if (!pair.mState.WasPassed() || !pair.mComponentId.WasPassed()) { michael@0: MOZ_CRASH(); michael@0: continue; michael@0: } michael@0: michael@0: // Note: this is not a "component" in the ICE definition, this is really a michael@0: // stream ID. This is just the way the stats API is standardized right now. michael@0: // Very confusing. michael@0: std::string streamId( michael@0: NS_ConvertUTF16toUTF8(pair.mComponentId.Value()).get()); michael@0: michael@0: streamResults[streamId].streamSucceeded |= michael@0: pair.mState.Value() == RTCStatsIceCandidatePairState::Succeeded; michael@0: } michael@0: michael@0: for (size_t i = 0; michael@0: i < query->report.mIceCandidateStats.Value().Length(); michael@0: ++i) { michael@0: const RTCIceCandidateStats &cand = michael@0: query->report.mIceCandidateStats.Value()[i]; michael@0: michael@0: if (!cand.mType.WasPassed() || michael@0: !cand.mCandidateType.WasPassed() || michael@0: !cand.mComponentId.WasPassed()) { michael@0: // Crash on debug, ignore this candidate otherwise. michael@0: MOZ_CRASH(); michael@0: continue; michael@0: } michael@0: michael@0: // Note: this is not a "component" in the ICE definition, this is really a michael@0: // stream ID. This is just the way the stats API is standardized right now michael@0: // Very confusing. michael@0: std::string streamId( michael@0: NS_ConvertUTF16toUTF8(cand.mComponentId.Value()).get()); michael@0: michael@0: if (cand.mCandidateType.Value() == RTCStatsIceCandidateType::Relayed) { michael@0: if (cand.mType.Value() == RTCStatsType::Localcandidate) { michael@0: NS_ConvertUTF16toUTF8 transport(cand.mMozLocalTransport.Value()); michael@0: if (transport == kNrIceTransportUdp) { michael@0: streamResults[streamId].candidateTypeBitpattern |= michael@0: LOCAL_GATHERED_TURN_UDP; michael@0: } else if (transport == kNrIceTransportTcp) { michael@0: streamResults[streamId].candidateTypeBitpattern |= michael@0: LOCAL_GATHERED_TURN_TCP; michael@0: } michael@0: } else { michael@0: streamResults[streamId].candidateTypeBitpattern |= REMOTE_GATHERED_TURN; michael@0: } michael@0: } else if (cand.mCandidateType.Value() == michael@0: RTCStatsIceCandidateType::Serverreflexive) { michael@0: if (cand.mType.Value() == RTCStatsType::Localcandidate) { michael@0: streamResults[streamId].candidateTypeBitpattern |= michael@0: LOCAL_GATHERED_SERVER_REFLEXIVE; michael@0: } else { michael@0: streamResults[streamId].candidateTypeBitpattern |= michael@0: REMOTE_GATHERED_SERVER_REFLEXIVE; michael@0: } michael@0: } michael@0: } michael@0: michael@0: for (auto i = streamResults.begin(); i != streamResults.end(); ++i) { michael@0: if (i->second.streamSucceeded) { michael@0: Telemetry::Accumulate(Telemetry::WEBRTC_CANDIDATE_TYPES_GIVEN_SUCCESS, michael@0: i->second.candidateTypeBitpattern); michael@0: } else { michael@0: Telemetry::Accumulate(Telemetry::WEBRTC_CANDIDATE_TYPES_GIVEN_FAILURE, michael@0: i->second.candidateTypeBitpattern); michael@0: } michael@0: } michael@0: } michael@0: michael@0: static void GetStatsForLongTermStorage_s( michael@0: nsAutoPtr query) { michael@0: michael@0: MOZ_ASSERT(query); michael@0: michael@0: nsresult rv = PeerConnectionImpl::ExecuteStatsQuery_s(query.get()); michael@0: michael@0: // Even if Telemetry::Accumulate is threadsafe, we still need to send the michael@0: // query back to main, since that is where it must be destroyed. michael@0: NS_DispatchToMainThread( michael@0: WrapRunnableNM( michael@0: &StoreLongTermICEStatisticsImpl_m, michael@0: rv, michael@0: query), michael@0: NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: void WebrtcGlobalInformation::StoreLongTermICEStatistics( michael@0: sipcc::PeerConnectionImpl& aPc) { michael@0: Telemetry::Accumulate(Telemetry::WEBRTC_ICE_FINAL_CONNECTION_STATE, michael@0: static_cast(aPc.IceConnectionState())); michael@0: michael@0: if (aPc.IceConnectionState() == PCImplIceConnectionState::New) { michael@0: // ICE has not started; we won't have any remote candidates, so recording michael@0: // statistics on gathered candidates is pointless. michael@0: return; michael@0: } michael@0: michael@0: nsAutoPtr query(new RTCStatsQuery(true)); michael@0: michael@0: nsresult rv = aPc.BuildStatsQuery_m(nullptr, query.get()); michael@0: michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: RUN_ON_THREAD(aPc.GetSTSThread(), michael@0: WrapRunnableNM(&GetStatsForLongTermStorage_s, michael@0: query), michael@0: NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: michael@0: } // namespace dom michael@0: } // namespace mozilla michael@0: