From 357a58dbb6fb1809cc453d01721a1094aa118022 Mon Sep 17 00:00:00 2001 From: spathlavath Date: Wed, 8 Jan 2025 19:12:39 +0530 Subject: [PATCH 01/32] feat: Introducing query performance monitoring for slow queries, blocking sessions and wait events (#7) * feat: Introducing query performance monitoring for slow queries, blocking sessions and wait events. * refactor: Implemented a limit for wait events, blocking sessions, and included bug fixes (#8) * refactor: Implemented a limit for wait events, blocking sessions, and included bug fixes. * Included a stepID for each row during the query execution iteration * Added fix for stepID * Added a fix to ensure that increments correctly. * Renamed FETCH_INTERVAL to SLOW_QUERY_FETCH_INTERVAL * Added detailed logging for each operation, including the time taken for execution. * refactor: Added configuration option to disable query performance metrics per database (#10) * refactor: Added configuration option to disable query performance metrics per database * Revised the list of input arguments for retrieving individual queries. * Updated logging messages and Revised the list of input arguments for retrieving wait events and blocking session queries. * Added a helper function to obtain a list of unique databases to exclude. * code refactoring * Added fix for number of arguments mismatch for the SQL query * removed rebind functionality * updated metricset limit * reverted metricset limit * minor code refactoring * fixed linting errors * fixed linting errors * refactor: resolving linting errors (#12) * refactor: resolving linting errors * fixing linting errors * fixing linting errors * fixing linting errors * fixing linting errors * fixing linting errors * refactor: resolving linting errors (#13) * refactor: changed log.info to log.debug and other bug fixes (#14) * refactor: code refactoring and addressing review comments (#15) * refactor: code refactoring and addressing review comments * lint issue fixes * lint issue fixes * lint issue fixes * lint issue fixes * lint issue fixes * refactor: Added a limit on individual query details and defined min/max values for the limit threshold. (#16) * refactor: Added a limit on individual query details and defined min/max values for the limit threshold. * minor enhancements * minor enhancements * minor enhancements * refactor: code resturcturing (#17) * refactor: code resturcturing * file name changes * Blocking sessions query update * Blocking sessions query update * package changes * Added code review fixes * Added code review fixes * Added code review fixes * Added code review fixes * Added code review fixes * Added code review fixes * Added code review fixes * query execution plan changes * file name changes * Added code review fixes --- go.mod | 3 +- go.sum | 8 +- mysql-config.yml.sample | 15 +- src/args/argument_list.go | 27 ++ src/mysql.go | 41 +-- .../constants/constants.go | 16 ++ .../blocking_sessions.go | 50 ++++ .../query_details.go | 205 +++++++++++++++ .../query_execution_plan.go | 245 +++++++++++++++++ .../wait_event_details.go | 52 ++++ .../performance_main.go | 71 +++++ .../utils/database.go | 123 +++++++++ .../utils/helpers.go | 233 ++++++++++++++++ .../utils/models.go | 94 +++++++ .../utils/queries.go | 246 +++++++++++++++++ .../validator/validations.go | 248 ++++++++++++++++++ 16 files changed, 1646 insertions(+), 31 deletions(-) create mode 100644 src/args/argument_list.go create mode 100644 src/query-performance-monitoring/constants/constants.go create mode 100644 src/query-performance-monitoring/performance-metrics-collectors/blocking_sessions.go create mode 100644 src/query-performance-monitoring/performance-metrics-collectors/query_details.go create mode 100644 src/query-performance-monitoring/performance-metrics-collectors/query_execution_plan.go create mode 100644 src/query-performance-monitoring/performance-metrics-collectors/wait_event_details.go create mode 100644 src/query-performance-monitoring/performance_main.go create mode 100644 src/query-performance-monitoring/utils/database.go create mode 100644 src/query-performance-monitoring/utils/helpers.go create mode 100644 src/query-performance-monitoring/utils/models.go create mode 100644 src/query-performance-monitoring/utils/queries.go create mode 100644 src/query-performance-monitoring/validator/validations.go diff --git a/go.mod b/go.mod index cdd93798..269a250f 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,10 @@ go 1.23.4 require ( github.com/bitly/go-simplejson v0.5.1 github.com/go-sql-driver/mysql v1.8.1 + github.com/jmoiron/sqlx v1.4.0 github.com/newrelic/infra-integrations-sdk/v3 v3.9.1 github.com/sirupsen/logrus v1.9.3 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.9.0 github.com/xeipuuv/gojsonschema v1.2.0 ) diff --git a/go.sum b/go.sum index 445b95c6..01fc2116 100644 --- a/go.sum +++ b/go.sum @@ -8,10 +8,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +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/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= @@ -25,8 +31,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= diff --git a/mysql-config.yml.sample b/mysql-config.yml.sample index c732f500..cbf79a4e 100644 --- a/mysql-config.yml.sample +++ b/mysql-config.yml.sample @@ -34,7 +34,20 @@ integrations: # versus remote entities: # https://github.com/newrelic/infra-integrations-sdk/blob/master/docs/entity-definition.md REMOTE_MONITORING: true - interval: 30s + + # Enable query performance monitoring + ENABLE_QUERY_PERFORMANCE: true + # Fetch interval in seconds for grouped slow queries. Should match the interval in mysql-config.yml + SLOW_QUERY_FETCH_INTERVAL: 15 + # Threshold in milliseconds for query response time to fetch individual query performance metrics. + QUERY_RESPONSE_TIME_THRESHOLD: 500 + # Query count limit for fetching grouped slow and individual query performance metrics. + QUERY_COUNT_THRESHOLD: 20 + # Example: + # EXCLUDED_DATABASES: '["employees","azure_sys"]' + # A JSON array that list databases that will be excluded from collection. system databases are excluded by default. + EXCLUDED_DATABASES: '[]' + interval: 15s labels: env: production role: write-replica diff --git a/src/args/argument_list.go b/src/args/argument_list.go new file mode 100644 index 00000000..f4d46367 --- /dev/null +++ b/src/args/argument_list.go @@ -0,0 +1,27 @@ +package args + +import sdk_args "github.com/newrelic/infra-integrations-sdk/v3/args" + +type ArgumentList struct { + sdk_args.DefaultArgumentList + Hostname string `default:"localhost" help:"Hostname or IP address where MySQL is running."` + Port int `default:"3306" help:"Port number on which MySQL server is listening."` + Socket string `default:"" help:"Path to the MySQL socket file."` + Username string `default:"root" help:"Username for database access."` + Password string `default:"password" help:"Password for the specified user."` + Database string `help:"Name of the database."` + ExtraConnectionURLArgs string `help:"Additional connection parameters in the format attr1=val1&attr2=val2."` // https://github.com/go-sql-driver/mysql#parameters + InsecureSkipVerify bool `default:"false" help:"Skip TLS certificate verification when connecting."` + EnableTLS bool `default:"false" help:"Use a secure (TLS) connection."` + RemoteMonitoring bool `default:"false" help:"Indicates if the monitored entity is remote. Set to true if unsure."` + ExtendedMetrics bool `default:"false" help:"Enable collection of extended metrics."` + ExtendedInnodbMetrics bool `default:"false" help:"Enable collection of extended InnoDB metrics."` + ExtendedMyIsamMetrics bool `default:"false" help:"Enable collection of extended MyISAM metrics."` + OldPasswords bool `default:"false" help:"Allow the use of old passwords: https://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_old_passwords"` + ShowVersion bool `default:"false" help:"Display build information and exit."` + EnableQueryPerformance bool `default:"false" help:"Enable query performance monitoring."` + SlowQueryFetchInterval int `default:"15" help:"Fetch interval in seconds for grouped slow queries. Should match the interval in mysql-config.yml."` + 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."` +} diff --git a/src/mysql.go b/src/mysql.go index 46eb4b0e..763c6855 100644 --- a/src/mysql.go +++ b/src/mysql.go @@ -3,6 +3,12 @@ package main import ( "fmt" + + "github.com/newrelic/infra-integrations-sdk/v3/data/attribute" + "github.com/newrelic/infra-integrations-sdk/v3/data/metric" + "github.com/newrelic/infra-integrations-sdk/v3/integration" + "github.com/newrelic/infra-integrations-sdk/v3/log" + "net" "net/url" "os" @@ -10,11 +16,8 @@ import ( "strconv" "strings" - sdk_args "github.com/newrelic/infra-integrations-sdk/v3/args" - "github.com/newrelic/infra-integrations-sdk/v3/data/attribute" - "github.com/newrelic/infra-integrations-sdk/v3/data/metric" - "github.com/newrelic/infra-integrations-sdk/v3/integration" - "github.com/newrelic/infra-integrations-sdk/v3/log" + arguments "github.com/newrelic/nri-mysql/src/args" + queryperformancemonitoring "github.com/newrelic/nri-mysql/src/query-performance-monitoring" ) const ( @@ -22,26 +25,7 @@ const ( nodeEntityType = "node" ) -type argumentList struct { - sdk_args.DefaultArgumentList - Hostname string `default:"localhost" help:"Hostname or IP where MySQL is running."` - Port int `default:"3306" help:"Port on which MySQL server is listening."` - Socket string `default:"" help:"MySQL Socket file."` - Username string `help:"Username for accessing the database."` - Password string `help:"Password for the given user."` - Database string `help:"Database name"` - ExtraConnectionURLArgs string `help:"Specify extra connection parameters as attr1=val1&attr2=val2."` // https://github.com/go-sql-driver/mysql#parameters - InsecureSkipVerify bool `default:"false" help:"Skip verification of the server's certificate when using TLS with the connection."` - EnableTLS bool `default:"false" help:"Use a secure (TLS) connection."` - RemoteMonitoring bool `default:"false" help:"Identifies the monitored entity as 'remote'. In doubt: set to true"` - ExtendedMetrics bool `default:"false" help:"Enable extended metrics"` - ExtendedInnodbMetrics bool `default:"false" help:"Enable InnoDB extended metrics"` - ExtendedMyIsamMetrics bool `default:"false" help:"Enable MyISAM extended metrics"` - OldPasswords bool `default:"false" help:"Allow old passwords: https://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_old_passwords"` - ShowVersion bool `default:"false" help:"Print build information and exit"` -} - -func generateDSN(args argumentList) string { +func generateDSN(args arguments.ArgumentList) string { // Format query parameters query := url.Values{} if args.OldPasswords { @@ -73,7 +57,7 @@ func generateDSN(args argumentList) string { } var ( - args argumentList + args arguments.ArgumentList integrationVersion = "0.0.0" gitCommit = "" buildDate = "" @@ -134,8 +118,11 @@ func main() { ) populateMetrics(ms, rawMetrics) } - fatalIfErr(i.Publish()) + // New functionality + if args.EnableQueryPerformance { + queryperformancemonitoring.PopulateQueryPerformanceMetrics(args, e, i) + } } func metricSet(e *integration.Entity, eventType, hostname string, port int, remoteMonitoring bool) *metric.Set { diff --git a/src/query-performance-monitoring/constants/constants.go b/src/query-performance-monitoring/constants/constants.go new file mode 100644 index 00000000..688445ec --- /dev/null +++ b/src/query-performance-monitoring/constants/constants.go @@ -0,0 +1,16 @@ +package constants + +import "time" + +const ( + IntegrationName = "com.newrelic.mysql" + NodeEntityType = "node" + MetricSetLimit = 100 + ExplainQueryFormat = "EXPLAIN FORMAT=JSON %s" + SupportedStatements = "SELECT INSERT UPDATE DELETE WITH" + QueryPlanTimeoutDuration = 10 * time.Second + TimeoutDuration = 5 * time.Second // TimeoutDuration defines the timeout duration for database queries + MaxQueryCountThreshold = 30 + IndividualQueryCountThreshold = 10 + MinVersionParts = 2 +) diff --git a/src/query-performance-monitoring/performance-metrics-collectors/blocking_sessions.go b/src/query-performance-monitoring/performance-metrics-collectors/blocking_sessions.go new file mode 100644 index 00000000..a56d0918 --- /dev/null +++ b/src/query-performance-monitoring/performance-metrics-collectors/blocking_sessions.go @@ -0,0 +1,50 @@ +package performancemetricscollectors + +import ( + "github.com/jmoiron/sqlx" + "github.com/newrelic/infra-integrations-sdk/v3/integration" + "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" + utils "github.com/newrelic/nri-mysql/src/query-performance-monitoring/utils" +) + +// 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) { + // Prepare the SQL query with the provided parameters + query, inputArgs, err := sqlx.In(utils.BlockingSessionsQuery, excludedDatabases, min(args.QueryCountThreshold, constants.MaxQueryCountThreshold)) + if err != nil { + log.Error("Failed to prepare blocking sessions query: %v", err) + } + + // Collect the blocking session metrics + metrics, err := utils.CollectMetrics[utils.BlockingSessionMetrics](db, query, inputArgs...) + if err != nil { + log.Error("Error collecting blocking session metrics: %v", err) + } + + // Return if no metrics are collected + if len(metrics) == 0 { + return + } + + // Set the blocking query metrics in the integration entity + err = setBlockingQueryMetrics(metrics, i, args) + if err != nil { + log.Error("Error setting blocking session metrics: %v", err) + } +} + +// setBlockingQueryMetrics sets the blocking session metrics into the integration entity. +func setBlockingQueryMetrics(metrics []utils.BlockingSessionMetrics, i *integration.Integration, args arguments.ArgumentList) error { + metricList := make([]interface{}, 0, len(metrics)) + for _, metricData := range metrics { + metricList = append(metricList, metricData) + } + + err := utils.IngestMetric(metricList, "MysqlBlockingSessionSample", i, args) + if err != nil { + return err + } + return nil +} diff --git a/src/query-performance-monitoring/performance-metrics-collectors/query_details.go b/src/query-performance-monitoring/performance-metrics-collectors/query_details.go new file mode 100644 index 00000000..57f97519 --- /dev/null +++ b/src/query-performance-monitoring/performance-metrics-collectors/query_details.go @@ -0,0 +1,205 @@ +package performancemetricscollectors + +import ( + "context" + + "github.com/jmoiron/sqlx" + "github.com/newrelic/infra-integrations-sdk/v3/integration" + "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" + utils "github.com/newrelic/nri-mysql/src/query-performance-monitoring/utils" +) + +// 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) + if err != nil { + log.Error("Failed to collect slow query metrics: %v", err) + return []string{} + } + + // Return if no metrics are collected + if len(rawMetrics) == 0 { + return []string{} + } + + err = setSlowQueryMetrics(i, rawMetrics, args) + if err != nil { + log.Error("Failed to set slow query metrics: %v", err) + return []string{} + } + + return queryIDList +} + +// 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) { + // Prepare the SQL query with the provided parameters + query, args, err := sqlx.In(utils.SlowQueries, slowQueryfetchInterval, excludedDatabases, min(queryCountThreshold, constants.MaxQueryCountThreshold)) + if err != nil { + return nil, []string{}, err + } + + ctx, cancel := context.WithTimeout(context.Background(), constants.TimeoutDuration) + defer cancel() + rows, err := db.QueryxContext(ctx, query, args...) + if err != nil { + return nil, []string{}, err + } + defer rows.Close() + + var metrics []utils.SlowQueryMetrics + var qIDList []string + for rows.Next() { + var metric utils.SlowQueryMetrics + var qID string + if err := rows.StructScan(&metric); err != nil { + return nil, []string{}, err + } + qID = *metric.QueryID + qIDList = append(qIDList, qID) + metrics = append(metrics, metric) + } + + if err := rows.Err(); err != nil { + return nil, []string{}, err + } + + return metrics, qIDList, nil +} + +// setSlowQueryMetrics sets the collected slow query metrics to the integration +func setSlowQueryMetrics(i *integration.Integration, metrics []utils.SlowQueryMetrics, args arguments.ArgumentList) error { + metricList := make([]interface{}, 0, len(metrics)) + for _, metricData := range metrics { + metricList = append(metricList, metricData) + } + + err := utils.IngestMetric(metricList, "MysqlSlowQueriesSample", i, args) + if err != nil { + return err + } + return nil +} + +// 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) + if currentQueryMetricsErr != nil { + log.Error("Failed to collect current query metrics: %v", currentQueryMetricsErr) + return nil + } + + recentQueryList, recentQueryErr := recentQueryMetrics(db, queryIDList, args) + if recentQueryErr != nil { + log.Error("Failed to collect recent query metrics: %v", recentQueryErr) + return nil + } + + extensiveQueryList, extensiveQueryErr := extensiveQueryMetrics(db, queryIDList, args) + if extensiveQueryErr != nil { + log.Error("Failed to collect history query metrics: %v", extensiveQueryErr) + return nil + } + + queryList := append(append(currentQueryMetrics, recentQueryList...), extensiveQueryList...) + newMetricsList := make([]utils.IndividualQueryMetrics, len(queryList)) + copy(newMetricsList, queryList) + metricList := make([]interface{}, 0, len(newMetricsList)) + for i := range newMetricsList { + newMetricsList[i].QueryText = nil + metricList = append(metricList, newMetricsList[i]) + } + + err := utils.IngestMetric(metricList, "MysqlIndividualQueriesSample", i, args) + if err != nil { + log.Error("Failed to ingest individual query metrics: %v", err) + return nil + } + groupQueriesByDatabase := groupQueriesByDatabase(queryList) + + return groupQueriesByDatabase +} + +// groupQueriesByDatabase groups queries by their database name +func groupQueriesByDatabase(filteredList []utils.IndividualQueryMetrics) []utils.QueryGroup { + groupMap := make(map[string][]utils.IndividualQueryMetrics) + + for _, query := range filteredList { + groupMap[*query.DatabaseName] = append(groupMap[*query.DatabaseName], query) + } + + // Pre-allocate the slice with the length of the groupMap + groupedQueries := make([]utils.QueryGroup, 0, len(groupMap)) + for dbName, queries := range groupMap { + groupedQueries = append(groupedQueries, utils.QueryGroup{ + Database: dbName, + Queries: queries, + }) + } + + return groupedQueries +} + +// 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) + if err != nil { + return nil, err + } + + return metrics, nil +} + +// 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) + if err != nil { + return nil, err + } + + return metrics, nil +} + +// 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) + if err != nil { + return nil, err + } + + return metrics, nil +} + +// 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) { + // Early exit if queryIDList is empty + if len(queryIDList) == 0 { + log.Warn("queryIDList is empty") + return []utils.IndividualQueryMetrics{}, nil + } + + var metricsList []utils.IndividualQueryMetrics + + for _, queryID := range queryIDList { + // Combine queryID and thresholds into args + args := []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...) + if err != nil { + return []utils.IndividualQueryMetrics{}, err + } + + // Collect the individual query metrics + metrics, err := utils.CollectMetrics[utils.IndividualQueryMetrics](db, query, args...) + if err != nil { + return []utils.IndividualQueryMetrics{}, err + } + + metricsList = append(metricsList, metrics...) + } + + return metricsList, nil +} 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 new file mode 100644 index 00000000..bb9bda80 --- /dev/null +++ b/src/query-performance-monitoring/performance-metrics-collectors/query_execution_plan.go @@ -0,0 +1,245 @@ +package performancemetricscollectors + +import ( + "context" + "encoding/json" + "fmt" + "reflect" + "strings" + + "github.com/bitly/go-simplejson" + "github.com/newrelic/infra-integrations-sdk/v3/integration" + "github.com/newrelic/infra-integrations-sdk/v3/log" + arguments "github.com/newrelic/nri-mysql/src/args" + "github.com/newrelic/nri-mysql/src/query-performance-monitoring/constants" + utils "github.com/newrelic/nri-mysql/src/query-performance-monitoring/utils" +) + +// 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) { + var events []utils.QueryPlanMetrics + + for _, group := range queryGroups { + dsn := utils.GenerateDSN(args, group.Database) + // Open the DB connection + db, err := utils.OpenDB(dsn) + utils.FatalIfErr(err) + defer db.Close() + + for _, query := range group.Queries { + tableIngestionDataList, err := processExecutionPlanMetrics(db, query) + if err != nil { + log.Error("Error processing execution plan metrics: %v", err) + } + events = append(events, tableIngestionDataList...) + } + } + + // Return if no metrics are collected + if len(events) == 0 { + return + } + + err := SetExecutionPlanMetrics(i, args, events) + if err != nil { + log.Error("Error publishing execution plan metrics: %v", err) + } +} + +// processExecutionPlanMetrics processes the execution plan metrics for a given query. +func processExecutionPlanMetrics(db utils.DataSource, query utils.IndividualQueryMetrics) ([]utils.QueryPlanMetrics, error) { + ctx, cancel := context.WithTimeout(context.Background(), constants.QueryPlanTimeoutDuration) + defer cancel() + + if *query.QueryText == "" { + log.Warn("Query text is empty, skipping.") + return []utils.QueryPlanMetrics{}, nil + } + queryText := strings.TrimSpace(*query.QueryText) + upperQueryText := strings.ToUpper(queryText) + + // Check if the query is a supported statement + if !isSupportedStatement(upperQueryText) { + log.Warn("Skipping unsupported query for EXPLAIN: %s", queryText) + return []utils.QueryPlanMetrics{}, nil + } + + // Skip queries with placeholders + if strings.Contains(queryText, "?") { + log.Warn("Skipping query with placeholders for EXPLAIN: %s", queryText) + return []utils.QueryPlanMetrics{}, nil + } + + // Execute the EXPLAIN query + execPlanQuery := fmt.Sprintf(constants.ExplainQueryFormat, queryText) + rows, err := db.QueryxContext(ctx, execPlanQuery) + if err != nil { + return []utils.QueryPlanMetrics{}, err + } + defer rows.Close() + + var execPlanJSON string + if rows.Next() { + err := rows.Scan(&execPlanJSON) + if err != nil { + return []utils.QueryPlanMetrics{}, err + } + } else { + log.Error("No rows returned from EXPLAIN for query '%s'", queryText) + return []utils.QueryPlanMetrics{}, nil + } + + // Extract metrics from the JSON string + dbPerformanceEvents, err := extractMetricsFromJSONString(execPlanJSON, *query.EventID, *query.ThreadID) + if err != nil { + return []utils.QueryPlanMetrics{}, err + } + + return dbPerformanceEvents, nil +} + +// extractMetricsFromJSONString extracts metrics from a JSON string. +func extractMetricsFromJSONString(jsonString string, eventID uint64, threadID uint64) ([]utils.QueryPlanMetrics, error) { + js, err := simplejson.NewJson([]byte(jsonString)) + if err != nil { + log.Error("Error creating simplejson from byte slice: %v", err) + return []utils.QueryPlanMetrics{}, err + } + + memo := utils.Memo{QueryCost: ""} + stepID := 0 + dbPerformanceEvents := make([]utils.QueryPlanMetrics, 0) + dbPerformanceEvents = extractMetrics(js, dbPerformanceEvents, eventID, threadID, memo, &stepID) + + return dbPerformanceEvents, nil +} + +// extractMetrics recursively extracts metrics from a simplejson.Json object. +func extractMetrics(js *simplejson.Json, dbPerformanceEvents []utils.QueryPlanMetrics, eventID uint64, threadID uint64, memo utils.Memo, stepID *int) []utils.QueryPlanMetrics { + tableName, _ := js.Get("table_name").String() + queryCost, _ := js.Get("cost_info").Get("query_cost").String() + accessType, _ := js.Get("access_type").String() + rowsExaminedPerScan, _ := js.Get("rows_examined_per_scan").Int64() + rowsProducedPerJoin, _ := js.Get("rows_produced_per_join").Int64() + filtered, _ := js.Get("filtered").String() + readCost, _ := js.Get("cost_info").Get("read_cost").String() + evalCost, _ := js.Get("cost_info").Get("eval_cost").String() + possibleKeysArray, _ := js.Get("possible_keys").StringArray() + key, _ := js.Get("key").String() + usedKeyPartsArray, _ := js.Get("used_key_parts").StringArray() + refArray, _ := js.Get("ref").StringArray() + + possibleKeys := strings.Join(possibleKeysArray, ",") + usedKeyParts := strings.Join(usedKeyPartsArray, ",") + ref := strings.Join(refArray, ",") + + if queryCost != "" { + memo.QueryCost = queryCost + } + + if tableName != "" || accessType != "" || rowsExaminedPerScan != 0 || rowsProducedPerJoin != 0 || filtered != "" || readCost != "" || evalCost != "" { + dbPerformanceEvents = append(dbPerformanceEvents, utils.QueryPlanMetrics{ + EventID: eventID, + ThreadID: threadID, + QueryCost: memo.QueryCost, + StepID: *stepID, + TableName: tableName, + AccessType: accessType, + RowsExaminedPerScan: rowsExaminedPerScan, + RowsProducedPerJoin: rowsProducedPerJoin, + Filtered: filtered, + ReadCost: readCost, + EvalCost: evalCost, + PossibleKeys: possibleKeys, + Key: key, + UsedKeyParts: usedKeyParts, + Ref: ref, + }) + *stepID++ + } + + if jsMap, _ := js.Map(); jsMap != nil { + dbPerformanceEvents = processMap(jsMap, dbPerformanceEvents, eventID, threadID, memo, stepID) + } + + return dbPerformanceEvents +} + +// processMap processes a map within the JSON object. +func processMap(jsMap map[string]interface{}, dbPerformanceEvents []utils.QueryPlanMetrics, eventID uint64, threadID uint64, memo utils.Memo, stepID *int) []utils.QueryPlanMetrics { + for _, value := range jsMap { + if value != nil { + t := reflect.TypeOf(value) + if t.Kind() == reflect.Map { + dbPerformanceEvents = processMapValue(value, dbPerformanceEvents, eventID, threadID, memo, stepID) + } else if t.Kind() == reflect.Slice { + dbPerformanceEvents = processSliceValue(value, dbPerformanceEvents, eventID, threadID, memo, stepID) + } + } + } + return dbPerformanceEvents +} + +// processMapValue processes a map value within the JSON object. +func processMapValue(value interface{}, dbPerformanceEvents []utils.QueryPlanMetrics, eventID uint64, threadID uint64, memo utils.Memo, stepID *int) []utils.QueryPlanMetrics { + if t := reflect.TypeOf(value); t.Key().Kind() == reflect.String && t.Elem().Kind() == reflect.Interface { + jsBytes, err := json.Marshal(value) + if err != nil { + log.Error("Error marshaling map: %v", err) + } + + convertedSimpleJSON, err := simplejson.NewJson(jsBytes) + if err != nil { + log.Error("Error creating simplejson from byte slice: %v", err) + } + + dbPerformanceEvents = extractMetrics(convertedSimpleJSON, dbPerformanceEvents, eventID, threadID, memo, stepID) + } + return dbPerformanceEvents +} + +// processSliceValue processes a slice value within the JSON object. +func processSliceValue(value interface{}, dbPerformanceEvents []utils.QueryPlanMetrics, eventID uint64, threadID uint64, memo utils.Memo, stepID *int) []utils.QueryPlanMetrics { + for _, element := range value.([]interface{}) { + if elementJSON, ok := element.(map[string]interface{}); ok { + jsBytes, err := json.Marshal(elementJSON) + if err != nil { + log.Error("Error marshaling map: %v", err) + } + + convertedSimpleJSON, err := simplejson.NewJson(jsBytes) + if err != nil { + log.Error("Error creating simplejson from byte slice: %v", err) + } + + dbPerformanceEvents = extractMetrics(convertedSimpleJSON, dbPerformanceEvents, eventID, threadID, memo, stepID) + } + } + return dbPerformanceEvents +} + +// SetExecutionPlanMetrics sets the execution plan metrics. +func SetExecutionPlanMetrics(i *integration.Integration, args arguments.ArgumentList, metrics []utils.QueryPlanMetrics) error { + // Pre-allocate the slice with the length of the metrics slice + metricList := make([]interface{}, 0, len(metrics)) + for _, metricData := range metrics { + metricList = append(metricList, metricData) + } + + err := utils.IngestMetric(metricList, "MysqlQueryExecutionSample", i, args) + if err != nil { + log.Error("Error setting execution plan metrics: %v", err) + return err + } + return nil +} + +// isSupportedStatement checks if the given query is a supported statement. +func isSupportedStatement(query string) bool { + for _, stmt := range strings.Split(constants.SupportedStatements, " ") { + if strings.HasPrefix(query, stmt) { + return true + } + } + return false +} 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 new file mode 100644 index 00000000..970d660b --- /dev/null +++ b/src/query-performance-monitoring/performance-metrics-collectors/wait_event_details.go @@ -0,0 +1,52 @@ +package performancemetricscollectors + +import ( + "github.com/jmoiron/sqlx" + "github.com/newrelic/infra-integrations-sdk/v3/integration" + "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" + utils "github.com/newrelic/nri-mysql/src/query-performance-monitoring/utils" +) + +// 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) { + // Prepare the arguments for the query + excludedDatabasesArgs := []interface{}{excludedDatabases, excludedDatabases, excludedDatabases, min(args.QueryCountThreshold, constants.MaxQueryCountThreshold)} + + // Prepare the SQL query with the provided parameters + preparedQuery, preparedArgs, err := sqlx.In(utils.WaitEventsQuery, excludedDatabasesArgs...) + if err != nil { + log.Error("Failed to prepare wait event query: %v", err) + } + + // Collect the wait event metrics + metrics, err := utils.CollectMetrics[utils.WaitEventQueryMetrics](db, preparedQuery, preparedArgs...) + if err != nil { + log.Error("Error collecting wait event metrics: %v", err) + } + + // Return if no metrics are collected + if len(metrics) == 0 { + return + } + // Set the retrieved metrics in the integration + err = setWaitEventMetrics(i, args, metrics) + if err != nil { + log.Error("Error setting wait event metrics: %v", err) + } +} + +// setWaitEventMetrics sets the wait event metrics in the integration. +func setWaitEventMetrics(i *integration.Integration, args arguments.ArgumentList, metrics []utils.WaitEventQueryMetrics) error { + metricList := make([]interface{}, 0, len(metrics)) + for _, metricData := range metrics { + metricList = append(metricList, metricData) + } + + err := utils.IngestMetric(metricList, "MysqlWaitEventsSample", i, args) + if err != nil { + return err + } + return nil +} diff --git a/src/query-performance-monitoring/performance_main.go b/src/query-performance-monitoring/performance_main.go new file mode 100644 index 00000000..729774ee --- /dev/null +++ b/src/query-performance-monitoring/performance_main.go @@ -0,0 +1,71 @@ +package queryperformancemonitoring + +import ( + "fmt" + "time" + + "github.com/newrelic/infra-integrations-sdk/v3/integration" + "github.com/newrelic/infra-integrations-sdk/v3/log" + arguments "github.com/newrelic/nri-mysql/src/args" + 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" +) + +// main +func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration.Entity, i *integration.Integration) { + var database string + + // Generate Data Source Name (DSN) for database connection + dsn := utils.GenerateDSN(args, database) + + // Open database connection + db, err := utils.OpenDB(dsn) + utils.FatalIfErr(err) + defer db.Close() + + // Validate preconditions before proceeding + preValidationErr := validator.ValidatePreconditions(db) + if preValidationErr != nil { + utils.FatalIfErr(fmt.Errorf("preconditions failed: %w", preValidationErr)) + } + + // Get the list of unique excluded databases + excludedDatabases, err := utils.GetExcludedDatabases(args.ExcludedDatabases) + if err != nil { + utils.FatalIfErr(fmt.Errorf("error unmarshaling json: %w", err)) + } + + // Populate metrics for slow queries + start := time.Now() + log.Debug("Beginning to retrieve slow query metrics") + queryIDList := performancemetricscollectors.PopulateSlowQueryMetrics(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() + log.Debug("Beginning to retrieve individual query metrics") + groupQueriesByDatabase := performancemetricscollectors.PopulateIndividualQueryDetails(db, queryIDList, i, e, args) + log.Debug("Completed fetching individual query metrics in %v", time.Since(start)) + + // Populate execution plan details + start = time.Now() + log.Debug("Beginning to retrieve query execution plan metrics") + performancemetricscollectors.PopulateExecutionPlans(db, groupQueriesByDatabase, i, e, args) + log.Debug("Completed fetching query execution plan metrics in %v", time.Since(start)) + } + + // Populate wait event metrics + start = time.Now() + log.Debug("Beginning to retrieve wait event metrics") + performancemetricscollectors.PopulateWaitEventMetrics(db, i, e, args, excludedDatabases) + log.Debug("Completed fetching wait event metrics in %v", time.Since(start)) + + // Populate blocking session metrics + start = time.Now() + log.Debug("Beginning to retrieve blocking session metrics") + performancemetricscollectors.PopulateBlockingSessionMetrics(db, i, e, args, excludedDatabases) + log.Debug("Completed fetching blocking session metrics in %v", time.Since(start)) + log.Debug("Query analysis completed.") +} diff --git a/src/query-performance-monitoring/utils/database.go b/src/query-performance-monitoring/utils/database.go new file mode 100644 index 00000000..100e923f --- /dev/null +++ b/src/query-performance-monitoring/utils/database.go @@ -0,0 +1,123 @@ +package utils + +import ( + "context" + "fmt" + "net" + "net/url" + "strconv" + + _ "github.com/go-sql-driver/mysql" + "github.com/jmoiron/sqlx" + "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" +) + +type DataSource interface { + Close() + QueryX(string) (*sqlx.Rows, error) + QueryxContext(ctx context.Context, query string, args ...interface{}) (*sqlx.Rows, error) +} + +type Database struct { + source *sqlx.DB +} + +func OpenDB(dsn string) (DataSource, error) { + source, err := sqlx.Open("mysql", dsn) + if err != nil { + return nil, fmt.Errorf("error opening DSN: %w", err) + } + + db := Database{ + source: source, + } + + return &db, nil +} + +func (db *Database) Close() { + db.source.Close() +} + +func (db *Database) QueryX(query string) (*sqlx.Rows, error) { + rows, err := db.source.Queryx(query) + fatalIfErr(err) + return rows, err +} + +func fatalIfErr(err error) { + if err != nil { + log.Fatal(err) + } +} + +// QueryxContext method implementation +func (db *Database) QueryxContext(ctx context.Context, query string, args ...interface{}) (*sqlx.Rows, error) { + return db.source.QueryxContext(ctx, query, args...) +} + +func GenerateDSN(args arguments.ArgumentList, database string) string { + query := url.Values{} + if args.OldPasswords { + query.Add("allowOldPasswords", "true") + } + if args.EnableTLS { + query.Add("tls", "true") + } + if args.InsecureSkipVerify { + query.Add("tls", "skip-verify") + } + extraArgsMap, err := url.ParseQuery(args.ExtraConnectionURLArgs) + if err == nil { + for k, v := range extraArgsMap { + query.Add(k, v[0]) + } + } else { + log.Warn("Could not successfully parse ExtraConnectionURLArgs.", err.Error()) + } + if args.Socket != "" { + log.Debug("Socket parameter is defined, ignoring host and port parameters") + return fmt.Sprintf("%s:%s@unix(%s)/%s?%s", args.Username, args.Password, args.Socket, determineDatabase(args, database), query.Encode()) + } + + // Convert hostname and port to DSN address format + mysqlURL := net.JoinHostPort(args.Hostname, strconv.Itoa(args.Port)) + + return fmt.Sprintf("%s:%s@tcp(%s)/%s?%s", args.Username, args.Password, mysqlURL, determineDatabase(args, database), query.Encode()) +} + +// determineDatabase determines which database name to use for the DSN. +func determineDatabase(args arguments.ArgumentList, database string) string { + if database != "" { + return database + } + return args.Database +} + +// collectMetrics collects metrics from the performance schema database +func CollectMetrics[T any](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...) + if err != nil { + return []T{}, err + } + defer rows.Close() + + var metrics []T + for rows.Next() { + var metric T + if err := rows.StructScan(&metric); err != nil { + return []T{}, err + } + metrics = append(metrics, metric) + } + if err := rows.Err(); err != nil { + return []T{}, err + } + + return metrics, nil +} diff --git a/src/query-performance-monitoring/utils/helpers.go b/src/query-performance-monitoring/utils/helpers.go new file mode 100644 index 00000000..5994bbb8 --- /dev/null +++ b/src/query-performance-monitoring/utils/helpers.go @@ -0,0 +1,233 @@ +package utils + +import ( + "encoding/json" + "errors" + "fmt" + "reflect" + "strconv" + "strings" + + "github.com/newrelic/infra-integrations-sdk/v3/data/attribute" + "github.com/newrelic/infra-integrations-sdk/v3/data/metric" + "github.com/newrelic/infra-integrations-sdk/v3/integration" + "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" +) + +// Default excluded databases +var defaultExcludedDatabases = []string{"", "mysql", "information_schema", "performance_schema", "sys"} + +// Dynamic error +var ( + ErrEssentialConsumerNotEnabled = errors.New("essential consumer is not enabled") + ErrEssentialInstrumentNotEnabled = errors.New("essential instrument is not fully enabled") + ErrMySQLVersion = errors.New("failed to determine MySQL version") + ErrModelIsNotValid = errors.New("model is not a valid struct") +) + +func CreateNodeEntity( + i *integration.Integration, + remoteMonitoring bool, + hostname string, + port int, +) (*integration.Entity, error) { + if remoteMonitoring { + return i.Entity(fmt.Sprint(hostname, ":", port), constants.NodeEntityType) + } + return i.LocalEntity(), nil +} + +func CreateMetricSet(e *integration.Entity, sampleName string, args arguments.ArgumentList) *metric.Set { + return MetricSet( + e, + sampleName, + args.Hostname, + args.Port, + args.RemoteMonitoring, + ) +} + +func MetricSet(e *integration.Entity, eventType, hostname string, port int, remoteMonitoring bool) *metric.Set { + if remoteMonitoring { + return e.NewMetricSet( + eventType, + attribute.Attr("hostname", hostname), + attribute.Attr("port", strconv.Itoa(port)), + ) + } + + return e.NewMetricSet( + eventType, + attribute.Attr("port", strconv.Itoa(port)), + ) +} + +func PrintMetricSet(ms *metric.Set) { + fmt.Println("Metric Set Contents:") + for name, metric := range ms.Metrics { + fmt.Printf("Name: %s, Value: %v, Type: %v\n", name, metric, "unknown") + } +} + +func getUniqueExcludedDatabases(excludedDBList string) []string { + // Create a map to store unique schemas + uniqueSchemas := make(map[string]struct{}) + + // Populate the map with default excluded databases + for _, schema := range defaultExcludedDatabases { + uniqueSchemas[schema] = struct{}{} + } + + // Populate the map with values from excludedDBList + for _, schema := range strings.Split(excludedDBList, ",") { + uniqueSchemas[strings.TrimSpace(schema)] = struct{}{} + } + + // Convert the map keys back into a slice + result := make([]string, 0, len(uniqueSchemas)) + for schema := range uniqueSchemas { + result = append(result, schema) + } + + return result +} + +// GetExcludedDatabases parses the excluded databases list from a JSON string and returns a list of unique excluded databases. +func GetExcludedDatabases(excludedDatabasesList string) ([]string, error) { + // Parse the excluded databases list from JSON string + var excludedDatabasesSlice []string + if err := json.Unmarshal([]byte(excludedDatabasesList), &excludedDatabasesSlice); err != nil { + return nil, err + } + + // Join the slice into a comma-separated string + excludedDatabasesStr := strings.Join(excludedDatabasesSlice, ",") + + // Assuming you have a map to store the results + var excludedDatabasesCache = make(map[string][]string) + + // Get the list of unique excluded databases + if cachedDatabases, found := excludedDatabasesCache[excludedDatabasesStr]; found { + return cachedDatabases, nil + } + + excludedDatabases := getUniqueExcludedDatabases(excludedDatabasesStr) + excludedDatabasesCache[excludedDatabasesStr] = excludedDatabases + + return excludedDatabases, nil +} + +// Helper function to convert a slice of strings to a slice of interfaces +func ConvertToInterfaceSlice(slice []string) []interface{} { + result := make([]interface{}, len(slice)) + for i, v := range slice { + result[i] = v + } + return result +} + +func FatalIfErr(err error) { + if err != nil { + log.Fatal(err) + } +} + +// SetMetric sets a metric in the given metric set. +func SetMetric(metricSet *metric.Set, name string, value interface{}, sourceType string) { + switch sourceType { + case "gauge": + err := metricSet.SetMetric(name, value, metric.GAUGE) + if err != nil { + log.Warn("Error setting gauge metric: %v", err) + } + case "attribute": + err := metricSet.SetMetric(name, value, metric.ATTRIBUTE) + if err != nil { + log.Warn("Error setting attribute metric: %v", err) + } + default: + err := metricSet.SetMetric(name, value, metric.GAUGE) + if err != nil { + log.Warn("Error setting default gauge metric: %v", err) + } + } +} + +// IngestMetric ingests a list of metrics into the integration. +func IngestMetric(metricList []interface{}, eventName string, i *integration.Integration, args arguments.ArgumentList) error { + instanceEntity, err := CreateNodeEntity(i, args.RemoteMonitoring, args.Hostname, args.Port) + if err != nil { + log.Error("Error creating entity: %v", err) + return err + } + + metricCount := 0 + for _, model := range metricList { + if model == nil { + continue + } + metricCount++ + err := processModel(model, instanceEntity, eventName, args) + if err != nil { + log.Error("Error processing model: %v", err) + return err + } + if metricCount > constants.MetricSetLimit { + metricCount = 0 + if err = publishMetrics(i); err != nil { + return err + } + instanceEntity, err = CreateNodeEntity(i, args.RemoteMonitoring, args.Hostname, args.Port) + if err != nil { + log.Error("Error creating entity: %v", err) + return err + } + } + } + + if metricCount > 0 { + if err := publishMetrics(i); err != nil { + return err + } + } + + return nil +} + +func processModel(model interface{}, instanceEntity *integration.Entity, eventName string, args arguments.ArgumentList) error { + metricSet := CreateMetricSet(instanceEntity, eventName, args) + + modelValue := reflect.ValueOf(model) + if modelValue.Kind() == reflect.Ptr { + modelValue = modelValue.Elem() + } + if !modelValue.IsValid() || modelValue.Kind() != reflect.Struct { + return ErrModelIsNotValid + } + + modelType := reflect.TypeOf(model) + for i := 0; i < modelValue.NumField(); i++ { + field := modelValue.Field(i) + fieldType := modelType.Field(i) + metricName := fieldType.Tag.Get("metric_name") + sourceType := fieldType.Tag.Get("source_type") + + if field.Kind() == reflect.Ptr && !field.IsNil() { + SetMetric(metricSet, metricName, field.Elem().Interface(), sourceType) + } else if field.Kind() != reflect.Ptr { + SetMetric(metricSet, metricName, field.Interface(), sourceType) + } + } + return nil +} + +func publishMetrics(i *integration.Integration) error { + err := i.Publish() + if err != nil { + log.Error("Error publishing metrics: %v", err) + return err + } + return nil +} diff --git a/src/query-performance-monitoring/utils/models.go b/src/query-performance-monitoring/utils/models.go new file mode 100644 index 00000000..2e0b01c1 --- /dev/null +++ b/src/query-performance-monitoring/utils/models.go @@ -0,0 +1,94 @@ +package utils + +type SlowQueryMetrics struct { + QueryID *string `json:"query_id" db:"query_id" metric_name:"query_id" source_type:"attribute"` + QueryText *string `json:"query_text" db:"query_text" metric_name:"query_text" source_type:"attribute"` + DatabaseName *string `json:"database_name" db:"database_name" metric_name:"database_name" source_type:"attribute"` + SchemaName *string `json:"schema_name" db:"schema_name" metric_name:"schema_name" source_type:"attribute"` + ExecutionCount *uint64 `json:"execution_count" db:"execution_count" metric_name:"execution_count" source_type:"gauge"` + AvgCPUTimeMs *float64 `json:"avg_cpu_time_ms" db:"avg_cpu_time_ms" metric_name:"avg_cpu_time_ms" source_type:"gauge"` + AvgElapsedTimeMs *float64 `json:"avg_elapsed_time_ms" db:"avg_elapsed_time_ms" metric_name:"avg_elapsed_time_ms" source_type:"gauge"` + AvgDiskReads *float64 `json:"avg_disk_reads" db:"avg_disk_reads" metric_name:"avg_disk_reads" source_type:"gauge"` + AvgDiskWrites *float64 `json:"avg_disk_writes" db:"avg_disk_writes" metric_name:"avg_disk_writes" source_type:"gauge"` + HasFullTableScan *string `json:"has_full_table_scan" db:"has_full_table_scan" metric_name:"has_full_table_scan" source_type:"attribute"` + StatementType *string `json:"statement_type" db:"statement_type" metric_name:"statement_type" source_type:"attribute"` + LastExecutionTimestamp *string `json:"last_execution_timestamp" db:"last_execution_timestamp" metric_name:"last_execution_timestamp" source_type:"attribute"` + CollectionTimestamp *string `json:"collection_timestamp" db:"collection_timestamp" metric_name:"collection_timestamp" source_type:"attribute"` +} + +type IndividualQueryMetrics struct { + QueryID *string `json:"query_id" db:"query_id" metric_name:"query_id" source_type:"attribute"` + AnonymizedQueryText *string `json:"query_text" db:"query_text" metric_name:"query_text" source_type:"attribute"` + QueryText *string `json:"query_sample_text" db:"query_sample_text" metric_name:"query_sample_text" source_type:"attribute"` + EventID *uint64 `json:"event_id" db:"event_id" metric_name:"event_id" source_type:"gauge"` + ThreadID *uint64 `json:"thread_id" db:"thread_id" metric_name:"thread_id" source_type:"gauge"` + ExecutionTimeMs *float64 `json:"execution_time_ms" db:"execution_time_ms" metric_name:"execution_time_ms" source_type:"gauge"` + RowsSent *int64 `json:"rows_sent" db:"rows_sent" metric_name:"rows_sent" source_type:"gauge"` + RowsExamined *int64 `json:"rows_examined" db:"rows_examined" metric_name:"rows_examined" source_type:"gauge"` + DatabaseName *string `json:"database_name" db:"database_name" metric_name:"database_name" source_type:"attribute"` +} + +type QueryGroup struct { + Database string + Queries []IndividualQueryMetrics +} + +type QueryPlanMetrics struct { + EventID uint64 `json:"event_id" metric_name:"event_id" source_type:"gauge"` + ThreadID uint64 `json:"thread_id" db:"thread_id" metric_name:"thread_id" source_type:"gauge"` + StepID int `json:"step_id" metric_name:"step_id" source_type:"gauge"` + QueryCost string `json:"query_cost" metric_name:"query_cost" source_type:"attribute"` + TableName string `json:"table_name" metric_name:"table_name" source_type:"attribute"` + AccessType string `json:"access_type" metric_name:"access_type" source_type:"attribute"` + RowsExaminedPerScan int64 `json:"rows_examined_per_scan" metric_name:"rows_examined_per_scan" source_type:"gauge"` + RowsProducedPerJoin int64 `json:"rows_produced_per_join" metric_name:"rows_produced_per_join" source_type:"gauge"` + Filtered string `json:"filtered" metric_name:"filtered" source_type:"attribute"` + ReadCost string `json:"read_cost" metric_name:"read_cost" source_type:"attribute"` + EvalCost string `json:"eval_cost" metric_name:"eval_cost" source_type:"attribute"` + PossibleKeys string `json:"possible_keys" metric_name:"possible_keys" source_type:"attribute"` + Key string `json:"key" metric_name:"key" source_type:"attribute"` + UsedKeyParts string `json:"used_key_parts" metric_name:"used_key_parts" source_type:"attribute"` + Ref string `json:"ref" metric_name:"ref" source_type:"attribute"` +} + +type Memo struct { + QueryCost string `json:"query_cost" metric_name:"query_cost" source_type:"gauge"` +} + +type WaitEventQueryMetrics struct { + TotalWaitTimeMs *float64 `json:"total_wait_time_ms" db:"total_wait_time_ms" metric_name:"total_wait_time_ms" source_type:"gauge"` + QueryID *string `json:"query_id" db:"query_id" metric_name:"query_id" source_type:"attribute"` + QueryText *string `json:"query_text" db:"query_text" metric_name:"query_text" source_type:"attribute"` + DatabaseName *string `json:"database_name" db:"database_name" metric_name:"database_name" source_type:"attribute"` + WaitCategory *string `json:"wait_category" db:"wait_category" metric_name:"wait_category" source_type:"attribute"` + CollectionTimestamp *string `json:"collection_timestamp" db:"collection_timestamp" metric_name:"collection_timestamp" source_type:"attribute"` + InstanceID *string `json:"instance_id" db:"instance_id" metric_name:"instance_id" source_type:"attribute"` + WaitEventName *string `json:"wait_event_name" db:"wait_event_name" metric_name:"wait_event_name" source_type:"attribute"` + WaitEventCount *uint64 `json:"wait_event_count" db:"wait_event_count" metric_name:"wait_event_count" source_type:"gauge"` + AvgWaitTimeMs *string `json:"avg_wait_time_ms" db:"avg_wait_time_ms" metric_name:"avg_wait_time_ms" source_type:"attribute"` +} + +type BlockingSessionMetrics struct { + BlockedTxnID *string `json:"blocked_txn_id" db:"blocked_txn_id" metric_name:"blocked_txn_id" source_type:"attribute"` + BlockedPID *string `json:"blocked_pid" db:"blocked_pid" metric_name:"blocked_pid" source_type:"attribute"` + BlockedThreadID *int64 `json:"blocked_thread_id" db:"blocked_thread_id" metric_name:"blocked_thread_id" source_type:"gauge"` + BlockedQueryID *string `json:"blocked_query_id" db:"blocked_query_id" metric_name:"blocked_query_id" source_type:"attribute"` + BlockedQuery *string `json:"blocked_query" db:"blocked_query" metric_name:"blocked_query" source_type:"attribute"` + BlockedStatus *string `json:"blocked_status" db:"blocked_status" metric_name:"blocked_status" source_type:"attribute"` + BlockedUser *string `json:"blocked_user" db:"blocked_user" metric_name:"blocked_user" source_type:"attribute"` + BlockedHost *string `json:"blocked_host" db:"blocked_host" metric_name:"blocked_host" source_type:"attribute"` + BlockedDB *string `json:"database_name" db:"database_name" metric_name:"database_name" source_type:"attribute"` + BlockingTxnID *string `json:"blocking_txn_id" db:"blocking_txn_id" metric_name:"blocking_txn_id" source_type:"attribute"` + BlockingPID *string `json:"blocking_pid" db:"blocking_pid" metric_name:"blocking_pid" source_type:"attribute"` + BlockingThreadID *int64 `json:"blocking_thread_id" db:"blocking_thread_id" metric_name:"blocking_thread_id" source_type:"gauge"` + BlockingUser *string `json:"blocking_user" db:"blocking_user" metric_name:"blocking_user" source_type:"attribute"` + BlockingHost *string `json:"blocking_host" db:"blocking_host" metric_name:"blocking_host" source_type:"attribute"` + BlockingQueryID *string `json:"blocking_query_id" db:"blocking_query_id" metric_name:"blocking_query_id" source_type:"attribute"` + BlockingQuery *string `json:"blocking_query" db:"blocking_query" metric_name:"blocking_query" source_type:"attribute"` + BlockingStatus *string `json:"blocking_status" db:"blocking_status" metric_name:"blocking_status" source_type:"attribute"` + BlockedQueryTimeMs *float64 `json:"blocked_query_time_ms" db:"blocked_query_time_ms" metric_name:"blocked_query_time_ms" source_type:"gauge"` + BlockingQueryTimeMs *float64 `json:"blocking_query_time_ms" db:"blocking_query_time_ms" metric_name:"blocking_query_time_ms" source_type:"gauge"` + BlockedTxnStartTime *string `json:"blocked_txn_start_time" db:"blocked_txn_start_time" metric_name:"blocked_txn_start_time" source_type:"attribute"` + BlockingTxnStartTime *string `json:"blocking_txn_start_time" db:"blocking_txn_start_time" metric_name:"blocking_txn_start_time" source_type:"attribute"` + CollectionTimestamp *string `json:"collection_timestamp" db:"collection_timestamp" metric_name:"collection_timestamp" source_type:"attribute"` +} diff --git a/src/query-performance-monitoring/utils/queries.go b/src/query-performance-monitoring/utils/queries.go new file mode 100644 index 00000000..855e60e2 --- /dev/null +++ b/src/query-performance-monitoring/utils/queries.go @@ -0,0 +1,246 @@ +package utils + +const ( + SlowQueries = ` + SELECT + DIGEST AS query_id, + CASE + WHEN CHAR_LENGTH(DIGEST_TEXT) > 4000 THEN CONCAT(LEFT(DIGEST_TEXT, 3997), '...') + ELSE DIGEST_TEXT + END AS query_text, + SCHEMA_NAME AS database_name, + 'N/A' AS schema_name, + COUNT_STAR AS execution_count, + ROUND((SUM_CPU_TIME / COUNT_STAR) / 1000000000, 3) AS avg_cpu_time_ms, + ROUND((SUM_TIMER_WAIT / COUNT_STAR) / 1000000000, 3) AS avg_elapsed_time_ms, + SUM_ROWS_EXAMINED / COUNT_STAR AS avg_disk_reads, + SUM_ROWS_AFFECTED / COUNT_STAR AS avg_disk_writes, + CASE + WHEN SUM_NO_INDEX_USED > 0 THEN 'Yes' + ELSE 'No' + END AS has_full_table_scan, + CASE + WHEN DIGEST_TEXT LIKE 'SELECT%' THEN 'SELECT' + WHEN DIGEST_TEXT LIKE 'INSERT%' THEN 'INSERT' + WHEN DIGEST_TEXT LIKE 'UPDATE%' THEN 'UPDATE' + WHEN DIGEST_TEXT LIKE 'DELETE%' THEN 'DELETE' + ELSE 'OTHER' + END AS statement_type, + DATE_FORMAT(LAST_SEEN, '%Y-%m-%dT%H:%i:%sZ') AS last_execution_timestamp, + DATE_FORMAT(UTC_TIMESTAMP(), '%Y-%m-%dT%H:%i:%sZ') AS collection_timestamp + FROM performance_schema.events_statements_summary_by_digest + WHERE LAST_SEEN >= UTC_TIMESTAMP() - INTERVAL ? SECOND + AND SCHEMA_NAME IS NOT NULL + AND SCHEMA_NAME NOT IN (?) + AND DIGEST_TEXT RLIKE '^(SELECT|INSERT|UPDATE|DELETE|WITH)' + AND DIGEST_TEXT NOT LIKE '%DIGEST_TEXT%' + ORDER BY avg_elapsed_time_ms DESC + LIMIT ?; + ` + CurrentRunningQueriesSearch = ` + SELECT + DIGEST AS query_id, + CASE + WHEN CHAR_LENGTH(DIGEST_TEXT) > 4000 THEN CONCAT(LEFT(DIGEST_TEXT, 3997), '...') + ELSE DIGEST_TEXT + END AS query_text, + SQL_TEXT AS query_sample_text, + EVENT_ID AS event_id, + THREAD_ID AS thread_id, + ROUND(TIMER_WAIT / 1000000000, 3) AS execution_time_ms, + ROWS_SENT AS rows_sent, + ROWS_EXAMINED AS rows_examined, + CURRENT_SCHEMA AS database_name + FROM performance_schema.events_statements_current + WHERE DIGEST = ? + AND TIMER_WAIT / 1000000000 > ? + ORDER BY TIMER_WAIT DESC + LIMIT ?; + ` + RecentQueriesSearch = ` + SELECT + DIGEST AS query_id, + CASE + WHEN CHAR_LENGTH(DIGEST_TEXT) > 4000 THEN CONCAT(LEFT(DIGEST_TEXT, 3997), '...') + ELSE DIGEST_TEXT + END AS query_text, + SQL_TEXT AS query_sample_text, + EVENT_ID AS event_id, + THREAD_ID AS thread_id, + ROUND(TIMER_WAIT / 1000000000, 3) AS execution_time_ms, + ROWS_SENT AS rows_sent, + ROWS_EXAMINED AS rows_examined, + CURRENT_SCHEMA AS database_name + FROM performance_schema.events_statements_history + WHERE DIGEST = ? + AND TIMER_WAIT / 1000000000 > ? + ORDER BY TIMER_WAIT DESC + LIMIT ?; + ` + PastQueriesSearch = ` + SELECT + DIGEST AS query_id, + CASE + WHEN CHAR_LENGTH(DIGEST_TEXT) > 4000 THEN CONCAT(LEFT(DIGEST_TEXT, 3997), '...') + ELSE DIGEST_TEXT + END AS query_text, + SQL_TEXT AS query_sample_text, + EVENT_ID AS event_id, + THREAD_ID AS thread_id, + ROUND(TIMER_WAIT / 1000000000, 3) AS execution_time_ms, + ROWS_SENT AS rows_sent, + ROWS_EXAMINED AS rows_examined, + CURRENT_SCHEMA AS database_name + FROM performance_schema.events_statements_history_long + WHERE DIGEST = ? + AND TIMER_WAIT / 1000000000 > ? + ORDER BY TIMER_WAIT DESC + LIMIT ?; + ` + WaitEventsQuery = ` + SELECT + schema_data.DIGEST AS query_id, + wait_data.instance_id, + schema_data.database_name, + wait_data.wait_event_name, + CASE + WHEN wait_data.wait_event_name LIKE 'wait/io/file/innodb/%' THEN 'InnoDB File IO' + WHEN wait_data.wait_event_name LIKE 'wait/io/file/sql/%' THEN 'SQL File IO' + WHEN wait_data.wait_event_name LIKE 'wait/io/socket/%' THEN 'Network IO' + WHEN wait_data.wait_event_name LIKE 'wait/synch/cond/%' THEN 'Condition Wait' + WHEN wait_data.wait_event_name LIKE 'wait/synch/mutex/%' THEN 'Mutex' + WHEN wait_data.wait_event_name LIKE 'wait/lock/table/%' THEN 'Table Lock' + WHEN wait_data.wait_event_name LIKE 'wait/lock/metadata/%' THEN 'Metadata Lock' + WHEN wait_data.wait_event_name LIKE 'wait/lock/transaction/%' THEN 'Transaction Lock' + ELSE 'Other' + END AS wait_category, + ROUND(IFNULL(SUM(wait_data.TIMER_WAIT), 0) / 1000000000, 3) AS total_wait_time_ms, + SUM(ewsg.COUNT_STAR) AS wait_event_count, + ROUND((IFNULL(SUM(wait_data.TIMER_WAIT), 0) / 1000000000) / IFNULL(SUM(ewsg.COUNT_STAR), 1), 3) AS avg_wait_time_ms, + CASE + WHEN CHAR_LENGTH(schema_data.query_text) > 4000 THEN CONCAT(LEFT(schema_data.query_text, 3997), '...') + ELSE schema_data.query_text + END AS query_text, + DATE_FORMAT(UTC_TIMESTAMP(), '%Y-%m-%dT%H:%i:%sZ') AS collection_timestamp + FROM ( + SELECT + THREAD_ID, + OBJECT_INSTANCE_BEGIN AS instance_id, + EVENT_NAME AS wait_event_name, + TIMER_WAIT + FROM performance_schema.events_waits_history_long + UNION ALL + SELECT + THREAD_ID, + OBJECT_INSTANCE_BEGIN AS instance_id, + EVENT_NAME AS wait_event_name, + TIMER_WAIT + FROM performance_schema.events_waits_history + UNION ALL + SELECT + THREAD_ID, + OBJECT_INSTANCE_BEGIN AS instance_id, + EVENT_NAME AS wait_event_name, + TIMER_WAIT + FROM performance_schema.events_waits_current + ) AS wait_data + JOIN ( + SELECT + THREAD_ID, + DIGEST, + CURRENT_SCHEMA AS database_name, + DIGEST_TEXT AS query_text + FROM performance_schema.events_statements_history_long + WHERE CURRENT_SCHEMA IS NOT NULL + AND CURRENT_SCHEMA NOT IN (?) + AND SQL_TEXT RLIKE '^(SELECT|INSERT|UPDATE|DELETE|WITH)' + AND SQL_TEXT NOT LIKE '%DIGEST_TEXT%' + UNION ALL + SELECT + THREAD_ID, + DIGEST, + CURRENT_SCHEMA AS database_name, + DIGEST_TEXT AS query_text + FROM performance_schema.events_statements_history + WHERE CURRENT_SCHEMA IS NOT NULL + AND CURRENT_SCHEMA NOT IN (?) + AND SQL_TEXT RLIKE '^(SELECT|INSERT|UPDATE|DELETE|WITH)' + AND SQL_TEXT NOT LIKE '%DIGEST_TEXT%' + UNION ALL + SELECT + THREAD_ID, + DIGEST, + CURRENT_SCHEMA AS database_name, + DIGEST_TEXT AS query_text + FROM performance_schema.events_statements_current + WHERE CURRENT_SCHEMA IS NOT NULL + AND CURRENT_SCHEMA NOT IN (?) + AND SQL_TEXT RLIKE '^(SELECT|INSERT|UPDATE|DELETE|WITH)' + AND SQL_TEXT NOT LIKE '%DIGEST_TEXT%' + ) AS schema_data + ON wait_data.THREAD_ID = schema_data.THREAD_ID + LEFT JOIN performance_schema.events_waits_summary_global_by_event_name ewsg + ON ewsg.EVENT_NAME = wait_data.wait_event_name + GROUP BY + query_id, + wait_data.instance_id, + wait_data.wait_event_name, + wait_category, + schema_data.database_name, + schema_data.query_text + ORDER BY + total_wait_time_ms DESC + LIMIT ?; + ` + BlockingSessionsQuery = ` + SELECT + r.trx_id AS blocked_txn_id, + r.trx_mysql_thread_id AS blocked_thread_id, + wt.PROCESSLIST_ID AS blocked_pid, + wt.PROCESSLIST_USER AS blocked_user, + wt.PROCESSLIST_HOST AS blocked_host, + wt.PROCESSLIST_DB AS database_name, + wt.PROCESSLIST_STATE AS blocked_status, + b.trx_id AS blocking_txn_id, + b.trx_mysql_thread_id AS blocking_thread_id, + bt.PROCESSLIST_ID AS blocking_pid, + bt.PROCESSLIST_USER AS blocking_user, + bt.PROCESSLIST_HOST AS blocking_host, + es_waiting.DIGEST_TEXT AS blocked_query, + es_blocking.DIGEST_TEXT AS blocking_query, + es_waiting.DIGEST AS blocked_query_id, + es_blocking.DIGEST AS blocking_query_id, + bt.PROCESSLIST_STATE AS blocking_status, + ROUND(esc_waiting.TIMER_WAIT / 1000000000, 3) AS blocked_query_time_ms, + ROUND(esc_blocking.TIMER_WAIT / 1000000000, 3) AS blocking_query_time_ms, + DATE_FORMAT(CONVERT_TZ(r.trx_started, @@session.time_zone, '+00:00'), '%Y-%m-%dT%H:%i:%sZ') AS blocked_txn_start_time, + DATE_FORMAT(CONVERT_TZ(b.trx_started, @@session.time_zone, '+00:00'), '%Y-%m-%dT%H:%i:%sZ') AS blocking_txn_start_time, + DATE_FORMAT(UTC_TIMESTAMP(), '%Y-%m-%dT%H:%i:%sZ') AS collection_timestamp + FROM + performance_schema.data_lock_waits w + JOIN + performance_schema.threads wt ON wt.THREAD_ID = w.REQUESTING_THREAD_ID + JOIN + information_schema.innodb_trx r ON r.trx_mysql_thread_id = wt.PROCESSLIST_ID + JOIN + performance_schema.threads bt ON bt.THREAD_ID = w.BLOCKING_THREAD_ID + JOIN + information_schema.innodb_trx b ON b.trx_mysql_thread_id = bt.PROCESSLIST_ID + JOIN + performance_schema.events_statements_current esc_waiting ON esc_waiting.THREAD_ID = wt.THREAD_ID + JOIN + performance_schema.events_statements_summary_by_digest es_waiting + ON esc_waiting.DIGEST = es_waiting.DIGEST + JOIN + performance_schema.events_statements_current esc_blocking ON esc_blocking.THREAD_ID = bt.THREAD_ID + JOIN + performance_schema.events_statements_summary_by_digest es_blocking + ON esc_blocking.DIGEST = es_blocking.DIGEST + WHERE + wt.PROCESSLIST_DB IS NOT NULL + AND wt.PROCESSLIST_DB NOT IN (?) + ORDER BY + blocked_txn_start_time ASC + LIMIT ?; + ` +) diff --git a/src/query-performance-monitoring/validator/validations.go b/src/query-performance-monitoring/validator/validations.go new file mode 100644 index 00000000..204cd366 --- /dev/null +++ b/src/query-performance-monitoring/validator/validations.go @@ -0,0 +1,248 @@ +package validator + +import ( + "database/sql" + "errors" + "fmt" + "strconv" + "strings" + + "github.com/newrelic/infra-integrations-sdk/v3/log" + constants "github.com/newrelic/nri-mysql/src/query-performance-monitoring/constants" + utils "github.com/newrelic/nri-mysql/src/query-performance-monitoring/utils" +) + +// Dynamic error +var ( + ErrPerformanceSchemaDisabled = errors.New("performance schema is not enabled") + ErrNoRowsFound = errors.New("no rows found") + ErrMysqlVersion = errors.New("only version 8.0+ is supported") +) + +// ValidatePreconditions checks if the necessary preconditions are met for performance monitoring. +func ValidatePreconditions(db utils.DataSource) error { + // Check if Performance Schema is enabled + performanceSchemaEnabled, errPerformanceEnabled := isPerformanceSchemaEnabled(db) + if errPerformanceEnabled != nil { + return errPerformanceEnabled + } + + if !performanceSchemaEnabled { + logEnablePerformanceSchemaInstructions(db) + return ErrPerformanceSchemaDisabled + } + + // Check if essential consumers are enabled + errEssentialConsumers := checkEssentialConsumers(db) + if errEssentialConsumers != nil { + return fmt.Errorf("essential consumer check failed: %w", errEssentialConsumers) + } + + // Check if essential instruments are enabled + errEssentialInstruments := checkEssentialInstruments(db) + if errEssentialInstruments != nil { + return fmt.Errorf("essential instruments check failed: %w", errEssentialInstruments) + } + return nil +} + +// isPerformanceSchemaEnabled checks if the Performance Schema is enabled in the MySQL database. +func isPerformanceSchemaEnabled(db utils.DataSource) (bool, error) { + var variableName, performanceSchemaEnabled string + rows, err := db.QueryX("SHOW GLOBAL VARIABLES LIKE 'performance_schema';") + if err != nil { + return false, fmt.Errorf("failed to check performance schema status: %w", err) + } + + if !rows.Next() { + log.Error("No rows found") + return false, ErrNoRowsFound + } + + errScanning := rows.Scan(&variableName, &performanceSchemaEnabled) + if errScanning != nil { + return false, errScanning + } + + return performanceSchemaEnabled == "ON", nil +} + +// checkEssentialConsumers checks if the essential consumers are enabled in the Performance Schema. +func checkEssentialConsumers(db utils.DataSource) error { + consumers := []string{ + "events_waits_current", + "events_waits_history_long", + "events_waits_history", + "events_statements_history_long", + "events_statements_history", + "events_statements_current", + "events_statements_cpu", + "events_transactions_current", + "events_stages_current", + } + + // Build the query to check the status of essential consumers + query := "SELECT NAME, ENABLED FROM performance_schema.setup_consumers WHERE NAME IN (" + for i, consumer := range consumers { + if i > 0 { + query += ", " + } + query += fmt.Sprintf("'%s'", consumer) + } + query += ");" + + rows, err := db.QueryX(query) + if err != nil { + return fmt.Errorf("failed to check essential consumers: %w", err) + } + defer func() { + if err := rows.Close(); err != nil { + log.Error("Failed to close rows: %v", err) + } + }() + + // Check if each essential consumer is enabled + for rows.Next() { + var name, enabled string + if err := rows.Scan(&name, &enabled); err != nil { + return fmt.Errorf("failed to scan consumer row: %w", err) + } + if enabled != "YES" { + log.Error("Essential consumer %s is not enabled. To enable it, run: UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME = '%s';", name, name) + return fmt.Errorf("%w: %s", utils.ErrEssentialConsumerNotEnabled, name) + } + } + + if err := rows.Err(); err != nil { + return fmt.Errorf("rows iteration error: %w", err) + } + + return nil +} + +// checkEssentialInstruments checks if the essential instruments are enabled in the Performance Schema. +func checkEssentialInstruments(db utils.DataSource) error { + instruments := []string{ + // Add other essential instruments here + "wait/%", + "statement/%", + "%lock%", + } + + // Pre-allocate the slice with the expected length + instrumentConditions := make([]string, 0, len(instruments)) + for _, instrument := range instruments { + instrumentConditions = append(instrumentConditions, fmt.Sprintf("NAME LIKE '%s'", instrument)) + } + + // Build the query to check the status of essential instruments + query := "SELECT NAME, ENABLED, TIMED FROM performance_schema.setup_instruments WHERE " + query += strings.Join(instrumentConditions, " OR ") + query += ";" + + rows, err := db.QueryX(query) + if err != nil { + return fmt.Errorf("failed to check essential instruments: %w", err) + } + defer func() { + if err := rows.Close(); err != nil { + log.Error("Failed to close rows: %v", err) + } + }() + + // Check if each essential instrument is enabled and timed + for rows.Next() { + var name, enabled string + var timed sql.NullString + if err := rows.Scan(&name, &enabled, &timed); err != nil { + return fmt.Errorf("failed to scan instrument row: %w", err) + } + if enabled != "YES" || (timed.Valid && timed.String != "YES") { + log.Error("Essential instrument %s is not fully enabled. To enable it, run: UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES' WHERE NAME = '%s';", name, name) + return fmt.Errorf("%w: %s", utils.ErrEssentialInstrumentNotEnabled, name) + } + } + + if err := rows.Err(); err != nil { + return fmt.Errorf("rows iteration error: %w", err) + } + + return nil +} + +// logEnablePerformanceSchemaInstructions logs instructions to enable the Performance Schema. +func logEnablePerformanceSchemaInstructions(db utils.DataSource) { + version, err := getMySQLVersion(db) + if err != nil { + log.Error("Failed to get MySQL version: %v", err) + utils.FatalIfErr(err) + } + + if isVersion8OrGreater(version) { + log.Debug("To enable the Performance Schema, add the following lines to your MySQL configuration file (my.cnf or my.ini) in the [mysqld] section and restart the MySQL server:") + log.Debug("performance_schema=ON") + + log.Debug("For MySQL 8.0 and higher, you may also need to set the following variables:") + log.Debug("performance_schema_instrument='%%=ON'") + log.Debug("performance_schema_consumer_events_statements_current=ON") + log.Debug("performance_schema_consumer_events_statements_history=ON") + log.Debug("performance_schema_consumer_events_statements_history_long=ON") + log.Debug("performance_schema_consumer_events_waits_current=ON") + log.Debug("performance_schema_consumer_events_waits_history=ON") + log.Debug("performance_schema_consumer_events_waits_history_long=ON") + } else { + log.Error("MySQL version %s is not supported. Only version 8.0+ is supported.", version) + utils.FatalIfErr(ErrMysqlVersion) + } +} + +// getMySQLVersion retrieves the MySQL version from the database. +func getMySQLVersion(db utils.DataSource) (string, error) { + query := "SELECT VERSION();" + rows, err := db.QueryX(query) + if err != nil { + return "", fmt.Errorf("failed to execute version query: %w", err) + } + defer rows.Close() + + var version string + if rows.Next() { + if err := rows.Scan(&version); err != nil { + return "", fmt.Errorf("failed to scan version: %w", err) + } + } + + if version == "" { + return "", utils.ErrMySQLVersion + } + + return version, nil +} + +// isVersion8OrGreater checks if the MySQL version is 8.0 or greater. +func isVersion8OrGreater(version string) bool { + majorVersion, minorVersion := parseVersion(version) + return (majorVersion > 8) || (majorVersion == 8 && minorVersion >= 0) +} + +// parseVersion extracts the major and minor version numbers from the version string +func parseVersion(version string) (int, int) { + parts := strings.Split(version, ".") + if len(parts) < constants.MinVersionParts { + return 0, 0 // Return 0 if the version string is improperly formatted + } + + majorVersion, err := strconv.Atoi(parts[0]) + if err != nil { + log.Error("Failed to parse major version: %v", err) + return 0, 0 + } + + minorVersion, err := strconv.Atoi(parts[1]) + if err != nil { + log.Error("Failed to parse minor version: %v", err) + return 0, 0 + } + + return majorVersion, minorVersion +} From 30e3b9f6a527638fea3d1fe2996f9a068e335e71 Mon Sep 17 00:00:00 2001 From: spathlavath Date: Thu, 9 Jan 2025 18:39:13 +0530 Subject: [PATCH 02/32] refactor: Enhanced query execution plan for supporting With, Insert, Delete and Update queries. Updated blocking sessions data model (#18) --- go.mod | 2 +- go.sum | 4 ++-- mysql-config.yml.sample | 10 +++++----- src/mysql.go | 2 +- .../constants/constants.go | 3 +++ .../query_execution_plan.go | 16 +++++++++++++++- .../performance_main.go | 5 +---- .../utils/helpers.go | 13 +++++-------- src/query-performance-monitoring/utils/models.go | 9 +++++++-- .../utils/queries.go | 2 -- 10 files changed, 40 insertions(+), 26 deletions(-) diff --git a/go.mod b/go.mod index 269a250f..9ab1977a 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/jmoiron/sqlx v1.4.0 github.com/newrelic/infra-integrations-sdk/v3 v3.9.1 github.com/sirupsen/logrus v1.9.3 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/xeipuuv/gojsonschema v1.2.0 ) diff --git a/go.sum b/go.sum index 01fc2116..0c89fb58 100644 --- a/go.sum +++ b/go.sum @@ -29,8 +29,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= diff --git a/mysql-config.yml.sample b/mysql-config.yml.sample index cbf79a4e..762d2bea 100644 --- a/mysql-config.yml.sample +++ b/mysql-config.yml.sample @@ -36,17 +36,17 @@ integrations: REMOTE_MONITORING: true # Enable query performance monitoring - ENABLE_QUERY_PERFORMANCE: true + # ENABLE_QUERY_PERFORMANCE: true # Fetch interval in seconds for grouped slow queries. Should match the interval in mysql-config.yml - SLOW_QUERY_FETCH_INTERVAL: 15 + # SLOW_QUERY_FETCH_INTERVAL: 15 # Threshold in milliseconds for query response time to fetch individual query performance metrics. - QUERY_RESPONSE_TIME_THRESHOLD: 500 + # QUERY_RESPONSE_TIME_THRESHOLD: 500 # Query count limit for fetching grouped slow and individual query performance metrics. - QUERY_COUNT_THRESHOLD: 20 + # QUERY_COUNT_THRESHOLD: 20 # Example: # EXCLUDED_DATABASES: '["employees","azure_sys"]' # A JSON array that list databases that will be excluded from collection. system databases are excluded by default. - EXCLUDED_DATABASES: '[]' + # EXCLUDED_DATABASES: '[]' interval: 15s labels: env: production diff --git a/src/mysql.go b/src/mysql.go index 763c6855..f2cbc301 100644 --- a/src/mysql.go +++ b/src/mysql.go @@ -119,7 +119,7 @@ func main() { populateMetrics(ms, rawMetrics) } fatalIfErr(i.Publish()) - // New functionality + if args.EnableQueryPerformance { queryperformancemonitoring.PopulateQueryPerformanceMetrics(args, e, i) } diff --git a/src/query-performance-monitoring/constants/constants.go b/src/query-performance-monitoring/constants/constants.go index 688445ec..0a7e2afe 100644 --- a/src/query-performance-monitoring/constants/constants.go +++ b/src/query-performance-monitoring/constants/constants.go @@ -14,3 +14,6 @@ const ( IndividualQueryCountThreshold = 10 MinVersionParts = 2 ) + +// Default excluded databases +var DefaultExcludedDatabases = []string{"", "mysql", "information_schema", "performance_schema", "sys"} 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 bb9bda80..031ee5e8 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 @@ -114,7 +114,7 @@ func extractMetricsFromJSONString(jsonString string, eventID uint64, threadID ui return dbPerformanceEvents, nil } -// extractMetrics recursively extracts metrics from a simplejson.Json object. +// extractMetrics recursively retrieves metrics from the query plan. func extractMetrics(js *simplejson.Json, dbPerformanceEvents []utils.QueryPlanMetrics, eventID uint64, threadID uint64, memo utils.Memo, stepID *int) []utils.QueryPlanMetrics { tableName, _ := js.Get("table_name").String() queryCost, _ := js.Get("cost_info").Get("query_cost").String() @@ -124,10 +124,17 @@ func extractMetrics(js *simplejson.Json, dbPerformanceEvents []utils.QueryPlanMe filtered, _ := js.Get("filtered").String() readCost, _ := js.Get("cost_info").Get("read_cost").String() evalCost, _ := js.Get("cost_info").Get("eval_cost").String() + prefixCost, _ := js.Get("cost_info").Get("prefix_cost").String() + dataReadPerJoin, _ := js.Get("cost_info").Get("data_read_per_join").String() + usingIndex, _ := js.Get("using_index").Bool() + keyLength, _ := js.Get("key_length").String() possibleKeysArray, _ := js.Get("possible_keys").StringArray() key, _ := js.Get("key").String() usedKeyPartsArray, _ := js.Get("used_key_parts").StringArray() refArray, _ := js.Get("ref").StringArray() + insert, _ := js.Get("insert").Bool() + update, _ := js.Get("update").Bool() + delete, _ := js.Get("delete").Bool() possibleKeys := strings.Join(possibleKeysArray, ",") usedKeyParts := strings.Join(usedKeyPartsArray, ",") @@ -154,6 +161,13 @@ func extractMetrics(js *simplejson.Json, dbPerformanceEvents []utils.QueryPlanMe Key: key, UsedKeyParts: usedKeyParts, Ref: ref, + PrefixCost: prefixCost, + DataReadPerJoin: dataReadPerJoin, + UsingIndex: fmt.Sprintf("%t", usingIndex), + KeyLength: keyLength, + InsertOperation: fmt.Sprintf("%t", insert), + UpdateOperation: fmt.Sprintf("%t", update), + DeleteOperation: fmt.Sprintf("%t", delete), }) *stepID++ } diff --git a/src/query-performance-monitoring/performance_main.go b/src/query-performance-monitoring/performance_main.go index 729774ee..3d2de36b 100644 --- a/src/query-performance-monitoring/performance_main.go +++ b/src/query-performance-monitoring/performance_main.go @@ -31,10 +31,7 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration } // Get the list of unique excluded databases - excludedDatabases, err := utils.GetExcludedDatabases(args.ExcludedDatabases) - if err != nil { - utils.FatalIfErr(fmt.Errorf("error unmarshaling json: %w", err)) - } + excludedDatabases := utils.GetExcludedDatabases(args.ExcludedDatabases) // Populate metrics for slow queries start := time.Now() diff --git a/src/query-performance-monitoring/utils/helpers.go b/src/query-performance-monitoring/utils/helpers.go index 5994bbb8..10f02780 100644 --- a/src/query-performance-monitoring/utils/helpers.go +++ b/src/query-performance-monitoring/utils/helpers.go @@ -16,9 +16,6 @@ import ( constants "github.com/newrelic/nri-mysql/src/query-performance-monitoring/constants" ) -// Default excluded databases -var defaultExcludedDatabases = []string{"", "mysql", "information_schema", "performance_schema", "sys"} - // Dynamic error var ( ErrEssentialConsumerNotEnabled = errors.New("essential consumer is not enabled") @@ -76,7 +73,7 @@ func getUniqueExcludedDatabases(excludedDBList string) []string { uniqueSchemas := make(map[string]struct{}) // Populate the map with default excluded databases - for _, schema := range defaultExcludedDatabases { + for _, schema := range constants.DefaultExcludedDatabases { uniqueSchemas[schema] = struct{}{} } @@ -95,11 +92,11 @@ func getUniqueExcludedDatabases(excludedDBList string) []string { } // GetExcludedDatabases parses the excluded databases list from a JSON string and returns a list of unique excluded databases. -func GetExcludedDatabases(excludedDatabasesList string) ([]string, error) { +func GetExcludedDatabases(excludedDatabasesList string) []string { // Parse the excluded databases list from JSON string var excludedDatabasesSlice []string if err := json.Unmarshal([]byte(excludedDatabasesList), &excludedDatabasesSlice); err != nil { - return nil, err + log.Warn("Error parsing excluded databases list: %v", err) } // Join the slice into a comma-separated string @@ -110,13 +107,13 @@ func GetExcludedDatabases(excludedDatabasesList string) ([]string, error) { // Get the list of unique excluded databases if cachedDatabases, found := excludedDatabasesCache[excludedDatabasesStr]; found { - return cachedDatabases, nil + return cachedDatabases } excludedDatabases := getUniqueExcludedDatabases(excludedDatabasesStr) excludedDatabasesCache[excludedDatabasesStr] = excludedDatabases - return excludedDatabases, nil + return excludedDatabases } // Helper function to convert a slice of strings to a slice of interfaces diff --git a/src/query-performance-monitoring/utils/models.go b/src/query-performance-monitoring/utils/models.go index 2e0b01c1..a77944a6 100644 --- a/src/query-performance-monitoring/utils/models.go +++ b/src/query-performance-monitoring/utils/models.go @@ -49,6 +49,13 @@ type QueryPlanMetrics struct { Key string `json:"key" metric_name:"key" source_type:"attribute"` UsedKeyParts string `json:"used_key_parts" metric_name:"used_key_parts" source_type:"attribute"` Ref string `json:"ref" metric_name:"ref" source_type:"attribute"` + PrefixCost string `json:"prefix_cost" metric_name:"prefix_cost" source_type:"attribute"` + DataReadPerJoin string `json:"data_read_per_join" metric_name:"data_read_per_join" source_type:"attribute"` + UsingIndex string `json:"using_index" metric_name:"using_index" source_type:"attribute"` + KeyLength string `json:"key_length" metric_name:"key_length" source_type:"attribute"` + UpdateOperation string `json:"update" metric_name:"update_operation" source_type:"attribute"` + InsertOperation string `json:"insert" metric_name:"insert_operation" source_type:"attribute"` + DeleteOperation string `json:"delete" metric_name:"delete_operation" source_type:"attribute"` } type Memo struct { @@ -75,13 +82,11 @@ type BlockingSessionMetrics struct { BlockedQueryID *string `json:"blocked_query_id" db:"blocked_query_id" metric_name:"blocked_query_id" source_type:"attribute"` BlockedQuery *string `json:"blocked_query" db:"blocked_query" metric_name:"blocked_query" source_type:"attribute"` BlockedStatus *string `json:"blocked_status" db:"blocked_status" metric_name:"blocked_status" source_type:"attribute"` - BlockedUser *string `json:"blocked_user" db:"blocked_user" metric_name:"blocked_user" source_type:"attribute"` BlockedHost *string `json:"blocked_host" db:"blocked_host" metric_name:"blocked_host" source_type:"attribute"` BlockedDB *string `json:"database_name" db:"database_name" metric_name:"database_name" source_type:"attribute"` BlockingTxnID *string `json:"blocking_txn_id" db:"blocking_txn_id" metric_name:"blocking_txn_id" source_type:"attribute"` BlockingPID *string `json:"blocking_pid" db:"blocking_pid" metric_name:"blocking_pid" source_type:"attribute"` BlockingThreadID *int64 `json:"blocking_thread_id" db:"blocking_thread_id" metric_name:"blocking_thread_id" source_type:"gauge"` - BlockingUser *string `json:"blocking_user" db:"blocking_user" metric_name:"blocking_user" source_type:"attribute"` BlockingHost *string `json:"blocking_host" db:"blocking_host" metric_name:"blocking_host" source_type:"attribute"` BlockingQueryID *string `json:"blocking_query_id" db:"blocking_query_id" metric_name:"blocking_query_id" source_type:"attribute"` BlockingQuery *string `json:"blocking_query" db:"blocking_query" metric_name:"blocking_query" source_type:"attribute"` diff --git a/src/query-performance-monitoring/utils/queries.go b/src/query-performance-monitoring/utils/queries.go index 855e60e2..f42e922f 100644 --- a/src/query-performance-monitoring/utils/queries.go +++ b/src/query-performance-monitoring/utils/queries.go @@ -197,14 +197,12 @@ const ( r.trx_id AS blocked_txn_id, r.trx_mysql_thread_id AS blocked_thread_id, wt.PROCESSLIST_ID AS blocked_pid, - wt.PROCESSLIST_USER AS blocked_user, wt.PROCESSLIST_HOST AS blocked_host, wt.PROCESSLIST_DB AS database_name, wt.PROCESSLIST_STATE AS blocked_status, b.trx_id AS blocking_txn_id, b.trx_mysql_thread_id AS blocking_thread_id, bt.PROCESSLIST_ID AS blocking_pid, - bt.PROCESSLIST_USER AS blocking_user, bt.PROCESSLIST_HOST AS blocking_host, es_waiting.DIGEST_TEXT AS blocked_query, es_blocking.DIGEST_TEXT AS blocking_query, From e5cda53c2d987b45b3b3befcddc4bdab1c4447b2 Mon Sep 17 00:00:00 2001 From: spathlavath Date: Fri, 10 Jan 2025 12:40:48 +0530 Subject: [PATCH 03/32] refactor: handle nil or empty query text/ID to resolve compile issues --- .../performance-metrics-collectors/query_details.go | 8 ++++++++ .../query_execution_plan.go | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) 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 57f97519..80f86d08 100644 --- a/src/query-performance-monitoring/performance-metrics-collectors/query_details.go +++ b/src/query-performance-monitoring/performance-metrics-collectors/query_details.go @@ -57,6 +57,10 @@ func collectGroupedSlowQueryMetrics(db utils.DataSource, slowQueryfetchInterval if err := rows.StructScan(&metric); err != nil { return nil, []string{}, err } + if metric.QueryID == nil { + log.Warn("Query ID is nil") + continue + } qID = *metric.QueryID qIDList = append(qIDList, qID) metrics = append(metrics, metric) @@ -127,6 +131,10 @@ func groupQueriesByDatabase(filteredList []utils.IndividualQueryMetrics) []utils groupMap := make(map[string][]utils.IndividualQueryMetrics) for _, query := range filteredList { + if query.DatabaseName == nil { + log.Warn("Database name is nil") + continue + } groupMap[*query.DatabaseName] = append(groupMap[*query.DatabaseName], query) } 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 031ee5e8..70560e20 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 @@ -51,8 +51,8 @@ func processExecutionPlanMetrics(db utils.DataSource, query utils.IndividualQuer ctx, cancel := context.WithTimeout(context.Background(), constants.QueryPlanTimeoutDuration) defer cancel() - if *query.QueryText == "" { - log.Warn("Query text is empty, skipping.") + if query.QueryText == nil || strings.TrimSpace(*query.QueryText) == "" { + log.Warn("Query text is empty or nil, skipping.") return []utils.QueryPlanMetrics{}, nil } queryText := strings.TrimSpace(*query.QueryText) From f3f19735e8fc19c01f4fc7a1e2b01ef86b725305 Mon Sep 17 00:00:00 2001 From: spathlavath Date: Sun, 12 Jan 2025 15:39:52 +0530 Subject: [PATCH 04/32] reafctor: Updated wait events query to analyze wait events and execution times (#20) Updated wait events query to analyze wait events and execution times --- .../wait_event_details.go | 2 +- .../utils/queries.go | 111 ++++++++---------- 2 files changed, 48 insertions(+), 65 deletions(-) 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 970d660b..b8819396 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 @@ -12,7 +12,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) { // Prepare the arguments for the query - excludedDatabasesArgs := []interface{}{excludedDatabases, excludedDatabases, excludedDatabases, min(args.QueryCountThreshold, constants.MaxQueryCountThreshold)} + excludedDatabasesArgs := []interface{}{excludedDatabases, excludedDatabases, min(args.QueryCountThreshold, constants.MaxQueryCountThreshold)} // Prepare the SQL query with the provided parameters preparedQuery, preparedArgs, err := sqlx.In(utils.WaitEventsQuery, excludedDatabasesArgs...) diff --git a/src/query-performance-monitoring/utils/queries.go b/src/query-performance-monitoring/utils/queries.go index f42e922f..c0452a41 100644 --- a/src/query-performance-monitoring/utils/queries.go +++ b/src/query-performance-monitoring/utils/queries.go @@ -98,6 +98,46 @@ const ( LIMIT ?; ` WaitEventsQuery = ` + WITH wait_data AS ( + SELECT DISTINCT + THREAD_ID, + OBJECT_INSTANCE_BEGIN AS instance_id, + EVENT_NAME AS wait_event_name, + TIMER_WAIT, + TIMER_START + FROM performance_schema.events_waits_current + UNION ALL + SELECT DISTINCT + THREAD_ID, + OBJECT_INSTANCE_BEGIN AS instance_id, + EVENT_NAME AS wait_event_name, + TIMER_WAIT, + TIMER_START + FROM performance_schema.events_waits_history + ), + schema_data AS ( + SELECT DISTINCT + THREAD_ID, + DIGEST, + CURRENT_SCHEMA AS database_name, + DIGEST_TEXT AS query_text, + ROUND(TIMER_WAIT / 1000000000, 3) AS execution_time_ms + FROM performance_schema.events_statements_current + WHERE CURRENT_SCHEMA NOT IN (?) + AND SQL_TEXT RLIKE '^(SELECT|INSERT|UPDATE|DELETE|WITH)' + AND DIGEST_TEXT NOT LIKE '%DIGEST_TEXT%' + UNION ALL + SELECT DISTINCT + THREAD_ID, + DIGEST, + CURRENT_SCHEMA AS database_name, + DIGEST_TEXT AS query_text, + ROUND(TIMER_WAIT / 1000000000, 3) AS execution_time_ms + FROM performance_schema.events_statements_history + WHERE CURRENT_SCHEMA NOT IN (?) + AND SQL_TEXT RLIKE '^(SELECT|INSERT|UPDATE|DELETE|WITH)' + AND DIGEST_TEXT NOT LIKE '%DIGEST_TEXT%' + ) SELECT schema_data.DIGEST AS query_id, wait_data.instance_id, @@ -114,81 +154,24 @@ const ( WHEN wait_data.wait_event_name LIKE 'wait/lock/transaction/%' THEN 'Transaction Lock' ELSE 'Other' END AS wait_category, - ROUND(IFNULL(SUM(wait_data.TIMER_WAIT), 0) / 1000000000, 3) AS total_wait_time_ms, - SUM(ewsg.COUNT_STAR) AS wait_event_count, - ROUND((IFNULL(SUM(wait_data.TIMER_WAIT), 0) / 1000000000) / IFNULL(SUM(ewsg.COUNT_STAR), 1), 3) AS avg_wait_time_ms, + ROUND(SUM(wait_data.TIMER_WAIT) / 1000000000, 3) AS total_wait_time_ms, + COUNT(DISTINCT wait_data.instance_id) AS wait_event_count, + ROUND(SUM(wait_data.TIMER_WAIT) / 1000000000 / COUNT(DISTINCT wait_data.instance_id), 3) AS avg_wait_time_ms, CASE WHEN CHAR_LENGTH(schema_data.query_text) > 4000 THEN CONCAT(LEFT(schema_data.query_text, 3997), '...') ELSE schema_data.query_text END AS query_text, DATE_FORMAT(UTC_TIMESTAMP(), '%Y-%m-%dT%H:%i:%sZ') AS collection_timestamp - FROM ( - SELECT - THREAD_ID, - OBJECT_INSTANCE_BEGIN AS instance_id, - EVENT_NAME AS wait_event_name, - TIMER_WAIT - FROM performance_schema.events_waits_history_long - UNION ALL - SELECT - THREAD_ID, - OBJECT_INSTANCE_BEGIN AS instance_id, - EVENT_NAME AS wait_event_name, - TIMER_WAIT - FROM performance_schema.events_waits_history - UNION ALL - SELECT - THREAD_ID, - OBJECT_INSTANCE_BEGIN AS instance_id, - EVENT_NAME AS wait_event_name, - TIMER_WAIT - FROM performance_schema.events_waits_current - ) AS wait_data - JOIN ( - SELECT - THREAD_ID, - DIGEST, - CURRENT_SCHEMA AS database_name, - DIGEST_TEXT AS query_text - FROM performance_schema.events_statements_history_long - WHERE CURRENT_SCHEMA IS NOT NULL - AND CURRENT_SCHEMA NOT IN (?) - AND SQL_TEXT RLIKE '^(SELECT|INSERT|UPDATE|DELETE|WITH)' - AND SQL_TEXT NOT LIKE '%DIGEST_TEXT%' - UNION ALL - SELECT - THREAD_ID, - DIGEST, - CURRENT_SCHEMA AS database_name, - DIGEST_TEXT AS query_text - FROM performance_schema.events_statements_history - WHERE CURRENT_SCHEMA IS NOT NULL - AND CURRENT_SCHEMA NOT IN (?) - AND SQL_TEXT RLIKE '^(SELECT|INSERT|UPDATE|DELETE|WITH)' - AND SQL_TEXT NOT LIKE '%DIGEST_TEXT%' - UNION ALL - SELECT - THREAD_ID, - DIGEST, - CURRENT_SCHEMA AS database_name, - DIGEST_TEXT AS query_text - FROM performance_schema.events_statements_current - WHERE CURRENT_SCHEMA IS NOT NULL - AND CURRENT_SCHEMA NOT IN (?) - AND SQL_TEXT RLIKE '^(SELECT|INSERT|UPDATE|DELETE|WITH)' - AND SQL_TEXT NOT LIKE '%DIGEST_TEXT%' - ) AS schema_data - ON wait_data.THREAD_ID = schema_data.THREAD_ID - LEFT JOIN performance_schema.events_waits_summary_global_by_event_name ewsg - ON ewsg.EVENT_NAME = wait_data.wait_event_name + FROM wait_data + JOIN schema_data ON wait_data.THREAD_ID = schema_data.THREAD_ID GROUP BY query_id, wait_data.instance_id, + schema_data.database_name, wait_data.wait_event_name, wait_category, - schema_data.database_name, schema_data.query_text - ORDER BY + ORDER BY total_wait_time_ms DESC LIMIT ?; ` From fb94bf2c909aa7a9ae03caa6c780798db9b14b9c Mon Sep 17 00:00:00 2001 From: spathlavath Date: Mon, 13 Jan 2025 13:29:34 +0530 Subject: [PATCH 05/32] Instrumented with Go APM Agent --- go.mod | 8 +++++++- go.sum | 14 ++++++++++++++ src/mysql.go | 11 ++++++++++- .../performance_main.go | 13 ++++++++++++- 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9ab1977a..8c9d0581 100644 --- a/go.mod +++ b/go.mod @@ -16,11 +16,17 @@ 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/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..3f3dcff3 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ 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/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 +39,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/mysql.go b/src/mysql.go index f2cbc301..580d2a77 100644 --- a/src/mysql.go +++ b/src/mysql.go @@ -16,6 +16,7 @@ import ( "strconv" "strings" + "github.com/newrelic/go-agent/v3/newrelic" arguments "github.com/newrelic/nri-mysql/src/args" queryperformancemonitoring "github.com/newrelic/nri-mysql/src/query-performance-monitoring" ) @@ -77,6 +78,14 @@ func createNodeEntity( } func main() { + app, err := newrelic.NewApplication( + newrelic.ConfigAppName("nri-mysql"), + newrelic.ConfigLicense("dacf7d740407774b3c079e3a520ca57bFFFFNRAL"), + newrelic.ConfigAppLogForwardingEnabled(true), + ) + if err != nil { + fmt.Println("error creating app:", err) + } i, err := integration.New(integrationName, integrationVersion, integration.Args(&args)) fatalIfErr(err) @@ -121,7 +130,7 @@ func main() { fatalIfErr(i.Publish()) if args.EnableQueryPerformance { - queryperformancemonitoring.PopulateQueryPerformanceMetrics(args, e, i) + queryperformancemonitoring.PopulateQueryPerformanceMetrics(args, e, i, app) } } diff --git a/src/query-performance-monitoring/performance_main.go b/src/query-performance-monitoring/performance_main.go index 3d2de36b..11257de9 100644 --- a/src/query-performance-monitoring/performance_main.go +++ b/src/query-performance-monitoring/performance_main.go @@ -4,6 +4,7 @@ import ( "fmt" "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" @@ -13,7 +14,7 @@ import ( ) // main -func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration.Entity, i *integration.Integration) { +func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration.Entity, i *integration.Integration, app *newrelic.Application) { var database string // Generate Data Source Name (DSN) for database connection @@ -35,34 +36,44 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration // Populate metrics for slow queries start := time.Now() + txn := app.StartTransaction("MysqlSlowQueriesSample") log.Debug("Beginning to retrieve slow query metrics") queryIDList := performancemetricscollectors.PopulateSlowQueryMetrics(i, e, db, args, excludedDatabases) log.Debug("Completed fetching slow query metrics in %v", time.Since(start)) + defer txn.End() if len(queryIDList) > 0 { // Populate metrics for individual queries start = time.Now() + txn = app.StartTransaction("MysqlIndividualQueriesSample") log.Debug("Beginning to retrieve individual query metrics") groupQueriesByDatabase := performancemetricscollectors.PopulateIndividualQueryDetails(db, queryIDList, i, e, args) log.Debug("Completed fetching individual query metrics in %v", time.Since(start)) + defer txn.End() // Populate execution plan details start = time.Now() + txn = app.StartTransaction("MysqlQueryExecutionSample") log.Debug("Beginning to retrieve query execution plan metrics") performancemetricscollectors.PopulateExecutionPlans(db, groupQueriesByDatabase, i, e, args) log.Debug("Completed fetching query execution plan metrics in %v", time.Since(start)) + defer txn.End() } // Populate wait event metrics start = time.Now() + txn = app.StartTransaction("MysqlWaitEventsSample") log.Debug("Beginning to retrieve wait event metrics") performancemetricscollectors.PopulateWaitEventMetrics(db, i, e, args, excludedDatabases) log.Debug("Completed fetching wait event metrics in %v", time.Since(start)) + defer txn.End() // Populate blocking session metrics start = time.Now() + txn = app.StartTransaction("MysqlBlockingSessionSample") log.Debug("Beginning to retrieve blocking session metrics") performancemetricscollectors.PopulateBlockingSessionMetrics(db, i, e, args, excludedDatabases) log.Debug("Completed fetching blocking session metrics in %v", time.Since(start)) log.Debug("Query analysis completed.") + defer txn.End() } From e29d31d31949f74f96d1bfae30cb6afc5955c733 Mon Sep 17 00:00:00 2001 From: spathlavath Date: Mon, 13 Jan 2025 13:35:40 +0530 Subject: [PATCH 06/32] Added license key to args --- src/args/argument_list.go | 1 + src/mysql.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) 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/mysql.go b/src/mysql.go index 580d2a77..e12029e3 100644 --- a/src/mysql.go +++ b/src/mysql.go @@ -80,7 +80,7 @@ func createNodeEntity( func main() { app, err := newrelic.NewApplication( newrelic.ConfigAppName("nri-mysql"), - newrelic.ConfigLicense("dacf7d740407774b3c079e3a520ca57bFFFFNRAL"), + newrelic.ConfigLicense(args.LicenseKey), newrelic.ConfigAppLogForwardingEnabled(true), ) if err != nil { From fec2443a116bdb2d5af341a71eafb3a05a1fea87 Mon Sep 17 00:00:00 2001 From: spathlavath Date: Mon, 13 Jan 2025 16:16:03 +0530 Subject: [PATCH 07/32] Added license key to args --- src/mysql.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mysql.go b/src/mysql.go index e12029e3..8bc8bd37 100644 --- a/src/mysql.go +++ b/src/mysql.go @@ -78,6 +78,9 @@ func createNodeEntity( } func main() { + i, err := integration.New(integrationName, integrationVersion, integration.Args(&args)) + fatalIfErr(err) + app, err := newrelic.NewApplication( newrelic.ConfigAppName("nri-mysql"), newrelic.ConfigLicense(args.LicenseKey), @@ -86,8 +89,6 @@ func main() { if err != nil { fmt.Println("error creating app:", err) } - i, err := integration.New(integrationName, integrationVersion, integration.Args(&args)) - fatalIfErr(err) if args.ShowVersion { fmt.Printf( From 3af35b15cc890d5934886453b406e55d1b5e12d8 Mon Sep 17 00:00:00 2001 From: spathlavath Date: Mon, 13 Jan 2025 16:22:16 +0530 Subject: [PATCH 08/32] Added license key to args --- src/mysql.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mysql.go b/src/mysql.go index 8bc8bd37..9fb5fe32 100644 --- a/src/mysql.go +++ b/src/mysql.go @@ -82,7 +82,7 @@ func main() { fatalIfErr(err) app, err := newrelic.NewApplication( - newrelic.ConfigAppName("nri-mysql"), + newrelic.ConfigAppName("nri-mysql-v3"), newrelic.ConfigLicense(args.LicenseKey), newrelic.ConfigAppLogForwardingEnabled(true), ) From b8bb8e02cb527e855196243683d71f550d848d2d Mon Sep 17 00:00:00 2001 From: spathlavath Date: Mon, 13 Jan 2025 23:40:12 +0530 Subject: [PATCH 09/32] Added nrmysql driver --- go.mod | 1 + go.sum | 2 ++ src/database.go | 3 ++- src/mysql.go | 4 ++-- .../performance_main.go | 17 ++++++++++++++++- .../utils/database.go | 3 ++- 6 files changed, 25 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 8c9d0581..8a0f077d 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( 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 diff --git a/go.sum b/go.sum index 3f3dcff3..ba3ab9ff 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,8 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o 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= diff --git a/src/database.go b/src/database.go index b44a1ace..33ea6c55 100644 --- a/src/database.go +++ b/src/database.go @@ -5,6 +5,7 @@ import ( "fmt" _ "github.com/go-sql-driver/mysql" + _ "github.com/newrelic/go-agent/v3/integrations/nrmysql" "github.com/newrelic/infra-integrations-sdk/v3/log" ) @@ -18,7 +19,7 @@ type database struct { } func openDB(dsn string) (dataSource, error) { - source, err := sql.Open("mysql", dsn) + source, err := sql.Open("nrmysql", dsn) if err != nil { return nil, fmt.Errorf("error opening %s: %v", dsn, err) } diff --git a/src/mysql.go b/src/mysql.go index 9fb5fe32..6f05127e 100644 --- a/src/mysql.go +++ b/src/mysql.go @@ -82,12 +82,12 @@ func main() { fatalIfErr(err) app, err := newrelic.NewApplication( - newrelic.ConfigAppName("nri-mysql-v3"), + newrelic.ConfigAppName("nri-mysql-go-agent"), newrelic.ConfigLicense(args.LicenseKey), newrelic.ConfigAppLogForwardingEnabled(true), ) if err != nil { - fmt.Println("error creating app:", err) + fatalIfErr(err) } if args.ShowVersion { diff --git a/src/query-performance-monitoring/performance_main.go b/src/query-performance-monitoring/performance_main.go index 11257de9..cdab6673 100644 --- a/src/query-performance-monitoring/performance_main.go +++ b/src/query-performance-monitoring/performance_main.go @@ -2,6 +2,7 @@ package queryperformancemonitoring import ( "fmt" + "strconv" "time" "github.com/newrelic/go-agent/v3/newrelic" @@ -37,10 +38,24 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration // Populate metrics for slow queries start := time.Now() txn := app.StartTransaction("MysqlSlowQueriesSample") + defer txn.End() + + // Wrap database calls in Datastore Segments + segment := newrelic.DatastoreSegment{ + StartTime: txn.StartSegmentNow(), + Product: newrelic.DatastoreMySQL, + Collection: "performance_schema.events_statements_summary_by_digest", // Appropriate table/collection + Operation: "SELECT", + ParameterizedQuery: utils.SlowQueries, // The query being executed + Host: args.Hostname, + PortPathOrID: strconv.Itoa(args.Port), + DatabaseName: args.Database, + } + log.Debug("Beginning to retrieve slow query metrics") queryIDList := performancemetricscollectors.PopulateSlowQueryMetrics(i, e, db, args, excludedDatabases) log.Debug("Completed fetching slow query metrics in %v", time.Since(start)) - defer txn.End() + segment.End() if len(queryIDList) > 0 { // Populate metrics for individual queries diff --git a/src/query-performance-monitoring/utils/database.go b/src/query-performance-monitoring/utils/database.go index 100e923f..a77ecf9f 100644 --- a/src/query-performance-monitoring/utils/database.go +++ b/src/query-performance-monitoring/utils/database.go @@ -9,6 +9,7 @@ import ( _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" + _ "github.com/newrelic/go-agent/v3/integrations/nrmysql" "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" @@ -25,7 +26,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) } From cd222b0eb5810fba867567c899180b95f2f6e1cf Mon Sep 17 00:00:00 2001 From: spathlavath Date: Mon, 13 Jan 2025 23:45:58 +0530 Subject: [PATCH 10/32] updated app name --- src/mysql.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mysql.go b/src/mysql.go index 6f05127e..c67dccbe 100644 --- a/src/mysql.go +++ b/src/mysql.go @@ -82,7 +82,7 @@ func main() { fatalIfErr(err) app, err := newrelic.NewApplication( - newrelic.ConfigAppName("nri-mysql-go-agent"), + newrelic.ConfigAppName("nri-mysql-v3"), newrelic.ConfigLicense(args.LicenseKey), newrelic.ConfigAppLogForwardingEnabled(true), ) From 7cc61b1b6783980f1429a6a25357db076dfbac53 Mon Sep 17 00:00:00 2001 From: spathlavath Date: Mon, 13 Jan 2025 23:51:35 +0530 Subject: [PATCH 11/32] updated app name --- src/mysql.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mysql.go b/src/mysql.go index c67dccbe..cb0ae9ae 100644 --- a/src/mysql.go +++ b/src/mysql.go @@ -82,7 +82,7 @@ func main() { fatalIfErr(err) app, err := newrelic.NewApplication( - newrelic.ConfigAppName("nri-mysql-v3"), + newrelic.ConfigAppName("nri-mysql-integration"), newrelic.ConfigLicense(args.LicenseKey), newrelic.ConfigAppLogForwardingEnabled(true), ) From 371c0ab33e39c1d17c1339e06a3b0b2814585931 Mon Sep 17 00:00:00 2001 From: spathlavath Date: Tue, 14 Jan 2025 01:11:21 +0530 Subject: [PATCH 12/32] instrumentation changes --- .../query_details.go | 58 ++++++++++++++----- .../performance_main.go | 22 ++----- 2 files changed, 50 insertions(+), 30 deletions(-) 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 80f86d08..bc72ae7c 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(ctx context.Context, i *integration.Integration, e *integration.Entity, db utils.DataSource, args arguments.ArgumentList, excludedDatabases []string) []string { + rawMetrics, queryIDList, err := collectGroupedSlowQueryMetrics(ctx, db, args.SlowQueryFetchInterval, args.QueryCountThreshold, excludedDatabases) if err != nil { log.Error("Failed to collect slow query metrics: %v", err) return []string{} @@ -34,7 +35,22 @@ 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(cntx context.Context, db utils.DataSource, slowQueryfetchInterval int, queryCountThreshold int, excludedDatabases []string) ([]utils.SlowQueryMetrics, []string, error) { + txn := newrelic.FromContext(cntx) + if txn == nil { + log.Error("Failed to get New Relic transaction from context") + return nil, []string{}, nil + } + // Wrap database calls in Datastore Segments + segment := newrelic.DatastoreSegment{ + StartTime: txn.StartSegmentNow(), + Product: newrelic.DatastoreMySQL, + Collection: "performance_schema.events_statements_summary_by_digest", // Appropriate table/collection + Operation: "SELECT", + ParameterizedQuery: utils.SlowQueries, // The query being executed + } + defer segment.End() + // Prepare the SQL query with the provided parameters query, args, err := sqlx.In(utils.SlowQueries, slowQueryfetchInterval, excludedDatabases, min(queryCountThreshold, constants.MaxQueryCountThreshold)) if err != nil { @@ -88,20 +104,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(ctx context.Context, db utils.DataSource, queryIDList []string, i *integration.Integration, e *integration.Entity, args arguments.ArgumentList) []utils.QueryGroup { + currentQueryMetrics, currentQueryMetricsErr := currentQueryMetrics(ctx, 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(ctx, 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(ctx, db, queryIDList, args) if extensiveQueryErr != nil { log.Error("Failed to collect history query metrics: %v", extensiveQueryErr) return nil @@ -151,8 +167,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(ctx context.Context, db utils.DataSource, queryIDList []string, args arguments.ArgumentList) ([]utils.IndividualQueryMetrics, error) { + metrics, err := collectIndividualQueryMetrics(ctx, db, queryIDList, utils.CurrentRunningQueriesSearch, args) if err != nil { return nil, err } @@ -161,8 +177,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(ctx context.Context, db utils.DataSource, queryIDList []string, args arguments.ArgumentList) ([]utils.IndividualQueryMetrics, error) { + metrics, err := collectIndividualQueryMetrics(ctx, db, queryIDList, utils.RecentQueriesSearch, args) if err != nil { return nil, err } @@ -171,8 +187,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(ctx context.Context, db utils.DataSource, queryIDList []string, args arguments.ArgumentList) ([]utils.IndividualQueryMetrics, error) { + metrics, err := collectIndividualQueryMetrics(ctx, db, queryIDList, utils.PastQueriesSearch, args) if err != nil { return nil, err } @@ -181,13 +197,27 @@ 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(ctx context.Context, 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") return []utils.IndividualQueryMetrics{}, nil } + txn := newrelic.FromContext(ctx) + if txn == nil { + log.Error("Failed to get New Relic transaction from context") + return []utils.IndividualQueryMetrics{}, nil + } + // Wrap database calls in Datastore Segments + segment := newrelic.DatastoreSegment{ + StartTime: txn.StartSegmentNow(), + Product: newrelic.DatastoreMySQL, + Operation: "SELECT", + ParameterizedQuery: queryString, // The query being executed + } + defer segment.End() + var metricsList []utils.IndividualQueryMetrics for _, queryID := range queryIDList { diff --git a/src/query-performance-monitoring/performance_main.go b/src/query-performance-monitoring/performance_main.go index cdab6673..4d6fcd61 100644 --- a/src/query-performance-monitoring/performance_main.go +++ b/src/query-performance-monitoring/performance_main.go @@ -1,8 +1,8 @@ package queryperformancemonitoring import ( + "context" "fmt" - "strconv" "time" "github.com/newrelic/go-agent/v3/newrelic" @@ -37,32 +37,22 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration // Populate metrics for slow queries start := time.Now() + ctx := context.Background() txn := app.StartTransaction("MysqlSlowQueriesSample") + ctx = newrelic.NewContext(ctx, txn) defer txn.End() - // Wrap database calls in Datastore Segments - segment := newrelic.DatastoreSegment{ - StartTime: txn.StartSegmentNow(), - Product: newrelic.DatastoreMySQL, - Collection: "performance_schema.events_statements_summary_by_digest", // Appropriate table/collection - Operation: "SELECT", - ParameterizedQuery: utils.SlowQueries, // The query being executed - Host: args.Hostname, - PortPathOrID: strconv.Itoa(args.Port), - DatabaseName: args.Database, - } - log.Debug("Beginning to retrieve slow query metrics") - queryIDList := performancemetricscollectors.PopulateSlowQueryMetrics(i, e, db, args, excludedDatabases) + queryIDList := performancemetricscollectors.PopulateSlowQueryMetrics(ctx, i, e, db, args, excludedDatabases) log.Debug("Completed fetching slow query metrics in %v", time.Since(start)) - segment.End() if len(queryIDList) > 0 { // Populate metrics for individual queries start = time.Now() txn = app.StartTransaction("MysqlIndividualQueriesSample") + ctx = newrelic.NewContext(ctx, txn) log.Debug("Beginning to retrieve individual query metrics") - groupQueriesByDatabase := performancemetricscollectors.PopulateIndividualQueryDetails(db, queryIDList, i, e, args) + groupQueriesByDatabase := performancemetricscollectors.PopulateIndividualQueryDetails(ctx, db, queryIDList, i, e, args) log.Debug("Completed fetching individual query metrics in %v", time.Since(start)) defer txn.End() From de1a5b195cd71f5d39dccdd2c02721c799d15928 Mon Sep 17 00:00:00 2001 From: spathlavath Date: Tue, 14 Jan 2025 02:23:23 +0530 Subject: [PATCH 13/32] instrumentation changes for all files --- src/mysql.go | 14 +++++- .../mysql-apm/mysql_apm.go | 6 +++ .../blocking_sessions.go | 5 ++- .../query_details.go | 43 ++++++------------- .../wait_event_details.go | 5 ++- .../performance_main.go | 11 +++-- .../utils/database.go | 39 ++++++++++++++++- 7 files changed, 80 insertions(+), 43 deletions(-) create mode 100644 src/query-performance-monitoring/mysql-apm/mysql_apm.go diff --git a/src/mysql.go b/src/mysql.go index cb0ae9ae..42e80749 100644 --- a/src/mysql.go +++ b/src/mysql.go @@ -19,6 +19,7 @@ import ( "github.com/newrelic/go-agent/v3/newrelic" arguments "github.com/newrelic/nri-mysql/src/args" queryperformancemonitoring "github.com/newrelic/nri-mysql/src/query-performance-monitoring" + mysql_apm "github.com/newrelic/nri-mysql/src/query-performance-monitoring/mysql-apm" ) const ( @@ -80,14 +81,23 @@ func createNodeEntity( func main() { i, err := integration.New(integrationName, integrationVersion, integration.Args(&args)) fatalIfErr(err) - + mysql_apm.ArgsGlobal = args.LicenseKey app, err := newrelic.NewApplication( newrelic.ConfigAppName("nri-mysql-integration"), newrelic.ConfigLicense(args.LicenseKey), newrelic.ConfigAppLogForwardingEnabled(true), ) if err != nil { - fatalIfErr(err) + log.Error("Error creating new relic application: %s", err.Error()) + } + + mysql_apm.NewrelicApp = *app + + txn := app.StartTransaction("test_performance_monitoring") + defer txn.End() + if err != nil { + log.Error(err.Error()) + os.Exit(1) } if args.ShowVersion { 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..ea8623f1 --- /dev/null +++ b/src/query-performance-monitoring/mysql-apm/mysql_apm.go @@ -0,0 +1,6 @@ +package mysql_apm + +import "github.com/newrelic/go-agent/v3/newrelic" + +var ArgsGlobal = "" +var NewrelicApp = newrelic.Application{} 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 bc72ae7c..c278d091 100644 --- a/src/query-performance-monitoring/performance-metrics-collectors/query_details.go +++ b/src/query-performance-monitoring/performance-metrics-collectors/query_details.go @@ -45,7 +45,6 @@ func collectGroupedSlowQueryMetrics(cntx context.Context, db utils.DataSource, s segment := newrelic.DatastoreSegment{ StartTime: txn.StartSegmentNow(), Product: newrelic.DatastoreMySQL, - Collection: "performance_schema.events_statements_summary_by_digest", // Appropriate table/collection Operation: "SELECT", ParameterizedQuery: utils.SlowQueries, // The query being executed } @@ -104,20 +103,20 @@ func setSlowQueryMetrics(i *integration.Integration, metrics []utils.SlowQueryMe } // PopulateIndividualQueryDetails collects and sets individual query details -func PopulateIndividualQueryDetails(ctx context.Context, db utils.DataSource, queryIDList []string, i *integration.Integration, e *integration.Entity, args arguments.ArgumentList) []utils.QueryGroup { - currentQueryMetrics, currentQueryMetricsErr := currentQueryMetrics(ctx, 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(ctx, 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(ctx, 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 @@ -167,8 +166,8 @@ func groupQueriesByDatabase(filteredList []utils.IndividualQueryMetrics) []utils } // currentQueryMetrics collects current query metrics from the performance schema database for the given query IDs -func currentQueryMetrics(ctx context.Context, db utils.DataSource, queryIDList []string, args arguments.ArgumentList) ([]utils.IndividualQueryMetrics, error) { - metrics, err := collectIndividualQueryMetrics(ctx, 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 } @@ -177,8 +176,8 @@ func currentQueryMetrics(ctx context.Context, db utils.DataSource, queryIDList [ } // recentQueryMetrics collects recent query metrics from the performance schema database for the given query IDs -func recentQueryMetrics(ctx context.Context, db utils.DataSource, queryIDList []string, args arguments.ArgumentList) ([]utils.IndividualQueryMetrics, error) { - metrics, err := collectIndividualQueryMetrics(ctx, 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 } @@ -187,8 +186,8 @@ func recentQueryMetrics(ctx context.Context, db utils.DataSource, queryIDList [] } // extensiveQueryMetrics collects extensive query metrics from the performance schema database for the given query IDs -func extensiveQueryMetrics(ctx context.Context, db utils.DataSource, queryIDList []string, args arguments.ArgumentList) ([]utils.IndividualQueryMetrics, error) { - metrics, err := collectIndividualQueryMetrics(ctx, 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 } @@ -197,41 +196,27 @@ func extensiveQueryMetrics(ctx context.Context, db utils.DataSource, queryIDList } // collectIndividualQueryMetrics collects current query metrics from the performance schema database for the given query IDs -func collectIndividualQueryMetrics(ctx context.Context, 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") return []utils.IndividualQueryMetrics{}, nil } - txn := newrelic.FromContext(ctx) - if txn == nil { - log.Error("Failed to get New Relic transaction from context") - return []utils.IndividualQueryMetrics{}, nil - } - // Wrap database calls in Datastore Segments - segment := newrelic.DatastoreSegment{ - StartTime: txn.StartSegmentNow(), - Product: newrelic.DatastoreMySQL, - Operation: "SELECT", - ParameterizedQuery: queryString, // The query being executed - } - defer segment.End() - var metricsList []utils.IndividualQueryMetrics 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/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 4d6fcd61..16dc67aa 100644 --- a/src/query-performance-monitoring/performance_main.go +++ b/src/query-performance-monitoring/performance_main.go @@ -40,19 +40,18 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration ctx := context.Background() txn := app.StartTransaction("MysqlSlowQueriesSample") ctx = newrelic.NewContext(ctx, txn) - defer txn.End() - log.Debug("Beginning to retrieve slow query metrics") queryIDList := performancemetricscollectors.PopulateSlowQueryMetrics(ctx, i, e, db, args, excludedDatabases) log.Debug("Completed fetching slow query metrics in %v", time.Since(start)) + defer txn.End() if len(queryIDList) > 0 { // Populate metrics for individual queries start = time.Now() txn = app.StartTransaction("MysqlIndividualQueriesSample") - ctx = newrelic.NewContext(ctx, txn) + // ctx = newrelic.NewContext(ctx, txn) log.Debug("Beginning to retrieve individual query metrics") - groupQueriesByDatabase := performancemetricscollectors.PopulateIndividualQueryDetails(ctx, 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 txn.End() @@ -69,7 +68,7 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration start = time.Now() txn = app.StartTransaction("MysqlWaitEventsSample") 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 txn.End() @@ -77,7 +76,7 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration start = time.Now() txn = app.StartTransaction("MysqlBlockingSessionSample") 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 txn.End() diff --git a/src/query-performance-monitoring/utils/database.go b/src/query-performance-monitoring/utils/database.go index a77ecf9f..4b32f2d0 100644 --- a/src/query-performance-monitoring/utils/database.go +++ b/src/query-performance-monitoring/utils/database.go @@ -6,13 +6,16 @@ import ( "net" "net/url" "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" + mysql_apm "github.com/newrelic/nri-mysql/src/query-performance-monitoring/mysql-apm" ) type DataSource interface { @@ -98,10 +101,42 @@ 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) { - ctx, cancel := context.WithTimeout(context.Background(), constants.TimeoutDuration) +func CollectMetrics[T any](app *newrelic.Application, db DataSource, preparedQuery string, preparedArgs ...interface{}) ([]T, error) { + // Initialize New Relic application + if app == nil { + _, err := newrelic.NewApplication( + newrelic.ConfigAppName("nri-mysql-integration"), + newrelic.ConfigLicense(mysql_apm.ArgsGlobal), + newrelic.ConfigAppLogForwardingEnabled(true), + ) + if err != nil { + log.Error("Error creating new relic application: %s", err.Error()) + return []T{}, err + } + } + + waitErr := app.WaitForConnection(5 * time.Second) + if waitErr != nil { + log.Error("Error waiting for connection: %s", waitErr.Error()) + return []T{}, waitErr + } + + txn := app.StartTransaction("nrmysqlQuery") + defer txn.End() + + ctx := newrelic.NewContext(context.Background(), txn) + ctx, cancel := context.WithTimeout(ctx, constants.TimeoutDuration) defer cancel() + // Wrap database calls in Datastore Segments + segment := newrelic.DatastoreSegment{ + StartTime: txn.StartSegmentNow(), + Product: newrelic.DatastoreMySQL, + Operation: "SELECT", + ParameterizedQuery: preparedQuery, // The query being executed + } + defer segment.End() + rows, err := db.QueryxContext(ctx, preparedQuery, preparedArgs...) if err != nil { return []T{}, err From 6cfc048b166a2223f7c24351466046016a584e62 Mon Sep 17 00:00:00 2001 From: spathlavath Date: Tue, 14 Jan 2025 02:36:50 +0530 Subject: [PATCH 14/32] instrumentation changes for all files --- src/database.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/database.go b/src/database.go index 33ea6c55..b44a1ace 100644 --- a/src/database.go +++ b/src/database.go @@ -5,7 +5,6 @@ import ( "fmt" _ "github.com/go-sql-driver/mysql" - _ "github.com/newrelic/go-agent/v3/integrations/nrmysql" "github.com/newrelic/infra-integrations-sdk/v3/log" ) @@ -19,7 +18,7 @@ type database struct { } func openDB(dsn string) (dataSource, error) { - source, err := sql.Open("nrmysql", dsn) + source, err := sql.Open("mysql", dsn) if err != nil { return nil, fmt.Errorf("error opening %s: %v", dsn, err) } From 8ae26c4839ac90b0b680508a3dadbb7e5317d9fd Mon Sep 17 00:00:00 2001 From: spathlavath Date: Tue, 14 Jan 2025 02:48:11 +0530 Subject: [PATCH 15/32] instrumentation fixes --- .../query_details.go | 26 +++++++++---------- .../performance_main.go | 16 ++++++------ 2 files changed, 21 insertions(+), 21 deletions(-) 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 c278d091..49b16389 100644 --- a/src/query-performance-monitoring/performance-metrics-collectors/query_details.go +++ b/src/query-performance-monitoring/performance-metrics-collectors/query_details.go @@ -36,19 +36,19 @@ func PopulateSlowQueryMetrics(ctx context.Context, i *integration.Integration, e // collectGroupedSlowQueryMetrics collects metrics from the performance schema database for slow queries func collectGroupedSlowQueryMetrics(cntx context.Context, db utils.DataSource, slowQueryfetchInterval int, queryCountThreshold int, excludedDatabases []string) ([]utils.SlowQueryMetrics, []string, error) { - txn := newrelic.FromContext(cntx) - if txn == nil { - log.Error("Failed to get New Relic transaction from context") - return nil, []string{}, nil - } - // Wrap database calls in Datastore Segments - segment := newrelic.DatastoreSegment{ - StartTime: txn.StartSegmentNow(), - Product: newrelic.DatastoreMySQL, - Operation: "SELECT", - ParameterizedQuery: utils.SlowQueries, // The query being executed - } - defer segment.End() + // txn := newrelic.FromContext(cntx) + // if txn == nil { + // log.Error("Failed to get New Relic transaction from context") + // return nil, []string{}, nil + // } + // // Wrap database calls in Datastore Segments + // segment := newrelic.DatastoreSegment{ + // StartTime: txn.StartSegmentNow(), + // Product: newrelic.DatastoreMySQL, + // Operation: "SELECT", + // ParameterizedQuery: utils.SlowQueries, // The query being executed + // } + // defer segment.End() // Prepare the SQL query with the provided parameters query, args, err := sqlx.In(utils.SlowQueries, slowQueryfetchInterval, excludedDatabases, min(queryCountThreshold, constants.MaxQueryCountThreshold)) diff --git a/src/query-performance-monitoring/performance_main.go b/src/query-performance-monitoring/performance_main.go index 16dc67aa..626e9e8a 100644 --- a/src/query-performance-monitoring/performance_main.go +++ b/src/query-performance-monitoring/performance_main.go @@ -48,36 +48,36 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration if len(queryIDList) > 0 { // Populate metrics for individual queries start = time.Now() - txn = app.StartTransaction("MysqlIndividualQueriesSample") + IndividualTxn := app.StartTransaction("MysqlIndividualQueriesSample") // ctx = newrelic.NewContext(ctx, txn) log.Debug("Beginning to retrieve individual query metrics") groupQueriesByDatabase := performancemetricscollectors.PopulateIndividualQueryDetails(app, db, queryIDList, i, e, args) log.Debug("Completed fetching individual query metrics in %v", time.Since(start)) - defer txn.End() + defer IndividualTxn.End() // Populate execution plan details start = time.Now() - txn = app.StartTransaction("MysqlQueryExecutionSample") + execPlanTxn := app.StartTransaction("MysqlQueryExecutionSample") log.Debug("Beginning to retrieve query execution plan metrics") performancemetricscollectors.PopulateExecutionPlans(db, groupQueriesByDatabase, i, e, args) log.Debug("Completed fetching query execution plan metrics in %v", time.Since(start)) - defer txn.End() + defer execPlanTxn.End() } // Populate wait event metrics start = time.Now() - txn = app.StartTransaction("MysqlWaitEventsSample") + waitEventsTxn := app.StartTransaction("MysqlWaitEventsSample") log.Debug("Beginning to retrieve wait event metrics") performancemetricscollectors.PopulateWaitEventMetrics(app, db, i, e, args, excludedDatabases) log.Debug("Completed fetching wait event metrics in %v", time.Since(start)) - defer txn.End() + defer waitEventsTxn.End() // Populate blocking session metrics start = time.Now() - txn = app.StartTransaction("MysqlBlockingSessionSample") + blockingSessionsTxn := app.StartTransaction("MysqlBlockingSessionSample") log.Debug("Beginning to retrieve blocking session metrics") 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 txn.End() + defer blockingSessionsTxn.End() } From d7f1926643e575cf8df48d53d6b8fc588b2726c7 Mon Sep 17 00:00:00 2001 From: spathlavath Date: Tue, 14 Jan 2025 02:52:23 +0530 Subject: [PATCH 16/32] instrumentation fixes --- src/mysql.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mysql.go b/src/mysql.go index 42e80749..82620f73 100644 --- a/src/mysql.go +++ b/src/mysql.go @@ -86,6 +86,7 @@ func main() { newrelic.ConfigAppName("nri-mysql-integration"), newrelic.ConfigLicense(args.LicenseKey), newrelic.ConfigAppLogForwardingEnabled(true), + newrelic.ConfigDistributedTracerEnabled(true), ) if err != nil { log.Error("Error creating new relic application: %s", err.Error()) From 6ec4dd3fa3423ac511d644f97f9344ab16ce63fe Mon Sep 17 00:00:00 2001 From: spathlavath Date: Tue, 14 Jan 2025 02:56:17 +0530 Subject: [PATCH 17/32] instrumentation fixes --- src/mysql.go | 3 ++- src/query-performance-monitoring/utils/database.go | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/mysql.go b/src/mysql.go index 82620f73..e957bee5 100644 --- a/src/mysql.go +++ b/src/mysql.go @@ -85,7 +85,8 @@ func main() { app, err := newrelic.NewApplication( newrelic.ConfigAppName("nri-mysql-integration"), newrelic.ConfigLicense(args.LicenseKey), - newrelic.ConfigAppLogForwardingEnabled(true), + newrelic.ConfigDebugLogger(os.Stdout), + newrelic.ConfigDatastoreRawQuery(true), newrelic.ConfigDistributedTracerEnabled(true), ) if err != nil { diff --git a/src/query-performance-monitoring/utils/database.go b/src/query-performance-monitoring/utils/database.go index 4b32f2d0..45870e44 100644 --- a/src/query-performance-monitoring/utils/database.go +++ b/src/query-performance-monitoring/utils/database.go @@ -5,6 +5,7 @@ import ( "fmt" "net" "net/url" + "os" "strconv" "time" @@ -107,7 +108,9 @@ func CollectMetrics[T any](app *newrelic.Application, db DataSource, preparedQue _, err := newrelic.NewApplication( newrelic.ConfigAppName("nri-mysql-integration"), newrelic.ConfigLicense(mysql_apm.ArgsGlobal), - newrelic.ConfigAppLogForwardingEnabled(true), + newrelic.ConfigDebugLogger(os.Stdout), + newrelic.ConfigDatastoreRawQuery(true), + newrelic.ConfigDistributedTracerEnabled(true), ) if err != nil { log.Error("Error creating new relic application: %s", err.Error()) From 908cabadca8a04580bc42cc15cdd4c80188961e6 Mon Sep 17 00:00:00 2001 From: spathlavath Date: Tue, 14 Jan 2025 03:02:57 +0530 Subject: [PATCH 18/32] instrumentation fixes --- .../query_details.go | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) 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 49b16389..c278d091 100644 --- a/src/query-performance-monitoring/performance-metrics-collectors/query_details.go +++ b/src/query-performance-monitoring/performance-metrics-collectors/query_details.go @@ -36,19 +36,19 @@ func PopulateSlowQueryMetrics(ctx context.Context, i *integration.Integration, e // collectGroupedSlowQueryMetrics collects metrics from the performance schema database for slow queries func collectGroupedSlowQueryMetrics(cntx context.Context, db utils.DataSource, slowQueryfetchInterval int, queryCountThreshold int, excludedDatabases []string) ([]utils.SlowQueryMetrics, []string, error) { - // txn := newrelic.FromContext(cntx) - // if txn == nil { - // log.Error("Failed to get New Relic transaction from context") - // return nil, []string{}, nil - // } - // // Wrap database calls in Datastore Segments - // segment := newrelic.DatastoreSegment{ - // StartTime: txn.StartSegmentNow(), - // Product: newrelic.DatastoreMySQL, - // Operation: "SELECT", - // ParameterizedQuery: utils.SlowQueries, // The query being executed - // } - // defer segment.End() + txn := newrelic.FromContext(cntx) + if txn == nil { + log.Error("Failed to get New Relic transaction from context") + return nil, []string{}, nil + } + // Wrap database calls in Datastore Segments + segment := newrelic.DatastoreSegment{ + StartTime: txn.StartSegmentNow(), + Product: newrelic.DatastoreMySQL, + Operation: "SELECT", + ParameterizedQuery: utils.SlowQueries, // The query being executed + } + defer segment.End() // Prepare the SQL query with the provided parameters query, args, err := sqlx.In(utils.SlowQueries, slowQueryfetchInterval, excludedDatabases, min(queryCountThreshold, constants.MaxQueryCountThreshold)) From bdb7151180ffbb9cdb8ce0579509ab98062287de Mon Sep 17 00:00:00 2001 From: spathlavath Date: Tue, 14 Jan 2025 03:13:19 +0530 Subject: [PATCH 19/32] debugging --- .../query_details.go | 32 +++++++++---------- .../performance_main.go | 7 ++-- 2 files changed, 19 insertions(+), 20 deletions(-) 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 c278d091..2cc946d5 100644 --- a/src/query-performance-monitoring/performance-metrics-collectors/query_details.go +++ b/src/query-performance-monitoring/performance-metrics-collectors/query_details.go @@ -13,8 +13,8 @@ import ( ) // PopulateSlowQueryMetrics collects and sets slow query metrics and returns the list of query IDs -func PopulateSlowQueryMetrics(ctx context.Context, i *integration.Integration, e *integration.Entity, db utils.DataSource, args arguments.ArgumentList, excludedDatabases []string) []string { - rawMetrics, queryIDList, err := collectGroupedSlowQueryMetrics(ctx, db, args.SlowQueryFetchInterval, args.QueryCountThreshold, excludedDatabases) +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) if err != nil { log.Error("Failed to collect slow query metrics: %v", err) return []string{} @@ -35,20 +35,20 @@ func PopulateSlowQueryMetrics(ctx context.Context, i *integration.Integration, e } // collectGroupedSlowQueryMetrics collects metrics from the performance schema database for slow queries -func collectGroupedSlowQueryMetrics(cntx context.Context, db utils.DataSource, slowQueryfetchInterval int, queryCountThreshold int, excludedDatabases []string) ([]utils.SlowQueryMetrics, []string, error) { - txn := newrelic.FromContext(cntx) - if txn == nil { - log.Error("Failed to get New Relic transaction from context") - return nil, []string{}, nil - } - // Wrap database calls in Datastore Segments - segment := newrelic.DatastoreSegment{ - StartTime: txn.StartSegmentNow(), - Product: newrelic.DatastoreMySQL, - Operation: "SELECT", - ParameterizedQuery: utils.SlowQueries, // The query being executed - } - defer segment.End() +func collectGroupedSlowQueryMetrics(db utils.DataSource, slowQueryfetchInterval int, queryCountThreshold int, excludedDatabases []string) ([]utils.SlowQueryMetrics, []string, error) { + // txn := newrelic.FromContext(cntx) + // if txn == nil { + // log.Error("Failed to get New Relic transaction from context") + // return nil, []string{}, nil + // } + // // Wrap database calls in Datastore Segments + // segment := newrelic.DatastoreSegment{ + // StartTime: txn.StartSegmentNow(), + // Product: newrelic.DatastoreMySQL, + // Operation: "SELECT", + // ParameterizedQuery: utils.SlowQueries, // The query being executed + // } + // defer segment.End() // Prepare the SQL query with the provided parameters query, args, err := sqlx.In(utils.SlowQueries, slowQueryfetchInterval, excludedDatabases, min(queryCountThreshold, constants.MaxQueryCountThreshold)) diff --git a/src/query-performance-monitoring/performance_main.go b/src/query-performance-monitoring/performance_main.go index 626e9e8a..4a19ba32 100644 --- a/src/query-performance-monitoring/performance_main.go +++ b/src/query-performance-monitoring/performance_main.go @@ -1,7 +1,6 @@ package queryperformancemonitoring import ( - "context" "fmt" "time" @@ -37,11 +36,11 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration // Populate metrics for slow queries start := time.Now() - ctx := context.Background() + // ctx := context.Background() txn := app.StartTransaction("MysqlSlowQueriesSample") - ctx = newrelic.NewContext(ctx, txn) + // ctx = newrelic.NewContext(ctx, txn) log.Debug("Beginning to retrieve slow query metrics") - queryIDList := performancemetricscollectors.PopulateSlowQueryMetrics(ctx, i, e, db, args, excludedDatabases) + queryIDList := performancemetricscollectors.PopulateSlowQueryMetrics(i, e, db, args, excludedDatabases) log.Debug("Completed fetching slow query metrics in %v", time.Since(start)) defer txn.End() From b9de9f1b6622b005a439d53f47be3fc4c7e845b7 Mon Sep 17 00:00:00 2001 From: spathlavath Date: Tue, 14 Jan 2025 03:19:38 +0530 Subject: [PATCH 20/32] debug --- src/mysql.go | 2 +- .../query_details.go | 32 ++++----- .../performance_main.go | 70 +++++++++---------- .../utils/database.go | 2 +- 4 files changed, 53 insertions(+), 53 deletions(-) diff --git a/src/mysql.go b/src/mysql.go index e957bee5..18221ba9 100644 --- a/src/mysql.go +++ b/src/mysql.go @@ -85,7 +85,7 @@ func main() { app, err := newrelic.NewApplication( newrelic.ConfigAppName("nri-mysql-integration"), newrelic.ConfigLicense(args.LicenseKey), - newrelic.ConfigDebugLogger(os.Stdout), + newrelic.ConfigDebugLogger(os.Stderr), newrelic.ConfigDatastoreRawQuery(true), newrelic.ConfigDistributedTracerEnabled(true), ) 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 2cc946d5..c278d091 100644 --- a/src/query-performance-monitoring/performance-metrics-collectors/query_details.go +++ b/src/query-performance-monitoring/performance-metrics-collectors/query_details.go @@ -13,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(ctx context.Context, i *integration.Integration, e *integration.Entity, db utils.DataSource, args arguments.ArgumentList, excludedDatabases []string) []string { + rawMetrics, queryIDList, err := collectGroupedSlowQueryMetrics(ctx, db, args.SlowQueryFetchInterval, args.QueryCountThreshold, excludedDatabases) if err != nil { log.Error("Failed to collect slow query metrics: %v", err) return []string{} @@ -35,20 +35,20 @@ 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) { - // txn := newrelic.FromContext(cntx) - // if txn == nil { - // log.Error("Failed to get New Relic transaction from context") - // return nil, []string{}, nil - // } - // // Wrap database calls in Datastore Segments - // segment := newrelic.DatastoreSegment{ - // StartTime: txn.StartSegmentNow(), - // Product: newrelic.DatastoreMySQL, - // Operation: "SELECT", - // ParameterizedQuery: utils.SlowQueries, // The query being executed - // } - // defer segment.End() +func collectGroupedSlowQueryMetrics(cntx context.Context, db utils.DataSource, slowQueryfetchInterval int, queryCountThreshold int, excludedDatabases []string) ([]utils.SlowQueryMetrics, []string, error) { + txn := newrelic.FromContext(cntx) + if txn == nil { + log.Error("Failed to get New Relic transaction from context") + return nil, []string{}, nil + } + // Wrap database calls in Datastore Segments + segment := newrelic.DatastoreSegment{ + StartTime: txn.StartSegmentNow(), + Product: newrelic.DatastoreMySQL, + Operation: "SELECT", + ParameterizedQuery: utils.SlowQueries, // The query being executed + } + defer segment.End() // Prepare the SQL query with the provided parameters query, args, err := sqlx.In(utils.SlowQueries, slowQueryfetchInterval, excludedDatabases, min(queryCountThreshold, constants.MaxQueryCountThreshold)) diff --git a/src/query-performance-monitoring/performance_main.go b/src/query-performance-monitoring/performance_main.go index 4a19ba32..f7304cab 100644 --- a/src/query-performance-monitoring/performance_main.go +++ b/src/query-performance-monitoring/performance_main.go @@ -1,6 +1,7 @@ package queryperformancemonitoring import ( + "context" "fmt" "time" @@ -36,47 +37,46 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration // Populate metrics for slow queries start := time.Now() - // ctx := context.Background() + ctx := context.Background() txn := app.StartTransaction("MysqlSlowQueriesSample") - // ctx = newrelic.NewContext(ctx, txn) + ctx = newrelic.NewContext(ctx, txn) log.Debug("Beginning to retrieve slow query metrics") - queryIDList := performancemetricscollectors.PopulateSlowQueryMetrics(i, e, db, args, excludedDatabases) + performancemetricscollectors.PopulateSlowQueryMetrics(ctx, i, e, db, args, excludedDatabases) log.Debug("Completed fetching slow query metrics in %v", time.Since(start)) defer txn.End() - if len(queryIDList) > 0 { - // Populate metrics for individual queries - start = time.Now() - IndividualTxn := app.StartTransaction("MysqlIndividualQueriesSample") - // ctx = newrelic.NewContext(ctx, txn) - log.Debug("Beginning to retrieve individual query metrics") - groupQueriesByDatabase := performancemetricscollectors.PopulateIndividualQueryDetails(app, db, queryIDList, i, e, args) - log.Debug("Completed fetching individual query metrics in %v", time.Since(start)) - defer IndividualTxn.End() + // if len(queryIDList) > 0 { + // // Populate metrics for individual queries + // start = time.Now() + // IndividualTxn := app.StartTransaction("MysqlIndividualQueriesSample") + // log.Debug("Beginning to retrieve individual query metrics") + // 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") - log.Debug("Beginning to retrieve query execution plan metrics") - performancemetricscollectors.PopulateExecutionPlans(db, groupQueriesByDatabase, i, e, args) - log.Debug("Completed fetching query execution plan metrics in %v", time.Since(start)) - defer execPlanTxn.End() - } + // // Populate execution plan details + // start = time.Now() + // execPlanTxn := app.StartTransaction("MysqlQueryExecutionSample") + // log.Debug("Beginning to retrieve query execution plan metrics") + // performancemetricscollectors.PopulateExecutionPlans(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") - log.Debug("Beginning to retrieve wait event metrics") - performancemetricscollectors.PopulateWaitEventMetrics(app, db, i, e, args, excludedDatabases) - log.Debug("Completed fetching wait event metrics in %v", time.Since(start)) - defer waitEventsTxn.End() + // // Populate wait event metrics + // start = time.Now() + // waitEventsTxn := app.StartTransaction("MysqlWaitEventsSample") + // log.Debug("Beginning to retrieve wait event metrics") + // 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") - log.Debug("Beginning to retrieve blocking session metrics") - 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() + // // Populate blocking session metrics + // start = time.Now() + // blockingSessionsTxn := app.StartTransaction("MysqlBlockingSessionSample") + // log.Debug("Beginning to retrieve blocking session metrics") + // 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 45870e44..1213823f 100644 --- a/src/query-performance-monitoring/utils/database.go +++ b/src/query-performance-monitoring/utils/database.go @@ -108,7 +108,7 @@ func CollectMetrics[T any](app *newrelic.Application, db DataSource, preparedQue _, err := newrelic.NewApplication( newrelic.ConfigAppName("nri-mysql-integration"), newrelic.ConfigLicense(mysql_apm.ArgsGlobal), - newrelic.ConfigDebugLogger(os.Stdout), + newrelic.ConfigDebugLogger(os.Stderr), newrelic.ConfigDatastoreRawQuery(true), newrelic.ConfigDistributedTracerEnabled(true), ) From 8957f39e9755e810fdde238e39738a79e744790c Mon Sep 17 00:00:00 2001 From: spathlavath Date: Tue, 14 Jan 2025 03:25:48 +0530 Subject: [PATCH 21/32] debug-1 --- src/mysql.go | 1 - src/query-performance-monitoring/utils/database.go | 1 - 2 files changed, 2 deletions(-) diff --git a/src/mysql.go b/src/mysql.go index 18221ba9..9a6bb726 100644 --- a/src/mysql.go +++ b/src/mysql.go @@ -87,7 +87,6 @@ func main() { newrelic.ConfigLicense(args.LicenseKey), newrelic.ConfigDebugLogger(os.Stderr), newrelic.ConfigDatastoreRawQuery(true), - newrelic.ConfigDistributedTracerEnabled(true), ) if err != nil { log.Error("Error creating new relic application: %s", err.Error()) diff --git a/src/query-performance-monitoring/utils/database.go b/src/query-performance-monitoring/utils/database.go index 1213823f..85b60b2b 100644 --- a/src/query-performance-monitoring/utils/database.go +++ b/src/query-performance-monitoring/utils/database.go @@ -110,7 +110,6 @@ func CollectMetrics[T any](app *newrelic.Application, db DataSource, preparedQue newrelic.ConfigLicense(mysql_apm.ArgsGlobal), newrelic.ConfigDebugLogger(os.Stderr), newrelic.ConfigDatastoreRawQuery(true), - newrelic.ConfigDistributedTracerEnabled(true), ) if err != nil { log.Error("Error creating new relic application: %s", err.Error()) From f875b8e41d66cbb1408020adfac5631bc89fd284 Mon Sep 17 00:00:00 2001 From: spathlavath Date: Tue, 14 Jan 2025 08:11:28 +0530 Subject: [PATCH 22/32] removed go-sql-driver --- src/query-performance-monitoring/utils/database.go | 1 - 1 file changed, 1 deletion(-) diff --git a/src/query-performance-monitoring/utils/database.go b/src/query-performance-monitoring/utils/database.go index 85b60b2b..ef7a84d5 100644 --- a/src/query-performance-monitoring/utils/database.go +++ b/src/query-performance-monitoring/utils/database.go @@ -9,7 +9,6 @@ import ( "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" From e0add7dc8aa82a5e25e42a04b66d64585e45ff4a Mon Sep 17 00:00:00 2001 From: spathlavath Date: Tue, 14 Jan 2025 09:27:28 +0530 Subject: [PATCH 23/32] refactored nrmysql changes in database.go --- .../query_details.go | 22 ++---- .../query_execution_plan.go | 9 +-- .../performance_main.go | 67 +++++++++---------- .../utils/database.go | 62 +++++++---------- 4 files changed, 66 insertions(+), 94 deletions(-) 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 c278d091..0c797b05 100644 --- a/src/query-performance-monitoring/performance-metrics-collectors/query_details.go +++ b/src/query-performance-monitoring/performance-metrics-collectors/query_details.go @@ -13,8 +13,8 @@ import ( ) // PopulateSlowQueryMetrics collects and sets slow query metrics and returns the list of query IDs -func PopulateSlowQueryMetrics(ctx context.Context, i *integration.Integration, e *integration.Entity, db utils.DataSource, args arguments.ArgumentList, excludedDatabases []string) []string { - rawMetrics, queryIDList, err := collectGroupedSlowQueryMetrics(ctx, 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{} @@ -35,21 +35,7 @@ func PopulateSlowQueryMetrics(ctx context.Context, i *integration.Integration, e } // collectGroupedSlowQueryMetrics collects metrics from the performance schema database for slow queries -func collectGroupedSlowQueryMetrics(cntx context.Context, db utils.DataSource, slowQueryfetchInterval int, queryCountThreshold int, excludedDatabases []string) ([]utils.SlowQueryMetrics, []string, error) { - txn := newrelic.FromContext(cntx) - if txn == nil { - log.Error("Failed to get New Relic transaction from context") - return nil, []string{}, nil - } - // Wrap database calls in Datastore Segments - segment := newrelic.DatastoreSegment{ - StartTime: txn.StartSegmentNow(), - Product: newrelic.DatastoreMySQL, - Operation: "SELECT", - ParameterizedQuery: utils.SlowQueries, // The query being executed - } - defer segment.End() - +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 { @@ -58,7 +44,7 @@ func collectGroupedSlowQueryMetrics(cntx context.Context, db utils.DataSource, s 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 } 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_main.go b/src/query-performance-monitoring/performance_main.go index f7304cab..881d204c 100644 --- a/src/query-performance-monitoring/performance_main.go +++ b/src/query-performance-monitoring/performance_main.go @@ -1,7 +1,6 @@ package queryperformancemonitoring import ( - "context" "fmt" "time" @@ -37,46 +36,44 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration // Populate metrics for slow queries start := time.Now() - ctx := context.Background() txn := app.StartTransaction("MysqlSlowQueriesSample") - ctx = newrelic.NewContext(ctx, txn) log.Debug("Beginning to retrieve slow query metrics") - performancemetricscollectors.PopulateSlowQueryMetrics(ctx, 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)) defer txn.End() - // if len(queryIDList) > 0 { - // // Populate metrics for individual queries - // start = time.Now() - // IndividualTxn := app.StartTransaction("MysqlIndividualQueriesSample") - // log.Debug("Beginning to retrieve individual query metrics") - // groupQueriesByDatabase := performancemetricscollectors.PopulateIndividualQueryDetails(app, db, queryIDList, i, e, args) - // log.Debug("Completed fetching individual query metrics in %v", time.Since(start)) - // defer IndividualTxn.End() + if len(queryIDList) > 0 { + // Populate metrics for individual queries + start = time.Now() + IndividualTxn := app.StartTransaction("MysqlIndividualQueriesSample") + log.Debug("Beginning to retrieve individual query metrics") + 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") - // log.Debug("Beginning to retrieve query execution plan metrics") - // performancemetricscollectors.PopulateExecutionPlans(db, groupQueriesByDatabase, i, e, args) - // log.Debug("Completed fetching query execution plan metrics in %v", time.Since(start)) - // defer execPlanTxn.End() - // } + // Populate execution plan details + start = time.Now() + execPlanTxn := app.StartTransaction("MysqlQueryExecutionSample") + log.Debug("Beginning to retrieve query execution plan metrics") + 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") - // log.Debug("Beginning to retrieve wait event metrics") - // performancemetricscollectors.PopulateWaitEventMetrics(app, db, i, e, args, excludedDatabases) - // log.Debug("Completed fetching wait event metrics in %v", time.Since(start)) - // defer waitEventsTxn.End() + // Populate wait event metrics + start = time.Now() + waitEventsTxn := app.StartTransaction("MysqlWaitEventsSample") + log.Debug("Beginning to retrieve wait event metrics") + 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") - // log.Debug("Beginning to retrieve blocking session metrics") - // 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() + // Populate blocking session metrics + start = time.Now() + blockingSessionsTxn := app.StartTransaction("MysqlBlockingSessionSample") + log.Debug("Beginning to retrieve blocking session metrics") + 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 ef7a84d5..9041667b 100644 --- a/src/query-performance-monitoring/utils/database.go +++ b/src/query-performance-monitoring/utils/database.go @@ -21,7 +21,7 @@ import ( 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 { @@ -58,7 +58,28 @@ 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 { + _, err := newrelic.NewApplication( + newrelic.ConfigAppName("nri-mysql-integration"), + newrelic.ConfigLicense(mysql_apm.ArgsGlobal), + newrelic.ConfigDebugLogger(os.Stderr), + 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 + } + + txn := app.StartTransaction("nrmysqlQuery") + ctx = newrelic.NewContext(ctx, txn) return db.source.QueryxContext(ctx, query, args...) } @@ -102,43 +123,10 @@ func determineDatabase(args arguments.ArgumentList, database string) string { // collectMetrics collects metrics from the performance schema database func CollectMetrics[T any](app *newrelic.Application, db DataSource, preparedQuery string, preparedArgs ...interface{}) ([]T, error) { - // Initialize New Relic application - if app == nil { - _, err := newrelic.NewApplication( - newrelic.ConfigAppName("nri-mysql-integration"), - newrelic.ConfigLicense(mysql_apm.ArgsGlobal), - newrelic.ConfigDebugLogger(os.Stderr), - newrelic.ConfigDatastoreRawQuery(true), - ) - if err != nil { - log.Error("Error creating new relic application: %s", err.Error()) - return []T{}, err - } - } - - waitErr := app.WaitForConnection(5 * time.Second) - if waitErr != nil { - log.Error("Error waiting for connection: %s", waitErr.Error()) - return []T{}, waitErr - } - - txn := app.StartTransaction("nrmysqlQuery") - defer txn.End() - - ctx := newrelic.NewContext(context.Background(), txn) - ctx, cancel := context.WithTimeout(ctx, constants.TimeoutDuration) + ctx, cancel := context.WithTimeout(context.Background(), constants.TimeoutDuration) defer cancel() - // Wrap database calls in Datastore Segments - segment := newrelic.DatastoreSegment{ - StartTime: txn.StartSegmentNow(), - Product: newrelic.DatastoreMySQL, - Operation: "SELECT", - ParameterizedQuery: preparedQuery, // The query being executed - } - defer segment.End() - - rows, err := db.QueryxContext(ctx, preparedQuery, preparedArgs...) + rows, err := db.QueryxContext(app, ctx, preparedQuery, preparedArgs...) if err != nil { return []T{}, err } From fc6f9811bd68260ded1b4b619ab71569621d69a6 Mon Sep 17 00:00:00 2001 From: spathlavath Date: Tue, 14 Jan 2025 09:39:51 +0530 Subject: [PATCH 24/32] refactored nrmysql changes in mysql.go --- src/mysql.go | 44 +++++++++---------- .../performance_main.go | 27 ++++++++++-- 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/src/mysql.go b/src/mysql.go index 9a6bb726..7c91cc48 100644 --- a/src/mysql.go +++ b/src/mysql.go @@ -16,10 +16,10 @@ import ( "strconv" "strings" - "github.com/newrelic/go-agent/v3/newrelic" + // "github.com/newrelic/go-agent/v3/newrelic" arguments "github.com/newrelic/nri-mysql/src/args" queryperformancemonitoring "github.com/newrelic/nri-mysql/src/query-performance-monitoring" - mysql_apm "github.com/newrelic/nri-mysql/src/query-performance-monitoring/mysql-apm" + // mysql_apm "github.com/newrelic/nri-mysql/src/query-performance-monitoring/mysql-apm" ) const ( @@ -81,25 +81,25 @@ func createNodeEntity( func main() { i, err := integration.New(integrationName, integrationVersion, integration.Args(&args)) fatalIfErr(err) - mysql_apm.ArgsGlobal = args.LicenseKey - app, err := newrelic.NewApplication( - newrelic.ConfigAppName("nri-mysql-integration"), - newrelic.ConfigLicense(args.LicenseKey), - newrelic.ConfigDebugLogger(os.Stderr), - newrelic.ConfigDatastoreRawQuery(true), - ) - if err != nil { - log.Error("Error creating new relic application: %s", err.Error()) - } - - mysql_apm.NewrelicApp = *app - - txn := app.StartTransaction("test_performance_monitoring") - defer txn.End() - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } + // mysql_apm.ArgsGlobal = args.LicenseKey + // app, err := newrelic.NewApplication( + // newrelic.ConfigAppName("nri-mysql-integration"), + // newrelic.ConfigLicense(args.LicenseKey), + // newrelic.ConfigDebugLogger(os.Stderr), + // newrelic.ConfigDatastoreRawQuery(true), + // ) + // if err != nil { + // log.Error("Error creating new relic application: %s", err.Error()) + // } + + // mysql_apm.NewrelicApp = *app + + // txn := app.StartTransaction("performance_monitoring") + // defer txn.End() + // if err != nil { + // log.Error(err.Error()) + // os.Exit(1) + // } if args.ShowVersion { fmt.Printf( @@ -142,7 +142,7 @@ func main() { fatalIfErr(i.Publish()) if args.EnableQueryPerformance { - queryperformancemonitoring.PopulateQueryPerformanceMetrics(args, e, i, app) + queryperformancemonitoring.PopulateQueryPerformanceMetrics(args, e, i) } } diff --git a/src/query-performance-monitoring/performance_main.go b/src/query-performance-monitoring/performance_main.go index 881d204c..238221a9 100644 --- a/src/query-performance-monitoring/performance_main.go +++ b/src/query-performance-monitoring/performance_main.go @@ -2,21 +2,42 @@ 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" + mysql_apm "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" ) // main -func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration.Entity, i *integration.Integration, app *newrelic.Application) { +func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration.Entity, i *integration.Integration) { var database string + mysql_apm.ArgsGlobal = args.LicenseKey + app, err := newrelic.NewApplication( + newrelic.ConfigAppName("nri-mysql-integration"), + newrelic.ConfigLicense(args.LicenseKey), + newrelic.ConfigDebugLogger(os.Stderr), + newrelic.ConfigDatastoreRawQuery(true), + ) + if err != nil { + log.Error("Error creating new relic application: %s", err.Error()) + } + + mysql_apm.NewrelicApp = *app + + txn := app.StartTransaction("performance_monitoring") + defer txn.End() + if err != nil { + log.Error(err.Error()) + os.Exit(1) + } // Generate Data Source Name (DSN) for database connection dsn := utils.GenerateDSN(args, database) @@ -36,11 +57,11 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration // Populate metrics for slow queries start := time.Now() - txn := app.StartTransaction("MysqlSlowQueriesSample") + slowQueriesTxn := app.StartTransaction("MysqlSlowQueriesSample") log.Debug("Beginning to retrieve slow query metrics") queryIDList := performancemetricscollectors.PopulateSlowQueryMetrics(app, i, e, db, args, excludedDatabases) log.Debug("Completed fetching slow query metrics in %v", time.Since(start)) - defer txn.End() + defer slowQueriesTxn.End() if len(queryIDList) > 0 { // Populate metrics for individual queries From 9361cc7e60da9e78ba1ec959c6a030081c514749 Mon Sep 17 00:00:00 2001 From: spathlavath Date: Tue, 14 Jan 2025 09:44:58 +0530 Subject: [PATCH 25/32] debug logger --- src/query-performance-monitoring/performance_main.go | 2 +- src/query-performance-monitoring/utils/database.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/query-performance-monitoring/performance_main.go b/src/query-performance-monitoring/performance_main.go index 238221a9..1534ea3b 100644 --- a/src/query-performance-monitoring/performance_main.go +++ b/src/query-performance-monitoring/performance_main.go @@ -23,7 +23,7 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration app, err := newrelic.NewApplication( newrelic.ConfigAppName("nri-mysql-integration"), newrelic.ConfigLicense(args.LicenseKey), - newrelic.ConfigDebugLogger(os.Stderr), + newrelic.ConfigDebugLogger(os.Stdout), newrelic.ConfigDatastoreRawQuery(true), ) if err != nil { diff --git a/src/query-performance-monitoring/utils/database.go b/src/query-performance-monitoring/utils/database.go index 9041667b..b049e9fa 100644 --- a/src/query-performance-monitoring/utils/database.go +++ b/src/query-performance-monitoring/utils/database.go @@ -64,7 +64,7 @@ func (db *Database) QueryxContext(app *newrelic.Application, ctx context.Context _, err := newrelic.NewApplication( newrelic.ConfigAppName("nri-mysql-integration"), newrelic.ConfigLicense(mysql_apm.ArgsGlobal), - newrelic.ConfigDebugLogger(os.Stderr), + newrelic.ConfigDebugLogger(os.Stdout), newrelic.ConfigDatastoreRawQuery(true), ) if err != nil { From 634a26462dc96fd738d803b791cae485a6abf7ca Mon Sep 17 00:00:00 2001 From: spathlavath Date: Tue, 14 Jan 2025 09:52:13 +0530 Subject: [PATCH 26/32] nymysql changes in database.go --- src/query-performance-monitoring/utils/database.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/query-performance-monitoring/utils/database.go b/src/query-performance-monitoring/utils/database.go index b049e9fa..32dc5205 100644 --- a/src/query-performance-monitoring/utils/database.go +++ b/src/query-performance-monitoring/utils/database.go @@ -61,7 +61,8 @@ func fatalIfErr(err 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 { - _, err := newrelic.NewApplication( + var err error + app, err = newrelic.NewApplication( newrelic.ConfigAppName("nri-mysql-integration"), newrelic.ConfigLicense(mysql_apm.ArgsGlobal), newrelic.ConfigDebugLogger(os.Stdout), From 3a226f988f4007b2248e85213f4595610473c23a Mon Sep 17 00:00:00 2001 From: spathlavath Date: Tue, 14 Jan 2025 10:07:33 +0530 Subject: [PATCH 27/32] nymysql changes in performance_main.go --- .../performance_main.go | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/src/query-performance-monitoring/performance_main.go b/src/query-performance-monitoring/performance_main.go index 1534ea3b..f7ddadd7 100644 --- a/src/query-performance-monitoring/performance_main.go +++ b/src/query-performance-monitoring/performance_main.go @@ -32,12 +32,21 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration mysql_apm.NewrelicApp = *app - txn := app.StartTransaction("performance_monitoring") - defer txn.End() - if err != nil { - log.Error(err.Error()) - os.Exit(1) + var txn *newrelic.Transaction + for i := 0; i < 3; i++ { // Retry up to 3 times + txn = app.StartTransaction("performance_monitoring") + if txn != nil { + break + } + time.Sleep(1 * time.Second) // Wait before retrying } + + if txn == nil { + log.Error("Failed to start New Relic transaction after retries") + return + } + defer txn.End() + // Generate Data Source Name (DSN) for database connection dsn := utils.GenerateDSN(args, database) @@ -58,6 +67,10 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration // Populate metrics for slow queries start := time.Now() slowQueriesTxn := app.StartTransaction("MysqlSlowQueriesSample") + if slowQueriesTxn == nil { + log.Error("Failed to start New Relic transaction for slow queries") + return + } log.Debug("Beginning to retrieve slow query metrics") queryIDList := performancemetricscollectors.PopulateSlowQueryMetrics(app, i, e, db, args, excludedDatabases) log.Debug("Completed fetching slow query metrics in %v", time.Since(start)) @@ -66,15 +79,23 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration if len(queryIDList) > 0 { // Populate metrics for individual queries start = time.Now() - IndividualTxn := app.StartTransaction("MysqlIndividualQueriesSample") + individualTxn := app.StartTransaction("MysqlIndividualQueriesSample") + if individualTxn == nil { + log.Error("Failed to start New Relic transaction for individual queries") + return + } log.Debug("Beginning to retrieve individual query metrics") groupQueriesByDatabase := performancemetricscollectors.PopulateIndividualQueryDetails(app, db, queryIDList, i, e, args) log.Debug("Completed fetching individual query metrics in %v", time.Since(start)) - defer IndividualTxn.End() + 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 + } log.Debug("Beginning to retrieve query execution plan metrics") performancemetricscollectors.PopulateExecutionPlans(app, db, groupQueriesByDatabase, i, e, args) log.Debug("Completed fetching query execution plan metrics in %v", time.Since(start)) @@ -84,6 +105,10 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration // 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 + } log.Debug("Beginning to retrieve wait event metrics") performancemetricscollectors.PopulateWaitEventMetrics(app, db, i, e, args, excludedDatabases) log.Debug("Completed fetching wait event metrics in %v", time.Since(start)) @@ -92,6 +117,10 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration // 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 + } log.Debug("Beginning to retrieve blocking session metrics") performancemetricscollectors.PopulateBlockingSessionMetrics(app, db, i, e, args, excludedDatabases) log.Debug("Completed fetching blocking session metrics in %v", time.Since(start)) From b117223936b8093211875209343775a2e0fda47b Mon Sep 17 00:00:00 2001 From: spathlavath Date: Tue, 14 Jan 2025 10:18:00 +0530 Subject: [PATCH 28/32] nymysql changes in performance_main.go --- src/query-performance-monitoring/performance_main.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/query-performance-monitoring/performance_main.go b/src/query-performance-monitoring/performance_main.go index f7ddadd7..fcb34254 100644 --- a/src/query-performance-monitoring/performance_main.go +++ b/src/query-performance-monitoring/performance_main.go @@ -30,7 +30,13 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration log.Error("Error creating new relic application: %s", err.Error()) } - mysql_apm.NewrelicApp = *app + // Log application connection status + if app != nil { + log.Debug("New Relic application initialized successfully") + mysql_apm.NewrelicApp = *app + } else { + log.Error("New Relic application initialization failed") + } var txn *newrelic.Transaction for i := 0; i < 3; i++ { // Retry up to 3 times From 18730946e669d578c4d1251ed60189f264cc11ed Mon Sep 17 00:00:00 2001 From: spathlavath Date: Tue, 14 Jan 2025 10:49:01 +0530 Subject: [PATCH 29/32] nymysql changes in performance_main.go --- src/query-performance-monitoring/performance_main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query-performance-monitoring/performance_main.go b/src/query-performance-monitoring/performance_main.go index fcb34254..80416e3c 100644 --- a/src/query-performance-monitoring/performance_main.go +++ b/src/query-performance-monitoring/performance_main.go @@ -73,6 +73,7 @@ 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 @@ -80,7 +81,6 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration log.Debug("Beginning to retrieve slow query metrics") queryIDList := performancemetricscollectors.PopulateSlowQueryMetrics(app, i, e, db, args, excludedDatabases) log.Debug("Completed fetching slow query metrics in %v", time.Since(start)) - defer slowQueriesTxn.End() if len(queryIDList) > 0 { // Populate metrics for individual queries From ed55c3ea227c01a046902f786ab4cc06c2b8703d Mon Sep 17 00:00:00 2001 From: spathlavath Date: Tue, 14 Jan 2025 11:03:12 +0530 Subject: [PATCH 30/32] nymysql changes in performance_main.go --- .../performance_main.go | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/query-performance-monitoring/performance_main.go b/src/query-performance-monitoring/performance_main.go index 80416e3c..91ca5f61 100644 --- a/src/query-performance-monitoring/performance_main.go +++ b/src/query-performance-monitoring/performance_main.go @@ -29,6 +29,13 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration 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 { @@ -38,21 +45,6 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration log.Error("New Relic application initialization failed") } - var txn *newrelic.Transaction - for i := 0; i < 3; i++ { // Retry up to 3 times - txn = app.StartTransaction("performance_monitoring") - if txn != nil { - break - } - time.Sleep(1 * time.Second) // Wait before retrying - } - - if txn == nil { - log.Error("Failed to start New Relic transaction after retries") - return - } - defer txn.End() - // Generate Data Source Name (DSN) for database connection dsn := utils.GenerateDSN(args, database) @@ -61,11 +53,13 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration utils.FatalIfErr(err) defer db.Close() + preCheckTxn := app.StartTransaction("MysqlSlowQueriesSample") // Validate preconditions before proceeding preValidationErr := validator.ValidatePreconditions(db) if preValidationErr != nil { utils.FatalIfErr(fmt.Errorf("preconditions failed: %w", preValidationErr)) } + preCheckTxn.End() // Get the list of unique excluded databases excludedDatabases := utils.GetExcludedDatabases(args.ExcludedDatabases) From 469cb4d811135c367d2b76b27f3299693c6a5989 Mon Sep 17 00:00:00 2001 From: spathlavath Date: Tue, 14 Jan 2025 13:17:08 +0530 Subject: [PATCH 31/32] Added datastore segment --- src/mysql.go | 21 ------------------- .../utils/database.go | 7 +++++++ 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/src/mysql.go b/src/mysql.go index 7c91cc48..f2cbc301 100644 --- a/src/mysql.go +++ b/src/mysql.go @@ -16,10 +16,8 @@ import ( "strconv" "strings" - // "github.com/newrelic/go-agent/v3/newrelic" arguments "github.com/newrelic/nri-mysql/src/args" queryperformancemonitoring "github.com/newrelic/nri-mysql/src/query-performance-monitoring" - // mysql_apm "github.com/newrelic/nri-mysql/src/query-performance-monitoring/mysql-apm" ) const ( @@ -81,25 +79,6 @@ func createNodeEntity( func main() { i, err := integration.New(integrationName, integrationVersion, integration.Args(&args)) fatalIfErr(err) - // mysql_apm.ArgsGlobal = args.LicenseKey - // app, err := newrelic.NewApplication( - // newrelic.ConfigAppName("nri-mysql-integration"), - // newrelic.ConfigLicense(args.LicenseKey), - // newrelic.ConfigDebugLogger(os.Stderr), - // newrelic.ConfigDatastoreRawQuery(true), - // ) - // if err != nil { - // log.Error("Error creating new relic application: %s", err.Error()) - // } - - // mysql_apm.NewrelicApp = *app - - // txn := app.StartTransaction("performance_monitoring") - // defer txn.End() - // if err != nil { - // log.Error(err.Error()) - // os.Exit(1) - // } if args.ShowVersion { fmt.Printf( diff --git a/src/query-performance-monitoring/utils/database.go b/src/query-performance-monitoring/utils/database.go index 32dc5205..c85364e2 100644 --- a/src/query-performance-monitoring/utils/database.go +++ b/src/query-performance-monitoring/utils/database.go @@ -81,6 +81,13 @@ func (db *Database) QueryxContext(app *newrelic.Application, ctx context.Context txn := app.StartTransaction("nrmysqlQuery") ctx = newrelic.NewContext(ctx, txn) + s := newrelic.DatastoreSegment{ + StartTime: txn.StartSegmentNow(), + Product: newrelic.DatastoreMySQL, + Operation: "SELECT", + ParameterizedQuery: query, + } + defer s.End() return db.source.QueryxContext(ctx, query, args...) } From a24f86fc50c6ff36291e4dc9f11b3ff2cd8cf4c9 Mon Sep 17 00:00:00 2001 From: spathlavath Date: Tue, 14 Jan 2025 13:43:53 +0530 Subject: [PATCH 32/32] datastore fixes --- .../mysql-apm/mysql_apm.go | 3 ++- .../performance_main.go | 13 ++++++++----- src/query-performance-monitoring/utils/database.go | 13 ++++++------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/query-performance-monitoring/mysql-apm/mysql_apm.go b/src/query-performance-monitoring/mysql-apm/mysql_apm.go index ea8623f1..1f59781f 100644 --- a/src/query-performance-monitoring/mysql-apm/mysql_apm.go +++ b/src/query-performance-monitoring/mysql-apm/mysql_apm.go @@ -1,6 +1,7 @@ -package mysql_apm +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_main.go b/src/query-performance-monitoring/performance_main.go index 91ca5f61..9ef67ab0 100644 --- a/src/query-performance-monitoring/performance_main.go +++ b/src/query-performance-monitoring/performance_main.go @@ -9,7 +9,7 @@ import ( "github.com/newrelic/infra-integrations-sdk/v3/integration" "github.com/newrelic/infra-integrations-sdk/v3/log" arguments "github.com/newrelic/nri-mysql/src/args" - mysql_apm "github.com/newrelic/nri-mysql/src/query-performance-monitoring/mysql-apm" + 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" @@ -19,7 +19,7 @@ import ( func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration.Entity, i *integration.Integration) { var database string - mysql_apm.ArgsGlobal = args.LicenseKey + mysqlapm.ArgsGlobal = args.LicenseKey app, err := newrelic.NewApplication( newrelic.ConfigAppName("nri-mysql-integration"), newrelic.ConfigLicense(args.LicenseKey), @@ -40,7 +40,7 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration // Log application connection status if app != nil { log.Debug("New Relic application initialized successfully") - mysql_apm.NewrelicApp = *app + mysqlapm.NewrelicApp = *app } else { log.Error("New Relic application initialization failed") } @@ -53,13 +53,11 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration utils.FatalIfErr(err) defer db.Close() - preCheckTxn := app.StartTransaction("MysqlSlowQueriesSample") // Validate preconditions before proceeding preValidationErr := validator.ValidatePreconditions(db) if preValidationErr != nil { utils.FatalIfErr(fmt.Errorf("preconditions failed: %w", preValidationErr)) } - preCheckTxn.End() // Get the list of unique excluded databases excludedDatabases := utils.GetExcludedDatabases(args.ExcludedDatabases) @@ -72,6 +70,7 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration 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(app, i, e, db, args, excludedDatabases) log.Debug("Completed fetching slow query metrics in %v", time.Since(start)) @@ -84,6 +83,7 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration 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(app, db, queryIDList, i, e, args) log.Debug("Completed fetching individual query metrics in %v", time.Since(start)) @@ -96,6 +96,7 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration 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(app, db, groupQueriesByDatabase, i, e, args) log.Debug("Completed fetching query execution plan metrics in %v", time.Since(start)) @@ -109,6 +110,7 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration 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(app, db, i, e, args, excludedDatabases) log.Debug("Completed fetching wait event metrics in %v", time.Since(start)) @@ -121,6 +123,7 @@ func PopulateQueryPerformanceMetrics(args arguments.ArgumentList, e *integration 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(app, db, i, e, args, excludedDatabases) log.Debug("Completed fetching blocking session metrics in %v", time.Since(start)) diff --git a/src/query-performance-monitoring/utils/database.go b/src/query-performance-monitoring/utils/database.go index c85364e2..bb4e43bd 100644 --- a/src/query-performance-monitoring/utils/database.go +++ b/src/query-performance-monitoring/utils/database.go @@ -15,7 +15,7 @@ import ( "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" - mysql_apm "github.com/newrelic/nri-mysql/src/query-performance-monitoring/mysql-apm" + mysqlapm "github.com/newrelic/nri-mysql/src/query-performance-monitoring/mysql-apm" ) type DataSource interface { @@ -64,7 +64,7 @@ func (db *Database) QueryxContext(app *newrelic.Application, ctx context.Context var err error app, err = newrelic.NewApplication( newrelic.ConfigAppName("nri-mysql-integration"), - newrelic.ConfigLicense(mysql_apm.ArgsGlobal), + newrelic.ConfigLicense(mysqlapm.ArgsGlobal), newrelic.ConfigDebugLogger(os.Stdout), newrelic.ConfigDatastoreRawQuery(true), ) @@ -79,12 +79,11 @@ func (db *Database) QueryxContext(app *newrelic.Application, ctx context.Context return nil, waitErr } - txn := app.StartTransaction("nrmysqlQuery") - ctx = newrelic.NewContext(ctx, txn) + ctx = newrelic.NewContext(ctx, mysqlapm.Txn) s := newrelic.DatastoreSegment{ - StartTime: txn.StartSegmentNow(), - Product: newrelic.DatastoreMySQL, - Operation: "SELECT", + StartTime: mysqlapm.Txn.StartSegmentNow(), + Product: newrelic.DatastoreMySQL, + Operation: "SELECT", ParameterizedQuery: query, } defer s.End()