| // 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/format_macros.h" | 
 | #include "base/logging.h" | 
 | #include "base/rand_util.h" | 
 | #include "base/strings/stringprintf.h" | 
 | #include "build/build_config.h" | 
 | #include "remoting/base/telemetry_log_writer.h" | 
 |  | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | #include <android/log.h> | 
 | #endif  // BUILDFLAG(IS_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 BUILDFLAG(IS_ANDROID) | 
 |   __android_log_print( | 
 |       ANDROID_LOG_INFO, "stats", | 
 | #else | 
 |   VLOG(0) << base::StringPrintf( | 
 | #endif  // BUILDFLAG(IS_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 = std::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::Days(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 |