qlog_file.go (AdGuardHome-0.104.1) | : | qlog_file.go (AdGuardHome-0.104.3) | ||
---|---|---|---|---|
package querylog | package querylog | |||
import ( | import ( | |||
"fmt" | ||||
"io" | "io" | |||
"os" | "os" | |||
"sync" | "sync" | |||
"time" | "time" | |||
"github.com/AdguardTeam/AdGuardHome/internal/agherr" | "github.com/AdguardTeam/AdGuardHome/internal/agherr" | |||
"github.com/AdguardTeam/golibs/log" | "github.com/AdguardTeam/golibs/log" | |||
) | ) | |||
// ErrSeekNotFound is returned from Seek if when it fails to find the requested | // Timestamp not found errors. | |||
// record. | const ( | |||
const ErrSeekNotFound agherr.Error = "seek: record not found" | ErrTSNotFound agherr.Error = "ts not found" | |||
ErrTSTooLate agherr.Error = "ts too late" | ||||
// ErrEndOfLog is returned from Seek when the end of the current log is reached. | ErrTSTooEarly agherr.Error = "ts too early" | |||
const ErrEndOfLog agherr.Error = "seek: end of log" | ) | |||
// TODO: Find a way to grow buffer instead of relying on this value when reading strings | // TODO: Find a way to grow buffer instead of relying on this value when reading strings | |||
const maxEntrySize = 16 * 1024 | const maxEntrySize = 16 * 1024 | |||
// buffer should be enough for at least this number of entries | // buffer should be enough for at least this number of entries | |||
const bufferSize = 100 * maxEntrySize | const bufferSize = 100 * maxEntrySize | |||
// QLogFile represents a single query log file | // QLogFile represents a single query log file | |||
// It allows reading from the file in the reverse order | // It allows reading from the file in the reverse order | |||
// | // | |||
skipping to change at line 71 | skipping to change at line 72 | |||
// 2. Shifts back to the beginning of the line | // 2. Shifts back to the beginning of the line | |||
// 3. Checks the log record timestamp | // 3. Checks the log record timestamp | |||
// 4. If it is lower than the timestamp we are looking for, | // 4. If it is lower than the timestamp we are looking for, | |||
// it shifts seek position to 3/4 of the file. Otherwise, to 1/4 of the file. | // it shifts seek position to 3/4 of the file. Otherwise, to 1/4 of the file. | |||
// 5. It performs the search again, every time the search scope is narrowed twic e. | // 5. It performs the search again, every time the search scope is narrowed twic e. | |||
// | // | |||
// Returns: | // Returns: | |||
// * It returns the position of the the line with the timestamp we were looking for | // * It returns the position of the the line with the timestamp we were looking for | |||
// so that when we call "ReadNext" this line was returned. | // so that when we call "ReadNext" this line was returned. | |||
// * Depth of the search (how many times we compared timestamps). | // * Depth of the search (how many times we compared timestamps). | |||
// * If we could not find it, it returns ErrSeekNotFound | // * If we could not find it, it returns one of the errors described above. | |||
func (q *QLogFile) Seek(timestamp int64) (int64, int, error) { | func (q *QLogFile) Seek(timestamp int64) (int64, int, error) { | |||
q.lock.Lock() | q.lock.Lock() | |||
defer q.lock.Unlock() | defer q.lock.Unlock() | |||
// Empty the buffer | // Empty the buffer | |||
q.buffer = nil | q.buffer = nil | |||
// First of all, check the file size | // First of all, check the file size | |||
fileInfo, err := q.file.Stat() | fileInfo, err := q.file.Stat() | |||
if err != nil { | if err != nil { | |||
skipping to change at line 106 | skipping to change at line 107 | |||
// If depth is too large, we should stop the search | // If depth is too large, we should stop the search | |||
depth := 0 | depth := 0 | |||
for { | for { | |||
// Get the line at the specified position | // Get the line at the specified position | |||
line, lineIdx, lineEndIdx, err = q.readProbeLine(probe) | line, lineIdx, lineEndIdx, err = q.readProbeLine(probe) | |||
if err != nil { | if err != nil { | |||
return 0, depth, err | return 0, depth, err | |||
} | } | |||
if lineIdx < start || lineEndIdx > end || lineIdx == lastProbeLin | if lineIdx == lastProbeLineIdx { | |||
eIdx { | if lineIdx == 0 { | |||
return 0, depth, ErrTSTooEarly | ||||
} | ||||
// If we're testing the same line twice then most likely | // If we're testing the same line twice then most likely | |||
// the scope is too narrow and we won't find anything any | // the scope is too narrow and we won't find anything | |||
more | // anymore in any other file. | |||
log.Error("querylog: didn't find timestamp:%v", timestamp | return 0, depth, fmt.Errorf("looking up timestamp %d in % | |||
) | q: %w", timestamp, q.file.Name(), ErrTSNotFound) | |||
return 0, depth, ErrSeekNotFound | } else if lineIdx == fileInfo.Size() { | |||
} else if lineIdx == end && lineEndIdx == end { | return 0, depth, ErrTSTooLate | |||
return 0, depth, ErrEndOfLog | ||||
} | } | |||
// Save the last found idx | // Save the last found idx | |||
lastProbeLineIdx = lineIdx | lastProbeLineIdx = lineIdx | |||
// Get the timestamp from the query log record | // Get the timestamp from the query log record | |||
ts := readQLogTimestamp(line) | ts := readQLogTimestamp(line) | |||
if ts == 0 { | if ts == 0 { | |||
return 0, depth, ErrSeekNotFound | return 0, depth, fmt.Errorf("looking up timestamp %d in % q: record %q has empty timestamp", timestamp, q.file.Name(), line) | |||
} | } | |||
if ts == timestamp { | if ts == timestamp { | |||
// Hurray, returning the result | // Hurray, returning the result | |||
break | break | |||
} | } | |||
// Narrow the scope and repeat the search | // Narrow the scope and repeat the search | |||
if ts > timestamp { | if ts > timestamp { | |||
// If the timestamp we're looking for is OLDER than what we found | // If the timestamp we're looking for is OLDER than what we found | |||
skipping to change at line 144 | skipping to change at line 148 | |||
end = lineIdx | end = lineIdx | |||
} else { | } else { | |||
// If the timestamp we're looking for is NEWER than what we found | // If the timestamp we're looking for is NEWER than what we found | |||
// Then the line is somewhere on the RIGHT side from the current probe position | // Then the line is somewhere on the RIGHT side from the current probe position | |||
start = lineEndIdx | start = lineEndIdx | |||
} | } | |||
probe = start + (end-start)/2 | probe = start + (end-start)/2 | |||
depth++ | depth++ | |||
if depth >= 100 { | if depth >= 100 { | |||
log.Error("Seek depth is too high, aborting. File %s, ts | return 0, depth, fmt.Errorf("looking up timestamp %d in % | |||
%v", q.file.Name(), timestamp) | q: depth %d too high: %w", timestamp, q.file.Name(), depth, ErrTSNotFound) | |||
return 0, depth, ErrSeekNotFound | ||||
} | } | |||
} | } | |||
q.position = lineIdx + int64(len(line)) | q.position = lineIdx + int64(len(line)) | |||
return q.position, depth, nil | return q.position, depth, nil | |||
} | } | |||
// SeekStart changes the current position to the end of the file | // SeekStart changes the current position to the end of the file | |||
// Please note that we're reading query log in the reverse order | // Please note that we're reading query log in the reverse order | |||
// and that's why log start is actually the end of file | // and that's why log start is actually the end of file | |||
End of changes. 8 change blocks. | ||||
21 lines changed or deleted | 22 lines changed or added |