diff --git a/go.mod b/go.mod index 9ab1977a..8a0f077d 100644 --- a/go.mod +++ b/go.mod @@ -16,11 +16,18 @@ require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/kr/text v0.2.0 // indirect + github.com/newrelic/go-agent/v3 v3.35.1 // indirect + github.com/newrelic/go-agent/v3/integrations/nrmysql v1.2.2 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect + google.golang.org/grpc v1.65.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 0c89fb58..ba3ab9ff 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,10 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/newrelic/go-agent/v3 v3.35.1 h1:N43qBNDILmnwLDCSfnE1yy6adyoVEU95nAOtdUgG4vA= +github.com/newrelic/go-agent/v3 v3.35.1/go.mod h1:GNTda53CohAhkgsc7/gqSsJhDZjj8vaky5u+vKz7wqM= +github.com/newrelic/go-agent/v3/integrations/nrmysql v1.2.2 h1:JtaJdL4y1hj5mH0JA2XIIIZtOsivsCmG0wsp3cGtoNo= +github.com/newrelic/go-agent/v3/integrations/nrmysql v1.2.2/go.mod h1:0JZ1gqlaBi9FUrQsg9LLZR357oDH4fGYYTbQQPhOd8o= github.com/newrelic/infra-integrations-sdk/v3 v3.9.1 h1:dCtVLsYNHWTQ5aAlAaHroomOUlqxlGTrdi6XTlvBDfI= github.com/newrelic/infra-integrations-sdk/v3 v3.9.1/go.mod h1:yPeidhcq9Cla0QDquGXH0KqvS2k9xtetFOD7aLA0Z8M= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= @@ -37,8 +41,20 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/src/args/argument_list.go b/src/args/argument_list.go index f4d46367..a448d216 100644 --- a/src/args/argument_list.go +++ b/src/args/argument_list.go @@ -24,4 +24,5 @@ type ArgumentList struct { QueryResponseTimeThreshold int `default:"500" help:"Threshold in milliseconds for query response time to fetch individual query performance metrics."` QueryCountThreshold int `default:"20" help:"Query count limit for fetching grouped slow and individual query performance metrics."` ExcludedDatabases string `default:"[]" help:"A JSON array that list databases that will be excluded from collection. system databases are excluded by default."` + LicenseKey string `default:"" help:"New Relic license key."` } diff --git a/src/query-performance-monitoring/mysql-apm/mysql_apm.go b/src/query-performance-monitoring/mysql-apm/mysql_apm.go new file mode 100644 index 00000000..1f59781f --- /dev/null +++ b/src/query-performance-monitoring/mysql-apm/mysql_apm.go @@ -0,0 +1,7 @@ +package mysqlapm + +import "github.com/newrelic/go-agent/v3/newrelic" + +var ArgsGlobal = "" +var NewrelicApp = newrelic.Application{} +var Txn *newrelic.Transaction = nil diff --git a/src/query-performance-monitoring/performance-metrics-collectors/blocking_sessions.go b/src/query-performance-monitoring/performance-metrics-collectors/blocking_sessions.go index a56d0918..ade4b27a 100644 --- a/src/query-performance-monitoring/performance-metrics-collectors/blocking_sessions.go +++ b/src/query-performance-monitoring/performance-metrics-collectors/blocking_sessions.go @@ -2,6 +2,7 @@ package performancemetricscollectors import ( "github.com/jmoiron/sqlx" + "github.com/newrelic/go-agent/v3/newrelic" "github.com/newrelic/infra-integrations-sdk/v3/integration" "github.com/newrelic/infra-integrations-sdk/v3/log" arguments "github.com/newrelic/nri-mysql/src/args" @@ -10,7 +11,7 @@ import ( ) // PopulateBlockingSessionMetrics retrieves blocking session metrics from the database and populates them into the integration entity. -func PopulateBlockingSessionMetrics(db utils.DataSource, i *integration.Integration, e *integration.Entity, args arguments.ArgumentList, excludedDatabases []string) { +func PopulateBlockingSessionMetrics(app *newrelic.Application, db utils.DataSource, i *integration.Integration, e *integration.Entity, args arguments.ArgumentList, excludedDatabases []string) { // Prepare the SQL query with the provided parameters query, inputArgs, err := sqlx.In(utils.BlockingSessionsQuery, excludedDatabases, min(args.QueryCountThreshold, constants.MaxQueryCountThreshold)) if err != nil { @@ -18,7 +19,7 @@ func PopulateBlockingSessionMetrics(db utils.DataSource, i *integration.Integrat } // Collect the blocking session metrics - metrics, err := utils.CollectMetrics[utils.BlockingSessionMetrics](db, query, inputArgs...) + metrics, err := utils.CollectMetrics[utils.BlockingSessionMetrics](app, db, query, inputArgs...) if err != nil { log.Error("Error collecting blocking session metrics: %v", err) } diff --git a/src/query-performance-monitoring/performance-metrics-collectors/query_details.go b/src/query-performance-monitoring/performance-metrics-collectors/query_details.go index d801ce3f..8ee1fc9f 100644 --- a/src/query-performance-monitoring/performance-metrics-collectors/query_details.go +++ b/src/query-performance-monitoring/performance-metrics-collectors/query_details.go @@ -4,6 +4,7 @@ import ( "context" "github.com/jmoiron/sqlx" + "github.com/newrelic/go-agent/v3/newrelic" "github.com/newrelic/infra-integrations-sdk/v3/integration" "github.com/newrelic/infra-integrations-sdk/v3/log" arguments "github.com/newrelic/nri-mysql/src/args" @@ -12,8 +13,8 @@ import ( ) // PopulateSlowQueryMetrics collects and sets slow query metrics and returns the list of query IDs -func PopulateSlowQueryMetrics(i *integration.Integration, e *integration.Entity, db utils.DataSource, args arguments.ArgumentList, excludedDatabases []string) []string { - rawMetrics, queryIDList, err := collectGroupedSlowQueryMetrics(db, args.SlowQueryFetchInterval, args.QueryCountThreshold, excludedDatabases) +func PopulateSlowQueryMetrics(app *newrelic.Application, i *integration.Integration, e *integration.Entity, db utils.DataSource, args arguments.ArgumentList, excludedDatabases []string) []string { + rawMetrics, queryIDList, err := collectGroupedSlowQueryMetrics(app, db, args.SlowQueryFetchInterval, args.QueryCountThreshold, excludedDatabases) if err != nil { log.Error("Failed to collect slow query metrics: %v", err) return []string{} @@ -34,7 +35,7 @@ func PopulateSlowQueryMetrics(i *integration.Integration, e *integration.Entity, } // collectGroupedSlowQueryMetrics collects metrics from the performance schema database for slow queries -func collectGroupedSlowQueryMetrics(db utils.DataSource, slowQueryfetchInterval int, queryCountThreshold int, excludedDatabases []string) ([]utils.SlowQueryMetrics, []string, error) { +func collectGroupedSlowQueryMetrics(app *newrelic.Application, db utils.DataSource, slowQueryfetchInterval int, queryCountThreshold int, excludedDatabases []string) ([]utils.SlowQueryMetrics, []string, error) { // Prepare the SQL query with the provided parameters query, args, err := sqlx.In(utils.SlowQueries, slowQueryfetchInterval, excludedDatabases, min(queryCountThreshold, constants.MaxQueryCountThreshold)) if err != nil { @@ -43,7 +44,7 @@ func collectGroupedSlowQueryMetrics(db utils.DataSource, slowQueryfetchInterval ctx, cancel := context.WithTimeout(context.Background(), constants.TimeoutDuration) defer cancel() - rows, err := db.QueryxContext(ctx, query, args...) + rows, err := db.QueryxContext(app, ctx, query, args...) if err != nil { return nil, []string{}, err } @@ -88,20 +89,20 @@ func setSlowQueryMetrics(i *integration.Integration, metrics []utils.SlowQueryMe } // PopulateIndividualQueryDetails collects and sets individual query details -func PopulateIndividualQueryDetails(db utils.DataSource, queryIDList []string, i *integration.Integration, e *integration.Entity, args arguments.ArgumentList) []utils.QueryGroup { - currentQueryMetrics, currentQueryMetricsErr := currentQueryMetrics(db, queryIDList, args) +func PopulateIndividualQueryDetails(app *newrelic.Application, db utils.DataSource, queryIDList []string, i *integration.Integration, e *integration.Entity, args arguments.ArgumentList) []utils.QueryGroup { + currentQueryMetrics, currentQueryMetricsErr := currentQueryMetrics(app, db, queryIDList, args) if currentQueryMetricsErr != nil { log.Error("Failed to collect current query metrics: %v", currentQueryMetricsErr) return nil } - recentQueryList, recentQueryErr := recentQueryMetrics(db, queryIDList, args) + recentQueryList, recentQueryErr := recentQueryMetrics(app, db, queryIDList, args) if recentQueryErr != nil { log.Error("Failed to collect recent query metrics: %v", recentQueryErr) return nil } - extensiveQueryList, extensiveQueryErr := extensiveQueryMetrics(db, queryIDList, args) + extensiveQueryList, extensiveQueryErr := extensiveQueryMetrics(app, db, queryIDList, args) if extensiveQueryErr != nil { log.Error("Failed to collect history query metrics: %v", extensiveQueryErr) return nil @@ -152,8 +153,8 @@ func groupQueriesByDatabase(filteredList []utils.IndividualQueryMetrics) []utils } // currentQueryMetrics collects current query metrics from the performance schema database for the given query IDs -func currentQueryMetrics(db utils.DataSource, queryIDList []string, args arguments.ArgumentList) ([]utils.IndividualQueryMetrics, error) { - metrics, err := collectIndividualQueryMetrics(db, queryIDList, utils.CurrentRunningQueriesSearch, args) +func currentQueryMetrics(app *newrelic.Application, db utils.DataSource, queryIDList []string, args arguments.ArgumentList) ([]utils.IndividualQueryMetrics, error) { + metrics, err := collectIndividualQueryMetrics(app, db, queryIDList, utils.CurrentRunningQueriesSearch, args) if err != nil { return nil, err } @@ -162,8 +163,8 @@ func currentQueryMetrics(db utils.DataSource, queryIDList []string, args argumen } // recentQueryMetrics collects recent query metrics from the performance schema database for the given query IDs -func recentQueryMetrics(db utils.DataSource, queryIDList []string, args arguments.ArgumentList) ([]utils.IndividualQueryMetrics, error) { - metrics, err := collectIndividualQueryMetrics(db, queryIDList, utils.RecentQueriesSearch, args) +func recentQueryMetrics(app *newrelic.Application, db utils.DataSource, queryIDList []string, args arguments.ArgumentList) ([]utils.IndividualQueryMetrics, error) { + metrics, err := collectIndividualQueryMetrics(app, db, queryIDList, utils.RecentQueriesSearch, args) if err != nil { return nil, err } @@ -172,8 +173,8 @@ func recentQueryMetrics(db utils.DataSource, queryIDList []string, args argument } // extensiveQueryMetrics collects extensive query metrics from the performance schema database for the given query IDs -func extensiveQueryMetrics(db utils.DataSource, queryIDList []string, args arguments.ArgumentList) ([]utils.IndividualQueryMetrics, error) { - metrics, err := collectIndividualQueryMetrics(db, queryIDList, utils.PastQueriesSearch, args) +func extensiveQueryMetrics(app *newrelic.Application, db utils.DataSource, queryIDList []string, args arguments.ArgumentList) ([]utils.IndividualQueryMetrics, error) { + metrics, err := collectIndividualQueryMetrics(app, db, queryIDList, utils.PastQueriesSearch, args) if err != nil { return nil, err } @@ -182,7 +183,7 @@ func extensiveQueryMetrics(db utils.DataSource, queryIDList []string, args argum } // collectIndividualQueryMetrics collects current query metrics from the performance schema database for the given query IDs -func collectIndividualQueryMetrics(db utils.DataSource, queryIDList []string, queryString string, args arguments.ArgumentList) ([]utils.IndividualQueryMetrics, error) { +func collectIndividualQueryMetrics(app *newrelic.Application, db utils.DataSource, queryIDList []string, queryString string, args arguments.ArgumentList) ([]utils.IndividualQueryMetrics, error) { // Early exit if queryIDList is empty if len(queryIDList) == 0 { log.Warn("queryIDList is empty") @@ -193,16 +194,16 @@ func collectIndividualQueryMetrics(db utils.DataSource, queryIDList []string, qu for _, queryID := range queryIDList { // Combine queryID and thresholds into args - args := []interface{}{queryID, args.QueryResponseTimeThreshold, min(constants.IndividualQueryCountThreshold, args.QueryCountThreshold)} + inputArgs := []interface{}{queryID, args.QueryResponseTimeThreshold, min(constants.IndividualQueryCountThreshold, args.QueryCountThreshold)} // Use sqlx.In to safely include the slices in the query - query, args, err := sqlx.In(queryString, args...) + query, preparedArgs, err := sqlx.In(queryString, inputArgs...) if err != nil { return []utils.IndividualQueryMetrics{}, err } // Collect the individual query metrics - metrics, err := utils.CollectMetrics[utils.IndividualQueryMetrics](db, query, args...) + metrics, err := utils.CollectMetrics[utils.IndividualQueryMetrics](app, db, query, preparedArgs...) if err != nil { return []utils.IndividualQueryMetrics{}, err } diff --git a/src/query-performance-monitoring/performance-metrics-collectors/query_execution_plan.go b/src/query-performance-monitoring/performance-metrics-collectors/query_execution_plan.go index 70560e20..9c45cb1a 100644 --- a/src/query-performance-monitoring/performance-metrics-collectors/query_execution_plan.go +++ b/src/query-performance-monitoring/performance-metrics-collectors/query_execution_plan.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/bitly/go-simplejson" + "github.com/newrelic/go-agent/v3/newrelic" "github.com/newrelic/infra-integrations-sdk/v3/integration" "github.com/newrelic/infra-integrations-sdk/v3/log" arguments "github.com/newrelic/nri-mysql/src/args" @@ -16,7 +17,7 @@ import ( ) // PopulateExecutionPlans populates execution plans for the given queries. -func PopulateExecutionPlans(db utils.DataSource, queryGroups []utils.QueryGroup, i *integration.Integration, e *integration.Entity, args arguments.ArgumentList) { +func PopulateExecutionPlans(app *newrelic.Application, db utils.DataSource, queryGroups []utils.QueryGroup, i *integration.Integration, e *integration.Entity, args arguments.ArgumentList) { var events []utils.QueryPlanMetrics for _, group := range queryGroups { @@ -27,7 +28,7 @@ func PopulateExecutionPlans(db utils.DataSource, queryGroups []utils.QueryGroup, defer db.Close() for _, query := range group.Queries { - tableIngestionDataList, err := processExecutionPlanMetrics(db, query) + tableIngestionDataList, err := processExecutionPlanMetrics(app, db, query) if err != nil { log.Error("Error processing execution plan metrics: %v", err) } @@ -47,7 +48,7 @@ func PopulateExecutionPlans(db utils.DataSource, queryGroups []utils.QueryGroup, } // processExecutionPlanMetrics processes the execution plan metrics for a given query. -func processExecutionPlanMetrics(db utils.DataSource, query utils.IndividualQueryMetrics) ([]utils.QueryPlanMetrics, error) { +func processExecutionPlanMetrics(app *newrelic.Application, db utils.DataSource, query utils.IndividualQueryMetrics) ([]utils.QueryPlanMetrics, error) { ctx, cancel := context.WithTimeout(context.Background(), constants.QueryPlanTimeoutDuration) defer cancel() @@ -72,7 +73,7 @@ func processExecutionPlanMetrics(db utils.DataSource, query utils.IndividualQuer // Execute the EXPLAIN query execPlanQuery := fmt.Sprintf(constants.ExplainQueryFormat, queryText) - rows, err := db.QueryxContext(ctx, execPlanQuery) + rows, err := db.QueryxContext(app, ctx, execPlanQuery) if err != nil { return []utils.QueryPlanMetrics{}, err } diff --git a/src/query-performance-monitoring/performance-metrics-collectors/wait_event_details.go b/src/query-performance-monitoring/performance-metrics-collectors/wait_event_details.go index b8819396..9a4183c6 100644 --- a/src/query-performance-monitoring/performance-metrics-collectors/wait_event_details.go +++ b/src/query-performance-monitoring/performance-metrics-collectors/wait_event_details.go @@ -2,6 +2,7 @@ package performancemetricscollectors import ( "github.com/jmoiron/sqlx" + "github.com/newrelic/go-agent/v3/newrelic" "github.com/newrelic/infra-integrations-sdk/v3/integration" "github.com/newrelic/infra-integrations-sdk/v3/log" arguments "github.com/newrelic/nri-mysql/src/args" @@ -10,7 +11,7 @@ import ( ) // PopulateWaitEventMetrics retrieves wait event metrics from the database and sets them in the integration. -func PopulateWaitEventMetrics(db utils.DataSource, i *integration.Integration, e *integration.Entity, args arguments.ArgumentList, excludedDatabases []string) { +func PopulateWaitEventMetrics(app *newrelic.Application, db utils.DataSource, i *integration.Integration, e *integration.Entity, args arguments.ArgumentList, excludedDatabases []string) { // Prepare the arguments for the query excludedDatabasesArgs := []interface{}{excludedDatabases, excludedDatabases, min(args.QueryCountThreshold, constants.MaxQueryCountThreshold)} @@ -21,7 +22,7 @@ func PopulateWaitEventMetrics(db utils.DataSource, i *integration.Integration, e } // Collect the wait event metrics - metrics, err := utils.CollectMetrics[utils.WaitEventQueryMetrics](db, preparedQuery, preparedArgs...) + metrics, err := utils.CollectMetrics[utils.WaitEventQueryMetrics](app, db, preparedQuery, preparedArgs...) if err != nil { log.Error("Error collecting wait event metrics: %v", err) } diff --git a/src/query-performance-monitoring/performance_main.go b/src/query-performance-monitoring/performance_main.go index 3d2de36b..9ef67ab0 100644 --- a/src/query-performance-monitoring/performance_main.go +++ b/src/query-performance-monitoring/performance_main.go @@ -2,11 +2,14 @@ package queryperformancemonitoring import ( "fmt" + "os" "time" + "github.com/newrelic/go-agent/v3/newrelic" "github.com/newrelic/infra-integrations-sdk/v3/integration" "github.com/newrelic/infra-integrations-sdk/v3/log" arguments "github.com/newrelic/nri-mysql/src/args" + mysqlapm "github.com/newrelic/nri-mysql/src/query-performance-monitoring/mysql-apm" performancemetricscollectors "github.com/newrelic/nri-mysql/src/query-performance-monitoring/performance-metrics-collectors" utils "github.com/newrelic/nri-mysql/src/query-performance-monitoring/utils" validator "github.com/newrelic/nri-mysql/src/query-performance-monitoring/validator" @@ -16,6 +19,32 @@ import ( func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration.Entity, i *integration.Integration) { var database string + mysqlapm.ArgsGlobal = args.LicenseKey + app, err := newrelic.NewApplication( + newrelic.ConfigAppName("nri-mysql-integration"), + newrelic.ConfigLicense(args.LicenseKey), + newrelic.ConfigDebugLogger(os.Stdout), + newrelic.ConfigDatastoreRawQuery(true), + ) + if err != nil { + log.Error("Error creating new relic application: %s", err.Error()) + } + defer app.Shutdown(10 * time.Second) + + // Ensure the application is connected + if err := app.WaitForConnection(10 * time.Second); err != nil { + log.Debug("New Relic Application did not connect:", err) + return + } + + // Log application connection status + if app != nil { + log.Debug("New Relic application initialized successfully") + mysqlapm.NewrelicApp = *app + } else { + log.Error("New Relic application initialization failed") + } + // Generate Data Source Name (DSN) for database connection dsn := utils.GenerateDSN(args, database) @@ -35,34 +64,69 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration // Populate metrics for slow queries start := time.Now() + slowQueriesTxn := app.StartTransaction("MysqlSlowQueriesSample") + defer slowQueriesTxn.End() + if slowQueriesTxn == nil { + log.Error("Failed to start New Relic transaction for slow queries") + return + } + mysqlapm.Txn = slowQueriesTxn log.Debug("Beginning to retrieve slow query metrics") - queryIDList := performancemetricscollectors.PopulateSlowQueryMetrics(i, e, db, args, excludedDatabases) + queryIDList := performancemetricscollectors.PopulateSlowQueryMetrics(app, i, e, db, args, excludedDatabases) log.Debug("Completed fetching slow query metrics in %v", time.Since(start)) if len(queryIDList) > 0 { // Populate metrics for individual queries start = time.Now() + individualTxn := app.StartTransaction("MysqlIndividualQueriesSample") + if individualTxn == nil { + log.Error("Failed to start New Relic transaction for individual queries") + return + } + mysqlapm.Txn = individualTxn log.Debug("Beginning to retrieve individual query metrics") - groupQueriesByDatabase := performancemetricscollectors.PopulateIndividualQueryDetails(db, queryIDList, i, e, args) + groupQueriesByDatabase := performancemetricscollectors.PopulateIndividualQueryDetails(app, db, queryIDList, i, e, args) log.Debug("Completed fetching individual query metrics in %v", time.Since(start)) + defer individualTxn.End() // Populate execution plan details start = time.Now() + execPlanTxn := app.StartTransaction("MysqlQueryExecutionSample") + if execPlanTxn == nil { + log.Error("Failed to start New Relic transaction for query execution plans") + return + } + mysqlapm.Txn = execPlanTxn log.Debug("Beginning to retrieve query execution plan metrics") - performancemetricscollectors.PopulateExecutionPlans(db, groupQueriesByDatabase, i, e, args) + performancemetricscollectors.PopulateExecutionPlans(app, db, groupQueriesByDatabase, i, e, args) log.Debug("Completed fetching query execution plan metrics in %v", time.Since(start)) + defer execPlanTxn.End() } // Populate wait event metrics start = time.Now() + waitEventsTxn := app.StartTransaction("MysqlWaitEventsSample") + if waitEventsTxn == nil { + log.Error("Failed to start New Relic transaction for wait events") + return + } + mysqlapm.Txn = waitEventsTxn log.Debug("Beginning to retrieve wait event metrics") - performancemetricscollectors.PopulateWaitEventMetrics(db, i, e, args, excludedDatabases) + performancemetricscollectors.PopulateWaitEventMetrics(app, db, i, e, args, excludedDatabases) log.Debug("Completed fetching wait event metrics in %v", time.Since(start)) + defer waitEventsTxn.End() // Populate blocking session metrics start = time.Now() + blockingSessionsTxn := app.StartTransaction("MysqlBlockingSessionSample") + if blockingSessionsTxn == nil { + log.Error("Failed to start New Relic transaction for blocking sessions") + return + } + mysqlapm.Txn = blockingSessionsTxn log.Debug("Beginning to retrieve blocking session metrics") - performancemetricscollectors.PopulateBlockingSessionMetrics(db, i, e, args, excludedDatabases) + performancemetricscollectors.PopulateBlockingSessionMetrics(app, db, i, e, args, excludedDatabases) log.Debug("Completed fetching blocking session metrics in %v", time.Since(start)) log.Debug("Query analysis completed.") + defer blockingSessionsTxn.End() } diff --git a/src/query-performance-monitoring/utils/database.go b/src/query-performance-monitoring/utils/database.go index 100e923f..bb4e43bd 100644 --- a/src/query-performance-monitoring/utils/database.go +++ b/src/query-performance-monitoring/utils/database.go @@ -5,19 +5,23 @@ import ( "fmt" "net" "net/url" + "os" "strconv" + "time" - _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" + _ "github.com/newrelic/go-agent/v3/integrations/nrmysql" + "github.com/newrelic/go-agent/v3/newrelic" "github.com/newrelic/infra-integrations-sdk/v3/log" arguments "github.com/newrelic/nri-mysql/src/args" constants "github.com/newrelic/nri-mysql/src/query-performance-monitoring/constants" + mysqlapm "github.com/newrelic/nri-mysql/src/query-performance-monitoring/mysql-apm" ) type DataSource interface { Close() QueryX(string) (*sqlx.Rows, error) - QueryxContext(ctx context.Context, query string, args ...interface{}) (*sqlx.Rows, error) + QueryxContext(app *newrelic.Application, ctx context.Context, query string, args ...interface{}) (*sqlx.Rows, error) } type Database struct { @@ -25,7 +29,7 @@ type Database struct { } func OpenDB(dsn string) (DataSource, error) { - source, err := sqlx.Open("mysql", dsn) + source, err := sqlx.Open("nrmysql", dsn) if err != nil { return nil, fmt.Errorf("error opening DSN: %w", err) } @@ -54,7 +58,35 @@ func fatalIfErr(err error) { } // QueryxContext method implementation -func (db *Database) QueryxContext(ctx context.Context, query string, args ...interface{}) (*sqlx.Rows, error) { +func (db *Database) QueryxContext(app *newrelic.Application, ctx context.Context, query string, args ...interface{}) (*sqlx.Rows, error) { + // Initialize New Relic application + if app == nil { + var err error + app, err = newrelic.NewApplication( + newrelic.ConfigAppName("nri-mysql-integration"), + newrelic.ConfigLicense(mysqlapm.ArgsGlobal), + newrelic.ConfigDebugLogger(os.Stdout), + newrelic.ConfigDatastoreRawQuery(true), + ) + if err != nil { + log.Error("Error creating new relic application: %s", err.Error()) + return nil, err + } + } + waitErr := app.WaitForConnection(5 * time.Second) + if waitErr != nil { + log.Error("Error waiting for connection: %s", waitErr.Error()) + return nil, waitErr + } + + ctx = newrelic.NewContext(ctx, mysqlapm.Txn) + s := newrelic.DatastoreSegment{ + StartTime: mysqlapm.Txn.StartSegmentNow(), + Product: newrelic.DatastoreMySQL, + Operation: "SELECT", + ParameterizedQuery: query, + } + defer s.End() return db.source.QueryxContext(ctx, query, args...) } @@ -97,11 +129,11 @@ func determineDatabase(args arguments.ArgumentList, database string) string { } // collectMetrics collects metrics from the performance schema database -func CollectMetrics[T any](db DataSource, preparedQuery string, preparedArgs ...interface{}) ([]T, error) { +func CollectMetrics[T any](app *newrelic.Application, db DataSource, preparedQuery string, preparedArgs ...interface{}) ([]T, error) { ctx, cancel := context.WithTimeout(context.Background(), constants.TimeoutDuration) defer cancel() - rows, err := db.QueryxContext(ctx, preparedQuery, preparedArgs...) + rows, err := db.QueryxContext(app, ctx, preparedQuery, preparedArgs...) if err != nil { return []T{}, err }