diff options
author | tim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-27 02:42:17 +0000 |
---|---|---|
committer | tim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-27 02:42:17 +0000 |
commit | 65d0b12bc9aaf8acdfe92a5d57e98d021b591ba4 (patch) | |
tree | b10d305b3bdc27dbc3ee305b1c8f31c15bca040c /chrome/browser/sync/util | |
parent | 71b0c7f62e9c674adb0ffc785ee0e8de05b55468 (diff) | |
download | chromium_src-65d0b12bc9aaf8acdfe92a5d57e98d021b591ba4.zip chromium_src-65d0b12bc9aaf8acdfe92a5d57e98d021b591ba4.tar.gz chromium_src-65d0b12bc9aaf8acdfe92a5d57e98d021b591ba4.tar.bz2 |
Introduce browser_sync::ExtensionsActivityMonitor to collect extensions API usage
for correlation to sync commit requests. Add ChromiumExtensionsActivity to sync.proto
to allow passing this data to sync servers.
BUG=25323
TEST=Added ExtensionsActivityMonitorTest. +Performing mutations on the bookmarks model via an extension should result in
ChromiumExtensionsActivity for each such extension showing up in CommitMessages.
Review URL: http://codereview.chromium.org/325001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@30153 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/sync/util')
3 files changed, 401 insertions, 0 deletions
diff --git a/chrome/browser/sync/util/extensions_activity_monitor.cc b/chrome/browser/sync/util/extensions_activity_monitor.cc new file mode 100644 index 0000000..e65dfb7 --- /dev/null +++ b/chrome/browser/sync/util/extensions_activity_monitor.cc @@ -0,0 +1,93 @@ +// Copyright (c) 2009 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 "chrome/browser/sync/util/extensions_activity_monitor.h" + +#include "base/task.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/extensions/extension_bookmarks_module.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/notification_service.h" + +namespace browser_sync { + +namespace { +// A helper task to register an ExtensionsActivityMonitor as an observer of +// events on the UI thread (even though the monitor may live on another thread). +// This liberates ExtensionsActivityMonitor from having to be ref counted. +class RegistrationTask : public Task { + public: + RegistrationTask(ExtensionsActivityMonitor* monitor, + MessageLoop* ui_loop, + NotificationRegistrar* registrar) + : monitor_(monitor), ui_loop_(ui_loop), registrar_(registrar) {} + virtual ~RegistrationTask() {} + + virtual void Run() { + DCHECK_EQ(MessageLoop::current(), + ChromeThread::GetMessageLoop(ChromeThread::UI)); + + // It would be nice if we could specify a Source for each specific function + // we wanted to observe, but the actual function objects are allocated on + // the fly so there is no reliable object to point to (same problem if we + // wanted to use the string name). Thus, we use all sources and filter in + // Observe. + registrar_->Add(monitor_, NotificationType::EXTENSION_BOOKMARKS_API_INVOKED, + NotificationService::AllSources()); + } + + private: + ExtensionsActivityMonitor* monitor_; + MessageLoop* const ui_loop_; + NotificationRegistrar* registrar_; + DISALLOW_COPY_AND_ASSIGN(RegistrationTask); +}; +} // namespace + +ExtensionsActivityMonitor::ExtensionsActivityMonitor(MessageLoop* ui_loop) + : ui_loop_(ui_loop) { + ui_loop_->PostTask(FROM_HERE, new RegistrationTask(this, ui_loop, + ®istrar_)); +} + +ExtensionsActivityMonitor::~ExtensionsActivityMonitor() { + DCHECK_EQ(MessageLoop::current(), ui_loop_); + // The registrar calls RemoveAll in its dtor (which would happen in a moment) + // but explicitly call this so it is clear why we need to be on the ui_loop_. + registrar_.RemoveAll(); +} + +void ExtensionsActivityMonitor::GetAndClearRecords(Records* buffer) { + AutoLock lock(records_lock_); + buffer->clear(); + buffer->swap(records_); +} + +void ExtensionsActivityMonitor::PutRecords(const Records& records) { + AutoLock lock(records_lock_); + for (Records::const_iterator i = records.begin(); i != records.end(); ++i) { + records_[i->first].extension_id = i->second.extension_id; + records_[i->first].bookmark_write_count += i->second.bookmark_write_count; + } +} + +void ExtensionsActivityMonitor::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + AutoLock lock(records_lock_); + DCHECK_EQ(MessageLoop::current(), ui_loop_); + const Extension* extension = Source<const Extension>(source).ptr(); + const BookmarksFunction* f = Details<const BookmarksFunction>(details).ptr(); + if (f->name() == "bookmarks.update" || + f->name() == "bookmarks.move" || + f->name() == "bookmarks.create" || + f->name() == "bookmarks.removeTree" || + f->name() == "bookmarks.remove") { + Record& record = records_[extension->id()]; + record.extension_id = extension->id(); + record.bookmark_write_count++; + } +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/util/extensions_activity_monitor.h b/chrome/browser/sync/util/extensions_activity_monitor.h new file mode 100644 index 0000000..5e4e4ae --- /dev/null +++ b/chrome/browser/sync/util/extensions_activity_monitor.h @@ -0,0 +1,74 @@ +// Copyright (c) 2009 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 CHROME_BROWSER_SYNC_UTIL_EXTENSIONS_ACTIVITY_MONITOR_H_ +#define CHROME_BROWSER_SYNC_UTIL_EXTENSIONS_ACTIVITY_MONITOR_H_ + +#include "base/lock.h" +#include "base/message_loop.h" +#include "base/ref_counted.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" + +namespace browser_sync { + +// A class to monitor usage of extensions APIs to send to sync servers, with +// the ability to purge data once sync servers have acknowledged it (successful +// commit response). +// +// This can be used from any thread (it is a 'monitor' in the synchronization +// sense as well), HOWEVER +// +// *** IT MUST BE DELETED FROM THE UI LOOP *** +// +// Consider using MessageLoop::DeleteSoon. (Yes, this means if you allocate +// an ExtensionsActivityMonitor on a thread other than UI, you must 'new' it). +class ExtensionsActivityMonitor : public NotificationObserver { + public: + // A data record of activity performed by extension |extension_id|. + struct Record { + Record() : bookmark_write_count(0U) {} + + // The human-readable ID identifying the extension responsible + // for the activity reported in this Record. + std::string extension_id; + + // How many times the extension successfully invoked a write + // operation through the bookmarks API since the last CommitMessage. + uint32 bookmark_write_count; + }; + + typedef std::map<std::string, Record> Records; + + // Creates an ExtensionsActivityMonitor to monitor extensions activities on + // |ui_loop|. + explicit ExtensionsActivityMonitor(MessageLoop* ui_loop); + ~ExtensionsActivityMonitor(); + + // Fills |buffer| with snapshot of current records in constant time by + // swapping. This is done mutually exclusively w.r.t methods of this class. + void GetAndClearRecords(Records* buffer); + + // Add |records| piece-wise (by extension id) to the set of active records. + // This is done mutually exclusively w.r.t the methods of this class. + void PutRecords(const Records& records); + + // NotificationObserver implementation. Called on |ui_loop_|. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + private: + Records records_; + mutable Lock records_lock_; + + // Kept for convenience. + MessageLoop* const ui_loop_; + + // Used only from UI loop. + NotificationRegistrar registrar_; +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_UTIL_EXTENSIONS_ACTIVITY_MONITOR_H_
\ No newline at end of file diff --git a/chrome/browser/sync/util/extensions_activity_monitor_unittest.cc b/chrome/browser/sync/util/extensions_activity_monitor_unittest.cc new file mode 100644 index 0000000..ae917df --- /dev/null +++ b/chrome/browser/sync/util/extensions_activity_monitor_unittest.cc @@ -0,0 +1,234 @@ +// Copyright (c) 2009 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 entry. + +#include "chrome/browser/sync/util/extensions_activity_monitor.h" + +#include "base/file_path.h" +#include "base/string_util.h" +#include "base/waitable_event.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/extensions/extension_bookmarks_module.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/notification_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +using browser_sync::ExtensionsActivityMonitor; +namespace keys = extension_manifest_keys; + +namespace { + +static const char* kTestExtensionPath1 = "c:\\testextension1"; +static const char* kTestExtensionPath2 = "c:\\testextension2"; +static const char* kTestExtensionVersion = "1.0.0.0"; +static const char* kTestExtensionName = "foo extension"; + +template <class FunctionType> +class BookmarkAPIEventTask : public Task { + public: + BookmarkAPIEventTask(FunctionType* t, Extension* e, size_t repeats, + base::WaitableEvent* done) : + function_(t), extension_(e), repeats_(repeats), done_(done) {} + virtual void Run() { + for (size_t i = 0; i < repeats_; i++) { + NotificationService::current()->Notify( + NotificationType::EXTENSION_BOOKMARKS_API_INVOKED, + Source<Extension>(extension_.get()), + Details<const BookmarksFunction>(function_.get())); + } + done_->Signal(); + } + private: + scoped_ptr<Extension> extension_; + scoped_refptr<FunctionType> function_; + size_t repeats_; + base::WaitableEvent* done_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkAPIEventTask); +}; + +class BookmarkAPIEventGenerator { + public: + BookmarkAPIEventGenerator(MessageLoop* ui_loop) : ui_loop_(ui_loop) {} + virtual ~BookmarkAPIEventGenerator() {} + template <class T> + void NewEvent(const std::string& extension_path, + T* bookmarks_function, size_t repeats) { + FilePath path(UTF8ToWide(extension_path)); + Extension* extension = new Extension(path); + std::string error; + DictionaryValue input; + input.SetString(keys::kVersion, kTestExtensionVersion); + input.SetString(keys::kName, kTestExtensionName); + extension->InitFromValue(input, false, &error); + bookmarks_function->set_name(T::function_name()); + base::WaitableEvent done_event(false, false); + ui_loop_->PostTask(FROM_HERE, new BookmarkAPIEventTask<T>( + bookmarks_function, extension, repeats, &done_event)); + done_event.Wait(); + } + + private: + MessageLoop* ui_loop_; + DISALLOW_COPY_AND_ASSIGN(BookmarkAPIEventGenerator); +}; +} // namespace + +class DoUIThreadSetupTask : public Task { + public: + DoUIThreadSetupTask(NotificationService** service, + base::WaitableEvent* done) + : service_(service), signal_when_done_(done) {} + virtual ~DoUIThreadSetupTask() {} + virtual void Run() { + *service_ = new NotificationService(); + signal_when_done_->Signal(); + } + private: + NotificationService** service_; + base::WaitableEvent* signal_when_done_; + DISALLOW_COPY_AND_ASSIGN(DoUIThreadSetupTask); +}; + +class ExtensionsActivityMonitorTest : public testing::Test { + public: + ExtensionsActivityMonitorTest() : service_(NULL), + ui_thread_(ChromeThread::UI) { } + virtual ~ExtensionsActivityMonitorTest() {} + + virtual void SetUp() { + ui_thread_.Start(); + base::WaitableEvent service_created(false, false); + ui_thread_.message_loop()->PostTask(FROM_HERE, + new DoUIThreadSetupTask(&service_, &service_created)); + service_created.Wait(); + } + + virtual void TearDown() { + ui_thread_.message_loop()->DeleteSoon(FROM_HERE, service_); + ui_thread_.Stop(); + } + + MessageLoop* ui_loop() { return ui_thread_.message_loop(); } + + static std::string GetExtensionIdForPath(const std::string& extension_path) { + std::string error; + FilePath path(UTF8ToWide(extension_path)); + Extension e(path); + DictionaryValue input; + input.SetString(keys::kVersion, kTestExtensionVersion); + input.SetString(keys::kName, kTestExtensionName); + e.InitFromValue(input, false, &error); + EXPECT_EQ("", error); + return e.id(); + } + private: + NotificationService* service_; + ChromeThread ui_thread_; +}; + +TEST_F(ExtensionsActivityMonitorTest, Basic) { + ExtensionsActivityMonitor* monitor = new ExtensionsActivityMonitor(ui_loop()); + BookmarkAPIEventGenerator generator(ui_loop()); + + generator.NewEvent<RemoveBookmarkFunction>(kTestExtensionPath1, + new RemoveBookmarkFunction(), 1); + generator.NewEvent<MoveBookmarkFunction>(kTestExtensionPath1, + new MoveBookmarkFunction(), 1); + generator.NewEvent<UpdateBookmarkFunction>(kTestExtensionPath1, + new UpdateBookmarkFunction(), 2); + generator.NewEvent<CreateBookmarkFunction>(kTestExtensionPath1, + new CreateBookmarkFunction(), 3); + generator.NewEvent<SearchBookmarksFunction>(kTestExtensionPath1, + new SearchBookmarksFunction(), 5); + const int writes_by_extension1 = 1 + 1 + 2 + 3; + + generator.NewEvent<RemoveTreeBookmarkFunction>(kTestExtensionPath2, + new RemoveTreeBookmarkFunction(), 8); + generator.NewEvent<GetBookmarkTreeFunction>(kTestExtensionPath2, + new GetBookmarkTreeFunction(), 13); + generator.NewEvent<GetBookmarkChildrenFunction>(kTestExtensionPath2, + new GetBookmarkChildrenFunction(), 21); + generator.NewEvent<GetBookmarksFunction>(kTestExtensionPath2, + new GetBookmarksFunction(), 33); + const int writes_by_extension2 = 8; + + ExtensionsActivityMonitor::Records results; + monitor->GetAndClearRecords(&results); + + std::string id1 = GetExtensionIdForPath(kTestExtensionPath1); + std::string id2 = GetExtensionIdForPath(kTestExtensionPath2); + + EXPECT_EQ(2, results.size()); + EXPECT_TRUE(results.end() != results.find(id1)); + EXPECT_TRUE(results.end() != results.find(id2)); + EXPECT_EQ(writes_by_extension1, results[id1].bookmark_write_count); + EXPECT_EQ(writes_by_extension2, results[id2].bookmark_write_count); + + ui_loop()->DeleteSoon(FROM_HERE, monitor); +} + +TEST_F(ExtensionsActivityMonitorTest, Put) { + ExtensionsActivityMonitor* monitor = new ExtensionsActivityMonitor(ui_loop()); + BookmarkAPIEventGenerator generator(ui_loop()); + std::string id1 = GetExtensionIdForPath(kTestExtensionPath1); + std::string id2 = GetExtensionIdForPath(kTestExtensionPath2); + + generator.NewEvent<CreateBookmarkFunction>(kTestExtensionPath1, + new CreateBookmarkFunction(), 5); + generator.NewEvent<MoveBookmarkFunction>(kTestExtensionPath2, + new MoveBookmarkFunction(), 8); + + ExtensionsActivityMonitor::Records results; + monitor->GetAndClearRecords(&results); + + EXPECT_EQ(2, results.size()); + EXPECT_EQ(5, results[id1].bookmark_write_count); + EXPECT_EQ(8, results[id2].bookmark_write_count); + + generator.NewEvent<GetBookmarksFunction>(kTestExtensionPath2, + new GetBookmarksFunction(), 3); + generator.NewEvent<UpdateBookmarkFunction>(kTestExtensionPath2, + new UpdateBookmarkFunction(), 2); + + // Simulate a commit failure, which augments the active record set with the + // refugee records. + monitor->PutRecords(results); + ExtensionsActivityMonitor::Records new_records; + monitor->GetAndClearRecords(&new_records); + + EXPECT_EQ(2, results.size()); + EXPECT_EQ(id1, new_records[id1].extension_id); + EXPECT_EQ(id2, new_records[id2].extension_id); + EXPECT_EQ(5, new_records[id1].bookmark_write_count); + EXPECT_EQ(8 + 2, new_records[id2].bookmark_write_count); + ui_loop()->DeleteSoon(FROM_HERE, monitor); +} + +TEST_F(ExtensionsActivityMonitorTest, MultiGet) { + ExtensionsActivityMonitor* monitor = new ExtensionsActivityMonitor(ui_loop()); + BookmarkAPIEventGenerator generator(ui_loop()); + std::string id1 = GetExtensionIdForPath(kTestExtensionPath1); + + generator.NewEvent<CreateBookmarkFunction>(kTestExtensionPath1, + new CreateBookmarkFunction(), 5); + + ExtensionsActivityMonitor::Records results; + monitor->GetAndClearRecords(&results); + + EXPECT_EQ(1, results.size()); + EXPECT_EQ(5, results[id1].bookmark_write_count); + + monitor->GetAndClearRecords(&results); + EXPECT_TRUE(results.empty()); + + generator.NewEvent<CreateBookmarkFunction>(kTestExtensionPath1, + new CreateBookmarkFunction(), 3); + monitor->GetAndClearRecords(&results); + + EXPECT_EQ(1, results.size()); + EXPECT_EQ(3, results[id1].bookmark_write_count); + + ui_loop()->DeleteSoon(FROM_HERE, monitor); +}
\ No newline at end of file |