Skip to content

Commit

Permalink
Query performance monitoring
Browse files Browse the repository at this point in the history
Query performance monitoring

undo change

remove test query

query performance monitoring

undo changelog changes

Query performance monitoring

Query performance monitoring

slow query len check in individual queries metrics populate

trim query text

lint issue fix

undo changelog file change

test

remove OH queries

remove OH queries

reusing existing DB connection (#22)

reusing existing DB connection

DB specific metric collection  (#23)

DB specific metric collection

query response time threshold  (#24)

query response time threshold

change in individual query (#25)

change in individual query

version specific queries  (#26)

verison specific queries

fix database name (#27)

fix database name

test

test

min max threshold on limit (#28)

min max threshold on limit

Feat version specific individual query (#29)

* version specific indivual query

Feat version specific individual query (#30)

Version specific indivual query

fix query id , ingestData:false and reuse individual version specific query (#31)

fix query id , ingestData:false and reuse individual version specific query

database list check (#32)

database list check

publish only if there are metrics (#33)

publish only if there are metrics

undo change log change
  • Loading branch information
tharun0064 committed Jan 8, 2025
1 parent c075367 commit a920d40
Show file tree
Hide file tree
Showing 18 changed files with 1,070 additions and 4 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/blang/semver/v4 v4.0.0
github.com/jmoiron/sqlx v1.4.0
github.com/lib/pq v1.10.9
github.com/mitchellh/mapstructure v1.5.0
github.com/newrelic/infra-integrations-sdk/v3 v3.9.1
github.com/stretchr/testify v1.10.0
github.com/xeipuuv/gojsonschema v1.2.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
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=
Expand Down
3 changes: 3 additions & 0 deletions src/args/argument_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ type ArgumentList struct {
CollectDbLockMetrics bool `default:"false" help:"If true, enables collection of lock metrics for the specified database. (Note: requires that the 'tablefunc' extension is installed)"` //nolint: stylecheck
CollectBloatMetrics bool `default:"true" help:"Enable collecting bloat metrics which can be performance intensive"`
ShowVersion bool `default:"false" help:"Print build information and exit"`
EnableQueryMonitoring bool `default:"true" help:"Query monitoring is enabled by default. Set to false to disable."`
QueryResponseTimeThreshold int `default:"0" 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."`
}

// Validate validates PostgreSQl arguments
Expand Down
3 changes: 1 addition & 2 deletions src/connection/pgsql_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ func (p PGSQLConnection) HaveExtensionInSchema(extensionName, schemaName string)
return true
}

// createConnectionURL creates the connection string. A list of paramters
// createConnectionURL creates the connection string. A list of parameters
// can be found here https://godoc.org/github.com/lib/pq#hdr-Connection_String_Parameters
func createConnectionURL(ci *connectionInfo, database string) string {
connectionURL := &url.URL{
Expand All @@ -170,7 +170,6 @@ func createConnectionURL(ci *connectionInfo, database string) string {
}

connectionURL.RawQuery = query.Encode()

return connectionURL.String()
}

Expand Down
10 changes: 8 additions & 2 deletions src/main.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
//go:generate goversioninfo
package main

import (
Expand All @@ -7,6 +6,8 @@ import (
"runtime"
"strings"

queryperformancemonitoring "github.com/newrelic/nri-postgresql/src/query-performance-monitoring"

"github.com/newrelic/infra-integrations-sdk/v3/integration"
"github.com/newrelic/infra-integrations-sdk/v3/log"
"github.com/newrelic/nri-postgresql/src/args"
Expand All @@ -27,6 +28,7 @@ var (
)

func main() {

var args args.ArgumentList
// Create Integration
pgIntegration, err := integration.New(integrationName, integrationVersion, integration.Args(&args))
Expand Down Expand Up @@ -62,7 +64,6 @@ func main() {
log.Error("Error creating list of entities to collect: %s", err)
os.Exit(1)
}

instance, err := pgIntegration.Entity(fmt.Sprintf("%s:%s", args.Hostname, args.Port), "pg-instance")
if err != nil {
log.Error("Error creating instance entity: %s", err.Error())
Expand All @@ -89,4 +90,9 @@ func main() {
if err = pgIntegration.Publish(); err != nil {
log.Error(err.Error())
}

if args.EnableQueryMonitoring {
queryperformancemonitoring.QueryPerformanceMain(args, pgIntegration, collectionList)
}

}
27 changes: 27 additions & 0 deletions src/query-performance-monitoring/common-utils/common-helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package commonutils

import (
"fmt"
"strings"

"github.com/newrelic/nri-postgresql/src/collection"
)

func GetQuotedStringFromArray(array []string) string {
var quotedDatabaseNames = make([]string, 0)
for _, name := range array {
quotedDatabaseNames = append(quotedDatabaseNames, fmt.Sprintf("'%s'", name))
}
return strings.Join(quotedDatabaseNames, ",")
}

func GetDatabaseListInString(dbList collection.DatabaseList) string {
var databaseNames = make([]string, 0)
for dbName := range dbList {
databaseNames = append(databaseNames, dbName)
}
if len(databaseNames) == 0 {
return ""
}
return GetQuotedStringFromArray(databaseNames)
}
4 changes: 4 additions & 0 deletions src/query-performance-monitoring/common-utils/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package commonutils

const MAX_QUERY_THRESHOLD = 30
const MAX_INDIVIDUAL_QUERY_THRESHOLD = 10
125 changes: 125 additions & 0 deletions src/query-performance-monitoring/common-utils/ingestion-helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package commonutils

import (
"crypto/rand"
"fmt"

Check failure on line 5 in src/query-performance-monitoring/common-utils/ingestion-helpers.go

View workflow job for this annotation

GitHub Actions / push-pr / static-analysis / Run all static analysis checks

File is not properly formatted (goimports)
_ "github.com/lib/pq"
"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"
"github.com/newrelic/nri-postgresql/src/args"
"math/big"
"reflect"
"time"
)

const publishThreshold = 100
const randomIntRange = 1000000

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 {
return
}
case `attribute`:
err := metricSet.SetMetric(name, value, metric.ATTRIBUTE)
if err != nil {
return
}
default:
err := metricSet.SetMetric(name, value, metric.GAUGE)
if err != nil {
return
}
}
}

func IngestMetric(metricList []interface{}, eventName string, pgIntegration *integration.Integration, args args.ArgumentList) {
instanceEntity, err := createEntity(pgIntegration, args)
if err != nil {
log.Error("Error creating entity: %v", err)
return
}

metricCount := 0
lenOfMetricList := len(metricList)

for _, model := range metricList {
if model == nil {
continue
}
metricCount += 1
metricSet := instanceEntity.NewMetricSet(eventName)

processModel(model, metricSet)

if metricCount == publishThreshold || metricCount == lenOfMetricList {
metricCount = 0
if err := publishMetrics(pgIntegration, &instanceEntity, args); err != nil {
log.Error("Error publishing metrics: %v", err)
return
}
}
}
if metricCount > 0 {
if err := publishMetrics(pgIntegration, &instanceEntity, args); err != nil {
log.Error("Error publishing metrics: %v", err)
return
}
}
}

func createEntity(pgIntegration *integration.Integration, args args.ArgumentList) (*integration.Entity, error) {
return pgIntegration.Entity(fmt.Sprintf("%s:%s", args.Hostname, args.Port), "pg-instance")
}

func processModel(model interface{}, metricSet *metric.Set) {
modelValue := reflect.ValueOf(model)
if modelValue.Kind() == reflect.Ptr {
modelValue = modelValue.Elem()
}
if !modelValue.IsValid() || modelValue.Kind() != reflect.Struct {
return
}

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")
ingestData := fieldType.Tag.Get("ingest_data")

if ingestData == "false" {
continue
}

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)
}
}
}

func publishMetrics(pgIntegration *integration.Integration, instanceEntity **integration.Entity, args args.ArgumentList) error {
if err := pgIntegration.Publish(); err != nil {
return err
}
var err error
*instanceEntity, err = pgIntegration.Entity(fmt.Sprintf("%s:%s", args.Hostname, args.Port), "pg-instance")
return err
}

func GenerateRandomIntegerString(queryID string) *string {
randomInt, err := rand.Int(rand.Reader, big.NewInt(randomIntRange))
if err != nil {
return nil
}
currentTime := time.Now().Format("20060102150405")
result := fmt.Sprintf("%s-%d-%s", queryID, randomInt.Int64(), currentTime)
return &result
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package commonutils

import (
"fmt"
"regexp"
"strconv"

"github.com/newrelic/infra-integrations-sdk/v3/log"
performancedbconnection "github.com/newrelic/nri-postgresql/src/connection"
"github.com/newrelic/nri-postgresql/src/query-performance-monitoring/queries"
)

func FetchVersion(conn *performancedbconnection.PGSQLConnection) (int, error) {
var versionStr string
rows, err := conn.Queryx("SELECT version()")
if err != nil {
log.Error("Error executing query: %v", err)
return 0, err
}
defer rows.Close()

if rows.Next() {
if err := rows.Scan(&versionStr); err != nil {
log.Error("Error scanning version: %v", err)
return 0, err
}
}
re := regexp.MustCompile(`PostgreSQL (\d+)\.`)
matches := re.FindStringSubmatch(versionStr)
if len(matches) < 2 {
log.Error("Unable to parse PostgreSQL version from string: %s", versionStr)
return 0, fmt.Errorf("unable to parse PostgreSQL version from string: %s", versionStr)

Check failure on line 32 in src/query-performance-monitoring/common-utils/query-fetch-helpers.go

View workflow job for this annotation

GitHub Actions / push-pr / static-analysis / Run all static analysis checks

do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf(\"unable to parse PostgreSQL version from string: %s\", versionStr)" (err113)
}

version, err := strconv.Atoi(matches[1])
log.Debug("version", version)
if err != nil {
log.Error("Error converting version to integer: %v", err)
return 0, err
}
return version, nil
}

func FetchVersionSpecificSlowQueries(conn *performancedbconnection.PGSQLConnection) (string, error) {
version, err := FetchVersion(conn)
if err != nil {
return "", err
}
switch {
case version == 12:

Check failure on line 50 in src/query-performance-monitoring/common-utils/query-fetch-helpers.go

View workflow job for this annotation

GitHub Actions / push-pr / static-analysis / Run all static analysis checks

Magic number: 12, in <case> detected (mnd)
return queries.SlowQueriesForV12, nil
case version >= 13:

Check failure on line 52 in src/query-performance-monitoring/common-utils/query-fetch-helpers.go

View workflow job for this annotation

GitHub Actions / push-pr / static-analysis / Run all static analysis checks

Magic number: 13, in <case> detected (mnd)
return queries.SlowQueriesForV13AndAbove, nil
default:
return "", fmt.Errorf("unsupported PostgreSQL version %d", version)

Check failure on line 55 in src/query-performance-monitoring/common-utils/query-fetch-helpers.go

View workflow job for this annotation

GitHub Actions / push-pr / static-analysis / Run all static analysis checks

do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf(\"unsupported PostgreSQL version %d\", version)" (err113)
}
}

func FetchVersionSpecificBlockingQueries(conn *performancedbconnection.PGSQLConnection) (string, error) {
version, err := FetchVersion(conn)
if err != nil {
return "", err
}
switch {
case version == 12, version == 13:

Check failure on line 65 in src/query-performance-monitoring/common-utils/query-fetch-helpers.go

View workflow job for this annotation

GitHub Actions / push-pr / static-analysis / Run all static analysis checks

Magic number: 12, in <case> detected (mnd)
return queries.BlockingQueriesForV12AndV13, nil
case version >= 14:
return queries.BlockingQueriesForV14AndAbove, nil
default:
return "", fmt.Errorf("unsupported PostgreSQL version: %d", version)

Check failure on line 70 in src/query-performance-monitoring/common-utils/query-fetch-helpers.go

View workflow job for this annotation

GitHub Actions / push-pr / static-analysis / Run all static analysis checks

do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf(\"unsupported PostgreSQL version: %d\", version)" (err113)
}
}

func FetchVersionSpecificIndividualQueries(conn *performancedbconnection.PGSQLConnection) (string, error) {
version, err := FetchVersion(conn)
if err != nil {
return "", err
}
switch {
case version == 12:
return queries.IndividualQuerySearchV12, nil
case version >= 13:
return queries.IndividualQuerySearchV13AndAbove, nil
default:
return "", fmt.Errorf("unsupported PostgreSQL version %d", version)

Check failure on line 85 in src/query-performance-monitoring/common-utils/query-fetch-helpers.go

View workflow job for this annotation

GitHub Actions / push-pr / static-analysis / Run all static analysis checks

do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf(\"unsupported PostgreSQL version %d\", version)" (err113)
}
}
Loading

0 comments on commit a920d40

Please sign in to comment.