|
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 "WebrtcGlobalInformation.h" |
|
6 |
|
7 #include <deque> |
|
8 #include <string> |
|
9 |
|
10 #include "CSFLog.h" |
|
11 |
|
12 #include "mozilla/dom/WebrtcGlobalInformationBinding.h" |
|
13 |
|
14 #include "nsAutoPtr.h" |
|
15 #include "nsNetCID.h" // NS_SOCKETTRANSPORTSERVICE_CONTRACTID |
|
16 #include "nsServiceManagerUtils.h" // do_GetService |
|
17 #include "mozilla/ErrorResult.h" |
|
18 #include "mozilla/Vector.h" |
|
19 #include "nsProxyRelease.h" |
|
20 #include "mozilla/Telemetry.h" |
|
21 |
|
22 #include "rlogringbuffer.h" |
|
23 #include "runnable_utils.h" |
|
24 #include "PeerConnectionCtx.h" |
|
25 #include "PeerConnectionImpl.h" |
|
26 |
|
27 using sipcc::PeerConnectionImpl; |
|
28 using sipcc::PeerConnectionCtx; |
|
29 using sipcc::RTCStatsQuery; |
|
30 |
|
31 static const char* logTag = "WebrtcGlobalInformation"; |
|
32 |
|
33 namespace mozilla { |
|
34 namespace dom { |
|
35 |
|
36 typedef Vector<nsAutoPtr<RTCStatsQuery>> RTCStatsQueries; |
|
37 |
|
38 static void OnStatsReport_m( |
|
39 nsMainThreadPtrHandle<WebrtcGlobalStatisticsCallback> aStatsCallback, |
|
40 nsAutoPtr<RTCStatsQueries> aQueryList) |
|
41 { |
|
42 MOZ_ASSERT(NS_IsMainThread()); |
|
43 MOZ_ASSERT(aQueryList); |
|
44 |
|
45 WebrtcGlobalStatisticsReport report; |
|
46 report.mReports.Construct(); |
|
47 for (auto q = aQueryList->begin(); q != aQueryList->end(); ++q) { |
|
48 MOZ_ASSERT(*q); |
|
49 report.mReports.Value().AppendElement((*q)->report); |
|
50 } |
|
51 |
|
52 ErrorResult rv; |
|
53 aStatsCallback.get()->Call(report, rv); |
|
54 |
|
55 if (rv.Failed()) { |
|
56 CSFLogError(logTag, "Error firing stats observer callback"); |
|
57 } |
|
58 } |
|
59 |
|
60 static void GetAllStats_s( |
|
61 nsMainThreadPtrHandle<WebrtcGlobalStatisticsCallback> aStatsCallback, |
|
62 nsAutoPtr<RTCStatsQueries> aQueryList) |
|
63 { |
|
64 MOZ_ASSERT(aQueryList); |
|
65 |
|
66 for (auto q = aQueryList->begin(); q != aQueryList->end(); ++q) { |
|
67 MOZ_ASSERT(*q); |
|
68 PeerConnectionImpl::ExecuteStatsQuery_s(*q); |
|
69 } |
|
70 |
|
71 NS_DispatchToMainThread(WrapRunnableNM(&OnStatsReport_m, |
|
72 aStatsCallback, |
|
73 aQueryList), |
|
74 NS_DISPATCH_NORMAL); |
|
75 } |
|
76 |
|
77 static void OnGetLogging_m( |
|
78 nsMainThreadPtrHandle<WebrtcGlobalLoggingCallback> aLoggingCallback, |
|
79 const std::string& aPattern, |
|
80 nsAutoPtr<std::deque<std::string>> aLogList) |
|
81 { |
|
82 ErrorResult rv; |
|
83 if (!aLogList->empty()) { |
|
84 Sequence<nsString> nsLogs; |
|
85 for (auto l = aLogList->begin(); l != aLogList->end(); ++l) { |
|
86 nsLogs.AppendElement(NS_ConvertUTF8toUTF16(l->c_str())); |
|
87 } |
|
88 aLoggingCallback.get()->Call(nsLogs, rv); |
|
89 } |
|
90 |
|
91 if (rv.Failed()) { |
|
92 CSFLogError(logTag, "Error firing logging observer callback"); |
|
93 } |
|
94 } |
|
95 |
|
96 static void GetLogging_s( |
|
97 nsMainThreadPtrHandle<WebrtcGlobalLoggingCallback> aLoggingCallback, |
|
98 const std::string& aPattern) |
|
99 { |
|
100 RLogRingBuffer* logs = RLogRingBuffer::GetInstance(); |
|
101 nsAutoPtr<std::deque<std::string>> result(new std::deque<std::string>); |
|
102 // Might not exist yet. |
|
103 if (logs) { |
|
104 logs->Filter(aPattern, 0, result); |
|
105 } |
|
106 NS_DispatchToMainThread(WrapRunnableNM(&OnGetLogging_m, |
|
107 aLoggingCallback, |
|
108 aPattern, |
|
109 result), |
|
110 NS_DISPATCH_NORMAL); |
|
111 } |
|
112 |
|
113 |
|
114 void |
|
115 WebrtcGlobalInformation::GetAllStats( |
|
116 const GlobalObject& aGlobal, |
|
117 WebrtcGlobalStatisticsCallback& aStatsCallback, |
|
118 ErrorResult& aRv) |
|
119 { |
|
120 if (!NS_IsMainThread()) { |
|
121 aRv.Throw(NS_ERROR_NOT_SAME_THREAD); |
|
122 return; |
|
123 } |
|
124 |
|
125 nsresult rv; |
|
126 nsCOMPtr<nsIEventTarget> stsThread = |
|
127 do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); |
|
128 |
|
129 if (NS_FAILED(rv)) { |
|
130 aRv.Throw(rv); |
|
131 return; |
|
132 } |
|
133 |
|
134 if (!stsThread) { |
|
135 aRv.Throw(NS_ERROR_UNEXPECTED); |
|
136 return; |
|
137 } |
|
138 |
|
139 nsAutoPtr<RTCStatsQueries> queries(new RTCStatsQueries); |
|
140 |
|
141 // If there is no PeerConnectionCtx, go through the same motions, since |
|
142 // the API consumer doesn't care why there are no PeerConnectionImpl. |
|
143 if (PeerConnectionCtx::isActive()) { |
|
144 PeerConnectionCtx *ctx = PeerConnectionCtx::GetInstance(); |
|
145 MOZ_ASSERT(ctx); |
|
146 for (auto p = ctx->mPeerConnections.begin(); |
|
147 p != ctx->mPeerConnections.end(); |
|
148 ++p) { |
|
149 MOZ_ASSERT(p->second); |
|
150 |
|
151 if (p->second->HasMedia()) { |
|
152 queries->append(nsAutoPtr<RTCStatsQuery>(new RTCStatsQuery(true))); |
|
153 p->second->BuildStatsQuery_m(nullptr, // all tracks |
|
154 queries->back()); |
|
155 } |
|
156 } |
|
157 } |
|
158 |
|
159 // CallbackObject does not support threadsafe refcounting, and must be |
|
160 // destroyed on main. |
|
161 nsMainThreadPtrHandle<WebrtcGlobalStatisticsCallback> callbackHandle( |
|
162 new nsMainThreadPtrHolder<WebrtcGlobalStatisticsCallback>(&aStatsCallback)); |
|
163 |
|
164 rv = RUN_ON_THREAD(stsThread, |
|
165 WrapRunnableNM(&GetAllStats_s, callbackHandle, queries), |
|
166 NS_DISPATCH_NORMAL); |
|
167 |
|
168 aRv = rv; |
|
169 } |
|
170 |
|
171 void |
|
172 WebrtcGlobalInformation::GetLogging( |
|
173 const GlobalObject& aGlobal, |
|
174 const nsAString& aPattern, |
|
175 WebrtcGlobalLoggingCallback& aLoggingCallback, |
|
176 ErrorResult& aRv) |
|
177 { |
|
178 if (!NS_IsMainThread()) { |
|
179 aRv.Throw(NS_ERROR_NOT_SAME_THREAD); |
|
180 return; |
|
181 } |
|
182 |
|
183 nsresult rv; |
|
184 nsCOMPtr<nsIEventTarget> stsThread = |
|
185 do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); |
|
186 |
|
187 if (NS_FAILED(rv)) { |
|
188 aRv.Throw(rv); |
|
189 return; |
|
190 } |
|
191 |
|
192 if (!stsThread) { |
|
193 aRv.Throw(NS_ERROR_UNEXPECTED); |
|
194 return; |
|
195 } |
|
196 |
|
197 std::string pattern(NS_ConvertUTF16toUTF8(aPattern).get()); |
|
198 |
|
199 // CallbackObject does not support threadsafe refcounting, and must be |
|
200 // destroyed on main. |
|
201 nsMainThreadPtrHandle<WebrtcGlobalLoggingCallback> callbackHandle( |
|
202 new nsMainThreadPtrHolder<WebrtcGlobalLoggingCallback>(&aLoggingCallback)); |
|
203 |
|
204 rv = RUN_ON_THREAD(stsThread, |
|
205 WrapRunnableNM(&GetLogging_s, callbackHandle, pattern), |
|
206 NS_DISPATCH_NORMAL); |
|
207 |
|
208 if (NS_FAILED(rv)) { |
|
209 aLoggingCallback.Release(); |
|
210 } |
|
211 |
|
212 aRv = rv; |
|
213 } |
|
214 |
|
215 struct StreamResult { |
|
216 StreamResult() : candidateTypeBitpattern(0), streamSucceeded(false) {} |
|
217 uint8_t candidateTypeBitpattern; |
|
218 bool streamSucceeded; |
|
219 }; |
|
220 |
|
221 static void StoreLongTermICEStatisticsImpl_m( |
|
222 nsresult result, |
|
223 nsAutoPtr<RTCStatsQuery> query) { |
|
224 |
|
225 if (NS_FAILED(result) || |
|
226 !query->error.empty() || |
|
227 !query->report.mIceCandidateStats.WasPassed()) { |
|
228 return; |
|
229 } |
|
230 |
|
231 // First, store stuff in telemetry |
|
232 enum { |
|
233 REMOTE_GATHERED_SERVER_REFLEXIVE = 1, |
|
234 REMOTE_GATHERED_TURN = 1 << 1, |
|
235 LOCAL_GATHERED_SERVER_REFLEXIVE = 1 << 2, |
|
236 LOCAL_GATHERED_TURN_UDP = 1 << 3, |
|
237 LOCAL_GATHERED_TURN_TCP = 1 << 4, |
|
238 LOCAL_GATHERED_TURN_TLS = 1 << 5, |
|
239 LOCAL_GATHERED_TURN_HTTPS = 1 << 6, |
|
240 }; |
|
241 |
|
242 // TODO(bcampen@mozilla.com): Do we need to watch out for cases where the |
|
243 // components within a stream didn't have the same types of relayed |
|
244 // candidates? I have a feeling that late trickle could cause this, but right |
|
245 // now we don't have enough information to detect it (we would need to know |
|
246 // the ICE component id for each candidate pair and candidate) |
|
247 |
|
248 std::map<std::string, StreamResult> streamResults; |
|
249 |
|
250 // Build list of streams, and whether or not they failed. |
|
251 for (size_t i = 0; |
|
252 i < query->report.mIceCandidatePairStats.Value().Length(); |
|
253 ++i) { |
|
254 const RTCIceCandidatePairStats &pair = |
|
255 query->report.mIceCandidatePairStats.Value()[i]; |
|
256 |
|
257 if (!pair.mState.WasPassed() || !pair.mComponentId.WasPassed()) { |
|
258 MOZ_CRASH(); |
|
259 continue; |
|
260 } |
|
261 |
|
262 // Note: this is not a "component" in the ICE definition, this is really a |
|
263 // stream ID. This is just the way the stats API is standardized right now. |
|
264 // Very confusing. |
|
265 std::string streamId( |
|
266 NS_ConvertUTF16toUTF8(pair.mComponentId.Value()).get()); |
|
267 |
|
268 streamResults[streamId].streamSucceeded |= |
|
269 pair.mState.Value() == RTCStatsIceCandidatePairState::Succeeded; |
|
270 } |
|
271 |
|
272 for (size_t i = 0; |
|
273 i < query->report.mIceCandidateStats.Value().Length(); |
|
274 ++i) { |
|
275 const RTCIceCandidateStats &cand = |
|
276 query->report.mIceCandidateStats.Value()[i]; |
|
277 |
|
278 if (!cand.mType.WasPassed() || |
|
279 !cand.mCandidateType.WasPassed() || |
|
280 !cand.mComponentId.WasPassed()) { |
|
281 // Crash on debug, ignore this candidate otherwise. |
|
282 MOZ_CRASH(); |
|
283 continue; |
|
284 } |
|
285 |
|
286 // Note: this is not a "component" in the ICE definition, this is really a |
|
287 // stream ID. This is just the way the stats API is standardized right now |
|
288 // Very confusing. |
|
289 std::string streamId( |
|
290 NS_ConvertUTF16toUTF8(cand.mComponentId.Value()).get()); |
|
291 |
|
292 if (cand.mCandidateType.Value() == RTCStatsIceCandidateType::Relayed) { |
|
293 if (cand.mType.Value() == RTCStatsType::Localcandidate) { |
|
294 NS_ConvertUTF16toUTF8 transport(cand.mMozLocalTransport.Value()); |
|
295 if (transport == kNrIceTransportUdp) { |
|
296 streamResults[streamId].candidateTypeBitpattern |= |
|
297 LOCAL_GATHERED_TURN_UDP; |
|
298 } else if (transport == kNrIceTransportTcp) { |
|
299 streamResults[streamId].candidateTypeBitpattern |= |
|
300 LOCAL_GATHERED_TURN_TCP; |
|
301 } |
|
302 } else { |
|
303 streamResults[streamId].candidateTypeBitpattern |= REMOTE_GATHERED_TURN; |
|
304 } |
|
305 } else if (cand.mCandidateType.Value() == |
|
306 RTCStatsIceCandidateType::Serverreflexive) { |
|
307 if (cand.mType.Value() == RTCStatsType::Localcandidate) { |
|
308 streamResults[streamId].candidateTypeBitpattern |= |
|
309 LOCAL_GATHERED_SERVER_REFLEXIVE; |
|
310 } else { |
|
311 streamResults[streamId].candidateTypeBitpattern |= |
|
312 REMOTE_GATHERED_SERVER_REFLEXIVE; |
|
313 } |
|
314 } |
|
315 } |
|
316 |
|
317 for (auto i = streamResults.begin(); i != streamResults.end(); ++i) { |
|
318 if (i->second.streamSucceeded) { |
|
319 Telemetry::Accumulate(Telemetry::WEBRTC_CANDIDATE_TYPES_GIVEN_SUCCESS, |
|
320 i->second.candidateTypeBitpattern); |
|
321 } else { |
|
322 Telemetry::Accumulate(Telemetry::WEBRTC_CANDIDATE_TYPES_GIVEN_FAILURE, |
|
323 i->second.candidateTypeBitpattern); |
|
324 } |
|
325 } |
|
326 } |
|
327 |
|
328 static void GetStatsForLongTermStorage_s( |
|
329 nsAutoPtr<RTCStatsQuery> query) { |
|
330 |
|
331 MOZ_ASSERT(query); |
|
332 |
|
333 nsresult rv = PeerConnectionImpl::ExecuteStatsQuery_s(query.get()); |
|
334 |
|
335 // Even if Telemetry::Accumulate is threadsafe, we still need to send the |
|
336 // query back to main, since that is where it must be destroyed. |
|
337 NS_DispatchToMainThread( |
|
338 WrapRunnableNM( |
|
339 &StoreLongTermICEStatisticsImpl_m, |
|
340 rv, |
|
341 query), |
|
342 NS_DISPATCH_NORMAL); |
|
343 } |
|
344 |
|
345 void WebrtcGlobalInformation::StoreLongTermICEStatistics( |
|
346 sipcc::PeerConnectionImpl& aPc) { |
|
347 Telemetry::Accumulate(Telemetry::WEBRTC_ICE_FINAL_CONNECTION_STATE, |
|
348 static_cast<uint32_t>(aPc.IceConnectionState())); |
|
349 |
|
350 if (aPc.IceConnectionState() == PCImplIceConnectionState::New) { |
|
351 // ICE has not started; we won't have any remote candidates, so recording |
|
352 // statistics on gathered candidates is pointless. |
|
353 return; |
|
354 } |
|
355 |
|
356 nsAutoPtr<RTCStatsQuery> query(new RTCStatsQuery(true)); |
|
357 |
|
358 nsresult rv = aPc.BuildStatsQuery_m(nullptr, query.get()); |
|
359 |
|
360 NS_ENSURE_SUCCESS_VOID(rv); |
|
361 |
|
362 RUN_ON_THREAD(aPc.GetSTSThread(), |
|
363 WrapRunnableNM(&GetStatsForLongTermStorage_s, |
|
364 query), |
|
365 NS_DISPATCH_NORMAL); |
|
366 } |
|
367 |
|
368 |
|
369 } // namespace dom |
|
370 } // namespace mozilla |
|
371 |