Index: sql/connection.cc |
diff --git a/sql/connection.cc b/sql/connection.cc |
index 872098588d36e9f9913d28016e0ccd9914a3a3d8..c196073ebfb549d39340316e4437e7f2c7cb61b5 100644 |
--- a/sql/connection.cc |
+++ b/sql/connection.cc |
@@ -141,6 +141,16 @@ int GetSqlite3File(sqlite3* db, sqlite3_file** file) { |
return rc; |
} |
+// This should match UMA_HISTOGRAM_MEDIUM_TIMES(). |
+base::HistogramBase* GetMediumTimeHistogram(const std::string& name) { |
+ return base::Histogram::FactoryTimeGet( |
+ name, |
+ base::TimeDelta::FromMilliseconds(10), |
+ base::TimeDelta::FromMinutes(3), |
+ 50, |
+ base::HistogramBase::kUmaTargetedHistogramFlag); |
+} |
+ |
} // namespace |
namespace sql { |
@@ -219,13 +229,74 @@ Connection::Connection() |
transaction_nesting_(0), |
needs_rollback_(false), |
in_memory_(false), |
- poisoned_(false) { |
+ poisoned_(false), |
+ stats_histogram_(NULL), |
+ commit_time_histogram_(NULL), |
+ autocommit_time_histogram_(NULL), |
+ update_time_histogram_(NULL), |
+ query_time_histogram_(NULL), |
+ clock_(new TimeSource()) { |
} |
Connection::~Connection() { |
Close(); |
} |
+void Connection::RecordEvent(Events event, size_t count) { |
+ for (size_t i = 0; i < count; ++i) { |
+ UMA_HISTOGRAM_ENUMERATION("Sqlite.Stats", event, EVENT_MAX_VALUE); |
+ } |
+ |
+ if (stats_histogram_) { |
+ for (size_t i = 0; i < count; ++i) { |
+ stats_histogram_->Add(event); |
+ } |
+ } |
+} |
+ |
+void Connection::RecordCommitTime(const base::TimeDelta& delta) { |
+ RecordUpdateTime(delta); |
+ UMA_HISTOGRAM_MEDIUM_TIMES("Sqlite.CommitTime", delta); |
+ if (commit_time_histogram_) |
+ commit_time_histogram_->AddTime(delta); |
+} |
+ |
+void Connection::RecordAutoCommitTime(const base::TimeDelta& delta) { |
+ RecordUpdateTime(delta); |
+ UMA_HISTOGRAM_MEDIUM_TIMES("Sqlite.AutoCommitTime", delta); |
+ if (autocommit_time_histogram_) |
+ autocommit_time_histogram_->AddTime(delta); |
+} |
+ |
+void Connection::RecordUpdateTime(const base::TimeDelta& delta) { |
+ RecordQueryTime(delta); |
+ UMA_HISTOGRAM_MEDIUM_TIMES("Sqlite.UpdateTime", delta); |
+ if (update_time_histogram_) |
+ update_time_histogram_->AddTime(delta); |
+} |
+ |
+void Connection::RecordQueryTime(const base::TimeDelta& delta) { |
+ UMA_HISTOGRAM_MEDIUM_TIMES("Sqlite.QueryTime", delta); |
+ if (query_time_histogram_) |
+ query_time_histogram_->AddTime(delta); |
+} |
+ |
+void Connection::RecordTimeAndChanges( |
+ const base::TimeDelta& delta, bool read_only) { |
+ if (read_only) { |
+ RecordQueryTime(delta); |
+ } else { |
+ const int changes = sqlite3_changes(db_); |
+ if (sqlite3_get_autocommit(db_)) { |
+ RecordAutoCommitTime(delta); |
+ RecordEvent(EVENT_CHANGES_AUTOCOMMIT, changes); |
+ } else { |
+ RecordUpdateTime(delta); |
+ RecordEvent(EVENT_CHANGES, changes); |
+ } |
+ } |
+} |
+ |
bool Connection::Open(const base::FilePath& path) { |
if (!histogram_tag_.empty()) { |
int64_t size_64 = 0; |
@@ -576,6 +647,7 @@ bool Connection::BeginTransaction() { |
needs_rollback_ = false; |
Statement begin(GetCachedStatement(SQL_FROM_HERE, "BEGIN TRANSACTION")); |
+ RecordOneEvent(EVENT_BEGIN); |
if (!begin.Run()) |
return false; |
} |
@@ -618,7 +690,17 @@ bool Connection::CommitTransaction() { |
} |
Statement commit(GetCachedStatement(SQL_FROM_HERE, "COMMIT")); |
- return commit.Run(); |
+ |
+ // Collect the commit time manually, sql::Statement would register it as query |
+ // time only. |
+ const base::TimeTicks before = Now(); |
+ bool ret = commit.RunWithoutTimers(); |
+ const base::TimeDelta delta = Now() - before; |
+ |
+ RecordCommitTime(delta); |
+ RecordOneEvent(EVENT_COMMIT); |
+ |
+ return ret; |
} |
void Connection::RollbackAllTransactions() { |
@@ -650,13 +732,65 @@ bool Connection::DetachDatabase(const char* attachment_point) { |
return s.Run(); |
} |
+// TODO(shess): Consider changing this to execute exactly one statement. If a |
+// caller wishes to execute multiple statements, that should be explicit, and |
+// perhaps tucked into an explicit transaction with rollback in case of error. |
int Connection::ExecuteAndReturnErrorCode(const char* sql) { |
AssertIOAllowed(); |
if (!db_) { |
DLOG_IF(FATAL, !poisoned_) << "Illegal use of connection without a db"; |
return SQLITE_ERROR; |
} |
- return sqlite3_exec(db_, sql, NULL, NULL, NULL); |
+ DCHECK(sql); |
+ |
+ RecordOneEvent(EVENT_EXECUTE); |
+ int rc = SQLITE_OK; |
+ while ((rc == SQLITE_OK) && *sql) { |
+ sqlite3_stmt *stmt = NULL; |
+ const char *leftover_sql; |
+ |
+ const base::TimeTicks before = Now(); |
+ rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, &leftover_sql); |
+ sql = leftover_sql; |
+ |
+ // Stop if an error is encountered. |
+ if (rc != SQLITE_OK) |
+ break; |
+ |
+ // This happens if |sql| originally only contained comments or whitespace. |
+ // TODO(shess): Audit to see if this can become a DCHECK(). Having |
+ // extraneous comments and whitespace in the SQL statements increases |
+ // runtime cost and can easily be shifted out to the C++ layer. |
+ if (!stmt) |
+ continue; |
+ |
+ // Save for use after statement is finalized. |
+ const bool read_only = !!sqlite3_stmt_readonly(stmt); |
+ |
+ RecordOneEvent(Connection::EVENT_STATEMENT_RUN); |
+ while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { |
+ // TODO(shess): Audit to see if this can become a DCHECK. I think PRAGMA |
+ // is the only legitimate case for this. |
+ RecordOneEvent(Connection::EVENT_STATEMENT_ROWS); |
+ } |
+ |
+ // sqlite3_finalize() returns SQLITE_OK if the most recent sqlite3_step() |
+ // returned SQLITE_DONE or SQLITE_ROW, otherwise the error code. |
+ rc = sqlite3_finalize(stmt); |
+ if (rc == SQLITE_OK) |
+ RecordOneEvent(Connection::EVENT_STATEMENT_SUCCESS); |
+ |
+ // sqlite3_exec() does this, presumably to avoid spinning the parser for |
+ // trailing whitespace. |
+ // TODO(shess): Audit to see if this can become a DCHECK. |
+ while (IsAsciiWhitespace(*sql)) { |
+ sql++; |
+ } |
+ |
+ const base::TimeDelta delta = Now() - before; |
+ RecordTimeAndChanges(delta, read_only); |
+ } |
+ return rc; |
} |
bool Connection::Execute(const char* sql) { |
@@ -886,6 +1020,32 @@ bool Connection::OpenInternal(const std::string& file_name, |
// Make sure sqlite3_initialize() is called before anything else. |
InitializeSqlite(); |
+ // Setup the stats histograms immediately rather than allocating lazily. |
+ // Connections which won't exercise all of these probably shouldn't exist. |
+ if (!histogram_tag_.empty()) { |
+ stats_histogram_ = |
+ base::LinearHistogram::FactoryGet( |
+ "Sqlite.Stats." + histogram_tag_, |
+ 1, EVENT_MAX_VALUE, EVENT_MAX_VALUE + 1, |
+ base::HistogramBase::kUmaTargetedHistogramFlag); |
+ |
+ // The timer setup matches UMA_HISTOGRAM_MEDIUM_TIMES(). 3 minutes is an |
+ // unreasonable time for any single operation, so there is not much value to |
+ // knowing if it was 3 minutes or 5 minutes. In reality at that point |
+ // things are entirely busted. |
+ commit_time_histogram_ = |
+ GetMediumTimeHistogram("Sqlite.CommitTime." + histogram_tag_); |
+ |
+ autocommit_time_histogram_ = |
+ GetMediumTimeHistogram("Sqlite.AutoCommitTime." + histogram_tag_); |
+ |
+ update_time_histogram_ = |
+ GetMediumTimeHistogram("Sqlite.UpdateTime." + histogram_tag_); |
+ |
+ query_time_histogram_ = |
+ GetMediumTimeHistogram("Sqlite.QueryTime." + histogram_tag_); |
+ } |
+ |
// If |poisoned_| is set, it means an error handler called |
// RazeAndClose(). Until regular Close() is called, the caller |
// should be treating the database as open, but is_open() currently |
@@ -1026,7 +1186,16 @@ bool Connection::OpenInternal(const std::string& file_name, |
void Connection::DoRollback() { |
Statement rollback(GetCachedStatement(SQL_FROM_HERE, "ROLLBACK")); |
- rollback.Run(); |
+ |
+ // Collect the rollback time manually, sql::Statement would register it as |
+ // query time only. |
+ const base::TimeTicks before = Now(); |
+ rollback.RunWithoutTimers(); |
+ const base::TimeDelta delta = Now() - before; |
+ |
+ RecordUpdateTime(delta); |
+ RecordOneEvent(EVENT_ROLLBACK); |
+ |
needs_rollback_ = false; |
} |
@@ -1043,6 +1212,11 @@ void Connection::StatementRefDeleted(StatementRef* ref) { |
open_statements_.erase(i); |
} |
+void Connection::set_histogram_tag(const std::string& tag) { |
+ DCHECK(!is_open()); |
+ histogram_tag_ = tag; |
+} |
+ |
void Connection::AddTaggedHistogram(const std::string& name, |
size_t sample) const { |
if (histogram_tag_.empty()) |
@@ -1135,4 +1309,8 @@ bool Connection::IntegrityCheckHelper( |
return ret; |
} |
+base::TimeTicks TimeSource::Now() { |
+ return base::TimeTicks::Now(); |
+} |
+ |
} // namespace sql |