vrack-db
Version:
This is an In Memory database designed for storing time series (graphs).
340 lines (339 loc) • 13.1 kB
JavaScript
"use strict";
/*
* Copyright © 2023 Boris Bobylev. All rights reserved.
* Licensed under the Apache License, Version 2.0
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const Interval_1 = __importDefault(require("./Interval"));
const MetricResult_1 = __importDefault(require("./MetricResult"));
const MetricWrite_1 = __importDefault(require("./MetricWrite"));
const LayerStorage_1 = __importStar(require("./LayerStorage/LayerStorage"));
const ErrorManager_1 = __importDefault(require("./Errors/ErrorManager"));
const Typing_1 = __importDefault(require("./Typing"));
ErrorManager_1.default.register('Aht5U892ICXv', 'VDB_LAYER_SIZE', 'Incorrect size of layer (interval > period)');
ErrorManager_1.default.register('iQT3d2SflYQY', 'VDB_LAYER_SECTION', 'Incorrect section - The beginning is greater than the end');
ErrorManager_1.default.register('ir1hjNJ0GA0j', 'VDB_LAYER_INITIAL_DATA', 'Incorrect interval or period type, values must be integer and greater than zero');
ErrorManager_1.default.register('VWkc3gQ9g1DM', 'VDB_LAYER_VALUE', 'Incorrect type given, type number is required');
ErrorManager_1.default.register('gVCGXR03XycW', 'VDB_LAYER_TIME', 'Incorrect type given, type integer is required');
ErrorManager_1.default.register('5I1vWpnAH2zM', 'VDB_LAYER_PRECISION', 'Incorrect type given, values must be integer and greater than zero');
/**
* A layer is a low level for storing information.
* The layer works with such a concept as MTU - minimum time unit. MTU is a unit of time represented as an integer.
* For class Interval MTU = 1 second. For IntervalMs MTU = 1 millisecond. For class IntervalUs = microsecond
*
* Example of creating a layer with 10 memory cells
*
* ```ts
* const lay = new Layer({ interval: 1, period: 10})
* ```
*
* For more details, it is recommended to read the official guide
*
* @link https://github.com/ponikrf/VRackDB/wiki/How-It-Works
*
*/
class Layer {
/**
* Creating a new storage with a certain accuracy (interval) and size (period)
*
* **The interval cannot be less than the period**
*
* @example new Layer({ interval: 1, period: 10}) // Creates a layer with an interval of 1 MTU and a period of 10 MTU
*
* @see ILayerOptions
* @link https://github.com/ponikrf/VRackDB/wiki/How-It-Works
*/
constructor({ interval, period, vStorage = null, tStorage = null, CInterval = Interval_1.default }) {
/** Interval class (see Interval, IntervalMs, IntervalUs) */
this.CInterval = Interval_1.default;
if (interval >= period)
throw ErrorManager_1.default.make(new Error, 'VDB_LAYER_SIZE', { interval, period });
if (!Typing_1.default.isUInt(interval) || !Typing_1.default.isUInt(period))
throw ErrorManager_1.default.make(new Error, 'VDB_LAYER_INITIAL_DATA', { interval, period });
this._interval = interval;
this._period = period;
this.CInterval = CInterval;
// get cells count in layer
this._cells = Math.floor(period / this._interval);
this._timeStorage = LayerStorage_1.default.make(tStorage, LayerStorage_1.StorageTypes.Uint64, this._cells);
this._valueStorage = LayerStorage_1.default.make(vStorage, LayerStorage_1.StorageTypes.Float, this._cells);
const time = this.CInterval.now();
// Fill basic data for new layer
this._startTime = this.CInterval.roundTime(time - (this._interval * (this._cells - 1)), this._interval);
this._endTime = this.CInterval.roundTime(time, this._interval);
}
/**
* Clear layer data
*/
clear() {
this._timeStorage.buffer.fill(0);
}
/**
* Returns the size of the layer in bytes
*/
get length() {
return this._valueStorage.buffer.length + this._timeStorage.buffer.length;
}
/**
* Layer accuracy in MTU
*/
get interval() {
return this._interval;
}
/**
* Number of intervals in a layer
*
* Old method timeSize is deprecated & deleted
*/
get period() {
return this._period;
}
/**
* Number of intervals in a layer
*/
get cells() {
return this._cells;
}
/**
* Initial point of time in the layer
*/
get startTime() {
return this._startTime;
}
/**
* End point of time in the layer
* */
get endTime() {
return this._endTime;
}
/**
* Writes the value into the layer
*
* Automatically adjusts the layer time (startTime/endTime).
*
* **It is necessary to take into account that the layer may not work correctly
* If you write data from different time intervals larger than the layer size**.
*
* Works best when data is written sequentially (in time)
*
* @param {number} time Time in MTU
* @param {number} value Value to be recorded
* @param {string} func Modification function
*/
write(time, value, func = 'last') {
if (typeof value !== 'number')
throw ErrorManager_1.default.make(new Error, 'VDB_LAYER_VALUE', { value });
if (typeof time !== 'number')
throw ErrorManager_1.default.make(new Error, 'VDB_LAYER_TIME', { time });
const oTime = this.CInterval.roundTime(time, this._interval);
/**
* If we receive a value with a time that exceeds the length of our buffer
* We consider that there is no more actual data in this buffer and we set
* startTime equal to our endTime and get a buffer with 1 value
*/
if (this._endTime < (oTime - this._cells * this._interval))
this._startTime = oTime;
/**
* We've got a value less than the start of the layer
* We need to rebuild it, so we need this.endTime
* To be less than oTime
* Then the following condition will do it for us
*/
if (oTime < this._startTime) {
this._endTime = oTime - this._interval;
}
/**
* We've got a time value greater than the last time, so we need to move
* the end of our queue to the next index and replace endTime
*/
if (this._endTime < oTime) {
const sTime = this.CInterval.roundTime(time - (this._interval * (this._cells - 1)), this._interval);
this._startTime = sTime;
this._endTime = oTime;
}
const index = this.getIndex(time);
this.writeBuffer(index, oTime, value, func);
}
/**
* Returns data in the interval start -> end with layer accuracy
*
* If the specified start and end are outside the layer, they will be converted to layer frames
* this will be indicated in the result
*
* Start -> end parameters are automatically rounded to layer accuracy
*
* If there is a need to get data with arbitrary precision
* it is better to use `readCustomInterval`.
*
* @see readCustomInterval
*
* @param {number} start Start time
* @param {number} end End time
*/
readInterval(start, end) {
if (start < this._startTime)
start = this._startTime;
if (end > this._endTime)
end = this._endTime;
if (start > end)
end = this._endTime;
const ils = this.CInterval.getIntervals(start, end, this._interval);
const result = { relevant: true, start, end, rows: [] };
for (let i = 0; i < ils.length; i++)
result.rows.push(this.readOne(ils[i]));
return result;
}
/**
* Allows data to be read from a layer at a different precision than the layer's native one
* Supports both smaller intra-layer precision and larger intra-layer precision.
* This can be useful for looking at a complex graph in more detail,
* or aggregating data with the LAST function to view a less detailed graph.
*
* If the specified start and end are outside the layer, they will be brought to the layer boundaries.
* this will be indicated in the result
*
* @param {number} start Start time
* @param {number} end End time
* @param {number} precision
*/
readCustomInterval(start, end, precision, func = 'last') {
if (!Typing_1.default.isUInt(precision))
throw ErrorManager_1.default.make(new Error, 'VDB_LAYER_PRECISION', { precision });
if (start < this._startTime)
start = this._startTime;
if (end > this._endTime)
end = this._endTime;
if (!precision)
precision = this._interval;
if (start > end)
throw ErrorManager_1.default.make(new Error, 'VDB_LAYER_SECTION', { start, end });
const result = { relevant: true, start, end, rows: [] };
const ils = this.CInterval.getIntervals(start, end, precision);
let to;
for (let i = 0; i < ils.length; i++) {
if (this._startTime > ils[i])
continue;
if (precision <= this._interval) {
to = ils[i];
}
else {
to = (ils[i] + precision) - this._interval;
if (to < ils[i])
to = ils[i];
}
const tres = this.readInterval(ils[i], to); // Count the interval with data
const val = {
time: ils[i],
value: MetricResult_1.default.aggregate(tres, func)
};
result.rows.push(val);
}
return result;
}
/**
* Return all points in layer
* See example!
*
* @example console.table(layer.dump())
*/
dump() {
const rows = [];
for (let i = 0; i < this._cells; i++)
rows.push(this.readBuffer(i));
return rows;
}
/**
* Reads 1 index by time and retrieves its value
*
* @param {number} time time
*/
readOne(time) {
const metric = this.readBuffer(this.getIndex(time));
metric.time = time;
return metric;
}
/**
* Returns the time index
*
* @param {number} time time in MTU
*/
getIndex(time) {
return Math.floor(this._cells + time / this._interval) % this._cells;
}
/**
* Checks if the time is valid for the current state
* of the layer, in fact it checks if the time is within the interval
* of the layer's current active time
*
* @param {number} time Time in MTU
*/
validTime(time) {
return (time >= this._startTime && time <= this._endTime);
}
/**
* Reading metrics by index
*
* @param {number} index Metrics Index
* @returns {IMetric} Metric value
*/
readBuffer(index) {
const time = this._timeStorage.readBuffer(index);
let value = this._valueStorage.readBuffer(index);
if (!this.validTime(time))
value = null;
return { time, value };
}
/**
* Writes data to the buffer
*
* @param {number} index Metric Index
* @param {number} time Time for recording
* @param {number} value Value for recording
* @param {string} func Modification function
*/
writeBuffer(index, time, value, func = 'last') {
value = this.modifyWrite(index, value, func);
this._timeStorage.writeBuffer(index, time);
this._valueStorage.writeBuffer(index, value);
}
/**
* Modifies the data before writing the buffer
*
* @param {number} index Metrics Index
* @param {number} value Value for recording
* @param {string} func Modification function
*/
modifyWrite(index, value, func) {
const val = this.readBuffer(index);
if (val.value === null)
return value;
return MetricWrite_1.default.modify(val.value, value, func);
}
}
exports.default = Layer;