gitlab.com/postgres-ai/database-lab/v3@v3.0.3/pkg/util/pglog/activity.go (about) 1 /* 2 2019 © Postgres.ai 3 */ 4 5 // Package pglog provides helpers for a Postgres logs processing. 6 package pglog 7 8 import ( 9 "errors" 10 "os" 11 "path" 12 "sort" 13 "strings" 14 "time" 15 16 errs "github.com/pkg/errors" 17 ) 18 19 const ( 20 csvLogDir = "log" 21 22 csvLogFilenameFormat = "postgresql-2006-01-02_150405.csv" 23 ) 24 25 var ( 26 // ErrNotFound defines an error if last session activity not found. 27 ErrNotFound = errors.New("pglog activity: not found") 28 29 // ErrLastFile defines an error if no more recent log files to discover last activity. 30 ErrLastFile = errors.New("no more recent log files") 31 32 // ErrTimeBoundary defines an error if the upper boundary of the interval exceeded. 33 ErrTimeBoundary = errors.New("time boundary exceeded") 34 ) 35 36 // Selector describes a struct to select CSV log files. 37 type Selector struct { 38 logDir string 39 currentIndex int 40 fileNames []string 41 minimumTime time.Time 42 } 43 44 // NewSelector creates a new Selector. 45 func NewSelector(dir string) *Selector { 46 return &Selector{ 47 logDir: buildLogDirName(dir), 48 fileNames: make([]string, 0), 49 } 50 } 51 52 // SetMinimumTime sets a minimum allowable time. 53 func (s *Selector) SetMinimumTime(minimumTime time.Time) { 54 s.minimumTime = minimumTime 55 } 56 57 // DiscoverLogDir discovers available CSV log files. 58 func (s *Selector) DiscoverLogDir() error { 59 logFilenames := []string{} 60 61 logDirFiles, err := os.ReadDir(s.logDir) 62 if err != nil { 63 return errs.Wrap(err, "failed to read a log directory") 64 } 65 66 for _, fileInfo := range logDirFiles { 67 if fileInfo.IsDir() { 68 continue 69 } 70 71 if !strings.HasSuffix(fileInfo.Name(), "csv") { 72 continue 73 } 74 75 logFilenames = append(logFilenames, fileInfo.Name()) 76 } 77 78 if len(logFilenames) == 0 { 79 return errors.New("log files not found") 80 } 81 82 sort.Strings(logFilenames) 83 s.fileNames = logFilenames 84 85 return nil 86 } 87 88 // Next returns the next CSV log filename to discover. 89 func (s *Selector) Next() (string, error) { 90 if len(s.fileNames) == 0 { 91 return "", errors.New("log fileNames not found") 92 } 93 94 if s.currentIndex >= len(s.fileNames) { 95 return "", ErrLastFile 96 } 97 98 logPath := path.Join(s.logDir, s.fileNames[s.currentIndex]) 99 s.currentIndex++ 100 101 return logPath, nil 102 } 103 104 // FilterOldFilesInList filters the original filename list. 105 func (s *Selector) FilterOldFilesInList() { 106 if s.minimumTime.IsZero() { 107 return 108 } 109 110 startIndex := 0 111 minimumTime := s.minimumTime.Format(csvLogFilenameFormat) 112 113 for i := range s.fileNames { 114 if len(s.fileNames) > i+1 { 115 if minimumTime < s.fileNames[i+1] { 116 break 117 } 118 119 startIndex = i 120 } 121 } 122 123 s.fileNames = s.fileNames[startIndex:] 124 } 125 126 // ParsePostgresLastActivity extracts the time of last session activity. 127 func ParsePostgresLastActivity(logTime, text string) (*time.Time, error) { 128 if logTime == "" || !(strings.Contains(text, "statement:") || strings.Contains(text, "duration:")) { 129 return nil, nil 130 } 131 132 lastActivityTime, err := time.Parse("2006-01-02 15:04:05.000 UTC", logTime) 133 if err != nil { 134 return nil, errs.Wrap(err, "failed to parse the last activity time") 135 } 136 137 return &lastActivityTime, nil 138 } 139 140 func buildLogDirName(cloneDir string) string { 141 return path.Join(cloneDir, csvLogDir) 142 }