rc1
This commit is contained in:
96
atree/redo/reader.go
Normal file
96
atree/redo/reader.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package redo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"gordenko.dev/dima/diploma/bin"
|
||||
)
|
||||
|
||||
type REDOFile struct {
|
||||
MetricID uint32
|
||||
Timestamp uint32
|
||||
Value float64
|
||||
IsDataPageReused bool
|
||||
DataPage PageToWrite
|
||||
IsRootChanged bool
|
||||
RootPageNo uint32
|
||||
ReusedIndexPages []uint32
|
||||
IndexPages []PageToWrite
|
||||
}
|
||||
|
||||
type ReadREDOFileReq struct {
|
||||
FileName string
|
||||
DataPageSize int
|
||||
IndexPageSize int
|
||||
}
|
||||
|
||||
func ReadREDOFile(req ReadREDOFileReq) (*REDOFile, error) {
|
||||
buf, err := os.ReadFile(req.FileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(buf) < 25 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
var (
|
||||
end = len(buf) - 4
|
||||
payload = buf[:end]
|
||||
checksum = bin.GetUint32(buf[end:])
|
||||
calculatedChecksum = crc32.ChecksumIEEE(payload)
|
||||
)
|
||||
|
||||
// Помилка чексуми означає що файл або недописаний, або пошкодженний
|
||||
if checksum != calculatedChecksum {
|
||||
return nil, fmt.Errorf("written checksum %d not equal calculated checksum %d",
|
||||
checksum, calculatedChecksum)
|
||||
}
|
||||
|
||||
var (
|
||||
redoLog = REDOFile{
|
||||
MetricID: bin.GetUint32(buf[0:]),
|
||||
Timestamp: bin.GetUint32(buf[4:]),
|
||||
Value: bin.GetFloat64(buf[8:]),
|
||||
IsDataPageReused: buf[16] == 1,
|
||||
DataPage: PageToWrite{
|
||||
PageNo: bin.GetUint32(buf[17:]),
|
||||
Data: buf[21 : 21+req.DataPageSize],
|
||||
},
|
||||
}
|
||||
pos = 21 + req.DataPageSize
|
||||
)
|
||||
|
||||
for {
|
||||
if pos == len(payload) {
|
||||
return &redoLog, nil
|
||||
}
|
||||
|
||||
if pos > len(payload) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
flags := buf[pos]
|
||||
|
||||
item := PageToWrite{
|
||||
PageNo: bin.GetUint32(buf[pos+1:]),
|
||||
}
|
||||
pos += 5 // flags + pageNo
|
||||
item.Data = buf[pos : pos+req.IndexPageSize]
|
||||
pos += req.IndexPageSize
|
||||
|
||||
redoLog.IndexPages = append(redoLog.IndexPages, item)
|
||||
|
||||
if (flags & FlagReused) == FlagReused {
|
||||
redoLog.ReusedIndexPages = append(redoLog.ReusedIndexPages, item.PageNo)
|
||||
}
|
||||
|
||||
if (flags & FlagNewRoot) == FlagNewRoot {
|
||||
redoLog.IsRootChanged = true
|
||||
redoLog.RootPageNo = item.PageNo
|
||||
}
|
||||
}
|
||||
}
|
||||
207
atree/redo/writer.go
Normal file
207
atree/redo/writer.go
Normal file
@@ -0,0 +1,207 @@
|
||||
package redo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"hash/crc32"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gordenko.dev/dima/diploma/bin"
|
||||
)
|
||||
|
||||
const (
|
||||
FlagReused byte = 1 // сторінка із FreeList
|
||||
FlagNewRoot byte = 2 // новая страница
|
||||
)
|
||||
|
||||
type PageToWrite struct {
|
||||
PageNo uint32
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type Writer struct {
|
||||
metricID uint32
|
||||
timestamp uint32
|
||||
value float64
|
||||
tmp []byte
|
||||
fileName string
|
||||
file *os.File
|
||||
hasher hash.Hash32
|
||||
isDataPageReused bool
|
||||
dataPageNo uint32
|
||||
isRootChanged bool
|
||||
newRootPageNo uint32
|
||||
indexPages []uint32
|
||||
reusedIndexPages []uint32
|
||||
indexPagesToWrite []PageToWrite
|
||||
}
|
||||
|
||||
type WriterOptions struct {
|
||||
Dir string
|
||||
MetricID uint32
|
||||
Value float64
|
||||
Timestamp uint32
|
||||
IsDataPageReused bool
|
||||
DataPageNo uint32
|
||||
Page []byte
|
||||
}
|
||||
|
||||
// dataPage можно записати 1 раз. Щоб не заплутувати інтерфейс - передаю data сторінку
|
||||
// через Options. Index сторінок може бути від 1 до N, тому виділяю окремий метод
|
||||
func NewWriter(opt WriterOptions) (*Writer, error) {
|
||||
if opt.Dir == "" {
|
||||
return nil, errors.New("Dir option is required")
|
||||
}
|
||||
if opt.MetricID == 0 {
|
||||
return nil, errors.New("MetricID option is required")
|
||||
}
|
||||
if opt.DataPageNo == 0 {
|
||||
return nil, errors.New("DataPageNo option is required")
|
||||
}
|
||||
// if len(opt.Page) != octopus.DataPageSize {
|
||||
// return nil, fmt.Errorf("bug: wrong data page size %d", len(opt.Page))
|
||||
// }
|
||||
|
||||
s := &Writer{
|
||||
fileName: JoinREDOFileName(opt.Dir, opt.MetricID),
|
||||
metricID: opt.MetricID,
|
||||
timestamp: opt.Timestamp,
|
||||
value: opt.Value,
|
||||
tmp: make([]byte, 21),
|
||||
isDataPageReused: opt.IsDataPageReused,
|
||||
dataPageNo: opt.DataPageNo,
|
||||
hasher: crc32.NewIEEE(),
|
||||
}
|
||||
|
||||
var err error
|
||||
s.file, err = os.OpenFile(s.fileName, os.O_CREATE|os.O_WRONLY, 0770)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.init(opt.Page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Формат:
|
||||
4b metricID
|
||||
8b value
|
||||
4b timestamp
|
||||
1b flags (reused)
|
||||
4b dataPageNo
|
||||
8KB dataPage
|
||||
*/
|
||||
func (s *Writer) init(dataPage []byte) error {
|
||||
bin.PutUint32(s.tmp[0:], s.metricID)
|
||||
bin.PutUint32(s.tmp[4:], s.timestamp)
|
||||
bin.PutFloat64(s.tmp[8:], s.value)
|
||||
if s.isDataPageReused {
|
||||
s.tmp[16] = 1
|
||||
}
|
||||
bin.PutUint32(s.tmp[17:], s.dataPageNo)
|
||||
|
||||
_, err := s.file.Write(s.tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = s.file.Write(dataPage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.hasher.Write(s.tmp)
|
||||
s.hasher.Write(dataPage)
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Формат
|
||||
1b index page flags
|
||||
4b indexPageNo
|
||||
Nb indexPage
|
||||
*/
|
||||
func (s *Writer) AppendIndexPage(indexPageNo uint32, indexPage []byte, flags byte) error {
|
||||
s.tmp[0] = flags
|
||||
bin.PutUint32(s.tmp[1:], indexPageNo)
|
||||
_, err := s.file.Write(s.tmp[:5])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.file.Write(indexPage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.hasher.Write(s.tmp[:5])
|
||||
s.hasher.Write(indexPage)
|
||||
|
||||
s.indexPages = append(s.indexPages, indexPageNo)
|
||||
|
||||
if (flags & FlagReused) == FlagReused {
|
||||
s.reusedIndexPages = append(s.reusedIndexPages, indexPageNo)
|
||||
}
|
||||
|
||||
if (flags & FlagNewRoot) == FlagNewRoot {
|
||||
s.newRootPageNo = indexPageNo
|
||||
s.isRootChanged = true
|
||||
}
|
||||
|
||||
s.indexPagesToWrite = append(s.indexPagesToWrite,
|
||||
PageToWrite{
|
||||
PageNo: indexPageNo,
|
||||
Data: indexPage,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Writer) IndexPagesToWrite() []PageToWrite {
|
||||
return s.indexPagesToWrite
|
||||
}
|
||||
|
||||
func (s *Writer) Close() (err error) {
|
||||
// финализирую запись
|
||||
bin.PutUint32(s.tmp, s.hasher.Sum32())
|
||||
_, err = s.file.Write(s.tmp[:4])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.file.Sync()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.file.Close()
|
||||
}
|
||||
|
||||
type Report struct {
|
||||
FileName string
|
||||
IsDataPageReused bool
|
||||
DataPageNo uint32
|
||||
IsRootChanged bool
|
||||
NewRootPageNo uint32
|
||||
ReusedIndexPages []uint32
|
||||
}
|
||||
|
||||
func (s *Writer) GetReport() Report {
|
||||
return Report{
|
||||
FileName: s.fileName,
|
||||
IsDataPageReused: s.isDataPageReused,
|
||||
DataPageNo: s.dataPageNo,
|
||||
//IndexPages: s.indexPages,
|
||||
IsRootChanged: s.isRootChanged,
|
||||
NewRootPageNo: s.newRootPageNo,
|
||||
ReusedIndexPages: s.reusedIndexPages,
|
||||
}
|
||||
}
|
||||
|
||||
// HELPERS
|
||||
|
||||
func JoinREDOFileName(dir string, metricID uint32) string {
|
||||
return filepath.Join(dir, fmt.Sprintf("m%d.redo", metricID))
|
||||
}
|
||||
Reference in New Issue
Block a user