2020-02-20 21:12:51 +03:00
|
|
|
package querylog
|
|
|
|
|
|
|
|
import (
|
2020-11-18 15:43:28 +03:00
|
|
|
"fmt"
|
2020-02-20 21:12:51 +03:00
|
|
|
"io"
|
2021-03-15 14:19:04 +03:00
|
|
|
"os"
|
2020-02-20 21:12:51 +03:00
|
|
|
|
2021-05-24 17:28:11 +03:00
|
|
|
"github.com/AdguardTeam/golibs/errors"
|
2021-03-15 14:19:04 +03:00
|
|
|
"github.com/AdguardTeam/golibs/log"
|
2020-02-20 21:12:51 +03:00
|
|
|
)
|
|
|
|
|
2023-05-24 16:33:15 +03:00
|
|
|
// qLogReader allows reading from multiple query log files in the reverse
|
|
|
|
// order.
|
2020-02-20 21:12:51 +03:00
|
|
|
//
|
2023-05-24 16:33:15 +03:00
|
|
|
// Please note that this is a stateful object. Internally, it contains a
|
|
|
|
// pointer to a particular query log file, and to a specific position in this
|
|
|
|
// file, and it reads lines in reverse order starting from that position.
|
|
|
|
type qLogReader struct {
|
|
|
|
// qFiles is an array with the query log files. The order is from oldest
|
|
|
|
// to newest.
|
|
|
|
qFiles []*qLogFile
|
|
|
|
|
|
|
|
// currentFile is the index of the current file.
|
|
|
|
currentFile int
|
2020-02-20 21:12:51 +03:00
|
|
|
}
|
|
|
|
|
2023-05-24 16:33:15 +03:00
|
|
|
// newQLogReader initializes a qLogReader instance with the specified files.
|
|
|
|
func newQLogReader(files []string) (*qLogReader, error) {
|
|
|
|
qFiles := make([]*qLogFile, 0)
|
2020-02-20 21:12:51 +03:00
|
|
|
|
|
|
|
for _, f := range files {
|
2023-05-24 16:33:15 +03:00
|
|
|
q, err := newQLogFile(f)
|
2020-02-20 21:12:51 +03:00
|
|
|
if err != nil {
|
2021-03-15 14:19:04 +03:00
|
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close what we've already opened.
|
2023-05-24 16:33:15 +03:00
|
|
|
cErr := closeQFiles(qFiles)
|
|
|
|
if cErr != nil {
|
|
|
|
log.Debug("querylog: closing files: %s", cErr)
|
2021-03-15 14:19:04 +03:00
|
|
|
}
|
|
|
|
|
2020-02-20 21:12:51 +03:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
qFiles = append(qFiles, q)
|
|
|
|
}
|
|
|
|
|
2023-05-24 16:33:15 +03:00
|
|
|
return &qLogReader{qFiles: qFiles, currentFile: len(qFiles) - 1}, nil
|
2020-02-20 21:12:51 +03:00
|
|
|
}
|
|
|
|
|
2021-12-13 13:55:41 +03:00
|
|
|
// seekTS performs binary search of a query log record with the specified
|
2023-05-24 16:33:15 +03:00
|
|
|
// timestamp. If the record is found, it sets qLogReader's position to point
|
|
|
|
// to that line, so that the next ReadNext call returned this line.
|
|
|
|
func (r *qLogReader) seekTS(timestamp int64) (err error) {
|
2020-11-19 12:53:31 +03:00
|
|
|
for i := len(r.qFiles) - 1; i >= 0; i-- {
|
|
|
|
q := r.qFiles[i]
|
2021-12-13 13:55:41 +03:00
|
|
|
_, _, err = q.seekTS(timestamp)
|
|
|
|
if err != nil {
|
2023-05-24 16:33:15 +03:00
|
|
|
if errors.Is(err, errTSTooEarly) {
|
2021-12-13 13:55:41 +03:00
|
|
|
// Look at the next file, since we've reached the end of this
|
|
|
|
// one. If there is no next file, it's not found.
|
2023-05-24 16:33:15 +03:00
|
|
|
err = errTSNotFound
|
2021-12-13 13:55:41 +03:00
|
|
|
|
|
|
|
continue
|
2023-05-24 16:33:15 +03:00
|
|
|
} else if errors.Is(err, errTSTooLate) {
|
2021-12-13 13:55:41 +03:00
|
|
|
// Just seek to the start then. timestamp is probably between
|
|
|
|
// the end of the previous one and the start of this one.
|
|
|
|
return r.SeekStart()
|
2023-05-24 16:33:15 +03:00
|
|
|
} else if errors.Is(err, errTSNotFound) {
|
2021-12-13 13:55:41 +03:00
|
|
|
return err
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("seekts: file at index %d: %w", i, err)
|
|
|
|
}
|
2020-11-18 15:43:28 +03:00
|
|
|
}
|
2021-12-13 13:55:41 +03:00
|
|
|
|
|
|
|
// The search is finished, and the searched element has been found.
|
|
|
|
// Update currentFile only, position is already set properly in
|
2023-05-24 16:33:15 +03:00
|
|
|
// qLogFile.
|
2021-12-13 13:55:41 +03:00
|
|
|
r.currentFile = i
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("seekts: %w", err)
|
2020-02-20 21:12:51 +03:00
|
|
|
}
|
|
|
|
|
2021-12-13 13:55:41 +03:00
|
|
|
return nil
|
2020-02-20 21:12:51 +03:00
|
|
|
}
|
|
|
|
|
2023-05-24 16:33:15 +03:00
|
|
|
// SeekStart changes the current position to the end of the newest file.
|
|
|
|
// Please note that we're reading query log in the reverse order and that's why
|
|
|
|
// the log starts actually at the end of file.
|
2020-02-20 21:12:51 +03:00
|
|
|
//
|
2023-05-24 16:33:15 +03:00
|
|
|
// Returns nil if we were able to change the current position. Returns error
|
|
|
|
// in any other cases.
|
|
|
|
func (r *qLogReader) SeekStart() error {
|
2020-02-20 21:12:51 +03:00
|
|
|
if len(r.qFiles) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
r.currentFile = len(r.qFiles) - 1
|
|
|
|
_, err := r.qFiles[r.currentFile].SeekStart()
|
2023-03-31 17:39:04 +03:00
|
|
|
|
2020-02-20 21:12:51 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-05-24 16:33:15 +03:00
|
|
|
// ReadNext reads the next line (in the reverse order) from the query log
|
|
|
|
// files. Then shifts the current position left to the next (actually prev)
|
|
|
|
// line (or the next file).
|
|
|
|
//
|
|
|
|
// Returns io.EOF if there is nothing more to read.
|
|
|
|
func (r *qLogReader) ReadNext() (string, error) {
|
2020-02-20 21:12:51 +03:00
|
|
|
if len(r.qFiles) == 0 {
|
|
|
|
return "", io.EOF
|
|
|
|
}
|
|
|
|
|
|
|
|
for r.currentFile >= 0 {
|
|
|
|
q := r.qFiles[r.currentFile]
|
|
|
|
line, err := q.ReadNext()
|
|
|
|
if err != nil {
|
2023-05-24 16:33:15 +03:00
|
|
|
// Shift to the older file.
|
2020-02-20 21:12:51 +03:00
|
|
|
r.currentFile--
|
|
|
|
if r.currentFile < 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
q = r.qFiles[r.currentFile]
|
|
|
|
|
2023-05-24 16:33:15 +03:00
|
|
|
// Set its position to the start right away.
|
2020-02-20 21:12:51 +03:00
|
|
|
_, err = q.SeekStart()
|
2023-05-24 16:33:15 +03:00
|
|
|
// This is unexpected, return an error right away.
|
2020-02-20 21:12:51 +03:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return line, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-24 16:33:15 +03:00
|
|
|
// Nothing to read anymore.
|
2020-02-20 21:12:51 +03:00
|
|
|
return "", io.EOF
|
|
|
|
}
|
|
|
|
|
2023-05-24 16:33:15 +03:00
|
|
|
// Close closes the qLogReader.
|
|
|
|
func (r *qLogReader) Close() error {
|
2020-02-20 21:12:51 +03:00
|
|
|
return closeQFiles(r.qFiles)
|
|
|
|
}
|
|
|
|
|
2023-05-24 16:33:15 +03:00
|
|
|
// closeQFiles is a helper method to close multiple qLogFile instances.
|
2023-08-15 15:09:08 +03:00
|
|
|
func closeQFiles(qFiles []*qLogFile) (err error) {
|
2020-02-20 21:12:51 +03:00
|
|
|
var errs []error
|
|
|
|
|
|
|
|
for _, q := range qFiles {
|
2023-08-15 15:09:08 +03:00
|
|
|
err = q.Close()
|
2020-02-20 21:12:51 +03:00
|
|
|
if err != nil {
|
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-15 15:09:08 +03:00
|
|
|
return errors.Annotate(errors.Join(errs...), "closing qLogReader: %w")
|
2020-02-20 21:12:51 +03:00
|
|
|
}
|