From c4de2a73d9b7b97b6d129f7c2f597bc52c554658 Mon Sep 17 00:00:00 2001 From: Lukas Fittl Date: Mon, 23 Dec 2024 16:57:04 -0800 Subject: [PATCH] Add "--generate-explain-analyze-helper-sql" command Like "--generate-stats-helper-sql" this allows generating a SQL script that installs the pganalyze.explain_analyze helper into all databases on a server, as specified by the server section name passed as an argument. Additionally, the "--generate-helper-explain-analyze-role" option allows setting the role that the helper function should be owned by as a value, defaulting to "pganalyze_explain". Note that the role creation, as well as any GRANT statements for the role must be taken care of separately. --- main.go | 52 +++++++++++++++++++---------------- runner/generate_helper_sql.go | 33 ++++++++++++++++++++++ runner/run.go | 28 +++++++++++++++++++ state/state.go | 18 ++++++------ 4 files changed, 100 insertions(+), 31 deletions(-) diff --git a/main.go b/main.go index 7d9137f77..26b5d5dc9 100644 --- a/main.go +++ b/main.go @@ -50,6 +50,8 @@ func main() { var testExplain bool var testSection string var generateStatsHelperSql string + var generateHelperExplainAnalyzeSql string + var generateHelperExplainAnalyzeRole string var forceStateUpdate bool var configFilename string var stateFilename string @@ -74,6 +76,8 @@ func main() { flag.BoolVar(&testExplain, "test-explain", false, "Tests whether EXPLAIN collection works by issuing a dummy query (ensure log collection works first)") flag.StringVar(&testSection, "test-section", "", "Tests a particular section of the config file, i.e. a specific server, and ignores all other config sections") flag.StringVar(&generateStatsHelperSql, "generate-stats-helper-sql", "", "Generates a SQL script for the given server (name of section in the config file, or \"default\" for env variables), that can be run with \"psql -f\" for installing the collector stats helpers on all configured databases") + flag.StringVar(&generateHelperExplainAnalyzeSql, "generate-explain-analyze-helper-sql", "", "Generates a SQL script for the given server (name of section in the config file, or \"default\" for env variables), that can be run with \"psql -f\" for installing the collector pganalyze.explain_analyze helper on all configured databases") + flag.StringVar(&generateHelperExplainAnalyzeRole, "generate-explain-analyze-helper-role", "pganalyze_explain", "Sets owner role of the pganalyze.explain_analyze helper function, defaults to \"pganalyze_explain\"") flag.BoolVar(&reloadRun, "reload", false, "Reloads the collector daemon that's running on the host") flag.BoolVar(&noReload, "no-reload", false, "Disables automatic config reloading during a test run") flag.BoolVarP(&logger.Verbose, "verbose", "v", false, "Outputs additional debugging information, use this if you're encountering errors or other problems") @@ -145,33 +149,35 @@ func main() { } } - if testRunLogs || testRunAndTrace || testExplain || generateStatsHelperSql != "" { + if testRunLogs || testRunAndTrace || testExplain || generateStatsHelperSql != "" || generateHelperExplainAnalyzeSql != "" { testRun = true } globalCollectionOpts := state.CollectionOpts{ - StartedAt: time.Now(), - SubmitCollectedData: !benchmark && true, - TestRun: testRun, - TestRunLogs: testRunLogs || dryRunLogs, - TestExplain: testExplain, - TestSection: testSection, - GenerateStatsHelperSql: generateStatsHelperSql, - DebugLogs: debugLogs, - DiscoverLogLocation: discoverLogLocation, - CollectPostgresRelations: !noPostgresRelations, - CollectPostgresSettings: !noPostgresSettings, - CollectPostgresLocks: !noPostgresLocks, - CollectPostgresFunctions: !noPostgresFunctions, - CollectPostgresBloat: !noPostgresBloat, - CollectPostgresViews: !noPostgresViews, - CollectLogs: !noLogs, - CollectExplain: !noExplain, - CollectSystemInformation: !noSystemInformation, - StateFilename: stateFilename, - WriteStateUpdate: (!dryRun && !dryRunLogs && !testRun) || forceStateUpdate, - ForceEmptyGrant: dryRun || dryRunLogs || testRunLogs || benchmark, - OutputAsJson: !benchmark, + StartedAt: time.Now(), + SubmitCollectedData: !benchmark && true, + TestRun: testRun, + TestRunLogs: testRunLogs || dryRunLogs, + TestExplain: testExplain, + TestSection: testSection, + GenerateStatsHelperSql: generateStatsHelperSql, + GenerateHelperExplainAnalyzeSql: generateHelperExplainAnalyzeSql, + GenerateHelperExplainAnalyzeRole: generateHelperExplainAnalyzeRole, + DebugLogs: debugLogs, + DiscoverLogLocation: discoverLogLocation, + CollectPostgresRelations: !noPostgresRelations, + CollectPostgresSettings: !noPostgresSettings, + CollectPostgresLocks: !noPostgresLocks, + CollectPostgresFunctions: !noPostgresFunctions, + CollectPostgresBloat: !noPostgresBloat, + CollectPostgresViews: !noPostgresViews, + CollectLogs: !noLogs, + CollectExplain: !noExplain, + CollectSystemInformation: !noSystemInformation, + StateFilename: stateFilename, + WriteStateUpdate: (!dryRun && !dryRunLogs && !testRun) || forceStateUpdate, + ForceEmptyGrant: dryRun || dryRunLogs || testRunLogs || benchmark, + OutputAsJson: !benchmark, } if reloadRun && !testRun { diff --git a/runner/generate_helper_sql.go b/runner/generate_helper_sql.go index 13ecedad0..853db07d7 100644 --- a/runner/generate_helper_sql.go +++ b/runner/generate_helper_sql.go @@ -68,3 +68,36 @@ func GenerateStatsHelperSql(ctx context.Context, server *state.Server, globalCol return output.String(), nil } + +func GenerateExplainAnalyzeHelperSql(ctx context.Context, server *state.Server, globalCollectionOpts state.CollectionOpts, logger *util.Logger) (string, error) { + db, err := postgres.EstablishConnection(ctx, server, logger, globalCollectionOpts, "") + if err != nil { + return "", err + } + defer db.Close() + + version, err := postgres.GetPostgresVersion(ctx, logger, db) + if err != nil { + return "", fmt.Errorf("error collecting Postgres version: %s", err) + } + + databases, _, err := postgres.GetDatabases(ctx, logger, db, version) + if err != nil { + return "", fmt.Errorf("error collecting pg_databases: %s", err) + } + + output := strings.Builder{} + for _, dbName := range postgres.GetDatabasesToCollect(server, databases) { + output.WriteString(fmt.Sprintf("\\c %s\n", pq.QuoteIdentifier(dbName))) + output.WriteString("CREATE SCHEMA IF NOT EXISTS pganalyze;\n") + output.WriteString(fmt.Sprintf("GRANT USAGE ON SCHEMA pganalyze TO %s;\n", server.Config.GetDbUsername())) + output.WriteString(fmt.Sprintf("GRANT CREATE ON SCHEMA pganalyze TO %s;\n", globalCollectionOpts.GenerateExplainAnalyzeHelperRole)) + output.WriteString(fmt.Sprintf("SET ROLE %s;\n", globalCollectionOpts.GenerateExplainAnalyzeHelperRole)) + output.WriteString(util.ExplainAnalyzeHelper + "\n") + output.WriteString("RESET ROLE;\n") + output.WriteString(fmt.Sprintf("REVOKE CREATE ON SCHEMA pganalyze FROM %s;\n", globalCollectionOpts.GenerateExplainAnalyzeHelperRole)) + output.WriteString("\n") + } + + return output.String(), nil +} diff --git a/runner/run.go b/runner/run.go index a087e64a9..d8e86ea26 100644 --- a/runner/run.go +++ b/runner/run.go @@ -130,6 +130,34 @@ func Run(ctx context.Context, wg *sync.WaitGroup, globalCollectionOpts state.Col return } + if globalCollectionOpts.GenerateExplainAnalyzeHelperSql != "" { + wg.Add(1) + testRunSuccess = make(chan bool) + go func() { + var matchingServer *state.Server + for _, server := range servers { + if globalCollectionOpts.GenerateExplainAnalyzeHelperSql == server.Config.SectionName { + matchingServer = server + } + } + if matchingServer == nil { + fmt.Fprintf(os.Stderr, "ERROR - Specified configuration section name '%s' not known\n", globalCollectionOpts.GenerateExplainAnalyzeHelperSql) + testRunSuccess <- false + } else { + output, err := GenerateExplainAnalyzeHelperSql(ctx, matchingServer, globalCollectionOpts, logger.WithPrefix(matchingServer.Config.SectionName)) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR - %s\n", err) + testRunSuccess <- false + } else { + fmt.Print(output) + testRunSuccess <- true + } + } + wg.Done() + }() + return + } + state.ReadStateFile(servers, globalCollectionOpts, logger) writeStateFile = func() { diff --git a/state/state.go b/state/state.go index 75e8dc6a2..ef76c364d 100644 --- a/state/state.go +++ b/state/state.go @@ -213,14 +213,16 @@ type CollectionOpts struct { DiffStatements bool - SubmitCollectedData bool - TestRun bool - TestRunLogs bool - TestExplain bool - TestSection string - GenerateStatsHelperSql string - DebugLogs bool - DiscoverLogLocation bool + SubmitCollectedData bool + TestRun bool + TestRunLogs bool + TestExplain bool + TestSection string + GenerateStatsHelperSql string + GenerateExplainAnalyzeHelperSql string + GenerateExplainAnalyzeHelperRole string + DebugLogs bool + DiscoverLogLocation bool StateFilename string WriteStateUpdate bool