| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -512,7 +512,16 @@ func getCentralDBData(ctx context.Context, zipWriter *zipWriter) error { | |||
| 512 | 512 | if activities.Error != "" { | |
| 513 | 513 | log.Errorw("error retrieving pg_stat_activity", logging.Err(errors.New(activities.Error))) | |
| 514 | 514 | } | |
| 515 | - return addJSONToZip(zipWriter, "central-db-pg-activity.json", activities) | ||
| 515 | + if err := addJSONToZip(zipWriter, "central-db-pg-activity.json", activities); err != nil { | ||
| 516 | + return err | ||
| 517 | + } | ||
| 518 | + | ||
| 519 | + // Get the index health stats | ||
| 520 | + indexStats := stats.GetPGIndexStats(ctx, db, pgStatStatementsMax) | ||
| 521 | + if indexStats.Error != "" { | ||
| 522 | + log.Errorw("error retrieving pg_stat_user_indexes", logging.Err(errors.New(indexStats.Error))) | ||
| 523 | + } | ||
| 524 | + return addJSONToZip(zipWriter, "central-db-pg-index-stats.json", indexStats) | ||
| 516 | 525 | } | |
| 517 | 526 | ||
| 518 | 527 | func (s *serviceImpl) getLogImbue(ctx context.Context, zipWriter *zipWriter) error { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -204,3 +204,55 @@ func GetPGStatActivities(ctx context.Context, db postgres.DB, limit int) *PGStat | |||
| 204 | 204 | } | |
| 205 | 205 | return &activities | |
| 206 | 206 | } | |
| 207 | + | ||
| 208 | + // PGIndexStat is the data model for a single index from pg_stat_user_indexes joined with pg_index | ||
| 209 | + type PGIndexStat struct { | ||
| 210 | + TableName string | ||
| 211 | + IndexName string | ||
| 212 | + IndexType string | ||
| 213 | + IndexSizeBytes int64 | ||
| 214 | + IsValid bool | ||
| 215 | + IsReady bool | ||
| 216 | + IndexScans int64 | ||
| 217 | + } | ||
| 218 | + | ||
| 219 | + // PGIndexStats is a wrapper around PGIndexStat | ||
| 220 | + type PGIndexStats struct { | ||
| 221 | + Indexes []*PGIndexStat | ||
| 222 | + Error string | ||
| 223 | + } | ||
| 224 | + | ||
| 225 | + // GetPGIndexStats returns index health information from pg_stat_user_indexes joined with pg_index. | ||
| 226 | + // pg_stat_user_indexes is used to scope results to the current user's indexes only, | ||
| 227 | + // avoiding exposure of indexes from management or infrastructure schemas on external databases. | ||
| 228 | + func GetPGIndexStats(ctx context.Context, db postgres.DB, limit int) *PGIndexStats { | ||
| 229 | + var indexStats PGIndexStats | ||
| 230 | + rows, err := db.Query(ctx, | ||
| 231 | + `SELECT s.relname, s.indexrelname, am.amname, | ||
| 232 | + pg_relation_size(ix.indexrelid), ix.indisvalid, ix.indisready, | ||
| 233 | + s.idx_scan | ||
| 234 | + FROM pg_stat_user_indexes s | ||
| 235 | + JOIN pg_index ix ON ix.indexrelid = s.indexrelid | ||
| 236 | + JOIN pg_class i ON i.oid = ix.indexrelid | ||
| 237 | + JOIN pg_am am ON am.oid = i.relam | ||
| 238 | + ORDER BY pg_relation_size(ix.indexrelid) DESC | ||
| 239 | + LIMIT $1`, limit) | ||
| 240 | + if err != nil { | ||
| 241 | + indexStats.Error = err.Error() | ||
| 242 | + return &indexStats | ||
| 243 | + } | ||
| 244 | + defer rows.Close() | ||
| 245 | + | ||
| 246 | + for rows.Next() { | ||
| 247 | + var idx PGIndexStat | ||
| 248 | + if err := rows.Scan(&idx.TableName, &idx.IndexName, &idx.IndexType, &idx.IndexSizeBytes, &idx.IsValid, &idx.IsReady, &idx.IndexScans); err != nil { | ||
| 249 | + indexStats.Error = errors.Wrap(err, "error scanning rows from pg_stat_user_indexes").Error() | ||
| 250 | + return &indexStats | ||
| 251 | + } | ||
| 252 | + indexStats.Indexes = append(indexStats.Indexes, &idx) | ||
| 253 | + } | ||
| 254 | + if err := rows.Err(); err != nil { | ||
| 255 | + indexStats.Error = err.Error() | ||
| 256 | + } | ||
| 257 | + return &indexStats | ||
| 258 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,29 @@ | |||
| 1 | + //go:build sql_integration | ||
| 2 | + | ||
| 3 | + package stats | ||
| 4 | + | ||
| 5 | + import ( | ||
| 6 | + "context" | ||
| 7 | + "testing" | ||
| 8 | + | ||
| 9 | + "github.com/stackrox/rox/pkg/postgres/pgtest" | ||
| 10 | + "github.com/stackrox/rox/pkg/sac" | ||
| 11 | + "github.com/stretchr/testify/assert" | ||
| 12 | + "github.com/stretchr/testify/require" | ||
| 13 | + ) | ||
| 14 | + | ||
| 15 | + func TestGetPGIndexStats(t *testing.T) { | ||
| 16 | + ctx := sac.WithAllAccess(context.Background()) | ||
| 17 | + tp := pgtest.ForT(t) | ||
| 18 | + defer tp.Close() | ||
| 19 | + | ||
| 20 | + result := GetPGIndexStats(ctx, tp.DB, 100) | ||
| 21 | + require.Empty(t, result.Error) | ||
| 22 | + assert.NotNil(t, result.Indexes) | ||
| 23 | + for _, idx := range result.Indexes { | ||
| 24 | + assert.NotEmpty(t, idx.TableName) | ||
| 25 | + assert.NotEmpty(t, idx.IndexName) | ||
| 26 | + assert.NotEmpty(t, idx.IndexType) | ||
| 27 | + assert.True(t, idx.IsValid, "index %s on %s should be valid", idx.IndexName, idx.TableName) | ||
| 28 | + } | ||
| 29 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments