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

Side by Side Diff: sql/connection.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: Shift diagnostic generation and reporting to sql::Connection. 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
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 "sql/connection.h" 5 #include "sql/connection.h"
6 6
7 #include <string.h> 7 #include <string.h>
8 8
9 #include "base/bind.h" 9 #include "base/bind.h"
10 #include "base/debug/dump_without_crashing.h"
10 #include "base/files/file_path.h" 11 #include "base/files/file_path.h"
11 #include "base/files/file_util.h" 12 #include "base/files/file_util.h"
13 #include "base/format_macros.h"
14 #include "base/json/json_file_value_serializer.h"
12 #include "base/lazy_instance.h" 15 #include "base/lazy_instance.h"
13 #include "base/logging.h" 16 #include "base/logging.h"
14 #include "base/message_loop/message_loop.h" 17 #include "base/message_loop/message_loop.h"
15 #include "base/metrics/histogram.h" 18 #include "base/metrics/histogram.h"
16 #include "base/metrics/sparse_histogram.h" 19 #include "base/metrics/sparse_histogram.h"
17 #include "base/strings/string_split.h" 20 #include "base/strings/string_split.h"
18 #include "base/strings/string_util.h" 21 #include "base/strings/string_util.h"
19 #include "base/strings/stringprintf.h" 22 #include "base/strings/stringprintf.h"
20 #include "base/strings/utf_string_conversions.h" 23 #include "base/strings/utf_string_conversions.h"
21 #include "base/synchronization/lock.h" 24 #include "base/synchronization/lock.h"
(...skipping 228 matching lines...) Expand 10 before | Expand all | Expand 10 after
250 cache_size); 253 cache_size);
251 dump->AddScalar("schema_size", 254 dump->AddScalar("schema_size",
252 base::trace_event::MemoryAllocatorDump::kUnitsBytes, 255 base::trace_event::MemoryAllocatorDump::kUnitsBytes,
253 schema_size); 256 schema_size);
254 dump->AddScalar("statement_size", 257 dump->AddScalar("statement_size",
255 base::trace_event::MemoryAllocatorDump::kUnitsBytes, 258 base::trace_event::MemoryAllocatorDump::kUnitsBytes,
256 statement_size); 259 statement_size);
257 return true; 260 return true;
258 } 261 }
259 262
263 void Connection::ReportDiagnosticInfo(int extended_error, Statement* stmt) {
264 AssertIOAllowed();
265
266 std::string debug_info;
267 const int error = (extended_error & 0xFF);
268 if (error == SQLITE_CORRUPT) {
269 debug_info = CollectCorruptionInfo();
270 } else {
271 debug_info = CollectErrorInfo(extended_error, stmt);
272 }
273
274 if (RegisterIntentToUpload()) {
275 char debug_buf[2000];
Scott Hess - ex-Googler 2015/10/19 22:23:41 SQLITE_ERROR uploads for Favicons tend to be in th
276 base::strlcpy(debug_buf, debug_info.c_str(), arraysize(debug_buf));
277 debug_buf[arraysize(debug_buf) - 1] = '\0';
278 base::debug::Alias(&debug_buf);
279
280 base::debug::DumpWithoutCrashing();
281 }
282 }
283
260 // static 284 // static
261 void Connection::SetErrorIgnorer(Connection::ErrorIgnorerCallback* cb) { 285 void Connection::SetErrorIgnorer(Connection::ErrorIgnorerCallback* cb) {
262 CHECK(current_ignorer_cb_ == NULL); 286 CHECK(current_ignorer_cb_ == NULL);
263 current_ignorer_cb_ = cb; 287 current_ignorer_cb_ = cb;
264 } 288 }
265 289
266 // static 290 // static
267 void Connection::ResetErrorIgnorer() { 291 void Connection::ResetErrorIgnorer() {
268 CHECK(current_ignorer_cb_); 292 CHECK(current_ignorer_cb_);
269 current_ignorer_cb_ = NULL; 293 current_ignorer_cb_ = NULL;
(...skipping 305 matching lines...) Expand 10 before | Expand all | Expand 10 after
575 // If no changes have been made, skip flushing. This allows the first page of 599 // If no changes have been made, skip flushing. This allows the first page of
576 // the database to remain in cache across multiple reads. 600 // the database to remain in cache across multiple reads.
577 const int total_changes = sqlite3_total_changes(db_); 601 const int total_changes = sqlite3_total_changes(db_);
578 if (total_changes == total_changes_at_last_release_) 602 if (total_changes == total_changes_at_last_release_)
579 return; 603 return;
580 604
581 total_changes_at_last_release_ = total_changes; 605 total_changes_at_last_release_ = total_changes;
582 sqlite3_db_release_memory(db_); 606 sqlite3_db_release_memory(db_);
583 } 607 }
584 608
609 base::FilePath Connection::DbPath() const {
610 if (!is_open())
611 return base::FilePath();
612
613 const char* path = sqlite3_db_filename(db_, "main");
614 const base::StringPiece db_path(path);
615 #if defined(OS_WIN)
616 return base::FilePath(base::UTF8ToWide(db_path));
617 #elif defined(OS_POSIX)
618 return base::FilePath(db_path);
619 #else
620 NOTREACHED();
621 return base::FilePath();
622 #endif
623 }
624
625 // Data is persisted in a file shared between databases in the same directory.
626 // The "sqlite-diag" file contains a dictionary with the version number, and an
627 // array of histogram tags for databases which have been dumped.
628 bool Connection::RegisterIntentToUpload() const {
629 static const char* kVersionKey = "version";
630 static const char* kDiagnosticDumpsKey = "DiagnosticDumps";
631 static int kVersion = 1;
632
633 AssertIOAllowed();
634
635 if (histogram_tag_.empty())
636 return false;
637
638 if (!is_open())
639 return false;
640
641 if (in_memory_)
642 return false;
643
644 const base::FilePath db_path = DbPath();
645 if (db_path.empty())
646 return false;
647
648 // Put the collection of diagnostic data next to the databases. In most
649 // cases, this is the profile directory, but safe-browsing stores a Cookies
650 // file in the directory above the profile directory.
651 base::FilePath breadcrumb_path(
652 db_path.DirName().Append(FILE_PATH_LITERAL("sqlite-diag")));
653
654 // Lock against multiple updates to the diagnostics file. This code should
655 // seldom be called in the first place, and when called it should seldom be
656 // called for multiple databases, and when called for multiple databases there
657 // is _probably_ something systemic wrong with the user's system. So the lock
658 // should never be contended, but when it is the database experience is
659 // already bad.
660 base::AutoLock lock(g_sqlite_init_lock.Get());
661
662 scoped_ptr<base::Value> root;
663 if (!base::PathExists(breadcrumb_path)) {
664 scoped_ptr<base::DictionaryValue> root_dict(new base::DictionaryValue());
665 root_dict->SetInteger(kVersionKey, kVersion);
666
667 scoped_ptr<base::ListValue> dumps(new base::ListValue);
668 dumps->AppendString(histogram_tag_);
669 root_dict->Set(kDiagnosticDumpsKey, dumps.Pass());
670
671 root = root_dict.Pass();
672 } else {
673 // Failure to read a valid dictionary implies that something is going wrong
674 // on the system.
675 JSONFileValueDeserializer deserializer(breadcrumb_path);
676 scoped_ptr<base::Value> read_root(
677 deserializer.Deserialize(nullptr, nullptr));
678 if (!read_root.get())
679 return false;
680 scoped_ptr<base::DictionaryValue> root_dict =
681 base::DictionaryValue::From(read_root.Pass());
682 if (!root_dict)
683 return false;
684
685 // Don't upload if the version is missing or newer.
686 int version = 0;
687 if (!root_dict->GetInteger(kVersionKey, &version) || version > kVersion)
688 return false;
689
690 base::ListValue* dumps = nullptr;
691 if (!root_dict->GetList(kDiagnosticDumpsKey, &dumps))
692 return false;
693
694 const size_t size = dumps->GetSize();
695 for (size_t i = 0; i < size; ++i) {
696 std::string s;
697
698 // Don't upload if the value isn't a string, or indicates a prior upload.
699 if (!dumps->GetString(i, &s) || s == histogram_tag_)
700 return false;
701 }
702
703 // Record intention to proceed with upload.
704 dumps->AppendString(histogram_tag_);
705 root = root_dict.Pass();
706 }
707
708 const base::FilePath breadcrumb_new =
709 breadcrumb_path.AddExtension(FILE_PATH_LITERAL("new"));
710 base::DeleteFile(breadcrumb_new, false);
711
712 // No upload if the breadcrumb file cannot be updated.
713 // TODO(shess): Consider ImportantFileWriter::WriteFileAtomically() to land
714 // the data on disk. For now, losing the data is not a big problem, so the
715 // sync overhead would probably not be worth it.
716 JSONFileValueSerializer serializer(breadcrumb_new);
717 if (!serializer.Serialize(*root))
718 return false;
719 if (!base::PathExists(breadcrumb_new))
720 return false;
721 if (!base::ReplaceFile(breadcrumb_new, breadcrumb_path, nullptr)) {
722 base::DeleteFile(breadcrumb_new, false);
723 return false;
724 }
725
726 return true;
727 }
728
729 std::string Connection::CollectErrorInfo(int error, Statement* stmt) const {
730 // Buffer for accumulating debugging info about the error. Place
731 // more-relevant information earlier, in case things overflow the
732 // fixed-size reporting buffer.
733 std::string debug_info;
734
735 // The error message from the failed operation.
736 base::StringAppendF(&debug_info, "db error: %d/%s\n",
737 GetErrorCode(), GetErrorMessage());
738
739 // TODO(shess): |error| and |GetErrorCode()| should always be the same, but
740 // reading code does not entirely convince me. Remove if they turn out to be
741 // the same.
742 if (error != GetErrorCode())
743 base::StringAppendF(&debug_info, "reported error: %d\n", error);
Scott Hess - ex-Googler 2015/10/19 22:23:41 Previously just recorded GetErrorCode(). I couldn
744
745 // System error information. Interpretation of Windows errors is different
746 // from posix.
Scott Hess - ex-Googler 2015/10/19 22:23:41 This is slightly different from before. Detecting
747 #if defined(OS_WIN)
748 base::StringAppendF(&debug_info, "LastError: %d\n", GetLastErrno());
749 #elif defined(OS_POSIX)
750 base::StringAppendF(&debug_info, "errno: %d\n", GetLastErrno());
751 #else
752 NOTREACHED(); // Add appropriate log info.
753 #endif
754
755 if (stmt) {
756 base::StringAppendF(&debug_info, "statement: %s\n",
757 stmt->GetSQLStatement());
758 } else {
759 base::StringAppendF(&debug_info, "statement: NULL\n");
760 }
Scott Hess - ex-Googler 2015/10/19 22:23:41 Previously the statement wasn't recorded, under th
761
762 // SQLITE_ERROR often indicates some sort of mismatch between the statement
763 // and the schema, possibly due to a failed schema migration.
764 if (error == SQLITE_ERROR) {
765 const char* kVersionSql = "SELECT value FROM meta WHERE key = 'version'";
766 sqlite3_stmt* s;
767 int rc = sqlite3_prepare_v2(db_, kVersionSql, -1, &s, nullptr);
Scott Hess - ex-Googler 2015/10/19 22:23:41 I changed this to be direct sqlite3 access, becaus
768 if (rc == SQLITE_OK) {
769 rc = sqlite3_step(s);
770 if (rc == SQLITE_ROW) {
771 base::StringAppendF(&debug_info, "version: %d\n",
772 sqlite3_column_int(s, 0));
773 } else if (rc == SQLITE_DONE) {
774 debug_info += "version: none\n";
775 } else {
776 base::StringAppendF(&debug_info, "version: error %d\n", rc);
777 }
778 sqlite3_finalize(s);
779 } else {
780 base::StringAppendF(&debug_info, "version: prepare error %d\n", rc);
781 }
782
783 debug_info += "schema:\n";
784
785 // sqlite_master has columns:
786 // type - "index" or "table".
787 // name - name of created element.
788 // tbl_name - name of element, or target table in case of index.
789 // rootpage - root page of the element in database file.
790 // sql - SQL to create the element.
791 // In general, the |sql| column is sufficient to derive the other columns.
792 // |rootpage| is not interesting for debugging, without the contents of the
793 // database. The COALESCE is because certain automatic elements will have a
794 // |name| but no |sql|,
795 const char* kSchemaSql = "SELECT COALESCE(sql, name) FROM sqlite_master";
796 rc = sqlite3_prepare_v2(db_, kSchemaSql, -1, &s, nullptr);
797 if (rc == SQLITE_OK) {
798 while ((rc = sqlite3_step(s)) == SQLITE_ROW) {
799 error = false;
Scott Hess - ex-Googler 2015/10/19 22:52:50 Oops.
800 base::StringAppendF(&debug_info, "%s\n", sqlite3_column_text(s, 0));
801 }
802 if (rc != SQLITE_DONE)
803 base::StringAppendF(&debug_info, "error %d\n", rc);
804 sqlite3_finalize(s);
805 } else {
806 base::StringAppendF(&debug_info, "prepare error %d\n", rc);
807 }
808 }
809
810 return debug_info;
811 }
812
813 // TODO(shess): Since this is only called in an error situation, it might be
814 // prudent to rewrite in terms of SQLite API calls, and mark the function const.
Scott Hess - ex-Googler 2015/10/19 22:23:41 I didn't do this at this time because it seemed mo
815 std::string Connection::CollectCorruptionInfo() {
816 // Buffer for accumulating debugging info about the error. Place
817 // more-relevant information earlier, in case things overflow the
818 // fixed-size reporting buffer.
819 std::string debug_info;
820
821 AssertIOAllowed();
822
823 const base::FilePath db_path = DbPath();
824 int64 db_size = -1;
825 if (!base::GetFileSize(db_path, &db_size))
826 db_size = -1;
827 base::StringAppendF(&debug_info, "SQLITE_CORRUPT, db size %" PRId64 "\n",
828 db_size);
829
830 // Only check files up to 8M to keep things from blocking too long.
831 const int64 kMaxIntegrityCheckSize = 8192 * 1024;
832 if (db_size < 0 || db_size > kMaxIntegrityCheckSize) {
833 debug_info += "integrity_check skipped due to size\n";
834 } else {
835 std::vector<std::string> messages;
836
837 // TODO(shess): FullIntegrityCheck() splits into a vector while this joins
838 // into a string. Probably should be refactored.
839 const base::TimeTicks before = base::TimeTicks::Now();
840 FullIntegrityCheck(&messages);
841 base::StringAppendF(
842 &debug_info,
843 "integrity_check %" PRId64 " ms, %" PRIuS " records:\n",
Scott Hess - ex-Googler 2015/10/19 22:23:41 The timed value was previous reported as PRIx64.
844 (base::TimeTicks::Now() - before).InMilliseconds(),
845 messages.size());
846
847 // SQLite returns up to 100 messages by default, trim deeper to
848 // keep close to the 2000-character size limit for dumping.
849 const size_t kMaxMessages = 20;
850 for (size_t i = 0; i < kMaxMessages && i < messages.size(); ++i) {
851 base::StringAppendF(&debug_info, "%s\n", messages[i].c_str());
852 }
853 }
854
855 return debug_info;
856 }
857
585 void Connection::TrimMemory(bool aggressively) { 858 void Connection::TrimMemory(bool aggressively) {
586 if (!db_) 859 if (!db_)
587 return; 860 return;
588 861
589 // TODO(shess): investigate using sqlite3_db_release_memory() when possible. 862 // TODO(shess): investigate using sqlite3_db_release_memory() when possible.
590 int original_cache_size; 863 int original_cache_size;
591 { 864 {
592 Statement sql_get_original(GetUniqueStatement("PRAGMA cache_size")); 865 Statement sql_get_original(GetUniqueStatement("PRAGMA cache_size"));
593 if (!sql_get_original.Step()) { 866 if (!sql_get_original.Step()) {
594 DLOG(WARNING) << "Could not get cache size " << GetErrorMessage(); 867 DLOG(WARNING) << "Could not get cache size " << GetErrorMessage();
(...skipping 932 matching lines...) Expand 10 before | Expand all | Expand 10 after
1527 ignore_result(Execute(kNoWritableSchema)); 1800 ignore_result(Execute(kNoWritableSchema));
1528 1801
1529 return ret; 1802 return ret;
1530 } 1803 }
1531 1804
1532 base::TimeTicks TimeSource::Now() { 1805 base::TimeTicks TimeSource::Now() {
1533 return base::TimeTicks::Now(); 1806 return base::TimeTicks::Now();
1534 } 1807 }
1535 1808
1536 } // namespace sql 1809 } // namespace sql
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698