diff --git a/agents/postgres/pgstatmonitor/models.go b/agents/postgres/pgstatmonitor/models.go index 82b589d2e..694aeb0ac 100644 --- a/agents/postgres/pgstatmonitor/models.go +++ b/agents/postgres/pgstatmonitor/models.go @@ -49,6 +49,15 @@ type pgStatMonitorSettingsTextValue struct { Value string `reform:"value"` } +// pgStatMonitorErrors represents a row in pg_stat_monitor_errors view. +//reform:pg_stat_monitor_errors +type pgStatMonitorErrors struct { + Severity string `reform:"severity"` + Message string `reform:"message"` + MessageTime string `reform:"msgtime"` + Calls int64 `reform:"calls"` +} + // pgStatMonitorExtended contains pgStatMonitor data and extends it with database, username and tables data. // It's made for performance reason. type pgStatMonitorExtended struct { diff --git a/agents/postgres/pgstatmonitor/models_reform.go b/agents/postgres/pgstatmonitor/models_reform.go index 38344853a..310c1fd99 100644 --- a/agents/postgres/pgstatmonitor/models_reform.go +++ b/agents/postgres/pgstatmonitor/models_reform.go @@ -332,9 +332,100 @@ var ( _ fmt.Stringer = (*pgStatMonitorSettingsTextValue)(nil) ) +type pgStatMonitorErrorsViewType struct { + s parse.StructInfo + z []interface{} +} + +// Schema returns a schema name in SQL database (""). +func (v *pgStatMonitorErrorsViewType) Schema() string { + return v.s.SQLSchema +} + +// Name returns a view or table name in SQL database ("pg_stat_monitor_errors"). +func (v *pgStatMonitorErrorsViewType) Name() string { + return v.s.SQLName +} + +// Columns returns a new slice of column names for that view or table in SQL database. +func (v *pgStatMonitorErrorsViewType) Columns() []string { + return []string{ + "severity", + "message", + "msgtime", + "calls", + } +} + +// NewStruct makes a new struct for that view or table. +func (v *pgStatMonitorErrorsViewType) NewStruct() reform.Struct { + return new(pgStatMonitorErrors) +} + +// pgStatMonitorErrorsView represents pg_stat_monitor_errors view or table in SQL database. +var pgStatMonitorErrorsView = &pgStatMonitorErrorsViewType{ + s: parse.StructInfo{ + Type: "pgStatMonitorErrors", + SQLName: "pg_stat_monitor_errors", + Fields: []parse.FieldInfo{ + {Name: "Severity", Type: "string", Column: "severity"}, + {Name: "Message", Type: "string", Column: "message"}, + {Name: "MessageTime", Type: "string", Column: "msgtime"}, + {Name: "Calls", Type: "int64", Column: "calls"}, + }, + PKFieldIndex: -1, + }, + z: new(pgStatMonitorErrors).Values(), +} + +// String returns a string representation of this struct or record. +func (s pgStatMonitorErrors) String() string { + res := make([]string, 4) + res[0] = "Severity: " + reform.Inspect(s.Severity, true) + res[1] = "Message: " + reform.Inspect(s.Message, true) + res[2] = "MessageTime: " + reform.Inspect(s.MessageTime, true) + res[3] = "Calls: " + reform.Inspect(s.Calls, true) + return strings.Join(res, ", ") +} + +// Values returns a slice of struct or record field values. +// Returned interface{} values are never untyped nils. +func (s *pgStatMonitorErrors) Values() []interface{} { + return []interface{}{ + s.Severity, + s.Message, + s.MessageTime, + s.Calls, + } +} + +// Pointers returns a slice of pointers to struct or record fields. +// Returned interface{} values are never untyped nils. +func (s *pgStatMonitorErrors) Pointers() []interface{} { + return []interface{}{ + &s.Severity, + &s.Message, + &s.MessageTime, + &s.Calls, + } +} + +// View returns View object for that struct. +func (s *pgStatMonitorErrors) View() reform.View { + return pgStatMonitorErrorsView +} + +// check interfaces +var ( + _ reform.View = pgStatMonitorErrorsView + _ reform.Struct = (*pgStatMonitorErrors)(nil) + _ fmt.Stringer = (*pgStatMonitorErrors)(nil) +) + func init() { parse.AssertUpToDate(&pgStatDatabaseView.s, new(pgStatDatabase)) parse.AssertUpToDate(&pgUserView.s, new(pgUser)) parse.AssertUpToDate(&pgStatMonitorSettingsView.s, new(pgStatMonitorSettings)) parse.AssertUpToDate(&pgStatMonitorSettingsTextValueView.s, new(pgStatMonitorSettingsTextValue)) + parse.AssertUpToDate(&pgStatMonitorErrorsView.s, new(pgStatMonitorErrors)) } diff --git a/agents/postgres/pgstatmonitor/pgstatmonitor.go b/agents/postgres/pgstatmonitor/pgstatmonitor.go index ae6c01e34..e4d2bb1a7 100644 --- a/agents/postgres/pgstatmonitor/pgstatmonitor.go +++ b/agents/postgres/pgstatmonitor/pgstatmonitor.go @@ -92,7 +92,7 @@ const ( commandTypeUpdate = "UPDATE" commandTypeInsert = "INSERT" commandTypeDelete = "DELETE" - commandTypeUtiity = "UTILITY" + commandTypeUtility = "UTILITY" ) var commandTypeToText = []string{ @@ -101,7 +101,7 @@ var commandTypeToText = []string{ commandTypeUpdate, commandTypeInsert, commandTypeDelete, - commandTypeUtiity, + commandTypeUtility, commandTextNotAvailable, } @@ -337,6 +337,11 @@ func (m *PGStatMonitorQAN) getNewBuckets(ctx context.Context, periodLengthSecs u return nil, err } + err = m.checkErrorsView(ctx) + if err != nil { + return nil, err + } + buckets := m.makeBuckets(current, prev) m.l.Debugf("Made %d buckets out of %d stat monitor in %d interval.", len(buckets), len(current), periodLengthSecs) @@ -356,6 +361,45 @@ func (m *PGStatMonitorQAN) getNewBuckets(ctx context.Context, periodLengthSecs u return buckets, nil } +func (m *PGStatMonitorQAN) checkErrorsView(ctx context.Context) error { + row := &pgStatMonitorErrors{} + rows, err := m.q.SelectRows(pgStatMonitorErrorsView, "") + if err != nil { + return errors.Wrap(err, "failed to query pg_stat_monitor_errors view") + } + + now := time.Now() + for ctx.Err() == nil { + if err = m.q.NextRow(row, rows); err != nil { + if errors.Is(err, reform.ErrNoRows) { + break + } + + return errors.Wrap(err, "cannot read row from errors view") + } + + messageTime, err := time.Parse("2006-01-02 15:04:05", row.MessageTime) + if err != nil { + return errors.Wrap(err, "cannot parse messageTime") + } + if now.After(messageTime) { + continue + } + + template := "Message: %s, Calls: %d" + switch row.Severity { + case "INFO": + m.l.Infof(template, row.Message, row.Calls) + case "WARNING": + m.l.Warningf(template, row.Message, row.Calls) + case "ERROR": + m.l.Errorf(template, row.Message, row.Calls) + } + } + + return nil +} + // makeBuckets uses current state of pg_stat_monitor table and accumulated previous state // to make metrics buckets. func (m *PGStatMonitorQAN) makeBuckets(current, cache map[time.Time]map[string]*pgStatMonitorExtended) []*agentpb.MetricsBucket {