174 lines
4.1 KiB
Go
174 lines
4.1 KiB
Go
package monitor
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/go-mysql-org/go-mysql/mysql"
|
|
)
|
|
|
|
type Metrics struct {
|
|
mu sync.RWMutex
|
|
|
|
startedAt time.Time
|
|
|
|
masterPosition mysql.Position
|
|
checkpointPosition mysql.Position
|
|
lastSyncedPosition mysql.Position
|
|
startPosition mysql.Position
|
|
|
|
lastFlushAt time.Time
|
|
lastFlushEvents int
|
|
totalFlushes uint64
|
|
totalEvents uint64
|
|
totalRetries uint64
|
|
totalLockWaits uint64
|
|
totalCleanupRows uint64
|
|
lastCleanupAt time.Time
|
|
lastCleanupRows int64
|
|
lastError string
|
|
lastErrorAt time.Time
|
|
lastBatchDuration time.Duration
|
|
}
|
|
|
|
func New() *Metrics {
|
|
return &Metrics{startedAt: time.Now()}
|
|
}
|
|
|
|
func (m *Metrics) SetStartPosition(pos mysql.Position) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.startPosition = pos
|
|
}
|
|
|
|
func (m *Metrics) SetMasterPosition(pos mysql.Position) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.masterPosition = pos
|
|
}
|
|
|
|
func (m *Metrics) SetCheckpointPosition(pos mysql.Position) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.checkpointPosition = pos
|
|
}
|
|
|
|
func (m *Metrics) SetLastSyncedPosition(pos mysql.Position) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.lastSyncedPosition = pos
|
|
}
|
|
|
|
func (m *Metrics) RecordBatch(events int, duration time.Duration) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.lastFlushAt = time.Now()
|
|
m.lastFlushEvents = events
|
|
m.totalFlushes++
|
|
m.totalEvents += uint64(events)
|
|
m.lastBatchDuration = duration
|
|
}
|
|
|
|
func (m *Metrics) RecordRetry() {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.totalRetries++
|
|
}
|
|
|
|
func (m *Metrics) RecordLockWait() {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.totalLockWaits++
|
|
}
|
|
|
|
func (m *Metrics) RecordCleanup(rows int64) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.lastCleanupAt = time.Now()
|
|
m.lastCleanupRows = rows
|
|
if rows > 0 {
|
|
m.totalCleanupRows += uint64(rows)
|
|
}
|
|
}
|
|
|
|
func (m *Metrics) RecordError(err error) {
|
|
if err == nil {
|
|
return
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.lastError = err.Error()
|
|
m.lastErrorAt = time.Now()
|
|
}
|
|
|
|
func (m *Metrics) Snapshot() map[string]interface{} {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
return map[string]interface{}{
|
|
"started_at": formatTime(m.startedAt),
|
|
"start_position": positionMap(m.startPosition),
|
|
"master_position": positionMap(m.masterPosition),
|
|
"checkpoint_position": positionMap(m.checkpointPosition),
|
|
"last_synced_position": positionMap(m.lastSyncedPosition),
|
|
"binlog_file_gap": fileGap(m.masterPosition, m.checkpointPosition),
|
|
"binlog_position_gap": positionGap(m.masterPosition, m.checkpointPosition),
|
|
"last_flush_at": formatTime(m.lastFlushAt),
|
|
"last_flush_events": m.lastFlushEvents,
|
|
"last_batch_duration_ms": m.lastBatchDuration.Milliseconds(),
|
|
"total_flushes": m.totalFlushes,
|
|
"total_events": m.totalEvents,
|
|
"total_retries": m.totalRetries,
|
|
"total_lock_waits": m.totalLockWaits,
|
|
"last_cleanup_at": formatTime(m.lastCleanupAt),
|
|
"last_cleanup_rows": m.lastCleanupRows,
|
|
"total_cleanup_rows": m.totalCleanupRows,
|
|
"last_error": m.lastError,
|
|
"last_error_at": formatTime(m.lastErrorAt),
|
|
}
|
|
}
|
|
|
|
func positionMap(pos mysql.Position) map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"file": pos.Name,
|
|
"pos": pos.Pos,
|
|
}
|
|
}
|
|
|
|
func positionGap(master mysql.Position, checkpoint mysql.Position) interface{} {
|
|
if master.Name == "" || checkpoint.Name == "" || master.Name != checkpoint.Name || master.Pos < checkpoint.Pos {
|
|
return nil
|
|
}
|
|
return master.Pos - checkpoint.Pos
|
|
}
|
|
|
|
func fileGap(master mysql.Position, checkpoint mysql.Position) interface{} {
|
|
masterSeq, ok := binlogSequence(master.Name)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
checkpointSeq, ok := binlogSequence(checkpoint.Name)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return masterSeq - checkpointSeq
|
|
}
|
|
|
|
func binlogSequence(file string) (int64, bool) {
|
|
index := strings.LastIndex(file, ".")
|
|
if index < 0 || index == len(file)-1 {
|
|
return 0, false
|
|
}
|
|
value, err := strconv.ParseInt(file[index+1:], 10, 64)
|
|
return value, err == nil
|
|
}
|
|
|
|
func formatTime(value time.Time) string {
|
|
if value.IsZero() {
|
|
return ""
|
|
}
|
|
return value.Format(time.RFC3339)
|
|
}
|