1 /*
2 * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "logging/rtc_event_log/rtc_event_log_impl.h"
12
13 #include <functional>
14 #include <limits>
15 #include <memory>
16 #include <utility>
17 #include <vector>
18
19 #include "absl/types/optional.h"
20 #include "api/task_queue/queued_task.h"
21 #include "api/task_queue/task_queue_base.h"
22 #include "logging/rtc_event_log/encoder/rtc_event_log_encoder_legacy.h"
23 #include "logging/rtc_event_log/encoder/rtc_event_log_encoder_new_format.h"
24 #include "rtc_base/checks.h"
25 #include "rtc_base/constructor_magic.h"
26 #include "rtc_base/event.h"
27 #include "rtc_base/logging.h"
28 #include "rtc_base/numerics/safe_conversions.h"
29 #include "rtc_base/numerics/safe_minmax.h"
30 #include "rtc_base/time_utils.h"
31
32 namespace webrtc {
33 namespace {
34 constexpr size_t kMaxEventsInHistory = 10000;
35 // The config-history is supposed to be unbounded, but needs to have some bound
36 // to prevent an attack via unreasonable memory use.
37 constexpr size_t kMaxEventsInConfigHistory = 1000;
38
CreateEncoder(RtcEventLog::EncodingType type)39 std::unique_ptr<RtcEventLogEncoder> CreateEncoder(
40 RtcEventLog::EncodingType type) {
41 switch (type) {
42 case RtcEventLog::EncodingType::Legacy:
43 RTC_LOG(LS_INFO) << "Creating legacy encoder for RTC event log.";
44 return std::make_unique<RtcEventLogEncoderLegacy>();
45 case RtcEventLog::EncodingType::NewFormat:
46 RTC_LOG(LS_INFO) << "Creating new format encoder for RTC event log.";
47 return std::make_unique<RtcEventLogEncoderNewFormat>();
48 default:
49 RTC_LOG(LS_ERROR) << "Unknown RtcEventLog encoder type (" << int(type)
50 << ")";
51 RTC_NOTREACHED();
52 return std::unique_ptr<RtcEventLogEncoder>(nullptr);
53 }
54 }
55 } // namespace
56
RtcEventLogImpl(RtcEventLog::EncodingType encoding_type,TaskQueueFactory * task_queue_factory)57 RtcEventLogImpl::RtcEventLogImpl(RtcEventLog::EncodingType encoding_type,
58 TaskQueueFactory* task_queue_factory)
59 : event_encoder_(CreateEncoder(encoding_type)),
60 num_config_events_written_(0),
61 last_output_ms_(rtc::TimeMillis()),
62 output_scheduled_(false),
63 logging_state_started_(false),
64 task_queue_(
65 std::make_unique<rtc::TaskQueue>(task_queue_factory->CreateTaskQueue(
66 "rtc_event_log",
67 TaskQueueFactory::Priority::NORMAL))) {}
68
~RtcEventLogImpl()69 RtcEventLogImpl::~RtcEventLogImpl() {
70 // If we're logging to the output, this will stop that. Blocking function.
71 if (logging_state_started_) {
72 logging_state_checker_.Detach();
73 StopLogging();
74 }
75
76 // We want to block on any executing task by invoking ~TaskQueue() before
77 // we set unique_ptr's internal pointer to null.
78 rtc::TaskQueue* tq = task_queue_.get();
79 delete tq;
80 task_queue_.release();
81 }
82
StartLogging(std::unique_ptr<RtcEventLogOutput> output,int64_t output_period_ms)83 bool RtcEventLogImpl::StartLogging(std::unique_ptr<RtcEventLogOutput> output,
84 int64_t output_period_ms) {
85 RTC_DCHECK(output_period_ms == kImmediateOutput || output_period_ms > 0);
86
87 if (!output->IsActive()) {
88 // TODO(eladalon): We may want to remove the IsActive method. Otherwise
89 // we probably want to be consistent and terminate any existing output.
90 return false;
91 }
92
93 const int64_t timestamp_us = rtc::TimeMicros();
94 const int64_t utc_time_us = rtc::TimeUTCMicros();
95 RTC_LOG(LS_INFO) << "Starting WebRTC event log. (Timestamp, UTC) = "
96 "("
97 << timestamp_us << ", " << utc_time_us << ").";
98
99 RTC_DCHECK_RUN_ON(&logging_state_checker_);
100 logging_state_started_ = true;
101 // Binding to |this| is safe because |this| outlives the |task_queue_|.
102 task_queue_->PostTask([this, output_period_ms, timestamp_us, utc_time_us,
103 output = std::move(output)]() mutable {
104 RTC_DCHECK_RUN_ON(task_queue_.get());
105 RTC_DCHECK(output->IsActive());
106 output_period_ms_ = output_period_ms;
107 event_output_ = std::move(output);
108 num_config_events_written_ = 0;
109 WriteToOutput(event_encoder_->EncodeLogStart(timestamp_us, utc_time_us));
110 LogEventsFromMemoryToOutput();
111 });
112
113 return true;
114 }
115
StopLogging()116 void RtcEventLogImpl::StopLogging() {
117 RTC_LOG(LS_INFO) << "Stopping WebRTC event log.";
118 // TODO(danilchap): Do not block current thread waiting on the task queue.
119 // It might work for now, for current callers, but disallows caller to share
120 // threads with the |task_queue_|.
121 rtc::Event output_stopped;
122 StopLogging([&output_stopped]() { output_stopped.Set(); });
123 output_stopped.Wait(rtc::Event::kForever);
124
125 RTC_LOG(LS_INFO) << "WebRTC event log successfully stopped.";
126 }
127
StopLogging(std::function<void ()> callback)128 void RtcEventLogImpl::StopLogging(std::function<void()> callback) {
129 RTC_DCHECK_RUN_ON(&logging_state_checker_);
130 logging_state_started_ = false;
131 task_queue_->PostTask([this, callback] {
132 RTC_DCHECK_RUN_ON(task_queue_.get());
133 if (event_output_) {
134 RTC_DCHECK(event_output_->IsActive());
135 LogEventsFromMemoryToOutput();
136 }
137 StopLoggingInternal();
138 callback();
139 });
140 }
141
Log(std::unique_ptr<RtcEvent> event)142 void RtcEventLogImpl::Log(std::unique_ptr<RtcEvent> event) {
143 RTC_CHECK(event);
144
145 // Binding to |this| is safe because |this| outlives the |task_queue_|.
146 task_queue_->PostTask([this, event = std::move(event)]() mutable {
147 RTC_DCHECK_RUN_ON(task_queue_.get());
148 LogToMemory(std::move(event));
149 if (event_output_)
150 ScheduleOutput();
151 });
152 }
153
ScheduleOutput()154 void RtcEventLogImpl::ScheduleOutput() {
155 RTC_DCHECK(event_output_ && event_output_->IsActive());
156 if (history_.size() >= kMaxEventsInHistory) {
157 // We have to emergency drain the buffer. We can't wait for the scheduled
158 // output task because there might be other event incoming before that.
159 LogEventsFromMemoryToOutput();
160 return;
161 }
162
163 RTC_DCHECK(output_period_ms_.has_value());
164 if (*output_period_ms_ == kImmediateOutput) {
165 // We are already on the |task_queue_| so there is no reason to post a task
166 // if we want to output immediately.
167 LogEventsFromMemoryToOutput();
168 return;
169 }
170
171 if (!output_scheduled_) {
172 output_scheduled_ = true;
173 // Binding to |this| is safe because |this| outlives the |task_queue_|.
174 auto output_task = [this]() {
175 RTC_DCHECK_RUN_ON(task_queue_.get());
176 if (event_output_) {
177 RTC_DCHECK(event_output_->IsActive());
178 LogEventsFromMemoryToOutput();
179 }
180 output_scheduled_ = false;
181 };
182 const int64_t now_ms = rtc::TimeMillis();
183 const int64_t time_since_output_ms = now_ms - last_output_ms_;
184 const uint32_t delay = rtc::SafeClamp(
185 *output_period_ms_ - time_since_output_ms, 0, *output_period_ms_);
186 task_queue_->PostDelayedTask(output_task, delay);
187 }
188 }
189
LogToMemory(std::unique_ptr<RtcEvent> event)190 void RtcEventLogImpl::LogToMemory(std::unique_ptr<RtcEvent> event) {
191 std::deque<std::unique_ptr<RtcEvent>>& container =
192 event->IsConfigEvent() ? config_history_ : history_;
193 const size_t container_max_size =
194 event->IsConfigEvent() ? kMaxEventsInConfigHistory : kMaxEventsInHistory;
195
196 if (container.size() >= container_max_size) {
197 RTC_DCHECK(!event_output_); // Shouldn't lose events if we have an output.
198 container.pop_front();
199 }
200 container.push_back(std::move(event));
201 }
202
LogEventsFromMemoryToOutput()203 void RtcEventLogImpl::LogEventsFromMemoryToOutput() {
204 RTC_DCHECK(event_output_ && event_output_->IsActive());
205 last_output_ms_ = rtc::TimeMillis();
206
207 // Serialize all stream configurations that haven't already been written to
208 // this output. |num_config_events_written_| is used to track which configs we
209 // have already written. (Note that the config may have been written to
210 // previous outputs; configs are not discarded.)
211 std::string encoded_configs;
212 RTC_DCHECK_LE(num_config_events_written_, config_history_.size());
213 if (num_config_events_written_ < config_history_.size()) {
214 const auto begin = config_history_.begin() + num_config_events_written_;
215 const auto end = config_history_.end();
216 encoded_configs = event_encoder_->EncodeBatch(begin, end);
217 num_config_events_written_ = config_history_.size();
218 }
219
220 // Serialize the events in the event queue. Note that the write may fail,
221 // for example if we are writing to a file and have reached the maximum limit.
222 // We don't get any feedback if this happens, so we still remove the events
223 // from the event log history. This is normally not a problem, but if another
224 // log is started immediately after the first one becomes full, then one
225 // cannot rely on the second log to contain everything that isn't in the first
226 // log; one batch of events might be missing.
227 std::string encoded_history =
228 event_encoder_->EncodeBatch(history_.begin(), history_.end());
229 history_.clear();
230
231 WriteConfigsAndHistoryToOutput(encoded_configs, encoded_history);
232 }
233
WriteConfigsAndHistoryToOutput(const std::string & encoded_configs,const std::string & encoded_history)234 void RtcEventLogImpl::WriteConfigsAndHistoryToOutput(
235 const std::string& encoded_configs,
236 const std::string& encoded_history) {
237 // This function is used to merge the strings instead of calling the output
238 // object twice with small strings. The function also avoids copying any
239 // strings in the typical case where there are no config events.
240 if (encoded_configs.empty()) {
241 WriteToOutput(encoded_history); // Typical case.
242 } else if (encoded_history.empty()) {
243 WriteToOutput(encoded_configs); // Very unusual case.
244 } else {
245 WriteToOutput(encoded_configs + encoded_history);
246 }
247 }
248
StopOutput()249 void RtcEventLogImpl::StopOutput() {
250 event_output_.reset();
251 }
252
StopLoggingInternal()253 void RtcEventLogImpl::StopLoggingInternal() {
254 if (event_output_) {
255 RTC_DCHECK(event_output_->IsActive());
256 const int64_t timestamp_us = rtc::TimeMicros();
257 event_output_->Write(event_encoder_->EncodeLogEnd(timestamp_us));
258 }
259 StopOutput();
260 }
261
WriteToOutput(const std::string & output_string)262 void RtcEventLogImpl::WriteToOutput(const std::string& output_string) {
263 RTC_DCHECK(event_output_ && event_output_->IsActive());
264 if (!event_output_->Write(output_string)) {
265 RTC_LOG(LS_ERROR) << "Failed to write RTC event to output.";
266 // The first failure closes the output.
267 RTC_DCHECK(!event_output_->IsActive());
268 StopOutput(); // Clean-up.
269 return;
270 }
271 }
272
273 } // namespace webrtc
274