Add chrome to the repository.


git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/safe_browsing/database_perftest.cc b/chrome/browser/safe_browsing/database_perftest.cc
new file mode 100644
index 0000000..ee61c38
--- /dev/null
+++ b/chrome/browser/safe_browsing/database_perftest.cc
@@ -0,0 +1,565 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <set>
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/perftimer.h"
+#include "base/string_util.h"
+#include "chrome/browser/safe_browsing/safe_browsing_database.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/sqlite_compiled_statement.h"
+#include "chrome/common/sqlite_utils.h"
+#include "chrome/test/test_file_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// These tests are slow, especially the ones that create databases.  So disable
+// them by default.
+//#define SAFE_BROWSING_DATABASE_TESTS_ENABLED
+#ifdef SAFE_BROWSING_DATABASE_TESTS_ENABLED
+
+namespace {
+
+// Base class for a safebrowsing database.  Derived classes can implement
+// different types of tables to compare performance characteristics.
+class Database {
+ public:
+  Database() : db_(NULL) {
+  }
+
+  ~Database() {
+    if (db_) {
+      statement_cache_.Cleanup();
+      sqlite3_close(db_);
+      db_ = NULL;
+    }
+  }
+
+  bool Init(const std::string& name, bool create) {
+    // get an empty file for the test DB
+    std::wstring filename;
+    PathService::Get(base::DIR_TEMP, &filename);
+    filename.push_back(file_util::kPathSeparator);
+    filename.append(ASCIIToWide(name));
+
+    if (create) {
+      DeleteFile(filename.c_str());
+    } else {
+      DLOG(INFO) << "evicting " << name << " ...";
+      file_util::EvictFileFromSystemCache(filename.c_str());
+      DLOG(INFO) << "... evicted";
+    }
+
+    if (sqlite3_open(WideToUTF8(filename).c_str(), &db_) != SQLITE_OK)
+      return false;
+
+    statement_cache_.set_db(db_);
+
+    if (!create)
+      return true;
+
+    return CreateTable();
+  }
+
+  virtual bool CreateTable() = 0;
+  virtual bool Add(int host_key, int* prefixes, int count) = 0;
+  virtual bool Read(int host_key, int* prefixes, int size, int* count) = 0;
+  virtual int Count() = 0;
+  virtual std::string GetDBSuffix() = 0;
+
+  sqlite3* db() { return db_; }
+
+ protected:
+  // The database connection.
+  sqlite3* db_;
+
+  // Cache of compiled statements for our database.
+  SqliteStatementCache statement_cache_;
+};
+
+class SimpleDatabase : public Database {
+ public:
+  virtual bool CreateTable() {
+    if (DoesSqliteTableExist(db_, "hosts"))
+      return false;
+
+    return sqlite3_exec(db_, "CREATE TABLE hosts ("
+        "host INTEGER,"
+        "prefixes BLOB)",
+        NULL, NULL, NULL) == SQLITE_OK;
+  }
+
+  virtual bool Add(int host_key, int* prefixes, int count) {
+    SQLITE_UNIQUE_STATEMENT(statement, statement_cache_,
+        "INSERT OR REPLACE INTO hosts"
+        "(host,prefixes)"
+        "VALUES (?,?)");
+    if (!statement.is_valid())
+      return false;
+
+    statement->bind_int(0, host_key);
+    statement->bind_blob(1, prefixes, count*sizeof(int));
+    return statement->step() == SQLITE_DONE;
+  }
+
+  virtual bool Read(int host_key, int* prefixes, int size, int* count) {
+    SQLITE_UNIQUE_STATEMENT(statement, statement_cache_,
+        "SELECT host, prefixes FROM hosts WHERE host=?");
+    if (!statement.is_valid())
+      return false;
+
+    statement->bind_int(0, host_key);
+
+    int rv = statement->step();
+    if (rv == SQLITE_DONE) {
+      // no hostkey found, not an error
+      *count = -1;
+      return true;
+    }
+
+    if (rv != SQLITE_ROW)
+      return false;
+
+    *count = statement->column_bytes(1);
+    if (*count > size)
+      return false;
+
+    memcpy(prefixes, statement->column_blob(0), *count);
+    return true;
+  }
+
+  int Count() {
+    SQLITE_UNIQUE_STATEMENT(statement, statement_cache_,
+        "SELECT COUNT(*) FROM hosts");
+    if (!statement.is_valid()) {
+      EXPECT_TRUE(false);
+      return -1;
+    }
+
+    if (statement->step() != SQLITE_ROW) {
+      EXPECT_TRUE(false);
+      return -1;
+    }
+
+    return statement->column_int(0);
+  }
+
+  std::string GetDBSuffix() {
+    return "Simple";
+  }
+};
+
+class IndexedDatabase : public SimpleDatabase {
+ public:
+  virtual bool CreateTable() {
+    return sqlite3_exec(db_, "CREATE TABLE hosts ("
+        "host INTEGER PRIMARY KEY,"
+        "prefixes BLOB)",
+        NULL, NULL, NULL) == SQLITE_OK;
+  }
+
+  std::string GetDBSuffix() {
+    return "Indexed";
+  }
+};
+
+class IndexedWithIDDatabase : public SimpleDatabase {
+ public:
+  virtual bool CreateTable() {
+    return sqlite3_exec(db_, "CREATE TABLE hosts ("
+        "id INTEGER PRIMARY KEY AUTOINCREMENT,"
+        "host INTEGER UNIQUE,"
+        "prefixes BLOB)",
+        NULL, NULL, NULL) == SQLITE_OK;
+  }
+
+  virtual bool Add(int host_key, int* prefixes, int count) {
+    SQLITE_UNIQUE_STATEMENT(statement, statement_cache_,
+        "INSERT OR REPLACE INTO hosts"
+        "(id,host,prefixes)"
+        "VALUES (NULL,?,?)");
+    if (!statement.is_valid())
+      return false;
+
+    statement->bind_int(0, host_key);
+    statement->bind_blob(1, prefixes, count * sizeof(int));
+    return statement->step() == SQLITE_DONE;
+  }
+
+  std::string GetDBSuffix() {
+    return "IndexedWithID";
+  }
+};
+
+}
+
+class SafeBrowsing: public testing::Test {
+ protected:
+  // Get the test parameters from the test case's name.
+  virtual void SetUp() {
+    logging::InitLogging(
+        NULL, logging::LOG_ONLY_TO_SYSTEM_DEBUG_LOG,
+        logging::LOCK_LOG_FILE,
+        logging::DELETE_OLD_LOG_FILE);
+
+    const testing::TestInfo* const test_info =
+        testing::UnitTest::GetInstance()->current_test_info();
+    std::string test_name = test_info->name();
+
+    TestType type;
+    if (test_name.find("Write") != std::string::npos) {
+      type = WRITE;
+    } else if (test_name.find("Read") != std::string::npos) {
+      type = READ;
+    } else {
+      type = COUNT;
+    }
+
+    if (test_name.find("IndexedWithID") != std::string::npos) {
+      db_ = new IndexedWithIDDatabase();
+    } else if (test_name.find("Indexed")  != std::string::npos) {
+      db_ = new IndexedDatabase();
+    } else {
+      db_ = new SimpleDatabase();
+    }
+
+
+    char multiplier_letter = test_name[test_name.size() - 1];
+    int multiplier = 0;
+    if (multiplier_letter == 'K') {
+      multiplier = 1000;
+    } else if (multiplier_letter == 'M') {
+      multiplier = 1000000;
+    } else {
+      NOTREACHED();
+    }
+
+    size_t index = test_name.size() - 1;
+    while (index != 0 && test_name[index] != '_')
+      index--;
+
+    DCHECK(index);
+    const char* count_start = test_name.c_str() + ++index;
+    int count = atoi(count_start);
+    int size = count * multiplier;
+
+    db_name_ = StringPrintf("TestSafeBrowsing");
+    db_name_.append(count_start);
+    db_name_.append(db_->GetDBSuffix());
+
+    ASSERT_TRUE(db_->Init(db_name_, type == WRITE));
+
+    if (type == WRITE) {
+      WriteEntries(size);
+    } else if (type == READ) {
+      ReadEntries(100);
+    } else {
+      CountEntries();
+    }
+  }
+
+  virtual void TearDown() {
+    delete db_;
+  }
+
+  // This writes the given number of entries to the database.
+  void WriteEntries(int count) {
+    int prefixes[4];
+
+    SQLTransaction transaction(db_->db());
+    transaction.Begin();
+
+    int inc = kint32max / count;
+    for (int i = 0; i < count; i++) {
+      int hostkey;
+      rand_s((unsigned int*)&hostkey);
+      ASSERT_TRUE(db_->Add(hostkey, prefixes, 1));
+    }
+
+    transaction.Commit();
+  }
+
+  // Read the given number of entries from the database.
+  void ReadEntries(int count) {
+    int prefixes[4];
+
+    int64 total_ms = 0;
+
+    for (int i = 0; i < count; ++i) {
+      int key;
+      rand_s((unsigned int*)&key);
+
+      PerfTimer timer;
+
+      int read;
+      ASSERT_TRUE(db_->Read(key, prefixes, sizeof(prefixes), &read));
+
+      int64 time_ms = timer.Elapsed().InMilliseconds();
+      total_ms += time_ms;
+      DLOG(INFO) << "Read in " << time_ms << " ms.";
+    }
+
+    DLOG(INFO) << db_name_ << " read " << count << " entries in average of " <<
+        total_ms/count << " ms.";
+  }
+
+  // Counts how many entries are in the database, which effectively does a full
+  // table scan.
+  void CountEntries() {
+    PerfTimer timer;
+
+    int count = db_->Count();
+
+    DLOG(INFO) << db_name_ << " counted " << count << " entries in " <<
+        timer.Elapsed().InMilliseconds() << " ms";
+  }
+
+  enum TestType {
+    WRITE,
+    READ,
+    COUNT,
+  };
+
+ private:
+
+  Database* db_;
+  std::string db_name_;
+};
+
+TEST_F(SafeBrowsing, Write_100K) {
+}
+
+TEST_F(SafeBrowsing, Read_100K) {
+}
+
+TEST_F(SafeBrowsing, WriteIndexed_100K) {
+}
+
+TEST_F(SafeBrowsing, ReadIndexed_100K) {
+}
+
+TEST_F(SafeBrowsing, WriteIndexed_250K) {
+}
+
+TEST_F(SafeBrowsing, ReadIndexed_250K) {
+}
+
+TEST_F(SafeBrowsing, WriteIndexed_500K) {
+}
+
+TEST_F(SafeBrowsing, ReadIndexed_500K) {
+}
+
+TEST_F(SafeBrowsing, ReadIndexedWithID_250K) {
+}
+
+TEST_F(SafeBrowsing, WriteIndexedWithID_250K) {
+}
+
+TEST_F(SafeBrowsing, ReadIndexedWithID_500K) {
+}
+
+TEST_F(SafeBrowsing, WriteIndexedWithID_500K) {
+}
+
+TEST_F(SafeBrowsing, CountIndexed_250K) {
+}
+
+TEST_F(SafeBrowsing, CountIndexed_500K) {
+}
+
+TEST_F(SafeBrowsing, CountIndexedWithID_250K) {
+}
+
+TEST_F(SafeBrowsing, CountIndexedWithID_500K) {
+}
+
+
+class SafeBrowsingDatabaseTest {
+ public:
+  SafeBrowsingDatabaseTest(const std::wstring& name) {
+    logging::InitLogging(
+        NULL, logging::LOG_ONLY_TO_SYSTEM_DEBUG_LOG,
+        logging::LOCK_LOG_FILE,
+        logging::DELETE_OLD_LOG_FILE);
+
+    PathService::Get(base::DIR_TEMP, &filename_);
+    filename_.push_back(file_util::kPathSeparator);
+    filename_.append(name);
+  }
+
+  void Create(int size) {
+    DeleteFile(filename_.c_str());
+
+    SafeBrowsingDatabase database;
+    database.set_synchronous();
+    EXPECT_TRUE(database.Init(filename_));
+
+    int chunk_id = 0;
+    int total_host_keys = size;
+    int host_keys_per_chunk = 100;
+
+    std::deque<SBChunk>* chunks = new std::deque<SBChunk>;
+
+    for (int i = 0; i < total_host_keys / host_keys_per_chunk; ++i) {
+      chunks->push_back(SBChunk());
+      chunks->back().chunk_number = ++chunk_id;
+
+      for (int j = 0; j < host_keys_per_chunk; ++j) {
+        SBChunkHost host;
+        rand_s((unsigned int*)&host.host);
+        host.entry = SBEntry::Create(SBEntry::ADD_PREFIX, 2);
+        host.entry->SetPrefixAt(0, 0x2425525);
+        host.entry->SetPrefixAt(1, 0x1536366);
+
+        chunks->back().hosts.push_back(host);
+      }
+    }
+
+    database.InsertChunks("goog-malware", chunks);
+  }
+
+  void Read(bool use_bloom_filter) {
+    int keys_to_read = 500;
+    file_util::EvictFileFromSystemCache(filename_.c_str());
+
+    SafeBrowsingDatabase database;
+    database.set_synchronous();
+    EXPECT_TRUE(database.Init(filename_));
+
+    PerfTimer total_timer;
+    int64 db_ms = 0;
+    int keys_from_db = 0;
+    for (int i = 0; i < keys_to_read; ++i) {
+      int key;
+      rand_s((unsigned int*)&key);
+
+      std::string url = StringPrintf("http://www.%d.com/blah.html", key);
+
+      std::string matching_list;
+      std::vector<SBPrefix> prefix_hits;
+      GURL gurl(url);
+      if (!use_bloom_filter || database.NeedToCheckUrl(gurl)) {
+        PerfTimer timer;
+        database.ContainsUrl(gurl, &matching_list, &prefix_hits);
+
+        int64 time_ms = timer.Elapsed().InMilliseconds();
+
+        DLOG(INFO) << "Read from db in " << time_ms << " ms.";
+
+        db_ms += time_ms;
+        keys_from_db++;
+      }
+    }
+
+    int64 total_ms = total_timer.Elapsed().InMilliseconds();
+
+    DLOG(INFO) << WideToASCII(file_util::GetFilenameFromPath(filename_)) <<
+        " read " << keys_to_read << " entries in " << total_ms << " ms.  "  <<
+        keys_from_db << " keys were read from the db, with average read taking " <<
+        db_ms / keys_from_db << " ms";
+  }
+
+  void BuildBloomFilter() {
+    file_util::EvictFileFromSystemCache(filename_.c_str());
+    file_util::Delete(SafeBrowsingDatabase::BloomFilterFilename(filename_), false);
+
+    PerfTimer total_timer;
+
+    SafeBrowsingDatabase database;
+    database.set_synchronous();
+    EXPECT_TRUE(database.Init(filename_));
+
+    int64 total_ms = total_timer.Elapsed().InMilliseconds();
+
+    DLOG(INFO) << WideToASCII(file_util::GetFilenameFromPath(filename_)) <<
+        " built bloom filter in " << total_ms << " ms.";
+  }
+
+ private:
+  std::wstring filename_;
+};
+
+// Adds 100K host records.
+TEST(SafeBrowsingDatabase, FillUp100K) {
+  SafeBrowsingDatabaseTest db(L"SafeBrowsing100K");
+  db.Create(100000);
+}
+
+// Adds 250K host records.
+TEST(SafeBrowsingDatabase, FillUp250K) {
+  SafeBrowsingDatabaseTest db(L"SafeBrowsing250K");
+  db.Create(250000);
+}
+
+// Adds 500K host records.
+TEST(SafeBrowsingDatabase, FillUp500K) {
+  SafeBrowsingDatabaseTest db(L"SafeBrowsing500K");
+  db.Create(500000);
+}
+
+// Reads 500 entries and prints the timing.
+TEST(SafeBrowsingDatabase, ReadFrom250K) {
+  SafeBrowsingDatabaseTest db(L"SafeBrowsing250K");
+  db.Read(false);
+}
+
+TEST(SafeBrowsingDatabase, ReadFrom500K) {
+  SafeBrowsingDatabaseTest db(L"SafeBrowsing500K");
+  db.Read(false);
+}
+
+// Read 500 entries with a bloom filter and print the timing.
+TEST(SafeBrowsingDatabase, BloomReadFrom250K) {
+  SafeBrowsingDatabaseTest db(L"SafeBrowsing250K");
+  db.Read(true);
+}
+
+TEST(SafeBrowsingDatabase, BloomReadFrom500K) {
+  SafeBrowsingDatabaseTest db(L"SafeBrowsing500K");
+  db.Read(true);
+}
+
+// Test how long bloom filter creation takes.
+TEST(SafeBrowsingDatabase, BuildBloomFilter250K) {
+  SafeBrowsingDatabaseTest db(L"SafeBrowsing250K");
+  db.BuildBloomFilter();
+}
+
+TEST(SafeBrowsingDatabase, BuildBloomFilter500K) {
+  SafeBrowsingDatabaseTest db(L"SafeBrowsing500K");
+  db.BuildBloomFilter();
+}
+
+#endif
\ No newline at end of file