Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat performance monitoring #189

Open
wants to merge 38 commits into
base: epic_db_query_performance_monitoring
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
53151cc
chore(deps): update golang patch version to v1.23.5 (#190)
renovate[bot] Jan 17, 2025
14cfcec
Update changelog with changes from v2.16.1
newrelic-coreint-bot Jan 20, 2025
cf04038
Query performance monitoring
tharun0064 Dec 30, 2024
38c4664
Refactor : version specific blocking session annonamization (#35)
tharun0064 Jan 8, 2025
bf8ccbb
refactor: resolved review comments (#36)
sjyothi54 Jan 9, 2025
ab19603
Feat review comments (#37)
tharun0064 Jan 9, 2025
cd21509
Feat review comments (#39)
sjyothi54 Jan 9, 2025
bc7a3f6
compile issue - resolved
tharun0064 Jan 10, 2025
3238d47
compile issue - resolved
tharun0064 Jan 10, 2025
fbd5eb8
refactor : rename variable
tharun0064 Jan 10, 2025
5a73a0b
Refactor : nil checks (#40)
tharun0064 Jan 10, 2025
d648911
Feat : resolved review comments (#42)
tharun0064 Jan 15, 2025
18ed92c
resolved: review comments (#44)
tharun0064 Jan 16, 2025
00e4097
Integration testing (#47)
rahulreddy15 Jan 16, 2025
852f29d
Feat : resolved review comments (#48)
tharun0064 Jan 17, 2025
a3cd73e
Feat : resolved review comments (#49)
tharun0064 Jan 17, 2025
4b2868a
Feat review comments (#51)
tharun0064 Jan 20, 2025
afbfa63
Feat: resolved review comments (#52)
tharun0064 Jan 20, 2025
4da1554
Refactored Integration Tests (#53)
rahulreddy15 Jan 20, 2025
0f765f2
Feat review comments (#54)
tharun0064 Jan 21, 2025
cfce344
fix lint issues (#55)
tharun0064 Jan 21, 2025
3711a86
Feat : resolved review comments (#56)
tharun0064 Jan 22, 2025
e7dff9d
Feat: refactored unit test cases (#57)
tharun0064 Jan 22, 2025
5f31038
Upgrade Go Version
rahulreddy15 Jan 23, 2025
2e87ba7
Feat: resolved review comments (#58)
tharun0064 Jan 27, 2025
e89cbcb
Feat review comments (#59)
tharun0064 Jan 27, 2025
08d9aa8
Feat: resolved review comments (#60)
tharun0064 Jan 27, 2025
4ed8799
Feat resolved review comments (#61)
tharun0064 Jan 27, 2025
2bf940a
Feat : resolved review comments (#62)
tharun0064 Jan 28, 2025
3461f3a
Feat review comments (#63)
tharun0064 Jan 28, 2025
2b972fe
Feat : resolved review comments (#64)
tharun0064 Jan 28, 2025
4a8d893
Feat: resolved review comments (#65)
tharun0064 Jan 28, 2025
b6b2b3a
Feat : resolved review comments (#66)
tharun0064 Jan 29, 2025
0739685
remove has metrics (#69)
tharun0064 Feb 4, 2025
7599cb9
Feat review comments (#67)
tharun0064 Feb 5, 2025
ddc9212
resolved: review comments (#70)
tharun0064 Feb 5, 2025
f974650
Execution plan datamodal fix (#71)
tharun0064 Feb 6, 2025
006c159
Add default description (#72)
tharun0064 Feb 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ Unreleased section should follow [Release Toolkit](https://github.com/newrelic/r

## Unreleased

## v2.16.1 - 2025-01-20

### ⛓️ Dependencies
- Updated golang patch version to v1.23.5

## v2.16.0 - 2024-11-11

### 🚀 Enhancements
Expand Down
14 changes: 11 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,17 @@ test:

integration-test:
@echo "=== $(INTEGRATION) === [ test ]: running integration tests..."
@docker compose -f tests/docker-compose.yml pull
@go test -v -tags=integration -count 1 ./tests/. || (ret=$$?; docker compose -f tests/docker-compose.yml down && exit $$ret)
@docker compose -f tests/docker-compose.yml down
@docker compose -f tests/docker-compose.yml up -d
@sleep 10
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before this sleep was not needed, why have we added that?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rahulreddy15 please check this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because before we were running docker compose to bring up the containers within the code.
Now, we are doing it as a command in the Makefile. Also, the postgres containers are being created with test data to simulate all the different kind of queries.
They need a couple moments to start up.

@go test -v -tags=integration -count 1 ./tests/postgresql_test.go -timeout 300s || (ret=$$?; docker compose -f tests/docker-compose.yml down -v && exit $$ret)
@docker compose -f tests/docker-compose.yml down -v
@echo "=== $(INTEGRATION) === [ test ]: running integration tests for query performance monitoring..."
@echo "Starting containers for performance tests..."
@docker compose -f tests/docker-compose-performance.yml up -d
@sleep 30
@go test -v -tags=query_performance ./tests/postgresqlperf_test.go -timeout 600s || (ret=$$?; docker compose -f tests/docker-compose-performance.yml down -v && exit $$ret)
@echo "Stopping performance test containers..."
@docker compose -f tests/docker-compose-performance.yml down -v

install: compile
@echo "=== $(INTEGRATION) === [ install ]: installing bin/$(BINARY_NAME)..."
Expand Down
2 changes: 1 addition & 1 deletion build/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.23.4-bookworm
FROM golang:1.23.5-bookworm

ARG GH_VERSION='1.9.2'

Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/newrelic/nri-postgresql

go 1.23.4
go 1.23.5

require (
github.com/blang/semver/v4 v4.0.0
Expand All @@ -11,6 +11,7 @@ require (
github.com/xeipuuv/gojsonschema v1.2.0
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0
gopkg.in/yaml.v3 v3.0.1
github.com/go-viper/mapstructure/v2 v2.2.1
)

require (
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ 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/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
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=
Expand Down
9 changes: 9 additions & 0 deletions postgresql-config.yml.sample
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ integrations:
# True if SSL is to be used. Defaults to false.
ENABLE_SSL: "false"

# Enable query performance monitoring - Defaults to false
# ENABLE_QUERY_MONITORING : "false"

# Threshold in milliseconds for query response time to fetch individual query performance metrics - Defaults to 500
# QUERY_RESPONSE_TIME_THRESHOLD : "500"

# The number of records for each query performance metrics (maximum slow query metrics can be 20 and execution plan metrics can be 30)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

number of records to be retrieved?

# QUERY_COUNT_THRESHOLD : "10"

# True if the SSL certificate should be trusted without validating.
# Setting this to true may open up the monitoring service to MITM attacks.
# Defaults to false.
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:"false" help:"Enable collection of detailed query performance metrics."`
QueryResponseTimeThreshold int `default:"500" help:"Threshold in milliseconds for query response time. If response time exceeds this threshold, the query will be considered slow."`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This argument is being used only while fetching individual query metrics. So instead of If response time exceeds this threshold, the query will be considered slow. adding If response time for the idividual query exceeds this threshold, the individaul query is reported in metrics or something similar makes sense?

QueryCountThreshold int `default:"20" help:"Maximum number of queries returned in query analysis results."`
}

// 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
9 changes: 8 additions & 1 deletion src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"runtime"
"strings"

queryperformancemonitoring "github.com/newrelic/nri-postgresql/src/query-performance-monitoring"
tharun0064 marked this conversation as resolved.
Show resolved Hide resolved

"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 +29,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 +65,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 +91,9 @@ func main() {
if err = pgIntegration.Publish(); err != nil {
log.Error(err.Error())
}

if args.EnableQueryMonitoring && args.HasMetrics() {
queryperformancemonitoring.QueryPerformanceMain(args, pgIntegration, collectionList)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we usually avoid passing around "args"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have to pass them as we need to have db specific connection in execution plan metrics

Copy link

@sairaj18 sairaj18 Jan 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Form yesterday's discussion with paolo, we can build the connection and pass it as an argument instead of passing args.

}

}
5 changes: 2 additions & 3 deletions src/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func PopulateMetrics(
}
defer con.Close()

version, err := collectVersion(con)
version, err := CollectVersion(con)
if err != nil {
log.Error("Metrics collection failed: error collecting version number: %s", err.Error())
return
Expand Down Expand Up @@ -223,15 +223,14 @@ type serverVersionRow struct {
Version string `db:"server_version"`
}

func collectVersion(connection *connection.PGSQLConnection) (*semver.Version, error) {
func CollectVersion(connection *connection.PGSQLConnection) (*semver.Version, error) {
var versionRows []*serverVersionRow
if err := connection.Query(&versionRows, versionQuery); err != nil {
return nil, err
}

re := regexp.MustCompile(`[0-9]+\.[0-9]+(\.[0-9])?`)
version := re.FindString(versionRows[0].Version)

// special cases for ubuntu/debian parsing
//version := versionRows[0].Version
//if strings.Contains(version, "Ubuntu") {
Expand Down
10 changes: 5 additions & 5 deletions src/metrics/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func Test_collectVersion(t *testing.T) {
Minor: 3,
}

version, err := collectVersion(testConnection)
version, err := CollectVersion(testConnection)

assert.Nil(t, err)
assert.Equal(t, expected, version)
Expand All @@ -42,7 +42,7 @@ func Test_collectVersion_EnterpriseDB(t *testing.T) {
Patch: 7,
}

version, err := collectVersion(testConnection)
version, err := CollectVersion(testConnection)

assert.Nil(t, err)
assert.Equal(t, expected, version)
Expand All @@ -61,7 +61,7 @@ func Test_collectVersion_Ubuntu(t *testing.T) {
Minor: 4,
}

version, err := collectVersion(testConnection)
version, err := CollectVersion(testConnection)

assert.Nil(t, err)
assert.Equal(t, expected, version)
Expand All @@ -80,7 +80,7 @@ func Test_collectVersion_Debian(t *testing.T) {
Minor: 4,
}

version, err := collectVersion(testConnection)
version, err := CollectVersion(testConnection)

assert.Nil(t, err)
assert.Equal(t, expected, version)
Expand All @@ -94,7 +94,7 @@ func Test_collectVersion_Err(t *testing.T) {

mock.ExpectQuery(versionQuery).WillReturnRows(versionRows)

_, err := collectVersion(testConnection)
_, err := CollectVersion(testConnection)

assert.NotNil(t, err)
}
42 changes: 42 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,42 @@
package commonutils

import (
"crypto/rand"
"fmt"
"math/big"
"regexp"
"strings"
"time"

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

// re is a regular expression that matches single-quoted strings, numbers, or double-quoted strings
var re = regexp.MustCompile(`'[^']*'|\d+|".*?"`)

func GetDatabaseListInString(dbMap collection.DatabaseList) string {
if len(dbMap) == 0 {
return ""
}
var quotedNames = make([]string, 0)
for dbName := range dbMap {
quotedNames = append(quotedNames, fmt.Sprintf("'%s'", dbName))
}
return strings.Join(quotedNames, ",")
}

func AnonymizeQueryText(query string) string {
anonymizedQuery := re.ReplaceAllString(query, "?")
return anonymizedQuery
}

// This function is used to generate a unique plan ID for a query
func GeneratePlanID(queryID string) *string {
tharun0064 marked this conversation as resolved.
Show resolved Hide resolved
randomInt, err := rand.Int(rand.Reader, big.NewInt(RandomIntRange))
if err != nil {
return nil
}
currentTime := time.Now().Format(TimeFormat)
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,49 @@
package commonutils_test

import (
"sort"
"testing"
"time"

"github.com/newrelic/nri-postgresql/src/collection"
commonutils "github.com/newrelic/nri-postgresql/src/query-performance-monitoring/common-utils"
"github.com/stretchr/testify/assert"
)

func TestGetDatabaseListInString(t *testing.T) {
dbListKeys := []string{"db1"}
sort.Strings(dbListKeys) // Sort the keys to ensure consistent order
dbList := collection.DatabaseList{}
for _, key := range dbListKeys {
dbList[key] = collection.SchemaList{}
}
expected := "'db1'"
result := commonutils.GetDatabaseListInString(dbList)
assert.Equal(t, expected, result)

// Test with empty database list
dbList = collection.DatabaseList{}
expected = ""
result = commonutils.GetDatabaseListInString(dbList)
assert.Equal(t, expected, result)
}

func TestAnonymizeQueryText(t *testing.T) {
tharun0064 marked this conversation as resolved.
Show resolved Hide resolved
query := "SELECT * FROM users WHERE id = 1 AND name = 'John'"
expected := "SELECT * FROM users WHERE id = ? AND name = ?"
result := commonutils.AnonymizeQueryText(query)
assert.Equal(t, expected, result)
query = "SELECT * FROM employees WHERE id = 10 OR name <> 'John Doe' OR name != 'John Doe' OR age < 30 OR age <= 30 OR salary > 50000OR salary >= 50000 OR department LIKE 'Sales%' OR department ILIKE 'sales%'OR join_date BETWEEN '2023-01-01' AND '2023-12-31' OR department IN ('HR', 'Engineering', 'Marketing') OR department IS NOT NULL OR department IS NULL;"
expected = "SELECT * FROM employees WHERE id = ? OR name <> ? OR name != ? OR age < ? OR age <= ? OR salary > ?OR salary >= ? OR department LIKE ? OR department ILIKE ?OR join_date BETWEEN ? AND ? OR department IN (?, ?, ?) OR department IS NOT NULL OR department IS NULL;"
result = commonutils.AnonymizeQueryText(query)
assert.Equal(t, expected, result)
}

func TestGeneratePlanID(t *testing.T) {
queryID := "query123"
result := commonutils.GeneratePlanID(queryID)
assert.NotNil(t, result)
assert.Contains(t, *result, queryID)
assert.Contains(t, *result, "-")
assert.Contains(t, *result, time.Now().Format(commonutils.TimeFormat)[:8]) // Check date part
}
25 changes: 25 additions & 0 deletions src/query-performance-monitoring/common-utils/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package commonutils

import "errors"

// The maximum number records that can be fetched in a single metrics
const MaxQueryCountThreshold = 30

// The maximum number of individual queries that can be fetched in a single metrics
const MaxIndividualQueryCountThreshold = 10

// The maximum number of metrics to be published in a single batch
const PublishThreshold = 100
const RandomIntRange = 1000000
const TimeFormat = "20060102150405"

var ErrUnsupportedVersion = errors.New("unsupported PostgreSQL version")
var ErrUnExpectedError = errors.New("unexpected error")

var ErrInvalidModelType = errors.New("invalid model type")
var ErrNotEligible = errors.New("not Eligible to fetch metrics")

const PostgresVersion12 = 12
const PostgresVersion11 = 11
const PostgresVersion13 = 13
const PostgresVersion14 = 14
Loading
Loading