OLD | NEW |
---|---|
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 Loading... | |
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 |
134 // Only pass 5% of new reports to prevent a thundering herd of dumps. | |
135 // TODO(shess): If this could be related to the time in the channel, then the | |
136 // rate could ramp up over time. Perhaps could remember the timestamp the | |
137 // first time upload is considered, and ramp up 1% per day? | |
138 static const uint64 kReportsPerMillion = 50000; | |
250 uint64 rand = base::RandGenerator(1000000); | 139 uint64 rand = base::RandGenerator(1000000); |
251 if (error == SQLITE_CORRUPT) { | 140 if (rand <= kReportsPerMillion) |
252 // Once the database is known to be corrupt, it will generate a | 141 db->ReportDiagnosticInfo(extended_error, stmt); |
Scott Hess - ex-Googler
2015/10/19 22:23:41
I considered pushing this into sql::Connection, bu
| |
253 // stream of errors until someone fixes it, so give one chance. | |
254 // Set first in case of errors in generating the report. | |
255 reported = true; | |
256 | |
257 // Corrupt cases currently dominate, report them very infrequently. | |
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 Loading... | |
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); |
Scott Hess - ex-Googler
2015/10/19 22:23:41
Removed |startup_kb| because DbPath() lets it just
| |
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 Loading... | |
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 Loading... | |
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 |
OLD | NEW |