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  }