summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjuyik@chromium.org <juyik@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-16 21:41:44 +0000
committerjuyik@chromium.org <juyik@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-16 21:41:44 +0000
commit268d23f13de7d41e2dc42b90205fbd86a66676a0 (patch)
tree445afa9ef278cd1462bdab4eb13dd12c555dbe09
parente888193914dc13c6961a64c810852fd0d6268afb (diff)
downloadchromium_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
-rw-r--r--chrome/browser/resources/gcm_internals.css22
-rw-r--r--chrome/browser/resources/gcm_internals.html82
-rw-r--r--chrome/browser/resources/gcm_internals.js82
-rw-r--r--chrome/browser/services/gcm/gcm_client_mock.cc6
-rw-r--r--chrome/browser/services/gcm/gcm_client_mock.h2
-rw-r--r--chrome/browser/services/gcm/gcm_profile_service.cc53
-rw-r--r--chrome/browser/services/gcm/gcm_profile_service.h19
-rw-r--r--chrome/browser/ui/webui/gcm_internals_ui.cc91
-rw-r--r--google_apis/gcm/engine/checkin_request.h1
-rw-r--r--google_apis/gcm/engine/gcm_store.h9
-rw-r--r--google_apis/gcm/engine/mcs_client.cc25
-rw-r--r--google_apis/gcm/engine/mcs_client.h24
-rw-r--r--google_apis/gcm/engine/mcs_client_unittest.cc11
-rw-r--r--google_apis/gcm/gcm.gyp5
-rw-r--r--google_apis/gcm/gcm_client.cc7
-rw-r--r--google_apis/gcm/gcm_client.h13
-rw-r--r--google_apis/gcm/gcm_client_impl.cc36
-rw-r--r--google_apis/gcm/gcm_client_impl.h14
-rw-r--r--google_apis/gcm/gcm_client_impl_unittest.cc18
-rw-r--r--google_apis/gcm/monitoring/gcm_stats_recorder.cc150
-rw-r--r--google_apis/gcm/monitoring/gcm_stats_recorder.h101
-rw-r--r--google_apis/gcm/monitoring/gcm_stats_recorder_unittest.cc136
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