Files
qb/database/metric.go
2026-06-12 00:29:02 +03:00

440 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package database
import (
"io"
bin "gordenko.dev/dima/bin/little"
"gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/storage"
)
// METRIC
const minBufferSize = 1024
var (
indexRecordSize = 8
)
type _metric struct {
metricType qb.MetricType
fracDigits byte
lastPageNo uint32
//SinceValue float64
//Since uint32
lastValue float64
//Until uint32
buffer []byte
timestamps qb.TimestampCompressor
values qb.ValueCompressor
XLock bool
RLocks int
WaitQueue []any
indexLevelTails []storage.IndexLevelTail // root - last element
capturedState *CapturedState
}
//IndexLevels [][]IndexRec // root - last element
// func (s *_metric) ReinitBy(timestamp uint32, value float64) {
// s.Timestamps.Renew()
// s.Values.Renew()
// s.Timestamps.Append(timestamp)
// s.Values.Append(value)
// s.Since = timestamp
// s.SinceValue = value
// s.Until = timestamp
// s.UntilValue = value
// }
func (s *_metric) DeleteMeasures() {
// s.Timestamps.Renew()
// s.Values.Renew()
// s.LastPageNo = 0
// s.Since = 0
// s.SinceValue = 0
// s.Until = 0
// s.UntilValue = 0
}
type CapturedState struct {
Pages []storage.DataPayload
TimeState qb.TimeDeltaCapturedState
ValueState qb.ValueDeltaCapturedState
}
// func (s *_metric) startAppendMeasures(req tryAppendMeasuresReq, sendToStorage func(storage.AppendedMeasures)) {
func (s *_metric) StartAppendMeasures(req tryAppendMeasuresReq, tmp []byte, sendToStorage func(any)) {
if s.capturedState != nil {
s.WaitQueue = append(s.WaitQueue, req)
}
var (
timestamps = s.timestamps
values = s.values
// офсети на head сторінці
timestampsOffset int
valuesOffset int
written int
resultCode byte
)
s.capturedState = &CapturedState{
//
TimeState: s.timestamps.CaptureState(),
ValueState: s.values.CaptureState(),
}
for idx, measure := range req.Measures {
if measure.Timestamp <= s.timestamps.LastTimestamp() {
resultCode = ExpiredMeasure
break
}
if s.metricType == qb.Cumulative && measure.Value < s.lastValue {
resultCode = NonMonotonicValue
break
}
// fix - 1 + 8 bytes
tReport := timestamps.Evaluate(tmp, measure.Timestamp)
vReport := values.Evaluate(tmp, measure.Value)
totalRequiredSpace := tReport.TotalSpace + vReport.TotalSpace
if totalRequiredSpace <= len(s.buffer) {
// якщо на сторінці є місце
if idx == 0 {
timestampsOffset = tReport.Offset
valuesOffset = vReport.Offset
}
} else {
if len(s.buffer) < storage.DataPageSize {
// allocate bigger buffer
buf, databuf := growBuffers(growBuffersIn{
databuf: s.buffer,
tSize: timestamps.Size(),
vSize: values.Size(),
requiredSpace: totalRequiredSpace,
})
s.buffer = buf
// replace buffer in timestamps and values
timestamps.Rotate(databuf) // fix pos
values.Rotate(databuf) // fix pos
} else {
// сторінка заповнена
since := s.timestamps.ReplaceSinceWithUntil()
if len(s.capturedState.Pages) == 0 {
// head page FIX
// timestampsPayload =
// valuesPayload =
}
s.capturedState.Pages = append(s.capturedState.Pages, storage.DataPayload{
Since: since,
Content: s.buffer,
TimestampsSize: timestamps.Size(),
ValuesSize: values.Size(),
})
buffer := make([]byte, minBufferSize)
timestamps.Rotate(buffer)
values.Rotate(buffer)
// renew
s.buffer = buffer
}
}
timestamps.Append(tReport.Offset, tmp[:tReport.ChangeSize], measure.Timestamp)
values.Append(vReport.Offset, tmp[7:7+vReport.ChangeSize], measure.Value, vReport.Delta)
//
s.lastValue = measure.Value
written++
}
if written == 0 {
s.capturedState = nil
req.ResultCh <- tryAppendMeasuresResult{
ResultCode: resultCode,
}
return
}
// виділити змінені байти.
// скопіювати. Причому можна скопіювати зрізи chunks
if len(s.capturedState.Pages) > 0 {
head := s.capturedState.Pages[0]
databuf := head.Content[:storage.DataPagePayloadSize]
// пишу в storage довгим шляхом через redo файл і запис в data файл
sendToStorage(storage.AppendedMeasures{
MetricID: req.MetricID,
LastPageNo: s.lastPageNo,
TimestampsOffset: timestampsOffset,
Timestamps: databuf[len(databuf)-head.TimestampsSize : len(databuf)-timestampsOffset],
ValuesOffset: valuesOffset,
Values: databuf[valuesOffset:head.ValuesSize],
IndexLevelTails: s.indexLevelTails,
DataPages: s.capturedState.Pages,
TailTimestamps: nil, // payload
TailValues: s.buffer[:values.Size()],
ResultCode: resultCode,
WrittenCount: written,
ResultCh: nil,
})
} else {
databuf := s.buffer
if len(s.buffer) == storage.DataPageSize {
databuf = s.buffer[:storage.DataPagePayloadSize]
}
// короткий шлях - запис лише в storage
sendToStorage(storage.AppendedMeasures{
MetricID: req.MetricID,
TimestampsOffset: timestampsOffset,
ValuesOffset: valuesOffset,
Timestamps: databuf[len(databuf)-timestamps.Size() : len(databuf)-timestampsOffset],
Values: databuf[valuesOffset:values.Size()],
ResultCode: resultCode,
WrittenCount: written,
ResultCh: nil,
})
}
}
func (s *_metric) FinAppendMeasures(rec storage.AppendMeasuresSummary) {
// Видаляю state. Оригінальні Timestamps і Values вже мають останню версію
s.capturedState = nil
if rec.LastPageNo > 0 {
s.lastPageNo = rec.LastPageNo
}
// В storage я передав повний індекс. У нього додали елементи (можливо нові рівні).
// Тому проста заміна
s.indexLevelTails = rec.Index
}
// READ
func (s *_metric) StartRangeScan(req tryRangeScanReq) {
// if s.Since == 0 {
// req.ResultCh <- rangeScanResult{
// ResultCode: QueryDone,
// }
// return
// }
// if req.Since > s.Until {
// req.ResultCh <- rangeScanResult{
// ResultCode: QueryDone,
// }
// return
// }
// if req.Until < s.Since {
// if s.RootPageNo > 0 {
// req.ResultCh <- rangeScanResult{
// ResultCode: UntilNotFound,
// RootPageNo: s.RootPageNo,
// FracDigits: s.FracDigits,
// }
// s.RLocks++
// return
// } else {
// req.ResultCh <- rangeScanResult{
// ResultCode: QueryDone,
// }
// return
// }
// }
// timestampDecompressor := s.Timestamps.CreateDecompressor()
// valueDecompressor := s.Values.CreateDecompressor(s.FracDigits)
// for {
// timestamp, done := timestampDecompressor.NextValue()
// if done {
// break
// }
// value, done := valueDecompressor.NextValue()
// if done {
// qb.Abort(qb.HasTimestampNoValueBug, ErrNoValueBug)
// }
// if timestamp <= req.Until {
// req.ResponseWriter.FeedNoSend(timestamp, value)
// if timestamp < req.Since {
// req.ResultCh <- rangeScanResult{
// ResultCode: QueryDone,
// }
// return
// }
// }
// }
// if s.LastPageNo > 0 {
// req.ResultCh <- rangeScanResult{
// ResultCode: UntilFound,
// LastPageNo: s.LastPageNo,
// FracDigits: s.FracDigits,
// }
// s.RLocks++
// } else {
// req.ResultCh <- rangeScanResult{
// ResultCode: QueryDone,
// }
// }
}
func (s *_metric) StartFullScan(req tryFullScanReq) {
// if s.Since == 0 {
// req.ResultCh <- fullScanResult{
// ResultCode: QueryDone,
// }
// return
// }
// timestampDecompressor := s.Timestamps.CreateDecompressor()
// valueDecompressor := s.Values.CreateDecompressor()
// for {
// timestamp, done := timestampDecompressor.NextValue()
// if done {
// break
// }
// value, done := valueDecompressor.NextValue()
// if done {
// qb.Abort(qb.HasTimestampNoValueBug, ErrNoValueBug)
// }
// req.ResponseWriter.FeedNoSend(timestamp, value)
// }
// if s.LastPageNo > 0 {
// req.ResultCh <- fullScanResult{
// ResultCode: UntilFound,
// LastPageNo: s.LastPageNo,
// FracDigits: s.FracDigits,
// }
// s.RLocks++
// } else {
// req.ResultCh <- fullScanResult{
// ResultCode: QueryDone,
// }
// }
}
// індекси
// Metric encode format:
// metricID - 4b
// metricType - 1b
// fracDigits - 1b
// lastPageNo - 4b
// timestamps size - 2b
// values size - 2b
// timestams payload - Nb
// values payload - Nb
// index levels count - varsize
// [
// records qty - varsize
// records - Nb
// ]
func (s *_metric) WriteTo(w io.Writer) (err error) {
_, err = w.Write([]byte{
byte(s.metricType),
s.fracDigits,
})
if err != nil {
return
}
err = bin.WriteUint32(w, s.lastPageNo)
if err != nil {
return
}
err = bin.WriteUint16(w, uint16(s.timestamps.Size()))
if err != nil {
return
}
err = bin.WriteUint16(w, uint16(s.values.Size()))
if err != nil {
return
}
var (
timestampsCapturedState *qb.TimeDeltaCapturedState
valuesCapturedState *qb.ValueDeltaCapturedState
)
if s.capturedState != nil {
timestampsCapturedState = &s.capturedState.TimeState
valuesCapturedState = &s.capturedState.ValueState
}
// timestamps payload
err = s.timestamps.WritePayloadTo(w, timestampsCapturedState)
if err != nil {
return
}
// values payload
err = s.values.WritePayloadTo(w, valuesCapturedState)
if err != nil {
return
}
// indexes
_, err = bin.WriteVarSize(w, len(s.indexLevelTails))
if err != nil {
return
}
for _, level := range s.indexLevelTails {
_, err = bin.WriteVarSize(w, level.RecordsCount)
if err != nil {
return
}
_, err = w.Write(level.Buffer)
if err != nil {
return
}
}
return
}
// since - 4b
// sinceValue - 8b -
// untilValue - 8b
// time
// if s.state != nil {
// h = s.state.H
// lastUnixtime = s.state.LastUnixtime
// lastDelta = s.state.LastDelta
// payload = s.state.Payload
// }
// return NewTimeDeltaDecompressorFromState(TimeDeltaDecompressorFromStateOptions{
// H: h,
// LastUnixtime: lastUnixtime,
// LastDelta: lastDelta,
// Payload: payload,
// })
// value
// if s.state != nil {
// h = s.state.H
// lastDelta = s.state.LastDelta
// payload = s.state.Payload
// }
// return NewValueDeltaDecompressorFromState(ValueDeltaDecompressorFromStateOptions{
// Coef: s.coef,
// ToFloat64: s.toFloat64,
// H: s.buf[s.pos-1],
// LastDelta: s.lastDelta,
// Payload: s.buf[:s.pos],
// })