Files
ttstd-infinite-canvas/backend/internal/repository/system_performance.go

108 lines
4.3 KiB
Go

package repository
import (
"context"
"database/sql"
"strconv"
"time"
"infinite-canvas/backend/internal/database"
)
type DatabasePoolStats struct {
MaxOpenConnections int `json:"maxOpenConnections"`
OpenConnections int `json:"openConnections"`
InUse int `json:"inUse"`
Idle int `json:"idle"`
WaitCount int64 `json:"waitCount"`
WaitDuration time.Duration `json:"-"`
WaitDurationMs int64 `json:"waitDurationMs"`
MaxIdleClosed int64 `json:"maxIdleClosed"`
MaxLifetimeClosed int64 `json:"maxLifetimeClosed"`
}
type PostgresRuntimeStats struct {
ServerVersion string `json:"serverVersion,omitempty"`
DatabaseBytes int64 `json:"databaseBytes,omitempty"`
Connections int64 `json:"connections,omitempty"`
MaxConnections int64 `json:"maxConnections,omitempty"`
Transactions int64 `json:"transactions,omitempty"`
Rollbacks int64 `json:"rollbacks,omitempty"`
CacheHitRate float64 `json:"cacheHitRate,omitempty"`
TempFiles int64 `json:"tempFiles,omitempty"`
TempBytes int64 `json:"tempBytes,omitempty"`
Deadlocks int64 `json:"deadlocks,omitempty"`
}
type DatabaseRuntimeStats struct {
Driver string `json:"driver"`
Connected bool `json:"connected"`
LatencyMs int64 `json:"latencyMs"`
DatabaseBytes int64 `json:"databaseBytes,omitempty"`
Schema database.SchemaStatus `json:"schema"`
Pool DatabasePoolStats `json:"pool"`
Postgres *PostgresRuntimeStats `json:"postgres,omitempty"`
}
func (r *Repository) DatabaseRuntimeStats(ctx context.Context) (DatabaseRuntimeStats, error) {
result := DatabaseRuntimeStats{Driver: r.Dialect(), Schema: database.SchemaStatus{Expected: database.CurrentSchemaVersion}}
sqlDB, err := r.db.DB()
if err != nil {
return result, err
}
started := time.Now()
if err := sqlDB.PingContext(ctx); err != nil {
return result, err
}
result.Connected = true
result.LatencyMs = max(1, time.Since(started).Milliseconds())
result.Pool = databasePoolStats(sqlDB.Stats())
if schema, schemaErr := database.ReadSchemaStatus(r.db.WithContext(ctx)); schemaErr == nil {
result.Schema = schema
}
if result.Driver == "postgres" {
result.Postgres, err = r.postgresRuntimeStats(ctx)
if result.Postgres != nil {
result.DatabaseBytes = result.Postgres.DatabaseBytes
}
return result, err
}
if result.Driver == "sqlite" {
var pageCount, pageSize int64
if r.db.WithContext(ctx).Raw("PRAGMA page_count").Scan(&pageCount).Error == nil && r.db.WithContext(ctx).Raw("PRAGMA page_size").Scan(&pageSize).Error == nil {
result.DatabaseBytes = pageCount * pageSize
}
}
return result, nil
}
func databasePoolStats(stats sql.DBStats) DatabasePoolStats {
return DatabasePoolStats{
MaxOpenConnections: stats.MaxOpenConnections, OpenConnections: stats.OpenConnections, InUse: stats.InUse, Idle: stats.Idle,
WaitCount: stats.WaitCount, WaitDuration: stats.WaitDuration, WaitDurationMs: stats.WaitDuration.Milliseconds(),
MaxIdleClosed: stats.MaxIdleClosed, MaxLifetimeClosed: stats.MaxLifetimeClosed,
}
}
func (r *Repository) postgresRuntimeStats(ctx context.Context) (*PostgresRuntimeStats, error) {
stats := &PostgresRuntimeStats{}
if err := r.db.WithContext(ctx).Raw("SHOW server_version").Scan(&stats.ServerVersion).Error; err != nil {
return stats, err
}
var maxConnections string
if err := r.db.WithContext(ctx).Raw("SHOW max_connections").Scan(&maxConnections).Error; err == nil {
stats.MaxConnections, _ = strconv.ParseInt(maxConnections, 10, 64)
}
var blocksRead, blocksHit int64
err := r.db.WithContext(ctx).Raw(`
SELECT pg_database_size(current_database()) AS database_bytes,
numbackends AS connections, xact_commit AS transactions, xact_rollback AS rollbacks,
blks_read, blks_hit, temp_files, temp_bytes, deadlocks
FROM pg_stat_database WHERE datname = current_database()
`).Row().Scan(&stats.DatabaseBytes, &stats.Connections, &stats.Transactions, &stats.Rollbacks, &blocksRead, &blocksHit, &stats.TempFiles, &stats.TempBytes, &stats.Deadlocks)
if blocksRead+blocksHit > 0 {
stats.CacheHitRate = float64(blocksHit) / float64(blocksRead+blocksHit) * 100
}
return stats, err
}