Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(425)

Side by Side Diff: components/history/core/browser/thumbnail_database.cc

Issue 1393393007: [sql] Track uploads of diagnostic data to prevent duplication. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Oh, mojo Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | sql/connection.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/history/core/browser/thumbnail_database.h" 5 #include "components/history/core/browser/thumbnail_database.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <string> 8 #include <string>
9 9
10 #include "base/bind.h" 10 #include "base/bind.h"
11 #include "base/debug/alias.h" 11 #include "base/debug/alias.h"
12 #include "base/debug/dump_without_crashing.h"
13 #include "base/files/file_util.h" 12 #include "base/files/file_util.h"
14 #include "base/format_macros.h"
15 #include "base/memory/ref_counted_memory.h" 13 #include "base/memory/ref_counted_memory.h"
16 #include "base/metrics/histogram_macros.h" 14 #include "base/metrics/histogram_macros.h"
17 #include "base/rand_util.h" 15 #include "base/rand_util.h"
18 #include "base/strings/string_util.h" 16 #include "base/strings/string_util.h"
19 #include "base/strings/stringprintf.h" 17 #include "base/strings/stringprintf.h"
20 #include "base/time/time.h" 18 #include "base/time/time.h"
21 #include "components/history/core/browser/history_backend_client.h" 19 #include "components/history/core/browser/history_backend_client.h"
22 #include "components/history/core/browser/url_database.h" 20 #include "components/history/core/browser/url_database.h"
23 #include "sql/recovery.h" 21 #include "sql/recovery.h"
24 #include "sql/statement.h" 22 #include "sql/statement.h"
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after
114 112
115 // Always keep this at the end. 113 // Always keep this at the end.
116 STRUCTURE_EVENT_MAX, 114 STRUCTURE_EVENT_MAX,
117 }; 115 };
118 116
119 void RecordInvalidStructure(InvalidStructureType invalid_type) { 117 void RecordInvalidStructure(InvalidStructureType invalid_type) {
120 UMA_HISTOGRAM_ENUMERATION("History.InvalidFaviconsDBStructure", 118 UMA_HISTOGRAM_ENUMERATION("History.InvalidFaviconsDBStructure",
121 invalid_type, STRUCTURE_EVENT_MAX); 119 invalid_type, STRUCTURE_EVENT_MAX);
122 } 120 }
123 121
124 // Attempt to pass 2000 bytes of |debug_info| into a crash dump. 122 // TODO(shess): If this proves out, move it all into sql::Connection to be
125 void DumpWithoutCrashing2000(const std::string& debug_info) { 123 // shared.
126 char debug_buf[2000];
127 base::strlcpy(debug_buf, debug_info.c_str(), arraysize(debug_buf));
128 base::debug::Alias(&debug_buf);
129
130 base::debug::DumpWithoutCrashing();
131 }
132
133 void ReportCorrupt(sql::Connection* db, size_t startup_kb) {
134 // Buffer for accumulating debugging info about the error. Place
135 // more-relevant information earlier, in case things overflow the
136 // fixed-size buffer.
137 std::string debug_info;
138
139 base::StringAppendF(&debug_info, "SQLITE_CORRUPT, integrity_check:\n");
140
141 // Check files up to 8M to keep things from blocking too long.
142 const size_t kMaxIntegrityCheckSize = 8192;
143 if (startup_kb > kMaxIntegrityCheckSize) {
144 base::StringAppendF(&debug_info, "too big %" PRIuS "\n", startup_kb);
145 } else {
146 std::vector<std::string> messages;
147
148 const base::TimeTicks before = base::TimeTicks::Now();
149 db->FullIntegrityCheck(&messages);
150 base::StringAppendF(&debug_info, "# %" PRIx64 " ms, %" PRIuS " records\n",
151 (base::TimeTicks::Now() - before).InMilliseconds(),
152 messages.size());
153
154 // SQLite returns up to 100 messages by default, trim deeper to
155 // keep close to the 2000-character size limit for dumping.
156 //
157 // TODO(shess): If the first 20 tend to be actionable, test if
158 // passing the count to integrity_check makes it exit earlier. In
159 // that case it may be possible to greatly ease the size
160 // restriction.
161 const size_t kMaxMessages = 20;
162 for (size_t i = 0; i < kMaxMessages && i < messages.size(); ++i) {
163 base::StringAppendF(&debug_info, "%s\n", messages[i].c_str());
164 }
165 }
166
167 DumpWithoutCrashing2000(debug_info);
168 }
169
170 void ReportError(sql::Connection* db, int error) {
171 // Buffer for accumulating debugging info about the error. Place
172 // more-relevant information earlier, in case things overflow the
173 // fixed-size buffer.
174 std::string debug_info;
175
176 // The error message from the failed operation.
177 base::StringAppendF(&debug_info, "db error: %d/%s\n",
178 db->GetErrorCode(), db->GetErrorMessage());
179
180 // System errno information.
181 base::StringAppendF(&debug_info, "errno: %d\n", db->GetLastErrno());
182
183 // SQLITE_ERROR reports seem to be attempts to upgrade invalid
184 // schema, try to log that info.
185 if (error == SQLITE_ERROR) {
186 const char* kVersionSql = "SELECT value FROM meta WHERE key = 'version'";
187 if (db->IsSQLValid(kVersionSql)) {
188 sql::Statement statement(db->GetUniqueStatement(kVersionSql));
189 if (statement.Step()) {
190 debug_info += "version: ";
191 debug_info += statement.ColumnString(0);
192 debug_info += '\n';
193 } else if (statement.Succeeded()) {
194 debug_info += "version: none\n";
195 } else {
196 debug_info += "version: error\n";
197 }
198 } else {
199 debug_info += "version: invalid\n";
200 }
201
202 debug_info += "schema:\n";
203
204 // sqlite_master has columns:
205 // type - "index" or "table".
206 // name - name of created element.
207 // tbl_name - name of element, or target table in case of index.
208 // rootpage - root page of the element in database file.
209 // sql - SQL to create the element.
210 // In general, the |sql| column is sufficient to derive the other
211 // columns. |rootpage| is not interesting for debugging, without
212 // the contents of the database. The COALESCE is because certain
213 // automatic elements will have a |name| but no |sql|,
214 const char* kSchemaSql = "SELECT COALESCE(sql, name) FROM sqlite_master";
215 sql::Statement statement(db->GetUniqueStatement(kSchemaSql));
216 while (statement.Step()) {
217 debug_info += statement.ColumnString(0);
218 debug_info += '\n';
219 }
220 if (!statement.Succeeded())
221 debug_info += "error\n";
222 }
223
224 // TODO(shess): Think of other things to log. Not logging the
225 // statement text because the backtrace should suffice in most
226 // cases. The database schema is a possibility, but the
227 // likelihood of recursive error callbacks makes that risky (same
228 // reasoning applies to other data fetched from the database).
229
230 DumpWithoutCrashing2000(debug_info);
231 }
232
233 // TODO(shess): If this proves out, perhaps lift the code out to
234 // chrome/browser/diagnostics/sqlite_diagnostics.{h,cc}.
235 void GenerateDiagnostics(sql::Connection* db, 124 void GenerateDiagnostics(sql::Connection* db,
236 size_t startup_kb, 125 int extended_error,
237 int extended_error) { 126 sql::Statement* stmt) {
238 int error = (extended_error & 0xFF);
239
240 // Infrequently report information about the error up to the crash
241 // server.
242 static const uint64 kReportsPerMillion = 50000;
243
244 // Since some/most errors will not resolve themselves, only report 127 // Since some/most errors will not resolve themselves, only report
245 // once per Chrome run. 128 // once per Chrome run.
246 static bool reported = false; 129 static bool reported = false;
247 if (reported) 130 if (reported)
248 return; 131 return;
132 reported = true;
249 133
250 uint64 rand = base::RandGenerator(1000000); 134 // Only pass 5% of new reports to prevent a thundering herd of dumps.
251 if (error == SQLITE_CORRUPT) { 135 // TODO(shess): If this could be related to the time in the channel, then the
252 // Once the database is known to be corrupt, it will generate a 136 // rate could ramp up over time. Perhaps could remember the timestamp the
253 // stream of errors until someone fixes it, so give one chance. 137 // first time upload is considered, and ramp up 1% per day?
254 // Set first in case of errors in generating the report. 138 static const uint64 kReportPercent = 5;
255 reported = true; 139 uint64 rand = base::RandGenerator(100);
256 140 if (rand <= kReportPercent)
257 // Corrupt cases currently dominate, report them very infrequently. 141 db->ReportDiagnosticInfo(extended_error, stmt);
258 static const uint64 kCorruptReportsPerMillion = 10000;
259 if (rand < kCorruptReportsPerMillion)
260 ReportCorrupt(db, startup_kb);
261 } else if (error == SQLITE_READONLY) {
262 // SQLITE_READONLY appears similar to SQLITE_CORRUPT - once it
263 // is seen, it is almost guaranteed to be seen again.
264 reported = true;
265
266 if (rand < kReportsPerMillion)
267 ReportError(db, extended_error);
268 } else {
269 // Only set the flag when making a report. This should allow
270 // later (potentially different) errors in a stream of errors to
271 // be reported.
272 //
273 // TODO(shess): Would it be worthwile to audit for which cases
274 // want once-only handling? Sqlite.Error.Thumbnail shows
275 // CORRUPT and READONLY as almost 95% of all reports on these
276 // channels, so probably easier to just harvest from the field.
277 if (rand < kReportsPerMillion) {
278 reported = true;
279 ReportError(db, extended_error);
280 }
281 }
282 } 142 }
283 143
284 // NOTE(shess): Schema modifications must consider initial creation in 144 // NOTE(shess): Schema modifications must consider initial creation in
285 // |InitImpl()|, recovery in |RecoverDatabaseOrRaze()|, and history pruning in 145 // |InitImpl()|, recovery in |RecoverDatabaseOrRaze()|, and history pruning in
286 // |RetainDataForPageUrls()|. 146 // |RetainDataForPageUrls()|.
287 bool InitTables(sql::Connection* db) { 147 bool InitTables(sql::Connection* db) {
288 const char kIconMappingSql[] = 148 const char kIconMappingSql[] =
289 "CREATE TABLE IF NOT EXISTS icon_mapping" 149 "CREATE TABLE IF NOT EXISTS icon_mapping"
290 "(" 150 "("
291 "id INTEGER PRIMARY KEY," 151 "id INTEGER PRIMARY KEY,"
(...skipping 250 matching lines...) Expand 10 before | Expand all | Expand 10 after
542 UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsFaviconBitmaps", 402 UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsFaviconBitmaps",
543 static_cast<int>(favicon_bitmaps_rows_recovered)); 403 static_cast<int>(favicon_bitmaps_rows_recovered));
544 UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsIconMapping", 404 UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsIconMapping",
545 static_cast<int>(icon_mapping_rows_recovered)); 405 static_cast<int>(icon_mapping_rows_recovered));
546 406
547 RecordRecoveryEvent(RECOVERY_EVENT_RECOVERED); 407 RecordRecoveryEvent(RECOVERY_EVENT_RECOVERED);
548 } 408 }
549 409
550 void DatabaseErrorCallback(sql::Connection* db, 410 void DatabaseErrorCallback(sql::Connection* db,
551 const base::FilePath& db_path, 411 const base::FilePath& db_path,
552 size_t startup_kb,
553 HistoryBackendClient* backend_client, 412 HistoryBackendClient* backend_client,
554 int extended_error, 413 int extended_error,
555 sql::Statement* stmt) { 414 sql::Statement* stmt) {
556 // TODO(shess): Assert that this is running on a safe thread. 415 // TODO(shess): Assert that this is running on a safe thread.
557 // AFAICT, should be the history thread, but at this level I can't 416 // AFAICT, should be the history thread, but at this level I can't
558 // see how to reach that. 417 // see how to reach that.
559 418
560 if (backend_client && backend_client->ShouldReportDatabaseError()) { 419 if (backend_client && backend_client->ShouldReportDatabaseError()) {
561 GenerateDiagnostics(db, startup_kb, extended_error); 420 GenerateDiagnostics(db, extended_error, stmt);
562 } 421 }
563 422
564 // Attempt to recover corrupt databases. 423 // Attempt to recover corrupt databases.
565 int error = (extended_error & 0xFF); 424 int error = (extended_error & 0xFF);
566 if (error == SQLITE_CORRUPT || 425 if (error == SQLITE_CORRUPT ||
567 error == SQLITE_CANTOPEN || 426 error == SQLITE_CANTOPEN ||
568 error == SQLITE_NOTADB) { 427 error == SQLITE_NOTADB) {
569 RecoverDatabaseOrRaze(db, db_path); 428 RecoverDatabaseOrRaze(db, db_path);
570 } 429 }
571 430
(...skipping 626 matching lines...) Expand 10 before | Expand all | Expand 10 after
1198 const char kIconMappingDrop[] = "DROP TABLE temp.icon_id_mapping"; 1057 const char kIconMappingDrop[] = "DROP TABLE temp.icon_id_mapping";
1199 const char kRetainedUrlsDrop[] = "DROP TABLE temp.retained_urls"; 1058 const char kRetainedUrlsDrop[] = "DROP TABLE temp.retained_urls";
1200 if (!db_.Execute(kIconMappingDrop) || !db_.Execute(kRetainedUrlsDrop)) 1059 if (!db_.Execute(kIconMappingDrop) || !db_.Execute(kRetainedUrlsDrop))
1201 return false; 1060 return false;
1202 1061
1203 return transaction.Commit(); 1062 return transaction.Commit();
1204 } 1063 }
1205 1064
1206 sql::InitStatus ThumbnailDatabase::OpenDatabase(sql::Connection* db, 1065 sql::InitStatus ThumbnailDatabase::OpenDatabase(sql::Connection* db,
1207 const base::FilePath& db_name) { 1066 const base::FilePath& db_name) {
1208 size_t startup_kb = 0;
1209 int64 size_64;
1210 if (base::GetFileSize(db_name, &size_64))
1211 startup_kb = static_cast<size_t>(size_64 / 1024);
1212
1213 db->set_histogram_tag("Thumbnail"); 1067 db->set_histogram_tag("Thumbnail");
1214 db->set_error_callback(base::Bind(&DatabaseErrorCallback, 1068 db->set_error_callback(base::Bind(&DatabaseErrorCallback,
1215 db, db_name, startup_kb, backend_client_)); 1069 db, db_name, backend_client_));
1216 1070
1217 // Thumbnails db now only stores favicons, so we don't need that big a page 1071 // Thumbnails db now only stores favicons, so we don't need that big a page
1218 // size or cache. 1072 // size or cache.
1219 db->set_page_size(2048); 1073 db->set_page_size(2048);
1220 db->set_cache_size(32); 1074 db->set_cache_size(32);
1221 1075
1222 // Run the database in exclusive mode. Nobody else should be accessing the 1076 // Run the database in exclusive mode. Nobody else should be accessing the
1223 // database while we're running, and this will give somewhat improved perf. 1077 // database while we're running, and this will give somewhat improved perf.
1224 db->set_exclusive_locking(); 1078 db->set_exclusive_locking();
1225 1079
(...skipping 152 matching lines...) Expand 10 before | Expand all | Expand 10 after
1378 meta_table_.SetVersionNumber(8); 1232 meta_table_.SetVersionNumber(8);
1379 meta_table_.SetCompatibleVersionNumber(std::min(8, kCompatibleVersionNumber)); 1233 meta_table_.SetCompatibleVersionNumber(std::min(8, kCompatibleVersionNumber));
1380 return true; 1234 return true;
1381 } 1235 }
1382 1236
1383 bool ThumbnailDatabase::IsFaviconDBStructureIncorrect() { 1237 bool ThumbnailDatabase::IsFaviconDBStructureIncorrect() {
1384 return !db_.IsSQLValid("SELECT id, url, icon_type FROM favicons"); 1238 return !db_.IsSQLValid("SELECT id, url, icon_type FROM favicons");
1385 } 1239 }
1386 1240
1387 } // namespace history 1241 } // namespace history
OLDNEW
« no previous file with comments | « no previous file | sql/connection.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698