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

Unified Diff: perf/go/db/db.go

Issue 608273002: Add versioning to perf SQL database (Closed) Base URL: https://skia.googlesource.com/buildbot@master
Patch Set: Clean up based on review in rietveld Created 6 years, 3 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 side-by-side diff with in-line comments
Download patch
Index: perf/go/db/db.go
diff --git a/perf/go/db/db.go b/perf/go/db/db.go
index 405d1201856501ab75168705138bb3247efcf9b5..7cc6a6697d202a16e821f8f1a421100afe2e578c 100644
--- a/perf/go/db/db.go
+++ b/perf/go/db/db.go
@@ -9,66 +9,67 @@ import (
"github.com/golang/glog"
_ "github.com/mattn/go-sqlite3"
"skia.googlesource.com/buildbot.git/perf/go/metadata"
+ "skia.googlesource.com/buildbot.git/perf/go/util"
)
var (
// DB is the sql database where we have commit and annotation information stored.
DB *sql.DB = nil
+
+ // Keeps track if we are connected to MySQL or SQLite
+ isMySQL = true
)
const (
+ // Key of the password for the readwrite user.
METADATA_KEY = "readwrite"
+
+ // Path where the SQLite database is stored when running locally.
+ SQLITE_DB_PATH = "./perf.db"
)
// Init must be called once before DB is used.
//
// Since it used glog, make sure it is also called after flag.Parse is called.
-func Init() {
- // Connect to MySQL server. First, get the password from the metadata server.
- // See https://developers.google.com/compute/docs/metadata#custom.
- password, err := metadata.Get(METADATA_KEY)
- if err == nil {
- // The IP address of the database is found here:
- // https://console.developers.google.com/project/31977622648/sql/instances/skiaperf/overview
- // And 3306 is the default port for MySQL.
- DB, err = sql.Open("mysql", fmt.Sprintf("readwrite:%s@tcp(173.194.104.24:3306)/skia?parseTime=true", password))
+func Init(mysqlConnStr string) {
+ // If there is a connection string then connect to the MySQL server.
+ // This is for testing only. In production we get the relevant information
+ // from the metadata server.
+ if mysqlConnStr != "" {
+ var err error
+ glog.Infoln("Opening local MySQL database: ", mysqlConnStr)
+ DB, err = sql.Open("mysql", mysqlConnStr)
if err != nil {
- glog.Fatalln("Failed to open connection to SQL server:", err)
+ glog.Fatalln("Unable to connect to server: " + mysqlConnStr)
}
+ isMySQL = true
} else {
- glog.Infoln("Failed to find metadata, unable to connect to MySQL server (Expected when running locally):", err)
- // Fallback to sqlite for local use.
- DB, err = sql.Open("sqlite3", "./perf.db")
- if err != nil {
- glog.Fatalln("Failed to open:", err)
+ // Connect to MySQL server. First, get the password from the metadata server.
+ // See https://developers.google.com/compute/docs/metadata#custom.
+ password, err := metadata.Get(METADATA_KEY)
+ if err == nil {
+ // The IP address of the database is found here:
+ // https://console.developers.google.com/project/31977622648/sql/instances/skiaperf/overview
+ // And 3306 is the default port for MySQL.
+ DB, err = sql.Open("mysql", fmt.Sprintf("readwrite:%s@tcp(173.194.104.24:3306)/skia?parseTime=true", password))
+ if err != nil {
+ glog.Fatalln("Failed to open connection to SQL server:", err)
+ }
+ } else {
+ glog.Infoln("Failed to find metadata, unable to connect to MySQL server (Expected when running locally):", err)
+ glog.Infof("Opening local sqlite database at: %s", SQLITE_DB_PATH)
+ // Fallback to sqlite for local use.
+ DB, err = sql.Open("sqlite3", SQLITE_DB_PATH)
+ if err != nil {
+ glog.Fatalln("Failed to open:", err)
+ }
+ isMySQL = false
}
+ }
- sql := `CREATE TABLE clusters (
- id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- ts TIMESTAMP NOT NULL,
- hash TEXT NOT NULL,
- regression FLOAT NOT NULL,
- cluster MEDIUMTEXT NOT NULL,
- status TEXT NOT NULL,
- message TEXT NOT NULL
- )`
- _, err = DB.Exec(sql)
- glog.Infoln("Status creating sqlite table for clusters:", err)
-
- sql = `CREATE TABLE shortcuts (
- id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- traces MEDIUMTEXT NOT NULL
- )`
- _, err = DB.Exec(sql)
- glog.Infoln("Status creating sqlite table for shortcuts:", err)
-
- sql = `CREATE TABLE tries (
- issue VARCHAR(255) NOT NULL PRIMARY KEY,
- lastUpdated TIMESTAMP NOT NULL,
- results MEDIUMTEXT NOT NULL
- )`
- _, err = DB.Exec(sql)
- glog.Infoln("Status creating sqlite table for tries:", err)
+ // Make sure the migration table exists.
+ if err := ensureVersionTable(); err != nil {
+ glog.Fatalln("Unable to guarantee valid version table:", err.Error())
}
// Ping the database to keep the connection fresh.
@@ -82,3 +83,209 @@ func Init() {
}
}()
}
+
+func Migrate(targetVersion int) error {
+ if targetVersion < 0 {
+ glog.Fatalln("Target db version must be at least 0.")
+ }
+
+ // start a transaction
+ txn, txErr := DB.Begin()
+ if txErr != nil {
+ return txErr
+ }
+ defer func() {
+ if txErr != nil {
+ txn.Rollback()
+ } else {
+ txn.Commit()
+ }
+ }()
+
+ currentVersion, txErr := DBVersion()
+ if txErr != nil {
+ return txErr
+ }
+
+ // run through the transactions
+ runSteps := getMigrations(isMySQL, currentVersion, targetVersion)
+ for _, step := range runSteps {
+ for _, stmt := range step {
+ _, txErr = txn.Exec(stmt)
+ if txErr != nil {
+ return txErr
+ }
+ }
+ }
+
+ // update the dbversion table
+ txErr = setDBVersion(txn, targetVersion)
+ if txErr != nil {
+ return txErr
+ }
+
+ return nil
+}
+
+func DBVersion() (int, error) {
+ stmt := `
+ SELECT version
tfarina 2014/09/29 18:56:33 does this fits in one line?
stephana 2014/09/30 13:43:06 It probably could, I find it easier to read this w
+ FROM sk_db_version
+ WHERE id=1`
+
+ var version int
+ err := DB.QueryRow(stmt).Scan(&version)
+ return version, err
+}
+
+func MaxDBVersion() int {
+ return len(migrationSteps)
+}
+
+func setDBVersion(txn *sql.Tx, newDBVersion int) error {
+ stmt := `REPLACE INTO sk_db_version (id, version, updated) VALUES(1, ?, ?)`
+ _, err := txn.Exec(stmt, newDBVersion, time.Now().Unix())
+ return err
+}
+
+func ensureVersionTable() error {
+ txn, txnErr := DB.Begin()
+ defer func() {
+ if txnErr != nil {
+ glog.Errorf("Encountered error rolling back: %s", txnErr.Error())
+ txn.Rollback()
+ } else {
+ glog.Infoln("Version table OK.")
+ txn.Commit()
+ }
+ }()
+
+ if txnErr != nil {
+ fmt.Errorf("Unable to start database transaction. %s", txnErr.Error())
+ }
+
+ glog.Infoln("At 1 ------------------------------------------------")
+
+ stmt := `CREATE TABLE IF NOT EXISTS sk_db_version (
jcgregorio 2014/09/29 16:59:07 I'm not sure if the current readwrite user has the
stephana 2014/09/29 18:07:50 Agreed. The readwrite user should not have those p
+ id INTEGER NOT NULL PRIMARY KEY,
+ version INTEGER NOT NULL,
+ updated BIGINT NOT NULL
+ )`
+ if _, txnErr = txn.Exec(stmt); txnErr != nil {
+ return fmt.Errorf("Creating version table failed: %s", txnErr.Error())
+ }
+
+ glog.Infoln("At 2 ------------------------------------------------")
+ var count int
+ if txnErr = txn.QueryRow("SELECT COUNT(*) FROM sk_db_version").Scan(&count); txnErr != nil {
+ return fmt.Errorf("Unable to read version table: %s", txnErr.Error())
+ }
+
+ glog.Infoln("At 3 ------------------------------------------------")
+
+ if count == 0 {
+ if txnErr = setDBVersion(txn, 0); txnErr != nil {
+ return txnErr
+ }
+ } else if count > 1 {
+ return fmt.Errorf("Version table contains more than one row.")
+ }
+
+ glog.Infoln("At 4 ------------------------------------------------")
+
+ return txnErr
+}
+
+// Add any new migration to this list of functions
+// NOTE: Do not change these once commited, but instead add a migration step.
+var migrationSteps = []func() ([]string, []string, []string, []string){
+ migration_1,
+}
+
+// Returns the SQL statements base on whether we are using MySQL and the
+// current and target DB version.
+func getMigrations(isMySQL bool, currentVersion int, targetVersion int) [][]string {
+ inc := util.SignInt(targetVersion - currentVersion)
+ if inc == 0 {
+ return [][]string{}
+ }
+
+ delta := util.AbsInt(targetVersion - currentVersion)
+ result := make([][]string, 0, delta)
+ for i := currentVersion + inc; (i >= 0) && (i < len(migrationSteps)) && (i != targetVersion); i += inc {
+ var temp []string
+ switch {
+ // using mysqlp
+ case (inc > 0) && isMySQL:
+ _, _, temp, _ = migrationSteps[i]()
+ case (inc < 0) && isMySQL:
+ _, _, _, temp = migrationSteps[i]()
+ // using sqlite
+ case (inc > 0):
+ temp, _, _, _ = migrationSteps[i]()
+ case (inc < 0):
+ _, temp, _, _ = migrationSteps[i]()
+ }
+ result = append(result, temp)
+ }
+ return result
+}
+
+func migration_1() (sqLiteUp, sqLiteDown, mySQLUp, mySQLDown []string) {
+ // SQLite statements to alter the datase.
+ sqLiteUp = []string{
+ `CREATE TABLE clusters (
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ ts TIMESTAMP NOT NULL,
+ hash TEXT NOT NULL,
+ regression FLOAT NOT NULL,
+ cluster MEDIUMTEXT NOT NULL,
+ status TEXT NOT NULL,
+ message TEXT NOT NULL
+ )`,
+ `CREATE TABLE shortcuts (
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ traces MEDIUMTEXT NOT NULL
+ )`,
+ `CREATE TABLE tries (
+ issue VARCHAR(255) NOT NULL PRIMARY KEY,
+ lastUpdated TIMESTAMP NOT NULL,
+ results MEDIUMTEXT NOT NULL
+ )`,
+ }
+
+ // When using SQLite we fully reverse the previous step.
+ sqLiteDown = []string{
+ `DROP TABLE IF EXISTS clusters`,
+ `DROP TABLE IF EXISTS shortcuts`,
+ `DROP TABLE IF EXISTS tries`,
+ }
+
+ mySQLUp = []string{
+ `CREATE TABLE shortcuts (
+ id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ traces MEDIUMTEXT NOT NULL
+ )`,
+
+ `CREATE TABLE clusters (
+ id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ ts BIGINT NOT NULL,
+ hash TEXT NOT NULL,
+ regression FLOAT NOT NULL,
+ cluster MEDIUMTEXT NOT NULL,
+ status TEXT NOT NULL,
+ message TEXT NOT NULL
+ )`,
+
+ `CREATE TABLE tries (
+ issue VARCHAR(255) NOT NULL PRIMARY KEY,
+ lastUpdated BIGINT NOT NULL,
+ results LONGTEXT NOT NULL
+ )`,
+ }
+
+ // We are not removing the tables to make sure we don't delete existing
+ // data. This is just for the first migration.
+ mySQLDown = []string{}
+ return
+}
« perf/db/reset_mysqldb.sh ('K') | « perf/db/reset_mysqldb.sh ('k') | perf/go/db/db_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698