1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/media/webrtc/webrtc_event_log_manager_remote.h"
6
7 #include <algorithm>
8 #include <iterator>
9 #include <utility>
10
11 #include "base/bind.h"
12 #include "base/command_line.h"
13 #include "base/files/file.h"
14 #include "base/files/file_enumerator.h"
15 #include "base/files/file_path.h"
16 #include "base/files/file_util.h"
17 #include "base/logging.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/string_util.h"
20 #include "chrome/common/chrome_switches.h"
21 #include "content/public/browser/browser_task_traits.h"
22 #include "content/public/browser/browser_thread.h"
23
24 namespace webrtc_event_logging {
25
26 // TODO(crbug.com/775415): Change max back to (1u << 29) after resolving the
27 // issue where we read the entire file into memory.
28 const size_t kMaxRemoteLogFileSizeBytes = 50000000u;
29
30 const int kDefaultOutputPeriodMs = 5000;
31 const int kMaxOutputPeriodMs = 60000;
32
33 namespace {
34 const base::TimeDelta kDefaultProactivePruningDelta =
35 base::TimeDelta::FromMinutes(5);
36
37 const base::TimeDelta kDefaultWebRtcRemoteEventLogUploadDelay =
38 base::TimeDelta::FromSeconds(30);
39
40 // Because history files are rarely used, their existence is not kept in memory.
41 // That means that pruning them involves inspecting data on disk. This is not
42 // terribly cheap (up to kMaxWebRtcEventLogHistoryFiles files per profile), and
43 // should therefore be done somewhat infrequently.
44 const base::TimeDelta kProactiveHistoryFilesPruneDelta =
45 base::TimeDelta::FromMinutes(30);
46
GetProactivePendingLogsPruneDelta()47 base::TimeDelta GetProactivePendingLogsPruneDelta() {
48 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
49 ::switches::kWebRtcRemoteEventLogProactivePruningDelta)) {
50 const std::string delta_seconds_str =
51 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
52 ::switches::kWebRtcRemoteEventLogProactivePruningDelta);
53 int64_t seconds;
54 if (base::StringToInt64(delta_seconds_str, &seconds) && seconds >= 0) {
55 return base::TimeDelta::FromSeconds(seconds);
56 } else {
57 LOG(WARNING) << "Proactive pruning delta could not be parsed.";
58 }
59 }
60
61 return kDefaultProactivePruningDelta;
62 }
63
GetUploadDelay()64 base::TimeDelta GetUploadDelay() {
65 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
66 ::switches::kWebRtcRemoteEventLogUploadDelayMs)) {
67 const std::string delta_seconds_str =
68 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
69 ::switches::kWebRtcRemoteEventLogUploadDelayMs);
70 int64_t ms;
71 if (base::StringToInt64(delta_seconds_str, &ms) && ms >= 0) {
72 return base::TimeDelta::FromMilliseconds(ms);
73 } else {
74 LOG(WARNING) << "Upload delay could not be parsed; using default delay.";
75 }
76 }
77
78 return kDefaultWebRtcRemoteEventLogUploadDelay;
79 }
80
TimePointInRange(const base::Time & time_point,const base::Time & range_begin,const base::Time & range_end)81 bool TimePointInRange(const base::Time& time_point,
82 const base::Time& range_begin,
83 const base::Time& range_end) {
84 DCHECK(!time_point.is_null());
85 DCHECK(range_begin.is_null() || range_end.is_null() ||
86 range_begin <= range_end);
87 return (range_begin.is_null() || range_begin <= time_point) &&
88 (range_end.is_null() || time_point < range_end);
89 }
90
91 // Do not attempt to upload when there is no active connection.
92 // Do not attempt to upload if the connection is known to be a mobile one.
93 // Note #1: A device may have multiple connections, so this is not bullet-proof.
94 // Note #2: Does not attempt to recognize mobile hotspots.
UploadSupportedUsingConnectionType(network::mojom::ConnectionType connection)95 bool UploadSupportedUsingConnectionType(
96 network::mojom::ConnectionType connection) {
97 return connection != network::mojom::ConnectionType::CONNECTION_NONE &&
98 connection != network::mojom::ConnectionType::CONNECTION_2G &&
99 connection != network::mojom::ConnectionType::CONNECTION_3G &&
100 connection != network::mojom::ConnectionType::CONNECTION_4G;
101 }
102
103 // Produce a history file for a given file.
CreateHistoryFile(const base::FilePath & log_file_path,const base::Time & capture_time)104 void CreateHistoryFile(const base::FilePath& log_file_path,
105 const base::Time& capture_time) {
106 std::unique_ptr<WebRtcEventLogHistoryFileWriter> writer =
107 WebRtcEventLogHistoryFileWriter::Create(
108 GetWebRtcEventLogHistoryFilePath(log_file_path));
109 if (!writer) {
110 LOG(ERROR) << "Could not create history file.";
111 return;
112 }
113
114 if (!writer->WriteCaptureTime(capture_time)) {
115 LOG(ERROR) << "Could not write capture time to history file.";
116 writer->Delete();
117 return;
118 }
119 }
120
121 // The following is a list of entry types used to transmit information
122 // from GetHistory() to the caller (normally - the UI).
123 // Each entry is of type UploadList::UploadInfo. Depending on the entry
124 // type, the fields in the UploadInfo have different values:
125 // 1+2. Currently-being-captured or pending -> State::Pending && !upload_time.
126 // 3. Currently-being-uploaded -> State::Pending && upload_time.
127 // 4. Pruned before being uploaded -> State::NotUploaded && !upload_time.
128 // 5. Unsuccessful upload attempt -> State::NotUploaded && upload_time.
129 // 6. Successfully uploaded -> State::Uploaded.
130 //
131 // As for the meaning of the local_id field, its semantics change according to
132 // the above entry type.
133 // * For cases 1-3 above, it is the filename, since the log is still on disk.
134 // * For cases 5-6 above, it is the local log ID that the now-deleted file used
135 // * to have.
136 namespace history {
CreateActivelyCapturedLogEntry(const base::FilePath & path,const base::Time & capture_time)137 UploadList::UploadInfo CreateActivelyCapturedLogEntry(
138 const base::FilePath& path,
139 const base::Time& capture_time) {
140 using State = UploadList::UploadInfo::State;
141 const std::string filename = path.BaseName().MaybeAsASCII();
142 DCHECK(!filename.empty());
143 return UploadList::UploadInfo(std::string(), base::Time(), filename,
144 capture_time, State::Pending);
145 }
146
CreatePendingLogEntry(const WebRtcLogFileInfo & log_info)147 UploadList::UploadInfo CreatePendingLogEntry(
148 const WebRtcLogFileInfo& log_info) {
149 using State = UploadList::UploadInfo::State;
150 const std::string filename = log_info.path.BaseName().MaybeAsASCII();
151 DCHECK(!filename.empty());
152 return UploadList::UploadInfo(std::string(), base::Time(), filename,
153 log_info.last_modified, State::Pending);
154 }
155
CreateActivelyUploadedLogEntry(const WebRtcLogFileInfo & log_info,const base::Time & upload_time)156 UploadList::UploadInfo CreateActivelyUploadedLogEntry(
157 const WebRtcLogFileInfo& log_info,
158 const base::Time& upload_time) {
159 using State = UploadList::UploadInfo::State;
160 const std::string filename = log_info.path.BaseName().MaybeAsASCII();
161 DCHECK(!filename.empty());
162 return UploadList::UploadInfo(std::string(), upload_time, filename,
163 log_info.last_modified, State::Pending);
164 }
165
CreateEntryFromHistoryFileReader(const WebRtcEventLogHistoryFileReader & reader)166 UploadList::UploadInfo CreateEntryFromHistoryFileReader(
167 const WebRtcEventLogHistoryFileReader& reader) {
168 using State = UploadList::UploadInfo::State;
169 const auto state =
170 reader.UploadId().empty() ? State::NotUploaded : State::Uploaded;
171 return UploadList::UploadInfo(reader.UploadId(), reader.UploadTime(),
172 reader.LocalId(), reader.CaptureTime(), state);
173 }
174 } // namespace history
175 } // namespace
176
177 const size_t kMaxActiveRemoteBoundWebRtcEventLogs = 3;
178 const size_t kMaxPendingRemoteBoundWebRtcEventLogs = 5;
179 static_assert(kMaxActiveRemoteBoundWebRtcEventLogs <=
180 kMaxPendingRemoteBoundWebRtcEventLogs,
181 "This assumption affects unit test coverage.");
182 const size_t kMaxWebRtcEventLogHistoryFiles = 50;
183
184 // Maximum time to keep remote-bound logs on disk.
185 const base::TimeDelta kRemoteBoundWebRtcEventLogsMaxRetention =
186 base::TimeDelta::FromDays(7);
187
188 // Maximum time to keep history files on disk. These serve to display an upload
189 // on chrome://webrtc-logs/. It is persisted for longer than the log itself.
190 const base::TimeDelta kHistoryFileRetention = base::TimeDelta::FromDays(30);
191
WebRtcRemoteEventLogManager(WebRtcRemoteEventLogsObserver * observer,scoped_refptr<base::SequencedTaskRunner> task_runner)192 WebRtcRemoteEventLogManager::WebRtcRemoteEventLogManager(
193 WebRtcRemoteEventLogsObserver* observer,
194 scoped_refptr<base::SequencedTaskRunner> task_runner)
195 : upload_suppression_disabled_(
196 base::CommandLine::ForCurrentProcess()->HasSwitch(
197 ::switches::kWebRtcRemoteEventLogUploadNoSuppression)),
198 upload_delay_(GetUploadDelay()),
199 proactive_pending_logs_prune_delta_(GetProactivePendingLogsPruneDelta()),
200 proactive_prune_scheduling_started_(false),
201 observer_(observer),
202 network_connection_tracker_(nullptr),
203 uploading_supported_for_connection_type_(false),
204 scheduled_upload_tasks_(0),
205 uploader_factory_(
206 std::make_unique<WebRtcEventLogUploaderImpl::Factory>(task_runner)),
207 task_runner_(task_runner),
208 weak_ptr_factory_(
209 std::make_unique<base::WeakPtrFactory<WebRtcRemoteEventLogManager>>(
210 this)) {
211 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
212 // Proactive pruning would not do anything at the moment; it will be started
213 // with the first enabled browser context. This will all have the benefit
214 // of doing so on |task_runner_| rather than the UI thread.
215 }
216
~WebRtcRemoteEventLogManager()217 WebRtcRemoteEventLogManager::~WebRtcRemoteEventLogManager() {
218 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
219 // TODO(crbug.com/775415): Purge from disk files which were being uploaded
220 // while destruction took place, thereby avoiding endless attempts to upload
221 // the same file.
222
223 if (weak_ptr_factory_) {
224 // Not a unit test; that would have gone through ShutDownForTesting().
225 const bool will_delete =
226 task_runner_->DeleteSoon(FROM_HERE, weak_ptr_factory_.release());
227 DCHECK(!will_delete)
228 << "Task runners must have been stopped by this stage of shutdown.";
229 }
230
231 if (network_connection_tracker_) {
232 // * |network_connection_tracker_| might already have posted a task back
233 // to us, but it will not run, because |task_runner_| has already been
234 // stopped.
235 // * RemoveNetworkConnectionObserver() should generally be called on the
236 // same thread as AddNetworkConnectionObserver(), but in this case it's
237 // okay to remove on a separate thread, because this only happens during
238 // Chrome shutdown, when no others tasks are running; there can be no
239 // concurrently executing notification from the tracker.
240 network_connection_tracker_->RemoveNetworkConnectionObserver(this);
241 }
242 }
243
SetNetworkConnectionTracker(network::NetworkConnectionTracker * network_connection_tracker)244 void WebRtcRemoteEventLogManager::SetNetworkConnectionTracker(
245 network::NetworkConnectionTracker* network_connection_tracker) {
246 DCHECK(task_runner_->RunsTasksInCurrentSequence());
247 DCHECK(network_connection_tracker);
248 DCHECK(!network_connection_tracker_);
249
250 // |this| is only destroyed (on the UI thread) after |task_runner_| stops,
251 // so AddNetworkConnectionObserver() is safe.
252
253 network_connection_tracker_ = network_connection_tracker;
254 network_connection_tracker_->AddNetworkConnectionObserver(this);
255
256 auto callback =
257 base::BindOnce(&WebRtcRemoteEventLogManager::OnConnectionChanged,
258 weak_ptr_factory_->GetWeakPtr());
259 network::mojom::ConnectionType connection_type;
260 const bool sync_answer = network_connection_tracker_->GetConnectionType(
261 &connection_type, std::move(callback));
262
263 if (sync_answer) {
264 OnConnectionChanged(connection_type);
265 }
266
267 // Because this happens while enabling the first browser context, there is no
268 // necessity to consider uploading yet.
269 DCHECK_EQ(enabled_browser_contexts_.size(), 0u);
270 }
271
SetLogFileWriterFactory(std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory)272 void WebRtcRemoteEventLogManager::SetLogFileWriterFactory(
273 std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory) {
274 DCHECK(task_runner_->RunsTasksInCurrentSequence());
275 DCHECK(log_file_writer_factory);
276 DCHECK(!log_file_writer_factory_);
277 log_file_writer_factory_ = std::move(log_file_writer_factory);
278 }
279
EnableForBrowserContext(BrowserContextId browser_context_id,const base::FilePath & browser_context_dir)280 void WebRtcRemoteEventLogManager::EnableForBrowserContext(
281 BrowserContextId browser_context_id,
282 const base::FilePath& browser_context_dir) {
283 DCHECK(task_runner_->RunsTasksInCurrentSequence());
284 DCHECK(network_connection_tracker_)
285 << "SetNetworkConnectionTracker not called.";
286 DCHECK(log_file_writer_factory_) << "SetLogFileWriterFactory() not called.";
287
288 if (BrowserContextEnabled(browser_context_id)) {
289 return;
290 }
291
292 const base::FilePath remote_bound_logs_dir =
293 GetRemoteBoundWebRtcEventLogsDir(browser_context_dir);
294 if (!MaybeCreateLogsDirectory(remote_bound_logs_dir)) {
295 LOG(WARNING)
296 << "WebRtcRemoteEventLogManager couldn't create logs directory.";
297 return;
298 }
299
300 enabled_browser_contexts_.emplace(browser_context_id, remote_bound_logs_dir);
301
302 LoadLogsDirectory(browser_context_id, remote_bound_logs_dir);
303
304 if (!proactive_prune_scheduling_started_) {
305 proactive_prune_scheduling_started_ = true;
306
307 if (!proactive_pending_logs_prune_delta_.is_zero()) {
308 RecurringlyPrunePendingLogs();
309 }
310
311 RecurringlyPruneHistoryFiles();
312 }
313 }
314
DisableForBrowserContext(BrowserContextId browser_context_id)315 void WebRtcRemoteEventLogManager::DisableForBrowserContext(
316 BrowserContextId browser_context_id) {
317 DCHECK(task_runner_->RunsTasksInCurrentSequence());
318
319 if (!BrowserContextEnabled(browser_context_id)) {
320 return; // Enabling may have failed due to lacking permissions.
321 }
322
323 enabled_browser_contexts_.erase(browser_context_id);
324
325 #if DCHECK_IS_ON()
326 // DisableForBrowserContext() is called in one of two cases:
327 // 1. If Chrome is shutting down. In that case, all the RPHs associated with
328 // this BrowserContext must already have exited, which should have
329 // implicitly stopped all active logs.
330 // 2. Remote-bound logging is no longer allowed for this BrowserContext.
331 // In that case, some peer connections associated with this BrowserContext
332 // might still be active, or become active at a later time, but all
333 // logs must have already been stopped.
334 auto pred = [browser_context_id](decltype(active_logs_)::value_type& log) {
335 return log.first.browser_context_id == browser_context_id;
336 };
337 DCHECK(std::count_if(active_logs_.begin(), active_logs_.end(), pred) == 0u);
338 #endif
339
340 // Pending logs for this BrowserContext are no longer eligible for upload.
341 for (auto it = pending_logs_.begin(); it != pending_logs_.end();) {
342 if (it->browser_context_id == browser_context_id) {
343 it = pending_logs_.erase(it);
344 } else {
345 ++it;
346 }
347 }
348
349 // Active uploads of logs associated with this BrowserContext must be stopped.
350 MaybeCancelUpload(base::Time::Min(), base::Time::Max(), browser_context_id);
351
352 // Active logs may have been removed, which could remove upload suppression,
353 // or pending logs which were about to be uploaded may have been removed,
354 // so uploading may no longer be possible.
355 ManageUploadSchedule();
356 }
357
PeerConnectionAdded(const PeerConnectionKey & key)358 bool WebRtcRemoteEventLogManager::PeerConnectionAdded(
359 const PeerConnectionKey& key) {
360 DCHECK(task_runner_->RunsTasksInCurrentSequence());
361
362 PrunePendingLogs(); // Infrequent event - good opportunity to prune.
363
364 const auto result = active_peer_connections_.emplace(key, std::string());
365
366 // An upload about to start might need to be suppressed.
367 ManageUploadSchedule();
368
369 return result.second;
370 }
371
PeerConnectionRemoved(const PeerConnectionKey & key)372 bool WebRtcRemoteEventLogManager::PeerConnectionRemoved(
373 const PeerConnectionKey& key) {
374 DCHECK(task_runner_->RunsTasksInCurrentSequence());
375
376 PrunePendingLogs(); // Infrequent event - good opportunity to prune.
377
378 const auto peer_connection = active_peer_connections_.find(key);
379 if (peer_connection == active_peer_connections_.end()) {
380 return false;
381 }
382
383 MaybeStopRemoteLogging(key);
384
385 active_peer_connections_.erase(peer_connection);
386
387 ManageUploadSchedule(); // Suppression might have been removed.
388
389 return true;
390 }
391
PeerConnectionSessionIdSet(const PeerConnectionKey & key,const std::string & session_id)392 bool WebRtcRemoteEventLogManager::PeerConnectionSessionIdSet(
393 const PeerConnectionKey& key,
394 const std::string& session_id) {
395 DCHECK(task_runner_->RunsTasksInCurrentSequence());
396
397 PrunePendingLogs(); // Infrequent event - good opportunity to prune.
398
399 if (session_id.empty()) {
400 LOG(ERROR) << "Empty session ID.";
401 return false;
402 }
403
404 auto peer_connection = active_peer_connections_.find(key);
405 if (peer_connection == active_peer_connections_.end()) {
406 return false; // Unknown peer connection; already closed?
407 }
408
409 if (peer_connection->second.empty()) {
410 peer_connection->second = session_id;
411 } else if (session_id != peer_connection->second) {
412 LOG(ERROR) << "Session ID already set to " << peer_connection->second
413 << ". Cannot change to " << session_id << ".";
414 return false;
415 }
416
417 return true;
418 }
419
StartRemoteLogging(int render_process_id,BrowserContextId browser_context_id,const std::string & session_id,const base::FilePath & browser_context_dir,size_t max_file_size_bytes,int output_period_ms,size_t web_app_id,std::string * log_id,std::string * error_message)420 bool WebRtcRemoteEventLogManager::StartRemoteLogging(
421 int render_process_id,
422 BrowserContextId browser_context_id,
423 const std::string& session_id,
424 const base::FilePath& browser_context_dir,
425 size_t max_file_size_bytes,
426 int output_period_ms,
427 size_t web_app_id,
428 std::string* log_id,
429 std::string* error_message) {
430 DCHECK(task_runner_->RunsTasksInCurrentSequence());
431 DCHECK(log_id);
432 DCHECK(log_id->empty());
433 DCHECK(error_message);
434 DCHECK(error_message->empty());
435
436 if (output_period_ms < 0) {
437 output_period_ms = kDefaultOutputPeriodMs;
438 }
439
440 if (!AreLogParametersValid(max_file_size_bytes, output_period_ms, web_app_id,
441 error_message)) {
442 // |error_message| will have been set by AreLogParametersValid().
443 DCHECK(!error_message->empty()) << "AreLogParametersValid() reported an "
444 "error without an error message.";
445 UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kInvalidArguments);
446 return false;
447 }
448
449 if (session_id.empty()) {
450 *error_message = kStartRemoteLoggingFailureUnknownOrInactivePeerConnection;
451 UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kIllegalSessionId);
452 return false;
453 }
454
455 if (!BrowserContextEnabled(browser_context_id)) {
456 // Remote-bound event logging has either not yet been enabled for this
457 // BrowserContext, or has been recently disabled. This error should not
458 // really be reached, barring a timing issue.
459 *error_message = kStartRemoteLoggingFailureLoggingDisabledBrowserContext;
460 UmaRecordWebRtcEventLoggingApi(
461 WebRtcEventLoggingApiUma::kDisabledBrowserContext);
462 return false;
463 }
464
465 PeerConnectionKey key;
466 if (!FindPeerConnection(render_process_id, session_id, &key)) {
467 *error_message = kStartRemoteLoggingFailureUnknownOrInactivePeerConnection;
468 UmaRecordWebRtcEventLoggingApi(
469 WebRtcEventLoggingApiUma::kUnknownOrInvalidPeerConnection);
470 return false;
471 }
472
473 // May not restart active remote logs.
474 auto it = active_logs_.find(key);
475 if (it != active_logs_.end()) {
476 LOG(ERROR) << "Remote logging already underway for " << session_id << ".";
477 *error_message = kStartRemoteLoggingFailureAlreadyLogging;
478 UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kAlreadyLogging);
479 return false;
480 }
481
482 // This is a good opportunity to prune the list of pending logs, potentially
483 // making room for this file.
484 PrunePendingLogs();
485
486 if (!AdditionalActiveLogAllowed(key.browser_context_id)) {
487 *error_message = kStartRemoteLoggingFailureNoAdditionalActiveLogsAllowed;
488 UmaRecordWebRtcEventLoggingApi(
489 WebRtcEventLoggingApiUma::kNoAdditionalLogsAllowed);
490 return false;
491 }
492
493 return StartWritingLog(key, browser_context_dir, max_file_size_bytes,
494 output_period_ms, web_app_id, log_id, error_message);
495 }
496
EventLogWrite(const PeerConnectionKey & key,const std::string & message)497 bool WebRtcRemoteEventLogManager::EventLogWrite(const PeerConnectionKey& key,
498 const std::string& message) {
499 DCHECK(task_runner_->RunsTasksInCurrentSequence());
500
501 auto it = active_logs_.find(key);
502 if (it == active_logs_.end()) {
503 return false;
504 }
505
506 const bool write_successful = it->second->Write(message);
507 if (!write_successful || it->second->MaxSizeReached()) {
508 // Note: If the file is invalid, CloseLogFile() will discard it.
509 CloseLogFile(it, /*make_pending=*/true);
510 ManageUploadSchedule();
511 }
512
513 return write_successful;
514 }
515
ClearCacheForBrowserContext(BrowserContextId browser_context_id,const base::Time & delete_begin,const base::Time & delete_end)516 void WebRtcRemoteEventLogManager::ClearCacheForBrowserContext(
517 BrowserContextId browser_context_id,
518 const base::Time& delete_begin,
519 const base::Time& delete_end) {
520 DCHECK(task_runner_->RunsTasksInCurrentSequence());
521 // Rationale for the order:
522 // 1. Active logs cancelled. This has no side effects, and can be safely
523 // done before anything else.
524 // 2. Pending logs removed, before they can be considered as the
525 // next log to be uploaded. This may cause history files to be created.
526 // 3. Remove history files, including those that #2 might have created.
527 // 4. Cancel any active upload precisely at a time when nothing being cleared
528 // by ClearCacheForBrowserContext() could accidentally replace it.
529 // 5. Explicitly consider uploading, now that things have changed.
530 MaybeCancelActiveLogs(delete_begin, delete_end, browser_context_id);
531 MaybeRemovePendingLogs(delete_begin, delete_end, browser_context_id,
532 /*is_cache_clear=*/true);
533 MaybeRemoveHistoryFiles(delete_begin, delete_end, browser_context_id);
534 MaybeCancelUpload(delete_begin, delete_end, browser_context_id);
535 ManageUploadSchedule();
536 }
537
GetHistory(BrowserContextId browser_context_id,base::OnceCallback<void (const std::vector<UploadList::UploadInfo> &)> reply)538 void WebRtcRemoteEventLogManager::GetHistory(
539 BrowserContextId browser_context_id,
540 base::OnceCallback<void(const std::vector<UploadList::UploadInfo>&)>
541 reply) {
542 DCHECK(task_runner_->RunsTasksInCurrentSequence());
543
544 std::vector<UploadList::UploadInfo> history;
545
546 if (!BrowserContextEnabled(browser_context_id)) {
547 // Either the browser context is unknown, or more likely, it's not
548 // enabled for remote logging.
549 content::GetUIThreadTaskRunner({})->PostTask(
550 FROM_HERE, base::BindOnce(std::move(reply), history));
551 return;
552 }
553
554 PrunePendingLogs(browser_context_id);
555
556 const base::Time now = base::Time::Now();
557
558 std::set<WebRtcEventLogHistoryFileReader> history_files =
559 PruneAndLoadHistoryFilesForBrowserContext(
560 base::Time::Min(), now - kHistoryFileRetention, browser_context_id);
561 for (const auto& history_file : history_files) {
562 history.push_back(history::CreateEntryFromHistoryFileReader(history_file));
563 }
564
565 for (const WebRtcLogFileInfo& log_info : pending_logs_) {
566 if (browser_context_id == log_info.browser_context_id) {
567 history.push_back(history::CreatePendingLogEntry(log_info));
568 }
569 }
570
571 for (const auto& it : active_logs_) {
572 if (browser_context_id == it.first.browser_context_id) {
573 history.push_back(
574 history::CreateActivelyCapturedLogEntry(it.second->path(), now));
575 }
576 }
577
578 if (uploader_) {
579 const WebRtcLogFileInfo log_info = uploader_->GetWebRtcLogFileInfo();
580 if (browser_context_id == log_info.browser_context_id) {
581 history.push_back(history::CreateActivelyUploadedLogEntry(log_info, now));
582 }
583 }
584
585 // Sort according to capture time, for consistent orders regardless of
586 // future operations on the log files.
587 auto cmp = [](const UploadList::UploadInfo& lhs,
588 const UploadList::UploadInfo& rhs) {
589 if (lhs.capture_time == rhs.capture_time) {
590 // Resolve ties arbitrarily, but consistently. (Local ID expected to be
591 // distinct for distinct items; if not, anything goes.)
592 return lhs.local_id < rhs.local_id;
593 }
594 return (lhs.capture_time < rhs.capture_time);
595 };
596 std::sort(history.begin(), history.end(), cmp);
597
598 content::GetUIThreadTaskRunner({})->PostTask(
599 FROM_HERE, base::BindOnce(std::move(reply), history));
600 }
601
RemovePendingLogsForNotEnabledBrowserContext(BrowserContextId browser_context_id,const base::FilePath & browser_context_dir)602 void WebRtcRemoteEventLogManager::RemovePendingLogsForNotEnabledBrowserContext(
603 BrowserContextId browser_context_id,
604 const base::FilePath& browser_context_dir) {
605 DCHECK(task_runner_->RunsTasksInCurrentSequence());
606 DCHECK(!BrowserContextEnabled(browser_context_id));
607 const base::FilePath remote_bound_logs_dir =
608 GetRemoteBoundWebRtcEventLogsDir(browser_context_dir);
609 if (!base::DeletePathRecursively(remote_bound_logs_dir)) {
610 LOG(ERROR) << "Failed to delete `" << remote_bound_logs_dir << ".";
611 }
612 }
613
RenderProcessHostExitedDestroyed(int render_process_id)614 void WebRtcRemoteEventLogManager::RenderProcessHostExitedDestroyed(
615 int render_process_id) {
616 DCHECK(task_runner_->RunsTasksInCurrentSequence());
617
618 // Remove all of the peer connections associated with this render process.
619 // It's important to do this before closing the actual files, because closing
620 // files can trigger a new upload if no active peer connections are present.
621 auto pc_it = active_peer_connections_.begin();
622 while (pc_it != active_peer_connections_.end()) {
623 if (pc_it->first.render_process_id == render_process_id) {
624 pc_it = active_peer_connections_.erase(pc_it);
625 } else {
626 ++pc_it;
627 }
628 }
629
630 // Close all of the files that were associated with peer connections which
631 // belonged to this render process.
632 auto log_it = active_logs_.begin();
633 while (log_it != active_logs_.end()) {
634 if (log_it->first.render_process_id == render_process_id) {
635 log_it = CloseLogFile(log_it, /*make_pending=*/true);
636 } else {
637 ++log_it;
638 }
639 }
640
641 ManageUploadSchedule();
642 }
643
OnConnectionChanged(network::mojom::ConnectionType type)644 void WebRtcRemoteEventLogManager::OnConnectionChanged(
645 network::mojom::ConnectionType type) {
646 DCHECK(task_runner_->RunsTasksInCurrentSequence());
647 // Even if switching from WiFi to Ethernet, or between to WiFi connections,
648 // reset the timer (if running) until an upload is permissible due to stable
649 // upload-supporting conditions.
650 time_when_upload_conditions_met_ = base::TimeTicks();
651
652 uploading_supported_for_connection_type_ =
653 UploadSupportedUsingConnectionType(type);
654
655 ManageUploadSchedule();
656
657 // TODO(crbug.com/775415): Support pausing uploads when connection goes down,
658 // or switches to an unsupported connection type.
659 }
660
SetWebRtcEventLogUploaderFactoryForTesting(std::unique_ptr<WebRtcEventLogUploader::Factory> uploader_factory)661 void WebRtcRemoteEventLogManager::SetWebRtcEventLogUploaderFactoryForTesting(
662 std::unique_ptr<WebRtcEventLogUploader::Factory> uploader_factory) {
663 DCHECK(task_runner_->RunsTasksInCurrentSequence());
664 DCHECK(uploader_factory);
665 uploader_factory_ = std::move(uploader_factory);
666 }
667
UploadConditionsHoldForTesting(base::OnceCallback<void (bool)> callback)668 void WebRtcRemoteEventLogManager::UploadConditionsHoldForTesting(
669 base::OnceCallback<void(bool)> callback) {
670 DCHECK(task_runner_->RunsTasksInCurrentSequence());
671 content::GetUIThreadTaskRunner({})->PostTask(
672 FROM_HERE, base::BindOnce(std::move(callback), UploadConditionsHold()));
673 }
674
ShutDownForTesting(base::OnceClosure reply)675 void WebRtcRemoteEventLogManager::ShutDownForTesting(base::OnceClosure reply) {
676 DCHECK(task_runner_->RunsTasksInCurrentSequence());
677 weak_ptr_factory_->InvalidateWeakPtrs();
678 weak_ptr_factory_.reset();
679 content::GetUIThreadTaskRunner({})->PostTask(
680 FROM_HERE, base::BindOnce(std::move(reply)));
681 }
682
AreLogParametersValid(size_t max_file_size_bytes,int output_period_ms,size_t web_app_id,std::string * error_message) const683 bool WebRtcRemoteEventLogManager::AreLogParametersValid(
684 size_t max_file_size_bytes,
685 int output_period_ms,
686 size_t web_app_id,
687 std::string* error_message) const {
688 DCHECK(task_runner_->RunsTasksInCurrentSequence());
689
690 if (max_file_size_bytes == kWebRtcEventLogManagerUnlimitedFileSize) {
691 LOG(WARNING) << "Unlimited file sizes not allowed for remote-bound logs.";
692 *error_message = kStartRemoteLoggingFailureUnlimitedSizeDisallowed;
693 return false;
694 }
695
696 if (max_file_size_bytes < log_file_writer_factory_->MinFileSizeBytes()) {
697 LOG(WARNING) << "File size below minimum allowed.";
698 *error_message = kStartRemoteLoggingFailureMaxSizeTooSmall;
699 return false;
700 }
701
702 if (max_file_size_bytes > kMaxRemoteLogFileSizeBytes) {
703 LOG(WARNING) << "File size exceeds maximum allowed.";
704 *error_message = kStartRemoteLoggingFailureMaxSizeTooLarge;
705 return false;
706 }
707
708 if (output_period_ms > kMaxOutputPeriodMs) {
709 LOG(WARNING) << "Output period (ms) exceeds maximum allowed.";
710 *error_message = kStartRemoteLoggingFailureOutputPeriodMsTooLarge;
711 return false;
712 }
713
714 if (web_app_id < kMinWebRtcEventLogWebAppId ||
715 web_app_id > kMaxWebRtcEventLogWebAppId) {
716 LOG(WARNING) << "Illegal web-app identifier.";
717 *error_message = kStartRemoteLoggingFailureIllegalWebAppId;
718 return false;
719 }
720
721 return true;
722 }
723
BrowserContextEnabled(BrowserContextId browser_context_id) const724 bool WebRtcRemoteEventLogManager::BrowserContextEnabled(
725 BrowserContextId browser_context_id) const {
726 DCHECK(task_runner_->RunsTasksInCurrentSequence());
727 const auto it = enabled_browser_contexts_.find(browser_context_id);
728 return it != enabled_browser_contexts_.cend();
729 }
730
731 WebRtcRemoteEventLogManager::LogFilesMap::iterator
CloseLogFile(LogFilesMap::iterator it,bool make_pending)732 WebRtcRemoteEventLogManager::CloseLogFile(LogFilesMap::iterator it,
733 bool make_pending) {
734 DCHECK(task_runner_->RunsTasksInCurrentSequence());
735
736 const PeerConnectionKey peer_connection = it->first; // Copy, not reference.
737
738 const bool valid_file = it->second->Close();
739 if (valid_file) {
740 if (make_pending) {
741 // The current time is a good enough approximation of the file's last
742 // modification time.
743 const base::Time last_modified = base::Time::Now();
744
745 // The stopped log becomes a pending log.
746 const auto emplace_result =
747 pending_logs_.emplace(peer_connection.browser_context_id,
748 it->second->path(), last_modified);
749 DCHECK(emplace_result.second); // No pre-existing entry.
750 } else {
751 const base::FilePath log_file_path = it->second->path();
752 if (!base::DeleteFile(log_file_path)) {
753 LOG(ERROR) << "Failed to delete " << log_file_path << ".";
754 }
755 }
756 } else { // !valid_file
757 // Close() deleted the file.
758 UmaRecordWebRtcEventLoggingUpload(
759 WebRtcEventLoggingUploadUma::kLogFileWriteError);
760 }
761
762 it = active_logs_.erase(it);
763
764 if (observer_) {
765 observer_->OnRemoteLogStopped(peer_connection);
766 }
767
768 return it;
769 }
770
MaybeCreateLogsDirectory(const base::FilePath & remote_bound_logs_dir)771 bool WebRtcRemoteEventLogManager::MaybeCreateLogsDirectory(
772 const base::FilePath& remote_bound_logs_dir) {
773 DCHECK(task_runner_->RunsTasksInCurrentSequence());
774
775 if (base::PathExists(remote_bound_logs_dir)) {
776 if (!base::DirectoryExists(remote_bound_logs_dir)) {
777 LOG(ERROR) << "Path for remote-bound logs is taken by a non-directory.";
778 return false;
779 }
780 } else if (!base::CreateDirectory(remote_bound_logs_dir)) {
781 LOG(ERROR) << "Failed to create the local directory for remote-bound logs.";
782 return false;
783 }
784
785 // TODO(crbug.com/775415): Test for appropriate permissions.
786
787 return true;
788 }
789
LoadLogsDirectory(BrowserContextId browser_context_id,const base::FilePath & remote_bound_logs_dir)790 void WebRtcRemoteEventLogManager::LoadLogsDirectory(
791 BrowserContextId browser_context_id,
792 const base::FilePath& remote_bound_logs_dir) {
793 DCHECK(task_runner_->RunsTasksInCurrentSequence());
794
795 const auto separator =
796 base::FilePath::StringType(1, base::FilePath::kExtensionSeparator);
797 const base::Time now = base::Time::Now();
798
799 std::set<std::pair<base::FilePath, base::Time>> log_files_to_delete;
800 std::set<base::FilePath> history_files_to_delete;
801
802 // Iterate over all of the files in the directory; find the ones that need
803 // to be deleted. Skip unknown files; they may belong to the OS.
804 base::FileEnumerator enumerator(remote_bound_logs_dir,
805 /*recursive=*/false,
806 base::FileEnumerator::FILES);
807 for (auto path = enumerator.Next(); !path.empty(); path = enumerator.Next()) {
808 const base::FileEnumerator::FileInfo info = enumerator.GetInfo();
809 const base::FilePath::StringType extension = info.GetName().Extension();
810 if (extension == separator + kWebRtcEventLogUncompressedExtension ||
811 extension == separator + kWebRtcEventLogGzippedExtension) {
812 const bool loaded = LoadPendingLogInfo(
813 browser_context_id, path, enumerator.GetInfo().GetLastModifiedTime());
814 if (!loaded) {
815 log_files_to_delete.insert(
816 std::make_pair(path, info.GetLastModifiedTime()));
817 }
818 } else if (extension == separator + kWebRtcEventLogHistoryExtension) {
819 auto reader = LoadHistoryFile(browser_context_id, path, base::Time::Min(),
820 now - kHistoryFileRetention);
821 if (!reader) {
822 history_files_to_delete.insert(path);
823 }
824 }
825 }
826
827 // Remove expired logs.
828 for (const auto& file_to_delete : log_files_to_delete) {
829 // Produce history file, unless we're discarding this log file precisely
830 // because we see it has a history file associated.
831 const base::FilePath& log_file_path = file_to_delete.first;
832 if (!base::PathExists(GetWebRtcEventLogHistoryFilePath(log_file_path))) {
833 const base::Time capture_time = file_to_delete.second;
834 CreateHistoryFile(log_file_path, capture_time);
835 }
836
837 // Remove the log file itself.
838 if (!base::DeleteFile(log_file_path)) {
839 LOG(ERROR) << "Failed to delete " << file_to_delete.first << ".";
840 }
841 }
842
843 // Remove expired history files.
844 for (const base::FilePath& history_file_path : history_files_to_delete) {
845 if (!base::DeleteFile(history_file_path)) {
846 LOG(ERROR) << "Failed to delete " << history_file_path << ".";
847 }
848 }
849
850 ManageUploadSchedule();
851 }
852
LoadPendingLogInfo(BrowserContextId browser_context_id,const base::FilePath & path,base::Time last_modified)853 bool WebRtcRemoteEventLogManager::LoadPendingLogInfo(
854 BrowserContextId browser_context_id,
855 const base::FilePath& path,
856 base::Time last_modified) {
857 DCHECK(task_runner_->RunsTasksInCurrentSequence());
858
859 if (!IsValidRemoteBoundLogFilePath(path)) {
860 return false;
861 }
862
863 const base::FilePath history_path = GetWebRtcEventLogHistoryFilePath(path);
864 if (base::PathExists(history_path)) {
865 // Log file has associated history file, indicating an upload was started
866 // for it. We should delete the original log from disk.
867 UmaRecordWebRtcEventLoggingUpload(
868 WebRtcEventLoggingUploadUma::kIncompletePastUpload);
869 return false;
870 }
871
872 const base::Time now = base::Time::Now();
873 if (last_modified + kRemoteBoundWebRtcEventLogsMaxRetention < now) {
874 UmaRecordWebRtcEventLoggingUpload(
875 WebRtcEventLoggingUploadUma::kExpiredLogFileAtChromeStart);
876 return false;
877 }
878
879 auto it = pending_logs_.emplace(browser_context_id, path, last_modified);
880 DCHECK(it.second); // No pre-existing entry.
881
882 return true;
883 }
884
885 std::unique_ptr<WebRtcEventLogHistoryFileReader>
LoadHistoryFile(BrowserContextId browser_context_id,const base::FilePath & path,const base::Time & prune_begin,const base::Time & prune_end)886 WebRtcRemoteEventLogManager::LoadHistoryFile(
887 BrowserContextId browser_context_id,
888 const base::FilePath& path,
889 const base::Time& prune_begin,
890 const base::Time& prune_end) {
891 DCHECK(task_runner_->RunsTasksInCurrentSequence());
892
893 if (!IsValidRemoteBoundLogFilePath(path)) {
894 return nullptr;
895 }
896
897 std::unique_ptr<WebRtcEventLogHistoryFileReader> reader =
898 WebRtcEventLogHistoryFileReader::Create(path);
899 if (!reader) {
900 return nullptr;
901 }
902
903 const base::Time capture_time = reader->CaptureTime();
904 if (prune_begin <= capture_time && capture_time <= prune_end) {
905 return nullptr;
906 }
907
908 const base::Time upload_time = reader->UploadTime();
909 if (!upload_time.is_null()) {
910 if (prune_begin <= upload_time && upload_time <= prune_end) {
911 return nullptr;
912 }
913 }
914
915 return reader;
916 }
917
918 std::set<WebRtcEventLogHistoryFileReader>
PruneAndLoadHistoryFilesForBrowserContext(const base::Time & prune_begin,const base::Time & prune_end,BrowserContextId browser_context_id)919 WebRtcRemoteEventLogManager::PruneAndLoadHistoryFilesForBrowserContext(
920 const base::Time& prune_begin,
921 const base::Time& prune_end,
922 BrowserContextId browser_context_id) {
923 DCHECK(task_runner_->RunsTasksInCurrentSequence());
924
925 std::set<WebRtcEventLogHistoryFileReader> history_files;
926
927 auto browser_contexts_it = enabled_browser_contexts_.find(browser_context_id);
928 if (browser_contexts_it == enabled_browser_contexts_.end()) {
929 return history_files;
930 }
931
932 std::set<base::FilePath> files_to_delete;
933
934 base::FileEnumerator enumerator(browser_contexts_it->second,
935 /*recursive=*/false,
936 base::FileEnumerator::FILES);
937
938 for (auto path = enumerator.Next(); !path.empty(); path = enumerator.Next()) {
939 const base::FileEnumerator::FileInfo info = enumerator.GetInfo();
940 const base::FilePath::StringType extension = info.GetName().Extension();
941 const auto separator =
942 base::FilePath::StringType(1, base::FilePath::kExtensionSeparator);
943 if (extension != separator + kWebRtcEventLogHistoryExtension) {
944 continue;
945 }
946
947 if (uploader_) {
948 const base::FilePath log_path = uploader_->GetWebRtcLogFileInfo().path;
949 const base::FilePath history_path =
950 GetWebRtcEventLogHistoryFilePath(log_path);
951 if (path == history_path) {
952 continue;
953 }
954 }
955
956 auto reader =
957 LoadHistoryFile(browser_context_id, path, prune_begin, prune_end);
958 if (reader) {
959 history_files.insert(std::move(*reader));
960 reader.reset(); // |reader| in undetermined state after move().
961 } else { // Defective or expired.
962 files_to_delete.insert(path);
963 }
964 }
965
966 // |history_files| is sorted by log capture time in ascending order;
967 // remove the oldest entries until kMaxWebRtcEventLogHistoryFiles is obeyed.
968 size_t num_history_files = history_files.size();
969 for (auto it = history_files.begin();
970 num_history_files > kMaxWebRtcEventLogHistoryFiles;
971 --num_history_files) {
972 DCHECK(it != history_files.end());
973 files_to_delete.insert(it->path());
974 it = history_files.erase(it);
975 }
976
977 for (const base::FilePath& path : files_to_delete) {
978 if (!base::DeleteFile(path)) {
979 LOG(ERROR) << "Failed to delete " << path << ".";
980 }
981 }
982
983 return history_files;
984 }
985
StartWritingLog(const PeerConnectionKey & key,const base::FilePath & browser_context_dir,size_t max_file_size_bytes,int output_period_ms,size_t web_app_id,std::string * log_id_out,std::string * error_message_out)986 bool WebRtcRemoteEventLogManager::StartWritingLog(
987 const PeerConnectionKey& key,
988 const base::FilePath& browser_context_dir,
989 size_t max_file_size_bytes,
990 int output_period_ms,
991 size_t web_app_id,
992 std::string* log_id_out,
993 std::string* error_message_out) {
994 DCHECK(task_runner_->RunsTasksInCurrentSequence());
995
996 // The log is assigned a universally unique ID (with high probability).
997 const std::string log_id = CreateWebRtcEventLogId();
998
999 // Use the log ID as part of the filename. In the highly unlikely event that
1000 // this filename is already taken, or that an earlier log with the same name
1001 // existed and left a history file behind, it will be treated the same way as
1002 // any other failure to start the log file.
1003 // TODO(crbug.com/775415): Add a unit test for above comment.
1004 const base::FilePath remote_logs_dir =
1005 GetRemoteBoundWebRtcEventLogsDir(browser_context_dir);
1006 const base::FilePath log_path =
1007 WebRtcEventLogPath(remote_logs_dir, log_id, web_app_id,
1008 log_file_writer_factory_->Extension());
1009
1010 if (base::PathExists(log_path)) {
1011 LOG(ERROR) << "Previously used ID selected.";
1012 *error_message_out = kStartRemoteLoggingFailureFilePathUsedLog;
1013 UmaRecordWebRtcEventLoggingApi(
1014 WebRtcEventLoggingApiUma::kLogPathNotAvailable);
1015 return false;
1016 }
1017
1018 const base::FilePath history_file_path =
1019 GetWebRtcEventLogHistoryFilePath(log_path);
1020 if (base::PathExists(history_file_path)) {
1021 LOG(ERROR) << "Previously used ID selected.";
1022 *error_message_out = kStartRemoteLoggingFailureFilePathUsedHistory;
1023 UmaRecordWebRtcEventLoggingApi(
1024 WebRtcEventLoggingApiUma::kHistoryPathNotAvailable);
1025 return false;
1026 }
1027
1028 // The log is now ACTIVE.
1029 DCHECK_NE(max_file_size_bytes, kWebRtcEventLogManagerUnlimitedFileSize);
1030 auto log_file =
1031 log_file_writer_factory_->Create(log_path, max_file_size_bytes);
1032 if (!log_file) {
1033 LOG(ERROR) << "Failed to initialize remote-bound WebRTC event log file.";
1034 *error_message_out = kStartRemoteLoggingFailureFileCreationError;
1035 UmaRecordWebRtcEventLoggingApi(
1036 WebRtcEventLoggingApiUma::kFileCreationError);
1037 return false;
1038 }
1039 const auto it = active_logs_.emplace(key, std::move(log_file));
1040 DCHECK(it.second);
1041
1042 observer_->OnRemoteLogStarted(key, it.first->second->path(),
1043 output_period_ms);
1044
1045 UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kSuccess);
1046
1047 *log_id_out = log_id;
1048 return true;
1049 }
1050
MaybeStopRemoteLogging(const PeerConnectionKey & key)1051 void WebRtcRemoteEventLogManager::MaybeStopRemoteLogging(
1052 const PeerConnectionKey& key) {
1053 DCHECK(task_runner_->RunsTasksInCurrentSequence());
1054
1055 const auto it = active_logs_.find(key);
1056 if (it == active_logs_.end()) {
1057 return;
1058 }
1059
1060 CloseLogFile(it, /*make_pending=*/true);
1061
1062 ManageUploadSchedule();
1063 }
1064
PrunePendingLogs(base::Optional<BrowserContextId> browser_context_id)1065 void WebRtcRemoteEventLogManager::PrunePendingLogs(
1066 base::Optional<BrowserContextId> browser_context_id) {
1067 DCHECK(task_runner_->RunsTasksInCurrentSequence());
1068 MaybeRemovePendingLogs(
1069 base::Time::Min(),
1070 base::Time::Now() - kRemoteBoundWebRtcEventLogsMaxRetention,
1071 browser_context_id, /*is_cache_clear=*/false);
1072 }
1073
RecurringlyPrunePendingLogs()1074 void WebRtcRemoteEventLogManager::RecurringlyPrunePendingLogs() {
1075 DCHECK(task_runner_->RunsTasksInCurrentSequence());
1076 DCHECK(!proactive_pending_logs_prune_delta_.is_zero());
1077 DCHECK(proactive_prune_scheduling_started_);
1078
1079 PrunePendingLogs();
1080
1081 task_runner_->PostDelayedTask(
1082 FROM_HERE,
1083 base::BindOnce(&WebRtcRemoteEventLogManager::RecurringlyPrunePendingLogs,
1084 weak_ptr_factory_->GetWeakPtr()),
1085 proactive_pending_logs_prune_delta_);
1086 }
1087
PruneHistoryFiles()1088 void WebRtcRemoteEventLogManager::PruneHistoryFiles() {
1089 DCHECK(task_runner_->RunsTasksInCurrentSequence());
1090 for (auto it = enabled_browser_contexts_.begin();
1091 it != enabled_browser_contexts_.end(); ++it) {
1092 const BrowserContextId browser_context_id = it->first;
1093 MaybeRemoveHistoryFiles(base::Time::Min(),
1094 base::Time::Now() - kHistoryFileRetention,
1095 browser_context_id);
1096 }
1097 }
1098
RecurringlyPruneHistoryFiles()1099 void WebRtcRemoteEventLogManager::RecurringlyPruneHistoryFiles() {
1100 DCHECK(task_runner_->RunsTasksInCurrentSequence());
1101 DCHECK(proactive_prune_scheduling_started_);
1102
1103 PruneHistoryFiles();
1104
1105 task_runner_->PostDelayedTask(
1106 FROM_HERE,
1107 base::BindOnce(&WebRtcRemoteEventLogManager::RecurringlyPruneHistoryFiles,
1108 weak_ptr_factory_->GetWeakPtr()),
1109 kProactiveHistoryFilesPruneDelta);
1110 }
1111
MaybeCancelActiveLogs(const base::Time & delete_begin,const base::Time & delete_end,BrowserContextId browser_context_id)1112 void WebRtcRemoteEventLogManager::MaybeCancelActiveLogs(
1113 const base::Time& delete_begin,
1114 const base::Time& delete_end,
1115 BrowserContextId browser_context_id) {
1116 DCHECK(task_runner_->RunsTasksInCurrentSequence());
1117 for (auto it = active_logs_.begin(); it != active_logs_.end();) {
1118 // Since the file is active, assume it's still being modified.
1119 if (MatchesFilter(it->first.browser_context_id, base::Time::Now(),
1120 browser_context_id, delete_begin, delete_end)) {
1121 UmaRecordWebRtcEventLoggingUpload(
1122 WebRtcEventLoggingUploadUma::kActiveLogCancelledDueToCacheClear);
1123 it = CloseLogFile(it, /*make_pending=*/false);
1124 } else {
1125 ++it;
1126 }
1127 }
1128 }
1129
MaybeRemovePendingLogs(const base::Time & delete_begin,const base::Time & delete_end,base::Optional<BrowserContextId> browser_context_id,bool is_cache_clear)1130 void WebRtcRemoteEventLogManager::MaybeRemovePendingLogs(
1131 const base::Time& delete_begin,
1132 const base::Time& delete_end,
1133 base::Optional<BrowserContextId> browser_context_id,
1134 bool is_cache_clear) {
1135 DCHECK(task_runner_->RunsTasksInCurrentSequence());
1136
1137 for (auto it = pending_logs_.begin(); it != pending_logs_.end();) {
1138 if (MatchesFilter(it->browser_context_id, it->last_modified,
1139 browser_context_id, delete_begin, delete_end)) {
1140 UmaRecordWebRtcEventLoggingUpload(
1141 is_cache_clear
1142 ? WebRtcEventLoggingUploadUma::kPendingLogDeletedDueToCacheClear
1143 : WebRtcEventLoggingUploadUma::kExpiredLogFileDuringSession);
1144
1145 if (!base::DeleteFile(it->path)) {
1146 LOG(ERROR) << "Failed to delete " << it->path << ".";
1147 }
1148
1149 // Produce a history file (they have longer retention) to replace the log.
1150 if (is_cache_clear) { // Will be immediately deleted otherwise.
1151 CreateHistoryFile(it->path, it->last_modified);
1152 }
1153
1154 it = pending_logs_.erase(it);
1155 } else {
1156 ++it;
1157 }
1158 }
1159
1160 // The last pending log might have been removed.
1161 if (!UploadConditionsHold()) {
1162 time_when_upload_conditions_met_ = base::TimeTicks();
1163 }
1164 }
1165
MaybeRemoveHistoryFiles(const base::Time & delete_begin,const base::Time & delete_end,BrowserContextId browser_context_id)1166 void WebRtcRemoteEventLogManager::MaybeRemoveHistoryFiles(
1167 const base::Time& delete_begin,
1168 const base::Time& delete_end,
1169 BrowserContextId browser_context_id) {
1170 DCHECK(task_runner_->RunsTasksInCurrentSequence());
1171 PruneAndLoadHistoryFilesForBrowserContext(delete_begin, delete_end,
1172 browser_context_id);
1173 return;
1174 }
1175
MaybeCancelUpload(const base::Time & delete_begin,const base::Time & delete_end,BrowserContextId browser_context_id)1176 void WebRtcRemoteEventLogManager::MaybeCancelUpload(
1177 const base::Time& delete_begin,
1178 const base::Time& delete_end,
1179 BrowserContextId browser_context_id) {
1180 DCHECK(task_runner_->RunsTasksInCurrentSequence());
1181
1182 if (!uploader_) {
1183 return;
1184 }
1185
1186 const WebRtcLogFileInfo& info = uploader_->GetWebRtcLogFileInfo();
1187 if (!MatchesFilter(info.browser_context_id, info.last_modified,
1188 browser_context_id, delete_begin, delete_end)) {
1189 return;
1190 }
1191
1192 // Cancel the upload. `uploader_` will be released when the callback,
1193 // `OnWebRtcEventLogUploadComplete`, is posted back.
1194 uploader_->Cancel();
1195 }
1196
MatchesFilter(BrowserContextId log_browser_context_id,const base::Time & log_last_modification,base::Optional<BrowserContextId> filter_browser_context_id,const base::Time & filter_range_begin,const base::Time & filter_range_end) const1197 bool WebRtcRemoteEventLogManager::MatchesFilter(
1198 BrowserContextId log_browser_context_id,
1199 const base::Time& log_last_modification,
1200 base::Optional<BrowserContextId> filter_browser_context_id,
1201 const base::Time& filter_range_begin,
1202 const base::Time& filter_range_end) const {
1203 DCHECK(task_runner_->RunsTasksInCurrentSequence());
1204 if (filter_browser_context_id &&
1205 *filter_browser_context_id != log_browser_context_id) {
1206 return false;
1207 }
1208 return TimePointInRange(log_last_modification, filter_range_begin,
1209 filter_range_end);
1210 }
1211
AdditionalActiveLogAllowed(BrowserContextId browser_context_id) const1212 bool WebRtcRemoteEventLogManager::AdditionalActiveLogAllowed(
1213 BrowserContextId browser_context_id) const {
1214 DCHECK(task_runner_->RunsTasksInCurrentSequence());
1215
1216 // Limit over concurrently active logs (across BrowserContext-s).
1217 if (active_logs_.size() >= kMaxActiveRemoteBoundWebRtcEventLogs) {
1218 return false;
1219 }
1220
1221 // Limit over the number of pending logs (per BrowserContext). We count active
1222 // logs too, since they become pending logs once completed.
1223 const size_t active_count = std::count_if(
1224 active_logs_.begin(), active_logs_.end(),
1225 [browser_context_id](const decltype(active_logs_)::value_type& log) {
1226 return log.first.browser_context_id == browser_context_id;
1227 });
1228 const size_t pending_count = std::count_if(
1229 pending_logs_.begin(), pending_logs_.end(),
1230 [browser_context_id](const decltype(pending_logs_)::value_type& log) {
1231 return log.browser_context_id == browser_context_id;
1232 });
1233 return active_count + pending_count < kMaxPendingRemoteBoundWebRtcEventLogs;
1234 }
1235
UploadSuppressed() const1236 bool WebRtcRemoteEventLogManager::UploadSuppressed() const {
1237 DCHECK(task_runner_->RunsTasksInCurrentSequence());
1238 return !upload_suppression_disabled_ && !active_peer_connections_.empty();
1239 }
1240
UploadConditionsHold() const1241 bool WebRtcRemoteEventLogManager::UploadConditionsHold() const {
1242 DCHECK(task_runner_->RunsTasksInCurrentSequence());
1243 return !uploader_ && !pending_logs_.empty() && !UploadSuppressed() &&
1244 uploading_supported_for_connection_type_;
1245 }
1246
ManageUploadSchedule()1247 void WebRtcRemoteEventLogManager::ManageUploadSchedule() {
1248 DCHECK(task_runner_->RunsTasksInCurrentSequence());
1249
1250 PrunePendingLogs(); // Avoid uploading freshly expired files.
1251
1252 if (!UploadConditionsHold()) {
1253 time_when_upload_conditions_met_ = base::TimeTicks();
1254 return;
1255 }
1256
1257 if (!time_when_upload_conditions_met_.is_null()) {
1258 // Conditions have been holding for a while; MaybeStartUploading() has
1259 // already been scheduled when |time_when_upload_conditions_met_| was set.
1260 return;
1261 }
1262
1263 ++scheduled_upload_tasks_;
1264
1265 time_when_upload_conditions_met_ = base::TimeTicks::Now();
1266
1267 task_runner_->PostDelayedTask(
1268 FROM_HERE,
1269 base::BindOnce(&WebRtcRemoteEventLogManager::MaybeStartUploading,
1270 weak_ptr_factory_->GetWeakPtr()),
1271 upload_delay_);
1272 }
1273
MaybeStartUploading()1274 void WebRtcRemoteEventLogManager::MaybeStartUploading() {
1275 DCHECK(task_runner_->RunsTasksInCurrentSequence());
1276 DCHECK_GT(scheduled_upload_tasks_, 0u);
1277
1278 // Since MaybeStartUploading() was scheduled, conditions might have stopped
1279 // holding at some point. They may have even stopped and started several times
1280 // while the currently running task was scheduled, meaning several tasks could
1281 // be pending now, only the last of which should really end up uploading.
1282
1283 if (time_when_upload_conditions_met_.is_null()) {
1284 // Conditions no longer hold; no way to know how many (now irrelevant) other
1285 // similar tasks are pending, if any.
1286 } else if (base::TimeTicks::Now() - time_when_upload_conditions_met_ <
1287 upload_delay_) {
1288 // Conditions have stopped holding, then started holding again; there has
1289 // to be a more recent task scheduled, that will take over later.
1290 DCHECK_GT(scheduled_upload_tasks_, 1u);
1291 } else {
1292 // It's up to the rest of the code to turn |scheduled_upload_tasks_| off
1293 // if the conditions have at some point stopped holding, or it wouldn't
1294 // know to turn it on when they resume.
1295 DCHECK(UploadConditionsHold());
1296
1297 // When the upload we're about to start finishes, there will be another
1298 // delay of length |upload_delay_| before the next one starts.
1299 time_when_upload_conditions_met_ = base::TimeTicks();
1300
1301 auto callback = base::BindOnce(
1302 &WebRtcRemoteEventLogManager::OnWebRtcEventLogUploadComplete,
1303 weak_ptr_factory_->GetWeakPtr());
1304
1305 // The uploader takes ownership of the file; it's no longer considered to be
1306 // pending. (If the upload fails, the log will be deleted.)
1307 // TODO(crbug.com/775415): Add more refined retry behavior, so that we would
1308 // not delete the log permanently if the network is just down, on the one
1309 // hand, but also would not be uploading unlimited data on endless retries
1310 // on the other hand.
1311 // TODO(crbug.com/775415): Rename the file before uploading, so that we
1312 // would not retry the upload after restarting Chrome, if the upload is
1313 // interrupted.
1314 currently_uploaded_file_ = pending_logs_.begin()->path;
1315 uploader_ =
1316 uploader_factory_->Create(*pending_logs_.begin(), std::move(callback));
1317 pending_logs_.erase(pending_logs_.begin());
1318 }
1319
1320 --scheduled_upload_tasks_;
1321 }
1322
OnWebRtcEventLogUploadComplete(const base::FilePath & log_file,bool upload_successful)1323 void WebRtcRemoteEventLogManager::OnWebRtcEventLogUploadComplete(
1324 const base::FilePath& log_file,
1325 bool upload_successful) {
1326 DCHECK(task_runner_->RunsTasksInCurrentSequence());
1327 DCHECK(uploader_);
1328
1329 // Make sure this callback refers to the currently uploaded file. This might
1330 // not be the case if the upload was cancelled right after succeeding, in
1331 // which case we'll get two callbacks, one reporting success and one failure.
1332 // It can also be that the uploader was cancelled more than once, e.g. if
1333 // the user cleared cache while PrefService were changing.
1334 if (!uploader_ ||
1335 uploader_->GetWebRtcLogFileInfo().path != currently_uploaded_file_) {
1336 return;
1337 }
1338
1339 uploader_.reset();
1340 currently_uploaded_file_.clear();
1341
1342 ManageUploadSchedule();
1343 }
1344
FindPeerConnection(int render_process_id,const std::string & session_id,PeerConnectionKey * key) const1345 bool WebRtcRemoteEventLogManager::FindPeerConnection(
1346 int render_process_id,
1347 const std::string& session_id,
1348 PeerConnectionKey* key) const {
1349 DCHECK(task_runner_->RunsTasksInCurrentSequence());
1350 DCHECK(!session_id.empty());
1351
1352 const auto it = FindNextPeerConnection(active_peer_connections_.cbegin(),
1353 render_process_id, session_id);
1354 if (it == active_peer_connections_.cend()) {
1355 return false;
1356 }
1357
1358 // Make sure that the session ID is unique for the renderer process,
1359 // though not necessarily between renderer processes.
1360 // (The helper exists solely to allow this DCHECK.)
1361 DCHECK(FindNextPeerConnection(std::next(it), render_process_id, session_id) ==
1362 active_peer_connections_.cend());
1363
1364 *key = it->first;
1365 return true;
1366 }
1367
1368 WebRtcRemoteEventLogManager::PeerConnectionMap::const_iterator
FindNextPeerConnection(PeerConnectionMap::const_iterator begin,int render_process_id,const std::string & session_id) const1369 WebRtcRemoteEventLogManager::FindNextPeerConnection(
1370 PeerConnectionMap::const_iterator begin,
1371 int render_process_id,
1372 const std::string& session_id) const {
1373 DCHECK(task_runner_->RunsTasksInCurrentSequence());
1374 DCHECK(!session_id.empty());
1375 const auto end = active_peer_connections_.cend();
1376 for (auto it = begin; it != end; ++it) {
1377 if (it->first.render_process_id == render_process_id &&
1378 it->second == session_id) {
1379 return it;
1380 }
1381 }
1382 return end;
1383 }
1384
1385 } // namespace webrtc_event_logging
1386