generated from permafrost-dev/go-project-template
-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add pg_stats optimization suggestions, sort output of queries
- Loading branch information
1 parent
c2da180
commit 03a9bcc
Showing
5 changed files
with
271 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
package pgstat | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
// OptimizationSuggestion represents a suggestion for a query optimization. | ||
type OptimizationSuggestion struct { | ||
QueryID int64 | ||
Query string | ||
Suggestions []string | ||
} | ||
|
||
// AnalyzeQueries analyzes an array of PgStatStatementEntry and returns optimization suggestions. | ||
func AnalyzeQueries(entries []PgStatStatementEntry) []OptimizationSuggestion { | ||
var suggestions []OptimizationSuggestion | ||
for _, entry := range entries { | ||
var entrySuggestions []string | ||
|
||
if strings.Contains(entry.Query, " pg_") || strings.Contains(entry.Query, " information_schema") { | ||
continue | ||
} | ||
|
||
if strings.Contains(entry.Query, "alter table") || strings.Contains(entry.Query, "create index") { | ||
continue | ||
} | ||
|
||
// Check for high total execution time. | ||
// if suggestion := checkHighTotalExecTime(entry); suggestion != "" { | ||
// entrySuggestions = append(entrySuggestions, suggestion) | ||
// } | ||
|
||
// Check for high average execution time. | ||
if suggestion := checkHighMeanExecTime(entry); suggestion != "" { | ||
entrySuggestions = append(entrySuggestions, suggestion) | ||
} | ||
|
||
// Check for high standard deviation in execution time. | ||
if suggestion := checkHighStdDevExecTime(entry); suggestion != "" { | ||
entrySuggestions = append(entrySuggestions, suggestion) | ||
} | ||
|
||
// Check for high number of temporary blocks read or written. | ||
if suggestion := checkTempBlksUsage(entry); suggestion != "" { | ||
entrySuggestions = append(entrySuggestions, suggestion) | ||
} | ||
|
||
// Check for high number of shared blocks read vs. hit. | ||
if suggestion := checkSharedBlksReadVsHit(entry); suggestion != "" { | ||
entrySuggestions = append(entrySuggestions, suggestion) | ||
} | ||
|
||
// Check for low rows returned per call. | ||
// if suggestion := checkRowsPerCall(entry); suggestion != "" { | ||
// entrySuggestions = append(entrySuggestions, suggestion) | ||
// } | ||
|
||
// Check for high WAL usage. | ||
if suggestion := checkHighWalUsage(entry); suggestion != "" { | ||
entrySuggestions = append(entrySuggestions, suggestion) | ||
} | ||
|
||
// Add suggestions for this query if any exist. | ||
if len(entrySuggestions) > 0 { | ||
suggestions = append(suggestions, OptimizationSuggestion{ | ||
QueryID: entry.QueryID, | ||
Query: entry.Query, | ||
Suggestions: entrySuggestions, | ||
}) | ||
} | ||
} | ||
return suggestions | ||
} | ||
|
||
// checkHighTotalExecTime checks if the total execution time is high. | ||
func checkHighTotalExecTime(entry PgStatStatementEntry) string { | ||
const totalExecTimeThreshold = 1000.0 // in milliseconds | ||
if entry.TotalExecTime > totalExecTimeThreshold { | ||
return fmt.Sprintf("Total execution time is high (%.2f ms). Consider optimizing the query or adding indexes.", entry.TotalExecTime) | ||
} | ||
return "" | ||
} | ||
|
||
// checkHighMeanExecTime checks if the mean execution time per call is high. | ||
func checkHighMeanExecTime(entry PgStatStatementEntry) string { | ||
const meanExecTimeThreshold = 100.0 // in milliseconds | ||
if entry.MeanExecTime > meanExecTimeThreshold { | ||
return fmt.Sprintf("Mean execution time per call is high (%.2f ms). Consider optimizing the query.", entry.MeanExecTime) | ||
} | ||
return "" | ||
} | ||
|
||
// checkHighStdDevExecTime checks if the standard deviation of execution time is high. | ||
func checkHighStdDevExecTime(entry PgStatStatementEntry) string { | ||
const stdDevThreshold = 50.0 // in milliseconds | ||
if entry.StddevExecTime > stdDevThreshold { | ||
return fmt.Sprintf("Execution time varies widely (stddev %.2f ms). Investigate possible causes for inconsistent performance.", entry.StddevExecTime) | ||
} | ||
return "" | ||
} | ||
|
||
// checkTempBlksUsage checks if the query uses a high number of temporary blocks. | ||
func checkTempBlksUsage(entry PgStatStatementEntry) string { | ||
if entry.TempBlksRead > 0 || entry.TempBlksWritten > 0 { | ||
return "Query uses temporary disk space. Consider optimizing to reduce disk I/O, such as adding indexes or rewriting the query." | ||
} | ||
return "" | ||
} | ||
|
||
// checkSharedBlksReadVsHit checks if the query reads many shared blocks compared to hits. | ||
func checkSharedBlksReadVsHit(entry PgStatStatementEntry) string { | ||
totalSharedBlks := entry.SharedBlksHit + entry.SharedBlksRead | ||
if totalSharedBlks == 0 { | ||
return "" | ||
} | ||
readRatio := float64(entry.SharedBlksRead) / float64(totalSharedBlks) | ||
if readRatio > 25.0 { | ||
return fmt.Sprintf("High shared block read ratio (%.2f%%). Consider adding indexes to reduce I/O.", readRatio*100) | ||
} | ||
return "" | ||
} | ||
|
||
// checkRowsPerCall checks if the query returns a low number of rows per call. | ||
func checkRowsPerCall(entry PgStatStatementEntry) string { | ||
if entry.Calls == 0 { | ||
return "" | ||
} | ||
rowsPerCall := float64(entry.Rows) / float64(entry.Calls) | ||
if rowsPerCall < 10.0 { | ||
return fmt.Sprintf("Low rows returned per call (%.2f). Verify if the query returns the expected results.", rowsPerCall) | ||
} | ||
return "" | ||
} | ||
|
||
// checkHighWalUsage checks if the query generates a high amount of WAL records or bytes. | ||
func checkHighWalUsage(entry PgStatStatementEntry) string { | ||
const walBytesThreshold = 1024 * 1024 * 50 // 50 MB | ||
if entry.WalBytes > walBytesThreshold { | ||
return fmt.Sprintf("High WAL usage (%.2f MB). Consider batching writes or optimizing the query.", float64(entry.WalBytes)/(1024*1024)) | ||
} | ||
return "" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package pgstat | ||
|
||
import ( | ||
//"database/sql" | ||
"fmt" | ||
|
||
"github.com/jmoiron/sqlx" | ||
_ "github.com/lib/pq" | ||
) | ||
|
||
// PgStatStatementEntry represents a single entry from pg_stat_statements. | ||
type PgStatStatementEntry struct { | ||
UserID int64 `db:"userid"` | ||
DbID int64 `db:"dbid"` | ||
TopLevel bool `db:"toplevel"` | ||
QueryID int64 `db:"queryid"` | ||
Query string `db:"query"` | ||
Plans int64 `db:"plans"` | ||
TotalPlanTime float64 `db:"total_plan_time"` | ||
MinPlanTime float64 `db:"min_plan_time"` | ||
MaxPlanTime float64 `db:"max_plan_time"` | ||
MeanPlanTime float64 `db:"mean_plan_time"` | ||
StddevPlanTime float64 `db:"stddev_plan_time"` | ||
Calls int64 `db:"calls"` | ||
TotalExecTime float64 `db:"total_exec_time"` | ||
MinExecTime float64 `db:"min_exec_time"` | ||
MaxExecTime float64 `db:"max_exec_time"` | ||
MeanExecTime float64 `db:"mean_exec_time"` | ||
StddevExecTime float64 `db:"stddev_exec_time"` | ||
Rows int64 `db:"rows"` | ||
SharedBlksHit int64 `db:"shared_blks_hit"` | ||
SharedBlksRead int64 `db:"shared_blks_read"` | ||
SharedBlksDirtied int64 `db:"shared_blks_dirtied"` | ||
SharedBlksWritten int64 `db:"shared_blks_written"` | ||
LocalBlksHit int64 `db:"local_blks_hit"` | ||
LocalBlksRead int64 `db:"local_blks_read"` | ||
LocalBlksDirtied int64 `db:"local_blks_dirtied"` | ||
LocalBlksWritten int64 `db:"local_blks_written"` | ||
TempBlksRead int64 `db:"temp_blks_read"` | ||
TempBlksWritten int64 `db:"temp_blks_written"` | ||
BlkReadTime float64 `db:"blk_read_time"` | ||
BlkWriteTime float64 `db:"blk_write_time"` | ||
TempBlkReadTime float64 `db:"temp_blk_read_time"` | ||
TempBlkWriteTime float64 `db:"temp_blk_write_time"` | ||
WalRecords int64 `db:"wal_records"` | ||
WalFPI int64 `db:"wal_fpi"` | ||
WalBytes int64 `db:"wal_bytes"` | ||
JitFunctions int64 `db:"jit_functions"` | ||
JitGenerationTime float64 `db:"jit_generation_time"` | ||
JitInliningCount int64 `db:"jit_inlining_count"` | ||
JitInliningTime float64 `db:"jit_inlining_time"` | ||
JitOptimizationCount int64 `db:"jit_optimization_count"` | ||
JitOptimizationTime float64 `db:"jit_optimization_time"` | ||
JitEmissionCount int64 `db:"jit_emission_count"` | ||
JitEmissionTime float64 `db:"jit_emission_time"` | ||
} | ||
|
||
// GetPgStatStatements fetches the pg_stat_statements from the database. | ||
func GetPgStatStatements(db *sqlx.DB) ([]PgStatStatementEntry, error) { | ||
var entries []PgStatStatementEntry | ||
query := `SELECT * FROM pg_stat_statements ORDER BY calls DESC;` | ||
err := db.Select(&entries, query) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return entries, nil | ||
} | ||
|
||
func BuildPostgresDsn(host string, port int, user string, password string, dbname string) string { | ||
return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname) | ||
} | ||
|
||
func ConnectAndFetchPgStatStatements(dsn string) ([]PgStatStatementEntry, error) { | ||
db, err := sqlx.Connect("postgres", dsn) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
defer db.Close() | ||
|
||
return GetPgStatStatements(db) | ||
} |