blob: 3240c69d7a5e81865a29b9db9e0ffd443f007236 [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 "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)
: mode_(mode), log_writer_(log_writer) {}
ClientTelemetryLogger::~ClientTelemetryLogger() {
DCHECK(thread_checker_.CalledOnValidThread());
}
void ClientTelemetryLogger::SetHostInfo(const std::string& host_version,
ChromotingEvent::Os host_os,
const std::string& host_os_version) {
host_info_.reset(new HostInfo{host_version, host_os, host_os_version});
}
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);
log_writer_->Log(event);
if (ChromotingEvent::IsEndOfSession(state)) {
session_id_.clear();
session_start_time_ = base::TimeTicks();
}
}
void ClientTelemetryLogger::LogStatistics(
protocol::PerformanceTracker* perf_tracker) {
DCHECK(thread_checker_.CalledOnValidThread());
RefreshSessionIdIfOutdated();
PrintLogStatistics(perf_tracker);
ChromotingEvent event = MakeStatsEvent(perf_tracker);
log_writer_->Log(event);
}
void ClientTelemetryLogger::PrintLogStatistics(
protocol::PerformanceTracker* perf_tracker) {
#if defined(OS_ANDROID)
__android_log_print(
ANDROID_LOG_INFO, "stats",
#else
VLOG(1) << 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 state) {
switch (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 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;
}
}
void ClientTelemetryLogger::FillEventContext(ChromotingEvent* event) const {
event->SetEnum(ChromotingEvent::kModeKey, mode_);
event->SetEnum(ChromotingEvent::kRoleKey, ChromotingEvent::Role::CLIENT);
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);
}
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);
}
}
void ClientTelemetryLogger::GenerateSessionId() {
session_id_.resize(kSessionIdLength);
for (int i = 0; i < kSessionIdLength; i++) {
const int alphabet_size = arraysize(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(
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