diff options
author | juyik@chromium.org <juyik@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-16 21:41:44 +0000 |
---|---|---|
committer | juyik@chromium.org <juyik@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-16 21:41:44 +0000 |
commit | 268d23f13de7d41e2dc42b90205fbd86a66676a0 (patch) | |
tree | 445afa9ef278cd1462bdab4eb13dd12c555dbe09 | |
parent | e888193914dc13c6961a64c810852fd0d6268afb (diff) | |
download | chromium_src-268d23f13de7d41e2dc42b90205fbd86a66676a0.zip chromium_src-268d23f13de7d41e2dc42b90205fbd86a66676a0.tar.gz chromium_src-268d23f13de7d41e2dc42b90205fbd86a66676a0.tar.bz2 |
Add activity recording capability to gcm internals page. User can refresh, start/stop recording, and clear recording logs.
Also added more information to the device info section and fixed a bug there.
arv: owner review of chrome/browser/resources/*.
jianli & fgorski please review the rest of the code, and zea for owner review of these code.
BUG=341256
Review URL: https://codereview.chromium.org/202083005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@264313 0039d316-1c4b-4281-b951-d872f2087c98
22 files changed, 834 insertions, 73 deletions
diff --git a/chrome/browser/resources/gcm_internals.css b/chrome/browser/resources/gcm_internals.css index 98b3544..f3f3c5e 100644 --- a/chrome/browser/resources/gcm_internals.css +++ b/chrome/browser/resources/gcm_internals.css @@ -12,12 +12,28 @@ td { padding: 4px; } -.row-caption { +tr:nth-child(odd) { + background-color: rgb(245, 245, 200); +} + +th { + background-color: rgb(160, 160, 125); + color: rgb(255, 255, 255); + font-weight: bold; +} + +.flexbar { + display: flex; + flex-direction: row; + margin: 5px; +} + +#device-info tr :first-child { font-weight: bold; padding-right: 10px; text-align: end; } -.odd-number-row { - background-color: rgb(245, 245, 200); +.log-table { + padding: 4px; } diff --git a/chrome/browser/resources/gcm_internals.html b/chrome/browser/resources/gcm_internals.html index 775a922..50a508f 100644 --- a/chrome/browser/resources/gcm_internals.html +++ b/chrome/browser/resources/gcm_internals.html @@ -13,74 +13,118 @@ </head> <body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize"> <h1>GCM Internals</h1> -<div> - <h2>Device Info</h2> - <table> - <tr class="odd-number-row"> - <td class="row-caption"> +<div class="flexbar"> + <button id="refresh">Refresh</button> + <button id="recording">Start Recording</button> + <button id="clear-logs">Clear All Logs</button> +</div> + +<h2>Device Info</h2> +<table id="device-info"> + <tbody> + <tr> + <td> Android Id </td> <td id="android-id"> </td> </tr> <tr> - <td class="row-caption"> + <td> User Profile Service Created </td> <td id="profile-service-created"> </td> </tr> - <tr class="odd-number-row"> - <td class="row-caption"> + <tr> + <td> GCM Enabled State </td> <td id="gcm-enabled-state"> </td> </tr> <tr> - <td class="row-caption"> + <td> Signed In Username </td> <td id="signed-in-username"> </td> </tr> - <tr class="odd-number-row"> - <td class="row-caption"> + <tr> + <td> GCM Client Created </td> <td id="gcm-client-created"> </td> </tr> <tr> - <td class="row-caption"> + <td> GCM Client State </td> <td id="gcm-client-state"> </td> </tr> - <tr class="odd-number-row"> - <td class="row-caption"> + <tr> + <td> GCM Client Is Ready </td> <td id="gcm-client-ready"> </td> </tr> <tr> - <td class="row-caption"> + <td> Connection Client Created </td> <td id="connection-client-created"> </td> </tr> - <tr class="odd-number-row"> - <td class="row-caption"> + <tr> + <td> Connection State </td> <td id="connection-state"> </td> </tr> - </table> -</div> + <tr> + <td> + Registered App Ids + </td> + <td id="registered-app-ids"> + </td> + </tr> + <tr> + <td> + Send Message Queue Size + </td> + <td id="send-queue-size"> + </td> + </tr> + <tr> + <td> + Resend Message Queue Size + </td> + <td id="resend-queue-size"> + </td> + </tr> + </tbody> +</table> + +<h2>Send Message Log</h2> +<table class="log-table"> + <thead> + <tr> + <th>Time</th> + <th>App Id</th> + <th>Receiver Id</th> + <th>Msg Id</th> + <th>Event</th> + <th>Details</th> + </tr> + </thead> + <tbody id="send-info"> + </tbody> +</table> + <script src="chrome://resources/js/i18n_template2.js"></script> </body> </html>
\ No newline at end of file diff --git a/chrome/browser/resources/gcm_internals.js b/chrome/browser/resources/gcm_internals.js index f309479..35100b3 100644 --- a/chrome/browser/resources/gcm_internals.js +++ b/chrome/browser/resources/gcm_internals.js @@ -5,9 +5,11 @@ cr.define('gcmInternals', function() { 'use strict'; + var isRecording = false; + /** * If the info dictionary has property prop, then set the text content of - * element to the value of this property. + * element to the value of this property. Otherwise clear the content. * @param {!Object} info A dictionary of device infos to be displayed. * @param {string} prop Name of the property. * @param {string} element The id of a HTML element. @@ -15,6 +17,8 @@ cr.define('gcmInternals', function() { function setIfExists(info, prop, element) { if (info[prop] !== undefined) { $(element).textContent = info[prop]; + } else { + $(element).textContent = ''; } } @@ -32,10 +36,73 @@ cr.define('gcmInternals', function() { setIfExists(info, 'gcmClientReady', 'gcm-client-ready'); setIfExists(info, 'connectionClientCreated', 'connection-client-created'); setIfExists(info, 'connectionState', 'connection-state'); + setIfExists(info, 'registeredAppIds', 'registered-app-ids'); + setIfExists(info, 'sendQueueSize', 'send-queue-size'); + setIfExists(info, 'resendQueueSize', 'resend-queue-size'); + } + + /** + * Remove all the child nodes of the element. + * @param {HTMLElement} element A HTML element. + */ + function removeAllChildNodes(element) { + element.textContent = ''; + } + + /** + * For each item in line, add a row to the table. Each item is actually a list + * of sub-items; each of which will have a corresponding cell created in that + * row, and the sub-item will be displayed in the cell. + * @param {HTMLElement} table A HTML tbody element. + * @param {!Object} list A list of list of item. + */ + function addRows(table, list) { + for (var i = 0; i < list.length; ++i) { + var row = document.createElement('tr'); + + // The first element is always a timestamp. + var cell = document.createElement('td'); + var d = new Date(list[i][0]); + cell.textContent = d; + row.appendChild(cell); + + for (var j = 1; j < list[i].length; ++j) { + var cell = document.createElement('td'); + cell.textContent = list[i][j]; + row.appendChild(cell); + } + table.appendChild(row); + } + } + + /** + * Refresh all displayed information. + */ + function refreshAll() { + chrome.send('getGcmInternalsInfo', [false]); + } + + /** + * Toggle the isRecording variable and send it to browser. + */ + function setRecording() { + isRecording = !isRecording; + chrome.send('setGcmInternalsRecording', [isRecording]); + } + + /** + * Clear all the activity logs. + */ + function clearLogs() { + chrome.send('getGcmInternalsInfo', [true]); } function initialize() { - chrome.send('getGcmInternalsInfo'); + $('recording').disabled = true; + $('refresh').onclick = refreshAll; + $('recording').onclick = setRecording; + $('clear-logs').onclick = clearLogs; + chrome.send('getGcmInternalsInfo', [false]); } /** @@ -43,9 +110,20 @@ cr.define('gcmInternals', function() { * @param {!Object} infos A dictionary of info items to be displayed. */ function setGcmInternalsInfo(infos) { + isRecording = infos.isRecording; + if (isRecording) + $('recording').textContent = 'Stop Recording'; + else + $('recording').textContent = 'Start Recording'; + $('recording').disabled = false; if (infos.deviceInfo !== undefined) { displayDeviceInfo(infos.deviceInfo); } + + removeAllChildNodes($('send-info')); + if (infos.sendInfo !== undefined) { + addRows($('send-info'), infos.sendInfo); + } } // Return an object with all of the exports. diff --git a/chrome/browser/services/gcm/gcm_client_mock.cc b/chrome/browser/services/gcm/gcm_client_mock.cc index 3b58cda..d924238 100644 --- a/chrome/browser/services/gcm/gcm_client_mock.cc +++ b/chrome/browser/services/gcm/gcm_client_mock.cc @@ -97,6 +97,12 @@ void GCMClientMock::Send(const std::string& app_id, message)); } +void GCMClientMock::SetRecording(bool recording) { +} + +void GCMClientMock::ClearActivityLogs() { +} + GCMClient::GCMStatistics GCMClientMock::GetStatistics() const { return GCMClient::GCMStatistics(); } diff --git a/chrome/browser/services/gcm/gcm_client_mock.h b/chrome/browser/services/gcm/gcm_client_mock.h index 2cdc104..ce080ce 100644 --- a/chrome/browser/services/gcm/gcm_client_mock.h +++ b/chrome/browser/services/gcm/gcm_client_mock.h @@ -48,6 +48,8 @@ class GCMClientMock : public GCMClient { virtual void Send(const std::string& app_id, const std::string& receiver_id, const OutgoingMessage& message) OVERRIDE; + virtual void SetRecording(bool recording) OVERRIDE; + virtual void ClearActivityLogs() OVERRIDE; virtual GCMStatistics GetStatistics() const OVERRIDE; // Initiate the loading that has been delayed. diff --git a/chrome/browser/services/gcm/gcm_profile_service.cc b/chrome/browser/services/gcm/gcm_profile_service.cc index cf7e1b5..f93b427 100644 --- a/chrome/browser/services/gcm/gcm_profile_service.cc +++ b/chrome/browser/services/gcm/gcm_profile_service.cc @@ -176,7 +176,8 @@ class GCMProfileService::IOWorker void Send(const std::string& app_id, const std::string& receiver_id, const GCMClient::OutgoingMessage& message); - void RequestGCMStatistics(); + void GetGCMStatistics(bool clear_logs); + void SetGCMRecording(bool recording); // For testing purpose. Can be called from UI thread. Use with care. GCMClient* gcm_client_for_testing() const { return gcm_client_.get(); } @@ -372,19 +373,38 @@ void GCMProfileService::IOWorker::Send( gcm_client_->Send(app_id, receiver_id, message); } -void GCMProfileService::IOWorker::RequestGCMStatistics() { +void GCMProfileService::IOWorker::GetGCMStatistics(bool clear_logs) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); gcm::GCMClient::GCMStatistics stats; if (gcm_client_.get()) { - stats.gcm_client_created = true; + if (clear_logs) + gcm_client_->ClearActivityLogs(); stats = gcm_client_->GetStatistics(); } content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, - base::Bind(&GCMProfileService::RequestGCMStatisticsFinished, + base::Bind(&GCMProfileService::GetGCMStatisticsFinished, + service_, + stats)); +} + +void GCMProfileService::IOWorker::SetGCMRecording(bool recording) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); + gcm::GCMClient::GCMStatistics stats; + + if (gcm_client_.get()) { + gcm_client_->SetRecording(recording); + stats = gcm_client_->GetStatistics(); + stats.gcm_client_created = true; + } + + content::BrowserThread::PostTask( + content::BrowserThread::UI, + FROM_HERE, + base::Bind(&GCMProfileService::GetGCMStatisticsFinished, service_, stats)); } @@ -688,8 +708,8 @@ bool GCMProfileService::IsGCMClientReady() const { return gcm_client_ready_; } -void GCMProfileService::RequestGCMStatistics( - RequestGCMStatisticsCallback callback) { +void GCMProfileService::GetGCMStatistics( + GetGCMStatisticsCallback callback, bool clear_logs) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); DCHECK(!callback.is_null()); @@ -697,8 +717,22 @@ void GCMProfileService::RequestGCMStatistics( content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, - base::Bind(&GCMProfileService::IOWorker::RequestGCMStatistics, - io_worker_)); + base::Bind(&GCMProfileService::IOWorker::GetGCMStatistics, + io_worker_, + clear_logs)); +} + +void GCMProfileService::SetGCMRecording( + GetGCMStatisticsCallback callback, bool recording) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + + request_gcm_statistics_callback_ = callback; + content::BrowserThread::PostTask( + content::BrowserThread::IO, + FROM_HERE, + base::Bind(&GCMProfileService::IOWorker::SetGCMRecording, + io_worker_, + recording)); } void GCMProfileService::Observe(int type, @@ -904,10 +938,9 @@ GCMAppHandler* GCMProfileService::GetAppHandler(const std::string& app_id) { return iter == app_handlers_.end() ? &default_app_handler_ : iter->second; } -void GCMProfileService::RequestGCMStatisticsFinished( +void GCMProfileService::GetGCMStatisticsFinished( GCMClient::GCMStatistics stats) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); - request_gcm_statistics_callback_.Run(stats); } diff --git a/chrome/browser/services/gcm/gcm_profile_service.h b/chrome/browser/services/gcm/gcm_profile_service.h index 3817b0e..b93bfa2 100644 --- a/chrome/browser/services/gcm/gcm_profile_service.h +++ b/chrome/browser/services/gcm/gcm_profile_service.h @@ -52,7 +52,7 @@ class GCMProfileService : public KeyedService, GCMClient::Result result)> SendCallback; typedef base::Callback<void(GCMClient::Result result)> UnregisterCallback; typedef base::Callback<void(const GCMClient::GCMStatistics& stats)> - RequestGCMStatisticsCallback; + GetGCMStatisticsCallback; // Any change made to this enum should have corresponding change in the // GetGCMEnabledStateString(...) function. @@ -129,9 +129,14 @@ class GCMProfileService : public KeyedService, // Returns true if the gcm client is ready. bool IsGCMClientReady() const; - // Get GCM client internal states and statistics. If it has not been created - // then stats won't be modified. - void RequestGCMStatistics(RequestGCMStatisticsCallback callback); + // Get GCM client internal states and statistics. + // If clear_logs is true then activity logs will be cleared before the stats + // are returned. + void GetGCMStatistics(GetGCMStatisticsCallback callback, + bool clear_logs); + + // Enables/disables GCM activity recording, and then returns the stats. + void SetGCMRecording(GetGCMStatisticsCallback callback, bool recording); private: friend class GCMProfileServiceTestConsumer; @@ -199,7 +204,7 @@ class GCMProfileService : public KeyedService, // Returns the handler for the given app. GCMAppHandler* GetAppHandler(const std::string& app_id); - void RequestGCMStatisticsFinished(GCMClient::GCMStatistics stats); + void GetGCMStatisticsFinished(GCMClient::GCMStatistics stats); // The profile which owns this object. Profile* profile_; @@ -233,8 +238,8 @@ class GCMProfileService : public KeyedService, // Callback map (from <app_id, message_id> to callback) for Send. std::map<std::pair<std::string, std::string>, SendCallback> send_callbacks_; - // Callback for RequestGCMStatistics. - RequestGCMStatisticsCallback request_gcm_statistics_callback_; + // Callback for GetGCMStatistics. + GetGCMStatisticsCallback request_gcm_statistics_callback_; // Used to pass a weak pointer to the IO worker. base::WeakPtrFactory<GCMProfileService> weak_ptr_factory_; diff --git a/chrome/browser/ui/webui/gcm_internals_ui.cc b/chrome/browser/ui/webui/gcm_internals_ui.cc index be9fe84..2130cd8 100644 --- a/chrome/browser/ui/webui/gcm_internals_ui.cc +++ b/chrome/browser/ui/webui/gcm_internals_ui.cc @@ -4,10 +4,13 @@ #include "chrome/browser/ui/webui/gcm_internals_ui.h" +#include <vector> + #include "base/bind.h" #include "base/bind_helpers.h" #include "base/format_macros.h" #include "base/memory/weak_ptr.h" +#include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/values.h" #include "chrome/browser/profiles/profile.h" @@ -23,6 +26,24 @@ namespace { +void SetSendingInfo( + const std::vector<gcm::GCMStatsRecorder::SendingActivity>& sends, + base::ListValue* send_info) { + std::vector<gcm::GCMStatsRecorder::SendingActivity>::const_iterator it = + sends.begin(); + for (; it < sends.end(); ++it) { + base::ListValue* row = new base::ListValue(); + send_info->Append(row); + + row->AppendDouble(it->time.ToJsTime()); + row->AppendString(it->app_id); + row->AppendString(it->receiver_id); + row->AppendString(it->message_id); + row->AppendString(it->event); + row->AppendString(it->details); + } +} + // Class acting as a controller of the chrome://gcm-internals WebUI. class GcmInternalsUIMessageHandler : public content::WebUIMessageHandler { public: @@ -42,6 +63,9 @@ class GcmInternalsUIMessageHandler : public content::WebUIMessageHandler { // Request all of the GCM related infos through gcm profile service. void RequestAllInfo(const base::ListValue* args); + // Enables/disables GCM activity recording through gcm profile service. + void SetRecording(const base::ListValue* args); + // Callback function of the request for all gcm related infos. void RequestGCMStatisticsFinished( const gcm::GCMClient::GCMStatistics& args) const; @@ -76,16 +100,27 @@ void GcmInternalsUIMessageHandler::ReturnResults( profile_service->IsGCMClientReady()); } if (stats) { + results.SetBoolean("isRecording", stats->is_recording); device_info->SetBoolean("gcmClientCreated", stats->gcm_client_created); device_info->SetString("gcmClientState", stats->gcm_client_state); device_info->SetBoolean("connectionClientCreated", stats->connection_client_created); + device_info->SetString("registeredAppIds", + JoinString(stats->registered_app_ids, ",")); if (stats->connection_client_created) device_info->SetString("connectionState", stats->connection_state); if (stats->android_id > 0) { device_info->SetString("androidId", base::StringPrintf("0x%" PRIx64, stats->android_id)); } + device_info->SetInteger("sendQueueSize", stats->send_queue_size); + device_info->SetInteger("resendQueueSize", stats->resend_queue_size); + + if (stats->sending_activities.size() > 0) { + base::ListValue* send_info = new base::ListValue(); + results.Set("sendInfo", send_info); + SetSendingInfo(stats->sending_activities, send_info); + } } web_ui()->CallJavascriptFunction("gcmInternals.setGcmInternalsInfo", results); @@ -93,17 +128,61 @@ void GcmInternalsUIMessageHandler::ReturnResults( void GcmInternalsUIMessageHandler::RequestAllInfo( const base::ListValue* args) { + if (args->GetSize() != 1) { + NOTREACHED(); + return; + } + bool clear_logs = false; + if (!args->GetBoolean(0, &clear_logs)) { + NOTREACHED(); + return; + } + Profile* profile = Profile::FromWebUI(web_ui()); gcm::GCMProfileService* profile_service = gcm::GCMProfileServiceFactory::GetForProfile(profile); if (!profile_service) { ReturnResults(profile, NULL, NULL); + } else if (profile_service->SignedInUserName().empty()) { + ReturnResults(profile, profile_service, NULL); } else { - profile_service->RequestGCMStatistics(base::Bind( - &GcmInternalsUIMessageHandler::RequestGCMStatisticsFinished, - weak_ptr_factory_.GetWeakPtr())); + profile_service->GetGCMStatistics( + base::Bind(&GcmInternalsUIMessageHandler::RequestGCMStatisticsFinished, + weak_ptr_factory_.GetWeakPtr()), + clear_logs); + } +} + +void GcmInternalsUIMessageHandler::SetRecording(const base::ListValue* args) { + if (args->GetSize() != 1) { + NOTREACHED(); + return; + } + bool recording = false; + if (!args->GetBoolean(0, &recording)) { + NOTREACHED(); + return; + } + + Profile* profile = Profile::FromWebUI(web_ui()); + gcm::GCMProfileService* profile_service = + gcm::GCMProfileServiceFactory::GetForProfile(profile); + + if (!profile_service) { + ReturnResults(profile, NULL, NULL); + return; + } + if (profile_service->SignedInUserName().empty()) { + ReturnResults(profile, profile_service, NULL); + return; } + // Get fresh stats after changing recording setting. + profile_service->SetGCMRecording( + base::Bind( + &GcmInternalsUIMessageHandler::RequestGCMStatisticsFinished, + weak_ptr_factory_.GetWeakPtr()), + recording); } void GcmInternalsUIMessageHandler::RequestGCMStatisticsFinished( @@ -120,7 +199,11 @@ void GcmInternalsUIMessageHandler::RegisterMessages() { web_ui()->RegisterMessageCallback( "getGcmInternalsInfo", base::Bind(&GcmInternalsUIMessageHandler::RequestAllInfo, - base::Unretained(this))); + weak_ptr_factory_.GetWeakPtr())); + web_ui()->RegisterMessageCallback( + "setGcmInternalsRecording", + base::Bind(&GcmInternalsUIMessageHandler::SetRecording, + weak_ptr_factory_.GetWeakPtr())); } } // namespace diff --git a/google_apis/gcm/engine/checkin_request.h b/google_apis/gcm/engine/checkin_request.h index 1d706a7..5ae8dd3 100644 --- a/google_apis/gcm/engine/checkin_request.h +++ b/google_apis/gcm/engine/checkin_request.h @@ -12,6 +12,7 @@ #include "base/callback.h" #include "base/memory/weak_ptr.h" #include "google_apis/gcm/base/gcm_export.h" +#include "google_apis/gcm/protocol/android_checkin.pb.h" #include "google_apis/gcm/protocol/checkin.pb.h" #include "net/base/backoff_entry.h" #include "net/url_request/url_fetcher_delegate.h" diff --git a/google_apis/gcm/engine/gcm_store.h b/google_apis/gcm/engine/gcm_store.h index 1b15e56b..8b9891d0 100644 --- a/google_apis/gcm/engine/gcm_store.h +++ b/google_apis/gcm/engine/gcm_store.h @@ -9,6 +9,8 @@ #include <string> #include <vector> +#include <google/protobuf/message_lite.h> + #include "base/basictypes.h" #include "base/callback_forward.h" #include "base/memory/linked_ptr.h" @@ -17,13 +19,6 @@ #include "base/time/time.h" #include "google_apis/gcm/base/gcm_export.h" #include "google_apis/gcm/engine/registration_info.h" -#include "google_apis/gcm/protocol/mcs.pb.h" - -namespace google { -namespace protobuf { -class MessageLite; -} // namespace protobuf -} // namespace google namespace gcm { diff --git a/google_apis/gcm/engine/mcs_client.cc b/google_apis/gcm/engine/mcs_client.cc index da8300c7..a99a333 100644 --- a/google_apis/gcm/engine/mcs_client.cc +++ b/google_apis/gcm/engine/mcs_client.cc @@ -15,6 +15,7 @@ #include "google_apis/gcm/base/mcs_util.h" #include "google_apis/gcm/base/socket_stream.h" #include "google_apis/gcm/engine/connection_factory.h" +#include "google_apis/gcm/monitoring/gcm_stats_recorder.h" using namespace google::protobuf::io; @@ -128,6 +129,14 @@ ReliablePacketInfo::ReliablePacketInfo() } ReliablePacketInfo::~ReliablePacketInfo() {} +int MCSClient::GetSendQueueSize() const { + return to_send_.size(); +} + +int MCSClient::GetResendQueueSize() const { + return to_resend_.size(); +} + std::string MCSClient::GetStateString() const { switch(state_) { case UNINITIALIZED: @@ -147,7 +156,8 @@ std::string MCSClient::GetStateString() const { MCSClient::MCSClient(const std::string& version_string, base::Clock* clock, ConnectionFactory* connection_factory, - GCMStore* gcm_store) + GCMStore* gcm_store, + GCMStatsRecorder* recorder) : version_string_(version_string), clock_(clock), state_(UNINITIALIZED), @@ -160,6 +170,7 @@ MCSClient::MCSClient(const std::string& version_string, stream_id_out_(0), stream_id_in_(0), gcm_store_(gcm_store), + recorder_(recorder), weak_ptr_factory_(this) { } @@ -495,6 +506,11 @@ void MCSClient::SendPacketToWire(ReliablePacketInfo* packet_info) { base::Time::kMicrosecondsPerSecond) - sent; DVLOG(1) << "Message was queued for " << queued << " seconds."; data_message->set_queued(queued); + recorder_->RecordDataSentToWire( + data_message->category(), + data_message->to(), + data_message->id(), + queued); } // Set the proper last received stream id to acknowledge received server @@ -862,6 +878,13 @@ void MCSClient::NotifyMessageSendStatus( const mcs_proto::DataMessageStanza* data_message_stanza = reinterpret_cast<const mcs_proto::DataMessageStanza*>(&protobuf); + recorder_->RecordNotifySendStatus( + data_message_stanza->category(), + data_message_stanza->to(), + data_message_stanza->id(), + status, + protobuf.ByteSize(), + data_message_stanza->ttl()); message_sent_callback_.Run( data_message_stanza->device_user_id(), data_message_stanza->category(), diff --git a/google_apis/gcm/engine/mcs_client.h b/google_apis/gcm/engine/mcs_client.h index 1943be6..cc915e4 100644 --- a/google_apis/gcm/engine/mcs_client.h +++ b/google_apis/gcm/engine/mcs_client.h @@ -37,6 +37,7 @@ namespace gcm { class CollapseKey; class ConnectionFactory; +class GCMStatsRecorder; struct ReliablePacketInfo; // An MCS client. This client is in charge of all communications with an @@ -54,6 +55,8 @@ class GCM_EXPORT MCSClient { CONNECTED, // Connected and running. }; + // Any change made to this enum should have corresponding change in the + // GetMessageSendStatusString(...) function in mcs_client.cc. enum MessageSendStatus { // Message was queued succcessfully. QUEUED, @@ -61,14 +64,19 @@ class GCM_EXPORT MCSClient { SENT, // Message not saved, because total queue size limit reached. QUEUE_SIZE_LIMIT_REACHED, - // Messgae not saved, because app queue size limit reached. + // Message not saved, because app queue size limit reached. APP_QUEUE_SIZE_LIMIT_REACHED, // Message too large to send. MESSAGE_TOO_LARGE, // Message not send becuase of TTL = 0 and no working connection. NO_CONNECTION_ON_ZERO_TTL, // Message exceeded TTL. - TTL_EXCEEDED + TTL_EXCEEDED, + + // NOTE: always keep this entry at the end. Add new status types only + // immediately above this line. Make sure to update the corresponding + // histogram enum accordingly. + SEND_STATUS_COUNT }; // Callback for MCSClient's error conditions. @@ -89,7 +97,8 @@ class GCM_EXPORT MCSClient { MCSClient(const std::string& version_string, base::Clock* clock, ConnectionFactory* connection_factory, - GCMStore* gcm_store); + GCMStore* gcm_store, + GCMStatsRecorder* recorder); virtual ~MCSClient(); // Initialize the client. Will load any previous id/token information as well @@ -127,6 +136,12 @@ class GCM_EXPORT MCSClient { // Returns the current state of the client. State state() const { return state_; } + // Returns the size of the send message queue. + int GetSendQueueSize() const; + + // Returns the size of the resend messaage queue. + int GetResendQueueSize() const; + // Returns text representation of the state enum. std::string GetStateString() const; @@ -259,6 +274,9 @@ class GCM_EXPORT MCSClient { // Manager to handle triggering/detecting heartbeats. HeartbeatManager heartbeat_manager_; + // Recorder that records GCM activities for debugging purpose. Not owned. + GCMStatsRecorder* recorder_; + base::WeakPtrFactory<MCSClient> weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(MCSClient); diff --git a/google_apis/gcm/engine/mcs_client_unittest.cc b/google_apis/gcm/engine/mcs_client_unittest.cc index b3e2f0d..a062136 100644 --- a/google_apis/gcm/engine/mcs_client_unittest.cc +++ b/google_apis/gcm/engine/mcs_client_unittest.cc @@ -15,6 +15,7 @@ #include "google_apis/gcm/engine/fake_connection_factory.h" #include "google_apis/gcm/engine/fake_connection_handler.h" #include "google_apis/gcm/engine/gcm_store_impl.h" +#include "google_apis/gcm/monitoring/gcm_stats_recorder.h" #include "testing/gtest/include/gtest/gtest.h" namespace gcm { @@ -67,8 +68,9 @@ class TestMCSClient : public MCSClient { public: TestMCSClient(base::Clock* clock, ConnectionFactory* connection_factory, - GCMStore* gcm_store) - : MCSClient("", clock, connection_factory, gcm_store), + GCMStore* gcm_store, + gcm::GCMStatsRecorder* recorder) + : MCSClient("", clock, connection_factory, gcm_store, recorder), next_id_(0) { } @@ -136,6 +138,8 @@ class MCSClientTest : public testing::Test { scoped_ptr<MCSMessage> received_message_; std::string sent_message_id_; MCSClient::MessageSendStatus message_send_status_; + + gcm::GCMStatsRecorder recorder_; }; MCSClientTest::MCSClientTest() @@ -166,7 +170,8 @@ void MCSClientTest::BuildMCSClient() { message_loop_.message_loop_proxy())); mcs_client_.reset(new TestMCSClient(&clock_, &connection_factory_, - gcm_store_.get())); + gcm_store_.get(), + &recorder_)); } void MCSClientTest::InitializeClient() { diff --git a/google_apis/gcm/gcm.gyp b/google_apis/gcm/gcm.gyp index 9843776..8f99174 100644 --- a/google_apis/gcm/gcm.gyp +++ b/google_apis/gcm/gcm.gyp @@ -72,6 +72,8 @@ 'gcm_client.h', 'gcm_client_impl.cc', 'gcm_client_impl.h', + 'monitoring/gcm_stats_recorder.cc', + 'monitoring/gcm_stats_recorder.h', 'protocol/android_checkin.proto', 'protocol/checkin.proto', 'protocol/mcs.proto', @@ -138,7 +140,8 @@ 'engine/mcs_client_unittest.cc', 'engine/registration_request_unittest.cc', 'engine/unregistration_request_unittest.cc', - 'gcm_client_impl_unittest.cc' + 'gcm_client_impl_unittest.cc', + 'monitoring/gcm_stats_recorder_unittest.cc' ] }, ], diff --git a/google_apis/gcm/gcm_client.cc b/google_apis/gcm/gcm_client.cc index 3bdd699..75884d6 100644 --- a/google_apis/gcm/gcm_client.cc +++ b/google_apis/gcm/gcm_client.cc @@ -24,7 +24,12 @@ GCMClient::SendErrorDetails::SendErrorDetails() : result(UNKNOWN_ERROR) {} GCMClient::SendErrorDetails::~SendErrorDetails() {} GCMClient::GCMStatistics::GCMStatistics() - : gcm_client_created(false), connection_client_created(false) { + : is_recording(false), + gcm_client_created(false), + connection_client_created(false), + android_id(0), + send_queue_size(0), + resend_queue_size(0) { } GCMClient::GCMStatistics::~GCMStatistics() { diff --git a/google_apis/gcm/gcm_client.h b/google_apis/gcm/gcm_client.h index 16ba53b..5996c06 100644 --- a/google_apis/gcm/gcm_client.h +++ b/google_apis/gcm/gcm_client.h @@ -11,6 +11,7 @@ #include "base/basictypes.h" #include "google_apis/gcm/base/gcm_export.h" +#include "google_apis/gcm/monitoring/gcm_stats_recorder.h" template <class T> class scoped_refptr; @@ -94,11 +95,17 @@ class GCM_EXPORT GCMClient { GCMStatistics(); ~GCMStatistics(); + bool is_recording; bool gcm_client_created; std::string gcm_client_state; bool connection_client_created; std::string connection_state; uint64 android_id; + std::vector<std::string> registered_app_ids; + int send_queue_size; + int resend_queue_size; + + std::vector<GCMStatsRecorder::SendingActivity> sending_activities; }; // A delegate interface that allows the GCMClient instance to interact with @@ -208,6 +215,12 @@ class GCM_EXPORT GCMClient { const std::string& receiver_id, const OutgoingMessage& message) = 0; + // Enables or disables internal activity recording. + virtual void SetRecording(bool recording) = 0; + + // Clear all recorded GCM activity logs. + virtual void ClearActivityLogs() = 0; + // Gets internal states and statistics. virtual GCMStatistics GetStatistics() const = 0; }; diff --git a/google_apis/gcm/gcm_client_impl.cc b/google_apis/gcm/gcm_client_impl.cc index 80c58d8..e152d63 100644 --- a/google_apis/gcm/gcm_client_impl.cc +++ b/google_apis/gcm/gcm_client_impl.cc @@ -12,6 +12,7 @@ #include "base/metrics/histogram.h" #include "base/sequenced_task_runner.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" #include "base/time/default_clock.h" #include "google_apis/gcm/base/mcs_message.h" #include "google_apis/gcm/base/mcs_util.h" @@ -19,6 +20,7 @@ #include "google_apis/gcm/engine/connection_factory_impl.h" #include "google_apis/gcm/engine/gcm_store_impl.h" #include "google_apis/gcm/engine/mcs_client.h" +#include "google_apis/gcm/monitoring/gcm_stats_recorder.h" #include "google_apis/gcm/protocol/mcs.pb.h" #include "net/http/http_network_session.h" #include "net/url_request/url_request_context.h" @@ -130,12 +132,14 @@ scoped_ptr<MCSClient> GCMInternalsBuilder::BuildMCSClient( const std::string& version, base::Clock* clock, ConnectionFactory* connection_factory, - GCMStore* gcm_store) { + GCMStore* gcm_store, + GCMStatsRecorder* recorder) { return make_scoped_ptr<MCSClient>( new MCSClient(version, clock, connection_factory, - gcm_store)); + gcm_store, + recorder)); } scoped_ptr<ConnectionFactory> GCMInternalsBuilder::BuildConnectionFactory( @@ -241,7 +245,8 @@ void GCMClientImpl::InitializeMCSClient( chrome_build_proto_.chrome_version(), clock_.get(), connection_factory_.get(), - gcm_store_.get()).Pass(); + gcm_store_.get(), + &recorder_).Pass(); mcs_client_->Initialize( base::Bind(&GCMClientImpl::OnMCSError, weak_ptr_factory_.GetWeakPtr()), @@ -557,16 +562,33 @@ std::string GCMClientImpl::GetStateString() const { } } +void GCMClientImpl::SetRecording(bool recording) { + recorder_.SetRecording(recording); +} + +void GCMClientImpl::ClearActivityLogs() { + recorder_.Clear(); +} + GCMClient::GCMStatistics GCMClientImpl::GetStatistics() const { GCMClient::GCMStatistics stats; - stats.gcm_client_state = GCMClientImpl::GetStateString(); + stats.gcm_client_created = true; + stats.is_recording = recorder_.is_recording(); + stats.gcm_client_state = GetStateString(); stats.connection_client_created = mcs_client_.get() != NULL; if (mcs_client_.get()) { stats.connection_state = mcs_client_->GetStateString(); - // TODO(juyik): add more statistics such as message metadata list, etc. + stats.send_queue_size = mcs_client_->GetSendQueueSize(); + stats.resend_queue_size = mcs_client_->GetResendQueueSize(); } if (device_checkin_info_.android_id > 0) stats.android_id = device_checkin_info_.android_id; + recorder_.CollectSendingActivities(&stats.sending_activities); + + for (RegistrationInfoMap::const_iterator it = registrations_.begin(); + it != registrations_.end(); ++it) { + stats.registered_app_ids.push_back(it->first); + } return stats; } @@ -695,6 +717,10 @@ void GCMClientImpl::HandleIncomingSendError( send_error_details.additional_data.erase(iter); } + recorder_.RecordIncomingSendError( + data_message_stanza.category(), + data_message_stanza.to(), + data_message_stanza.id()); delegate_->OnMessageSendError(data_message_stanza.category(), send_error_details); } diff --git a/google_apis/gcm/gcm_client_impl.h b/google_apis/gcm/gcm_client_impl.h index 7458c70..c4e6068 100644 --- a/google_apis/gcm/gcm_client_impl.h +++ b/google_apis/gcm/gcm_client_impl.h @@ -19,6 +19,8 @@ #include "google_apis/gcm/engine/registration_request.h" #include "google_apis/gcm/engine/unregistration_request.h" #include "google_apis/gcm/gcm_client.h" +#include "google_apis/gcm/monitoring/gcm_stats_recorder.h" +#include "google_apis/gcm/protocol/android_checkin.pb.h" #include "google_apis/gcm/protocol/checkin.pb.h" #include "net/base/net_log.h" #include "net/url_request/url_request_context_getter.h" @@ -29,6 +31,10 @@ namespace base { class Clock; } // namespace base +namespace mcs_proto { +class DataMessageStanza; +} // namespace mcs_proto + namespace net { class HttpNetworkSession; } // namespace net @@ -51,7 +57,8 @@ class GCM_EXPORT GCMInternalsBuilder { const std::string& version, base::Clock* clock, ConnectionFactory* connection_factory, - GCMStore* gcm_store); + GCMStore* gcm_store, + GCMStatsRecorder* recorder); virtual scoped_ptr<ConnectionFactory> BuildConnectionFactory( const std::vector<GURL>& endpoints, const net::BackoffEntry::Policy& backoff_policy, @@ -86,6 +93,8 @@ class GCM_EXPORT GCMClientImpl : public GCMClient { virtual void Send(const std::string& app_id, const std::string& receiver_id, const OutgoingMessage& message) OVERRIDE; + virtual void SetRecording(bool recording) OVERRIDE; + virtual void ClearActivityLogs() OVERRIDE; virtual GCMStatistics GetStatistics() const OVERRIDE; private: @@ -210,6 +219,9 @@ class GCM_EXPORT GCMClientImpl : public GCMClient { // Builder for the GCM internals (mcs client, etc.). scoped_ptr<GCMInternalsBuilder> internals_builder_; + // Recorder that logs GCM activities. + GCMStatsRecorder recorder_; + // State of the GCM Client Implementation. State state_; diff --git a/google_apis/gcm/gcm_client_impl_unittest.cc b/google_apis/gcm/gcm_client_impl_unittest.cc index 709418c..7f6a937 100644 --- a/google_apis/gcm/gcm_client_impl_unittest.cc +++ b/google_apis/gcm/gcm_client_impl_unittest.cc @@ -14,6 +14,7 @@ #include "google_apis/gcm/base/mcs_util.h" #include "google_apis/gcm/engine/fake_connection_factory.h" #include "google_apis/gcm/engine/fake_connection_handler.h" +#include "google_apis/gcm/monitoring/gcm_stats_recorder.h" #include "google_apis/gcm/protocol/android_checkin.pb.h" #include "google_apis/gcm/protocol/checkin.pb.h" #include "google_apis/gcm/protocol/mcs.pb.h" @@ -67,7 +68,8 @@ class FakeMCSClient : public MCSClient { public: FakeMCSClient(base::Clock* clock, ConnectionFactory* connection_factory, - GCMStore* gcm_store); + GCMStore* gcm_store, + GCMStatsRecorder* recorder); virtual ~FakeMCSClient(); virtual void Login(uint64 android_id, uint64 security_token) OVERRIDE; virtual void SendMessage(const MCSMessage& message) OVERRIDE; @@ -88,8 +90,9 @@ class FakeMCSClient : public MCSClient { FakeMCSClient::FakeMCSClient(base::Clock* clock, ConnectionFactory* connection_factory, - GCMStore* gcm_store) - : MCSClient("", clock, connection_factory, gcm_store), + GCMStore* gcm_store, + GCMStatsRecorder* recorder) + : MCSClient("", clock, connection_factory, gcm_store, recorder), last_android_id_(0u), last_security_token_(0u), last_message_tag_(kNumProtoTypes) { @@ -122,7 +125,8 @@ class FakeGCMInternalsBuilder : public GCMInternalsBuilder { const std::string& version, base::Clock* clock, ConnectionFactory* connection_factory, - GCMStore* gcm_store) OVERRIDE; + GCMStore* gcm_store, + GCMStatsRecorder* recorder) OVERRIDE; virtual scoped_ptr<ConnectionFactory> BuildConnectionFactory( const std::vector<GURL>& endpoints, const net::BackoffEntry::Policy& backoff_policy, @@ -142,10 +146,12 @@ scoped_ptr<MCSClient> FakeGCMInternalsBuilder::BuildMCSClient( const std::string& version, base::Clock* clock, ConnectionFactory* connection_factory, - GCMStore* gcm_store) { + GCMStore* gcm_store, + GCMStatsRecorder* recorder) { return make_scoped_ptr<MCSClient>(new FakeMCSClient(clock, connection_factory, - gcm_store)); + gcm_store, + recorder)); } scoped_ptr<ConnectionFactory> FakeGCMInternalsBuilder::BuildConnectionFactory( diff --git a/google_apis/gcm/monitoring/gcm_stats_recorder.cc b/google_apis/gcm/monitoring/gcm_stats_recorder.cc new file mode 100644 index 0000000..6e0cb48 --- /dev/null +++ b/google_apis/gcm/monitoring/gcm_stats_recorder.cc @@ -0,0 +1,150 @@ +// Copyright 2014 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 "google_apis/gcm/monitoring/gcm_stats_recorder.h" + +#include <deque> +#include <vector> + +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" + +namespace gcm { + +const uint32 MAX_LOGGED_ACTIVITY_COUNT = 100; + +namespace { + +// Insert an itme to the front of deque while maintaining the size of the deque. +// Overflow item is discarded. +template <typename T> +T* InsertCircularBuffer(std::deque<T>* q, const T& item) { + DCHECK(q); + q->push_front(item); + if (q->size() > MAX_LOGGED_ACTIVITY_COUNT) { + q->pop_back(); + } + return &q->front(); +} + +// Helper for getting string representation of the MessageSendStatus enum. +std::string GetMessageSendStatusString( + gcm::MCSClient::MessageSendStatus status) { + switch (status) { + case gcm::MCSClient::QUEUED: + return "QUEUED"; + case gcm::MCSClient::SENT: + return "SENT"; + case gcm::MCSClient::QUEUE_SIZE_LIMIT_REACHED: + return "QUEUE_SIZE_LIMIT_REACHED"; + case gcm::MCSClient::APP_QUEUE_SIZE_LIMIT_REACHED: + return "APP_QUEUE_SIZE_LIMIT_REACHED"; + case gcm::MCSClient::MESSAGE_TOO_LARGE: + return "MESSAGE_TOO_LARGE"; + case gcm::MCSClient::NO_CONNECTION_ON_ZERO_TTL: + return "NO_CONNECTION_ON_ZERO_TTL"; + case gcm::MCSClient::TTL_EXCEEDED: + return "TTL_EXCEEDED"; + default: + NOTREACHED(); + return "UNKNOWN"; + } +} + +} // namespace + +GCMStatsRecorder::Activity::Activity() + : time(base::Time::Now()) { +} + +GCMStatsRecorder::Activity::~Activity() { +} + +GCMStatsRecorder::SendingActivity::SendingActivity() { +} + +GCMStatsRecorder::SendingActivity::~SendingActivity() { +} + +GCMStatsRecorder::GCMStatsRecorder() : is_recording_(false) { +} + +GCMStatsRecorder::~GCMStatsRecorder() { +} + +void GCMStatsRecorder::SetRecording(bool recording) { + is_recording_ = recording; +} + +void GCMStatsRecorder::Clear() { + sending_activities_.clear(); +} + +void GCMStatsRecorder::CollectSendingActivities( + std::vector<SendingActivity>* activities) const { + activities->insert(activities->begin(), + sending_activities_.begin(), + sending_activities_.end()); +} + +void GCMStatsRecorder::RecordSending(const std::string& app_id, + const std::string& receiver_id, + const std::string& message_id, + const std::string& event, + const std::string& details) { + SendingActivity data; + SendingActivity* inserted_data = InsertCircularBuffer( + &sending_activities_, data); + inserted_data->app_id = app_id; + inserted_data->receiver_id = receiver_id; + inserted_data->message_id = message_id; + inserted_data->event = event; + inserted_data->details = details; +} + +void GCMStatsRecorder::RecordDataSentToWire( + const std::string& app_id, + const std::string& receiver_id, + const std::string& message_id, + int queued) { + if (is_recording_) { + RecordSending(app_id, receiver_id, message_id, "Data msg sent to wire", + base::StringPrintf("Msg queued for %d seconds", queued)); + } +} + +void GCMStatsRecorder::RecordNotifySendStatus( + const std::string& app_id, + const std::string& receiver_id, + const std::string& message_id, + gcm::MCSClient::MessageSendStatus status, + int byte_size, + int ttl) { + UMA_HISTOGRAM_ENUMERATION("GCM.SendMessageStatus", status, + gcm::MCSClient::SEND_STATUS_COUNT); + if (is_recording_) { + RecordSending( + app_id, + receiver_id, + message_id, + base::StringPrintf("SEND status: %s", + GetMessageSendStatusString(status).c_str()), + base::StringPrintf("Msg size: %d bytes, TTL: %d", byte_size, ttl)); + } +} + +void GCMStatsRecorder::RecordIncomingSendError( + const std::string& app_id, + const std::string& receiver_id, + const std::string& message_id) { + UMA_HISTOGRAM_COUNTS("GCM.IncomingSendErrors", 1); + if (is_recording_) { + RecordSending(app_id, receiver_id, message_id, "Received 'send error' msg", + std::string()); + } +} + +} // namespace gcm diff --git a/google_apis/gcm/monitoring/gcm_stats_recorder.h b/google_apis/gcm/monitoring/gcm_stats_recorder.h new file mode 100644 index 0000000..71b6c6a --- /dev/null +++ b/google_apis/gcm/monitoring/gcm_stats_recorder.h @@ -0,0 +1,101 @@ +// Copyright 2014 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. + +#ifndef GOOGLE_APIS_GCM_GCM_STATS_RECORDER_H_ +#define GOOGLE_APIS_GCM_GCM_STATS_RECORDER_H_ + +#include <deque> +#include <string> +#include <vector> + +#include "base/time/time.h" +#include "google_apis/gcm/base/gcm_export.h" +#include "google_apis/gcm/engine/mcs_client.h" + +namespace gcm { + +// Records GCM internal stats and activities for debugging purpose. Recording +// can be turned on/off by calling SetRecording(...) function. It is turned off +// by default. +// This class is not thread safe. It is meant to be owned by a gcm client +// instance. +class GCM_EXPORT GCMStatsRecorder { + public: + // Contains data that are common to all activity kinds below. + struct GCM_EXPORT Activity { + Activity(); + virtual ~Activity(); + + base::Time time; + std::string event; // A short description of the event. + std::string details; // Any additional detail about the event. + }; + + // Contains relevant data of a send-message step. + struct GCM_EXPORT SendingActivity : Activity { + SendingActivity(); + virtual ~SendingActivity(); + + std::string app_id; + std::string receiver_id; + std::string message_id; + }; + + GCMStatsRecorder(); + virtual ~GCMStatsRecorder(); + + // Indicates whether the recorder is currently recording activities or not. + bool is_recording() const { + return is_recording_; + } + + // Turns recording on/off. + void SetRecording(bool recording); + + // Clear all recorded activities. + void Clear(); + + // Records that an outgoing data message was sent over the wire. + void RecordDataSentToWire(const std::string& app_id, + const std::string& receiver_id, + const std::string& message_id, + int queued); + // Records that the MCS client sent a 'send status' notification to callback. + void RecordNotifySendStatus(const std::string& app_id, + const std::string& receiver_id, + const std::string& message_id, + MCSClient::MessageSendStatus status, + int byte_size, + int ttl); + // Records that a 'send error' message was received. + void RecordIncomingSendError(const std::string& app_id, + const std::string& receiver_id, + const std::string& message_id); + + // Records that a sending activity has occurred. It will be inserted to the + // front of a queue ao that entries in the queue had reverse chronological + // order. + void CollectSendingActivities(std::vector<SendingActivity>* activities) const; + + const std::deque<SendingActivity>& sending_activities() const { + return sending_activities_; + } + + protected: + void RecordSending(const std::string& app_id, + const std::string& receiver_id, + const std::string& message_id, + const std::string& event, + const std::string& details); + + bool is_recording_; + + std::deque<SendingActivity> sending_activities_; + + DISALLOW_COPY_AND_ASSIGN(GCMStatsRecorder); +}; + +} // namespace gcm + +#endif // GOOGLE_APIS_GCM_GCM_STATS_RECORDER_H_ diff --git a/google_apis/gcm/monitoring/gcm_stats_recorder_unittest.cc b/google_apis/gcm/monitoring/gcm_stats_recorder_unittest.cc new file mode 100644 index 0000000..488b31b --- /dev/null +++ b/google_apis/gcm/monitoring/gcm_stats_recorder_unittest.cc @@ -0,0 +1,136 @@ +// Copyright 2014 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 "google_apis/gcm/monitoring/gcm_stats_recorder.h" + +#include <deque> +#include <string> + +#include "google_apis/gcm/engine/mcs_client.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace gcm { + +namespace { + +static const char kAppId[] = "app id 1"; +static const char kReceiverId[] = "receiver 1"; +static const char kMessageId[] = "message id 1"; +static const int kQueuedSec = 5; +static const gcm::MCSClient::MessageSendStatus kMessageSendStatus = + gcm::MCSClient::QUEUED; +static const int kByteSize = 99; +static const int kTTL = 7; + +static const char kDataSentToWireEvent[] = "Data msg sent to wire"; +static const char kSentToWireDetails[] = "Msg queued for 5 seconds"; +static const char kNotifySendStatusEvent[] = "SEND status: QUEUED"; +static const char kNotifySendStatusDetails[] = "Msg size: 99 bytes, TTL: 7"; +static const char kIncomingSendErrorEvent[] = "Received 'send error' msg"; +static const char kIncomingSendErrorDetails[] = ""; + +} // namespace + +class GCMStatsRecorderTest : public testing::Test { + public: + GCMStatsRecorderTest(); + virtual ~GCMStatsRecorderTest(); + virtual void SetUp() OVERRIDE; + + void VerifyRecordedSendingCount(int expected_count) { + EXPECT_EQ(expected_count, + static_cast<int>(recorder_.sending_activities().size())); + } + + void VerifyDataSentToWire(const std::string& remark){ + VerifyData(recorder_.sending_activities(), + kDataSentToWireEvent, + kSentToWireDetails, + remark); + } + + void VerifyNotifySendStatus(const std::string& remark){ + VerifyData(recorder_.sending_activities(), + kNotifySendStatusEvent, + kNotifySendStatusDetails, + remark); + } + + void VerifyIncomingSendError(const std::string& remark){ + VerifyData(recorder_.sending_activities(), + kIncomingSendErrorEvent, + kIncomingSendErrorDetails, + remark); + } + + protected: + template <typename T> + void VerifyData(const std::deque<T>& queue, const std::string& event, + const std::string& details, const std::string& remark) { + EXPECT_EQ(kAppId, queue.front().app_id) << remark; + EXPECT_EQ(kReceiverId, queue.front().receiver_id) << remark; + EXPECT_EQ(kMessageId, queue.front().message_id) << remark; + EXPECT_EQ(event, queue.front().event) << remark; + EXPECT_EQ(details, queue.front().details) << remark; + } + + GCMStatsRecorder recorder_; +}; + +GCMStatsRecorderTest::GCMStatsRecorderTest(){ +} + +GCMStatsRecorderTest::~GCMStatsRecorderTest() {} + +void GCMStatsRecorderTest::SetUp(){ + recorder_.SetRecording(true); +} + +TEST_F(GCMStatsRecorderTest, StartStopRecordingTest) { + EXPECT_TRUE(recorder_.is_recording()); + recorder_.RecordDataSentToWire(kAppId, kReceiverId, kMessageId, kQueuedSec); + VerifyRecordedSendingCount(1); + VerifyDataSentToWire("1st call"); + + recorder_.SetRecording(false); + EXPECT_FALSE(recorder_.is_recording()); + recorder_.RecordDataSentToWire(kAppId, kReceiverId, kMessageId, kQueuedSec); + VerifyRecordedSendingCount(1); + VerifyDataSentToWire("2nd call"); +} + +TEST_F(GCMStatsRecorderTest, ClearLogTest) { + recorder_.RecordDataSentToWire(kAppId, kReceiverId, kMessageId, kQueuedSec); + VerifyRecordedSendingCount(1); + VerifyDataSentToWire("1st call"); + + recorder_.RecordNotifySendStatus(kAppId, kReceiverId, kMessageId, + kMessageSendStatus, kByteSize, kTTL); + VerifyRecordedSendingCount(2); + VerifyNotifySendStatus("2nd call"); + + recorder_.Clear(); + VerifyRecordedSendingCount(0); +} + +TEST_F(GCMStatsRecorderTest, RecordSendingTest) { + recorder_.RecordDataSentToWire(kAppId, kReceiverId, kMessageId, kQueuedSec); + VerifyRecordedSendingCount(1); + VerifyDataSentToWire("1st call"); + + recorder_.RecordNotifySendStatus(kAppId, kReceiverId, kMessageId, + kMessageSendStatus, kByteSize, kTTL); + VerifyRecordedSendingCount(2); + VerifyNotifySendStatus("2nd call"); + + recorder_.RecordIncomingSendError(kAppId, kReceiverId, kMessageId); + VerifyRecordedSendingCount(3); + VerifyIncomingSendError("3rd call"); + + recorder_.RecordDataSentToWire(kAppId, kReceiverId, kMessageId, kQueuedSec); + VerifyRecordedSendingCount(4); + VerifyDataSentToWire("4th call"); +} + +} // namespace gcm |