Skip to content

Commit dec56d0

Browse files
shreyakhajanchipratickchokhani
authored andcommitted
Adding performance schema collector for query assessments (GoogleCloudPlatform#1168)
* Adding performance collector for query assessments * addressing comments * addressing comments
1 parent 75863c3 commit dec56d0

File tree

9 files changed

+752
-34
lines changed

9 files changed

+752
-34
lines changed

assessment/assessment_engine.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ import (
3232
)
3333

3434
type assessmentCollectors struct {
35-
sampleCollector *assessment.SampleCollector
36-
infoSchemaCollector *assessment.InfoSchemaCollector
37-
appAssessmentCollector *assessment.MigrationCodeSummarizer
35+
sampleCollector *assessment.SampleCollector
36+
infoSchemaCollector *assessment.InfoSchemaCollector
37+
appAssessmentCollector *assessment.MigrationCodeSummarizer
38+
performanceSchemaCollector *assessment.PerformanceSchemaCollector
3839
}
3940

4041
type assessmentTaskInput struct {
@@ -152,6 +153,17 @@ func initializeCollectors(conv *internal.Conv, sourceProfile profiles.SourceProf
152153
logger.Log.Info("app code info unavailable")
153154
}
154155

156+
// Initialize Performance Schema Collector
157+
logger.Log.Info("initializing performance schema collector")
158+
performanceSchemaCollector, err := assessment.GetDefaultPerformanceSchemaCollector(sourceProfile)
159+
if err != nil {
160+
logger.Log.Warn("failed to initialize performance schema collector", zap.Error(err))
161+
logger.Log.Info("performance schema assessment will be skipped")
162+
} else {
163+
c.performanceSchemaCollector = &performanceSchemaCollector
164+
logger.Log.Info("initialized performance schema collector")
165+
}
166+
155167
return c, err
156168
}
157169

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/* Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.*/
14+
15+
package common
16+
17+
import (
18+
"database/sql"
19+
"fmt"
20+
21+
"github.com/GoogleCloudPlatform/spanner-migration-tool/conversion"
22+
"github.com/GoogleCloudPlatform/spanner-migration-tool/profiles"
23+
)
24+
25+
// ConnectionConfigProvider interface for getting database connection configuration
26+
type ConnectionConfigProvider interface {
27+
GetConnectionConfig(sourceProfile profiles.SourceProfile) (interface{}, error)
28+
}
29+
30+
// DefaultConnectionConfigProvider provides default connection configuration
31+
type DefaultConnectionConfigProvider struct{}
32+
33+
// GetConnectionConfig returns the connection configuration for the given source profile
34+
func (d DefaultConnectionConfigProvider) GetConnectionConfig(sourceProfile profiles.SourceProfile) (interface{}, error) {
35+
return conversion.ConnectionConfig(sourceProfile)
36+
}
37+
38+
// DBConnector interface for establishing database connections
39+
type DBConnector interface {
40+
Connect(driver string, connectionConfig interface{}) (*sql.DB, error)
41+
}
42+
43+
// SQLDBConnector provides default SQL database connection functionality
44+
type SQLDBConnector struct{}
45+
46+
// Connect establishes a database connection using the provided configuration
47+
func (d SQLDBConnector) Connect(driver string, connectionConfig interface{}) (*sql.DB, error) {
48+
connectionStr, ok := connectionConfig.(string)
49+
if !ok {
50+
return nil, fmt.Errorf("invalid connection configuration type")
51+
}
52+
53+
db, err := sql.Open(driver, connectionStr)
54+
if err != nil {
55+
return nil, fmt.Errorf("failed to open database connection: %w", err)
56+
}
57+
58+
// Test the connection
59+
if err := db.Ping(); err != nil {
60+
db.Close()
61+
return nil, fmt.Errorf("failed to ping database: %w", err)
62+
}
63+
64+
return db, nil
65+
}

assessment/collectors/infoschema_collector.go

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ import (
2020
"sort"
2121
"strings"
2222

23+
collectorCommon "github.com/GoogleCloudPlatform/spanner-migration-tool/assessment/collectors/common"
2324
common "github.com/GoogleCloudPlatform/spanner-migration-tool/assessment/sources"
2425
"github.com/GoogleCloudPlatform/spanner-migration-tool/assessment/sources/mysql"
2526
"github.com/GoogleCloudPlatform/spanner-migration-tool/assessment/utils"
2627
"github.com/GoogleCloudPlatform/spanner-migration-tool/common/constants"
27-
"github.com/GoogleCloudPlatform/spanner-migration-tool/conversion"
2828
"github.com/GoogleCloudPlatform/spanner-migration-tool/internal"
2929
"github.com/GoogleCloudPlatform/spanner-migration-tool/logger"
3030
"github.com/GoogleCloudPlatform/spanner-migration-tool/profiles"
@@ -50,10 +50,10 @@ func (c InfoSchemaCollector) IsEmpty() bool {
5050
}
5151

5252
func GetDefaultInfoSchemaCollector(conv *internal.Conv, sourceProfile profiles.SourceProfile) (InfoSchemaCollector, error) {
53-
return GetInfoSchemaCollector(conv, sourceProfile, SQLDBConnector{}, DefaultConnectionConfigProvider{}, getInfoSchema)
53+
return GetInfoSchemaCollector(conv, sourceProfile, collectorCommon.SQLDBConnector{}, collectorCommon.DefaultConnectionConfigProvider{}, getInfoSchema)
5454
}
5555

56-
func GetInfoSchemaCollector(conv *internal.Conv, sourceProfile profiles.SourceProfile, dbConnector DBConnector, configProvider ConnectionConfigProvider, infoSchemaProvider func(*sql.DB, profiles.SourceProfile) (common.InfoSchema, error)) (InfoSchemaCollector, error) {
56+
func GetInfoSchemaCollector(conv *internal.Conv, sourceProfile profiles.SourceProfile, dbConnector collectorCommon.DBConnector, configProvider collectorCommon.ConnectionConfigProvider, infoSchemaProvider func(*sql.DB, profiles.SourceProfile) (common.InfoSchema, error)) (InfoSchemaCollector, error) {
5757
logger.Log.Info("initializing infoschema collector")
5858
var errString string
5959
connectionConfig, err := configProvider.GetConnectionConfig(sourceProfile)
@@ -428,27 +428,3 @@ func (c InfoSchemaCollector) ListStoredProcedures() map[string]utils.StoredProce
428428
func (c InfoSchemaCollector) ListSpannerSequences() map[string]ddl.Sequence {
429429
return c.conv.SpSequences
430430
}
431-
432-
type ConnectionConfigProvider interface {
433-
GetConnectionConfig(sourceProfile profiles.SourceProfile) (interface{}, error)
434-
}
435-
436-
type DefaultConnectionConfigProvider struct{}
437-
438-
func (d DefaultConnectionConfigProvider) GetConnectionConfig(sourceProfile profiles.SourceProfile) (interface{}, error) {
439-
return conversion.ConnectionConfig(sourceProfile)
440-
}
441-
442-
type DBConnector interface {
443-
Connect(driver string, connectionConfig interface{}) (*sql.DB, error)
444-
}
445-
446-
type SQLDBConnector struct{}
447-
448-
func (d SQLDBConnector) Connect(driver string, connectionConfig interface{}) (*sql.DB, error) {
449-
db, err := sql.Open(driver, connectionConfig.(string))
450-
if err != nil {
451-
return nil, err
452-
}
453-
return db, nil
454-
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package assessment
16+
17+
import (
18+
"database/sql"
19+
_ "embed"
20+
"fmt"
21+
22+
collectorCommon "github.com/GoogleCloudPlatform/spanner-migration-tool/assessment/collectors/common"
23+
sourcesCommon "github.com/GoogleCloudPlatform/spanner-migration-tool/assessment/sources"
24+
"github.com/GoogleCloudPlatform/spanner-migration-tool/assessment/sources/mysql"
25+
"github.com/GoogleCloudPlatform/spanner-migration-tool/assessment/utils"
26+
"github.com/GoogleCloudPlatform/spanner-migration-tool/common/constants"
27+
"github.com/GoogleCloudPlatform/spanner-migration-tool/logger"
28+
"github.com/GoogleCloudPlatform/spanner-migration-tool/profiles"
29+
"go.uber.org/zap"
30+
)
31+
32+
// PerformanceSchemaCollector collects performance schema data from source databases
33+
type PerformanceSchemaCollector struct {
34+
queries []utils.QueryAssessmentInfo
35+
}
36+
37+
// IsEmpty checks if the collector has any data
38+
func (c PerformanceSchemaCollector) IsEmpty() bool {
39+
return len(c.queries) == 0
40+
}
41+
42+
// GetDefaultPerformanceSchemaCollector creates a new PerformanceSchemaCollector with default settings
43+
func GetDefaultPerformanceSchemaCollector(sourceProfile profiles.SourceProfile) (PerformanceSchemaCollector, error) {
44+
return GetPerformanceSchemaCollector(sourceProfile, collectorCommon.SQLDBConnector{}, collectorCommon.DefaultConnectionConfigProvider{}, DefaultPerformanceSchemaProvider{})
45+
}
46+
47+
// GetPerformanceSchemaCollector creates a new PerformanceSchemaCollector with custom dependencies
48+
func GetPerformanceSchemaCollector(sourceProfile profiles.SourceProfile, dbConnector collectorCommon.DBConnector, configProvider collectorCommon.ConnectionConfigProvider, performanceSchemaProvider PerformanceSchemaProvider) (PerformanceSchemaCollector, error) {
49+
logger.Log.Info("initializing performance schema collector")
50+
51+
connectionConfig, err := configProvider.GetConnectionConfig(sourceProfile)
52+
if err != nil {
53+
return PerformanceSchemaCollector{}, fmt.Errorf("failed to get connection config: %w", err)
54+
}
55+
56+
db, err := dbConnector.Connect(sourceProfile.Driver, connectionConfig)
57+
if err != nil {
58+
return PerformanceSchemaCollector{}, fmt.Errorf("failed to connect to database: %w", err)
59+
}
60+
61+
performanceSchema, err := performanceSchemaProvider.getPerformanceSchema(db, sourceProfile)
62+
if err != nil {
63+
return PerformanceSchemaCollector{}, fmt.Errorf("failed to get performance schema: %w", err)
64+
}
65+
66+
queries, err := performanceSchema.GetAllQueryAssessments()
67+
if err != nil {
68+
return PerformanceSchemaCollector{}, fmt.Errorf("failed to get all queries: %w", err)
69+
}
70+
71+
logger.Log.Info("performance schema collector initialized successfully",
72+
zap.Int("query_count", len(queries)))
73+
74+
return PerformanceSchemaCollector{
75+
queries: queries,
76+
}, nil
77+
}
78+
79+
// DBConnector interface for establishing database connections
80+
type PerformanceSchemaProvider interface {
81+
getPerformanceSchema(db *sql.DB, sourceProfile profiles.SourceProfile) (sourcesCommon.PerformanceSchema, error)
82+
}
83+
84+
// SQLDBConnector provides default SQL database connection functionality
85+
type DefaultPerformanceSchemaProvider struct{}
86+
87+
// getPerformanceSchema creates a performance schema implementation based on the database driver
88+
func (d DefaultPerformanceSchemaProvider) getPerformanceSchema(db *sql.DB, sourceProfile profiles.SourceProfile) (sourcesCommon.PerformanceSchema, error) {
89+
driver := sourceProfile.Driver
90+
switch driver {
91+
case constants.MYSQL:
92+
return mysql.PerformanceSchemaImpl{
93+
Db: db,
94+
DbName: sourceProfile.Conn.Mysql.Db,
95+
}, nil
96+
default:
97+
return nil, fmt.Errorf("driver %s not supported for performance schema", driver)
98+
}
99+
}

0 commit comments

Comments
 (0)