blob: d139f8cc461fdd16f94e9436d9c1920523d18bee [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/client/client_telemetry_logger.h"
#include <memory>
#include "base/cxx17_backports.h"
#include "base/format_macros.h"
#include "base/logging.h"
#include "base/rand_util.h"
#include "base/strings/stringprintf.h"
#include "remoting/base/telemetry_log_writer.h"
#if defined(OS_ANDROID)
#include <android/log.h>
#endif // OS_ANDROID
namespace {
const char kSessionIdAlphabet[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
const int kSessionIdLength = 20;
const int kMaxSessionIdAgeDays = 1;
} // namespace
namespace remoting {
struct ClientTelemetryLogger::HostInfo {
const std::string host_version;
const ChromotingEvent::Os host_os;
const std::string host_os_version;
};
ClientTelemetryLogger::ClientTelemetryLogger(
ChromotingEventLogWriter* log_writer,
ChromotingEvent::Mode mode,
ChromotingEvent::SessionEntryPoint entry_point)
: mode_(mode), entry_point_(entry_point), log_writer_(log_writer) {
thread_checker_.DetachFromThread();
}
ClientTelemetryLogger::~ClientTelemetryLogger() {
DCHECK(thread_checker_.CalledOnValidThread());
}
void ClientTelemetryLogger::SetAuthMethod(
ChromotingEvent::AuthMethod auth_method) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_NE(ChromotingEvent::AuthMethod::NOT_SET, auth_method);
auth_method_ = auth_method;
}
void ClientTelemetryLogger::SetHostInfo(const std::string& host_version,
ChromotingEvent::Os host_os,
const std::string& host_os_version) {
DCHECK(thread_checker_.CalledOnValidThread());
host_info_ = std::make_unique<HostInfo>(
HostInfo{host_version, host_os, host_os_version});
}
void ClientTelemetryLogger::SetTransportRoute(
const protocol::TransportRoute& route) {
DCHECK(thread_checker_.CalledOnValidThread());
transport_route_ = std::make_unique<protocol::TransportRoute>(route);
}
void ClientTelemetryLogger::SetSignalStrategyType(
ChromotingEvent::SignalStrategyType signal_strategy_type) {
DCHECK(thread_checker_.CalledOnValidThread());
signal_strategy_type_ = signal_strategy_type;
}
void ClientTelemetryLogger::LogSessionStateChange(
ChromotingEvent::SessionState state,
ChromotingEvent::ConnectionError error) {
DCHECK(thread_checker_.CalledOnValidThread());
RefreshSessionIdIfOutdated();
if (session_start_time_.is_null()) {
session_start_time_ = base::TimeTicks::Now();
}
ChromotingEvent event =
ClientTelemetryLogger::MakeSessionStateChangeEvent(state, error);
const base::Value* previous_state =
current_session_state_event_.GetValue(ChromotingEvent::kSessionStateKey);
if (previous_state) {
event.SetInteger(ChromotingEvent::kPreviousSessionStateKey,
previous_state->GetInt());
}
log_writer_->Log(event);
current_session_state_event_ = std::move(event);
if (ChromotingEvent::IsEndOfSession(state)) {
session_id_.clear();
session_start_time_ = base::TimeTicks();
}
}
void ClientTelemetryLogger::LogStatistics(
const protocol::PerformanceTracker& perf_tracker) {
DCHECK(thread_checker_.CalledOnValidThread());
RefreshSessionIdIfOutdated();
PrintLogStatistics(perf_tracker);
ChromotingEvent event = MakeStatsEvent(perf_tracker);
log_writer_->Log(event);
}
void ClientTelemetryLogger::PrintLogStatistics(
const protocol::PerformanceTracker& perf_tracker) {
#if defined(OS_ANDROID)
__android_log_print(
ANDROID_LOG_INFO, "stats",
#else
VLOG(0) << base::StringPrintf(
#endif // OS_ANDROID
"Bandwidth:%.0f FrameRate:%.1f;"
" (Avg, Max) Capture:%.1f, %" PRId64 " Encode:%.1f, %" PRId64
" Decode:%.1f, %" PRId64 " Render:%.1f, %" PRId64 " RTL:%.0f, %" PRId64,
perf_tracker.video_bandwidth(), perf_tracker.video_frame_rate(),
perf_tracker.video_capture_ms().Average(),
perf_tracker.video_capture_ms().Max(),
perf_tracker.video_encode_ms().Average(),
perf_tracker.video_encode_ms().Max(),
perf_tracker.video_decode_ms().Average(),
perf_tracker.video_decode_ms().Max(),
perf_tracker.video_paint_ms().Average(),
perf_tracker.video_paint_ms().Max(),
perf_tracker.round_trip_ms().Average(),
perf_tracker.round_trip_ms().Max());
}
void ClientTelemetryLogger::SetSessionIdGenerationTimeForTest(
base::TimeTicks gen_time) {
session_id_generation_time_ = gen_time;
}
// static
ChromotingEvent::SessionState ClientTelemetryLogger::TranslateState(
protocol::ConnectionToHost::State current_state,
protocol::ConnectionToHost::State previous_state) {
switch (current_state) {
case protocol::ConnectionToHost::State::INITIALIZING:
return ChromotingEvent::SessionState::INITIALIZING;
case protocol::ConnectionToHost::State::CONNECTING:
return ChromotingEvent::SessionState::CONNECTING;
case protocol::ConnectionToHost::State::AUTHENTICATED:
return ChromotingEvent::SessionState::AUTHENTICATED;
case protocol::ConnectionToHost::State::CONNECTED:
return ChromotingEvent::SessionState::CONNECTED;
case protocol::ConnectionToHost::State::FAILED:
return previous_state == protocol::ConnectionToHost::State::CONNECTED
? ChromotingEvent::SessionState::CONNECTION_DROPPED
: ChromotingEvent::SessionState::CONNECTION_FAILED;
case protocol::ConnectionToHost::State::CLOSED:
return ChromotingEvent::SessionState::CLOSED;
default:
NOTREACHED();
return ChromotingEvent::SessionState::UNKNOWN;
}
}
// static
ChromotingEvent::ConnectionError ClientTelemetryLogger::TranslateError(
protocol::ErrorCode error) {
switch (error) {
case protocol::OK:
return ChromotingEvent::ConnectionError::NONE;
case protocol::PEER_IS_OFFLINE:
return ChromotingEvent::ConnectionError::HOST_OFFLINE;
case protocol::SESSION_REJECTED:
return ChromotingEvent::ConnectionError::SESSION_REJECTED;
case protocol::INCOMPATIBLE_PROTOCOL:
return ChromotingEvent::ConnectionError::INCOMPATIBLE_PROTOCOL;
case protocol::AUTHENTICATION_FAILED:
return ChromotingEvent::ConnectionError::AUTHENTICATION_FAILED;
case protocol::INVALID_ACCOUNT:
return ChromotingEvent::ConnectionError::INVALID_ACCOUNT;
case protocol::CHANNEL_CONNECTION_ERROR:
return ChromotingEvent::ConnectionError::P2P_FAILURE;
case protocol::SIGNALING_ERROR:
return ChromotingEvent::ConnectionError::NETWORK_FAILURE;
case protocol::SIGNALING_TIMEOUT:
return ChromotingEvent::ConnectionError::NETWORK_FAILURE;
case protocol::HOST_OVERLOAD:
return ChromotingEvent::ConnectionError::HOST_OVERLOAD;
case protocol::MAX_SESSION_LENGTH:
return ChromotingEvent::ConnectionError::MAX_SESSION_LENGTH;
case protocol::HOST_CONFIGURATION_ERROR:
return ChromotingEvent::ConnectionError::HOST_CONFIGURATION_ERROR;
case protocol::UNKNOWN_ERROR:
return ChromotingEvent::ConnectionError::UNKNOWN_ERROR;
default:
NOTREACHED();
return ChromotingEvent::ConnectionError::UNEXPECTED;
}
}
// static
ChromotingEvent::ConnectionType ClientTelemetryLogger::TranslateConnectionType(
protocol::TransportRoute::RouteType type) {
switch (type) {
case protocol::TransportRoute::DIRECT:
return ChromotingEvent::ConnectionType::DIRECT;
case protocol::TransportRoute::STUN:
return ChromotingEvent::ConnectionType::STUN;
case protocol::TransportRoute::RELAY:
return ChromotingEvent::ConnectionType::RELAY;
default:
NOTREACHED();
return ChromotingEvent::ConnectionType::DIRECT;
}
}
void ClientTelemetryLogger::FillEventContext(ChromotingEvent* event) const {
event->SetEnum(ChromotingEvent::kModeKey, mode_);
event->SetEnum(ChromotingEvent::kRoleKey, ChromotingEvent::Role::CLIENT);
event->SetEnum(ChromotingEvent::kSessionEntryPointKey, entry_point_);
if (auth_method_ != ChromotingEvent::AuthMethod::NOT_SET) {
event->SetEnum(ChromotingEvent::kAuthMethodKey, auth_method_);
}
if (host_info_) {
event->SetString(ChromotingEvent::kHostVersionKey,
host_info_->host_version);
event->SetEnum(ChromotingEvent::kHostOsKey, host_info_->host_os);
event->SetString(ChromotingEvent::kHostOsVersionKey,
host_info_->host_os_version);
}
if (transport_route_) {
ChromotingEvent::ConnectionType connection_type =
TranslateConnectionType(transport_route_->type);
event->SetEnum(ChromotingEvent::kConnectionTypeKey, connection_type);
}
event->AddSystemInfo();
if (!session_id_.empty()) {
event->SetString(ChromotingEvent::kSessionIdKey, session_id_);
}
if (!session_start_time_.is_null()) {
int session_duration =
(base::TimeTicks::Now() - session_start_time_).InSeconds();
event->SetInteger(ChromotingEvent::kSessionDurationKey, session_duration);
}
if (signal_strategy_type_ != ChromotingEvent::SignalStrategyType::NOT_SET) {
event->SetInteger(ChromotingEvent::kSignalStrategyTypeKey,
signal_strategy_type_);
}
}
void ClientTelemetryLogger::GenerateSessionId() {
session_id_.resize(kSessionIdLength);
for (int i = 0; i < kSessionIdLength; i++) {
const int alphabet_size = base::size(kSessionIdAlphabet) - 1;
session_id_[i] = kSessionIdAlphabet[base::RandGenerator(alphabet_size)];
}
session_id_generation_time_ = base::TimeTicks::Now();
}
void ClientTelemetryLogger::RefreshSessionIdIfOutdated() {
if (session_id_.empty()) {
GenerateSessionId();
return;
}
base::TimeDelta max_age = base::TimeDelta::FromDays(kMaxSessionIdAgeDays);
if (base::TimeTicks::Now() - session_id_generation_time_ > max_age) {
// Log the old session ID.
ChromotingEvent event = MakeSessionIdOldEvent();
log_writer_->Log(event);
// Generate a new session ID.
GenerateSessionId();
// Log the new session ID.
ChromotingEvent new_id_event = MakeSessionIdNewEvent();
log_writer_->Log(new_id_event);
}
}
ChromotingEvent ClientTelemetryLogger::MakeStatsEvent(
const protocol::PerformanceTracker& perf_tracker) {
ChromotingEvent event(ChromotingEvent::Type::CONNECTION_STATISTICS);
FillEventContext(&event);
event.SetDouble(ChromotingEvent::kVideoBandwidthKey,
perf_tracker.video_bandwidth());
event.SetDouble(ChromotingEvent::kCaptureLatencyKey,
perf_tracker.video_capture_ms().Average());
event.SetDouble(ChromotingEvent::kEncodeLatencyKey,
perf_tracker.video_encode_ms().Average());
event.SetDouble(ChromotingEvent::kDecodeLatencyKey,
perf_tracker.video_decode_ms().Average());
event.SetDouble(ChromotingEvent::kRenderLatencyKey,
perf_tracker.video_paint_ms().Average());
event.SetDouble(ChromotingEvent::kRoundtripLatencyKey,
perf_tracker.round_trip_ms().Average());
event.SetDouble(ChromotingEvent::kMaxCaptureLatencyKey,
perf_tracker.video_capture_ms().Max());
event.SetDouble(ChromotingEvent::kMaxEncodeLatencyKey,
perf_tracker.video_encode_ms().Max());
event.SetDouble(ChromotingEvent::kMaxDecodeLatencyKey,
perf_tracker.video_decode_ms().Max());
event.SetDouble(ChromotingEvent::kMaxRenderLatencyKey,
perf_tracker.video_paint_ms().Max());
event.SetDouble(ChromotingEvent::kMaxRoundtripLatencyKey,
perf_tracker.round_trip_ms().Max());
return event;
}
ChromotingEvent ClientTelemetryLogger::MakeSessionStateChangeEvent(
ChromotingEvent::SessionState state,
ChromotingEvent::ConnectionError error) {
ChromotingEvent event(ChromotingEvent::Type::SESSION_STATE);
FillEventContext(&event);
event.SetEnum(ChromotingEvent::kSessionStateKey, state);
event.SetEnum(ChromotingEvent::kConnectionErrorKey, error);
return event;
}
ChromotingEvent ClientTelemetryLogger::MakeSessionIdOldEvent() {
ChromotingEvent event(ChromotingEvent::Type::SESSION_ID_OLD);
FillEventContext(&event);
return event;
}
ChromotingEvent ClientTelemetryLogger::MakeSessionIdNewEvent() {
ChromotingEvent event(ChromotingEvent::Type::SESSION_ID_NEW);
FillEventContext(&event);
return event;
}
} // namespace remoting