@gent-js/gent
Version:
template-based data generator.
118 lines (117 loc) • 4.53 kB
JavaScript
import { Buffer } from "node:buffer";
import * as stream from "node:stream";
import { GeneratingDocument } from "./document/index.js";
const Per1000Window = 1000;
const Per100Window = 100;
export class DocumentTransformStream extends stream.Transform {
documentTransformer;
timeWindow;
numOfEventPerTimeWindow;
timeWindowIntervalTimeout;
numOfEventInCurrentTimeWindow;
pendingTransformTasks = [];
nThTimeWindow = 0;
totalNumOfEvent = 0;
constructor(options, debug = false) {
super({
highWaterMark: 0,
objectMode: true,
});
const transformMode = options.transformMode;
if (transformMode === "object") {
this.documentTransformer = undefined;
}
else if (transformMode === "buffer") {
const framing = options.framing;
if (framing === "octet-counting") {
this.documentTransformer = transformDocumentIntoOctetCountingBuffer;
}
else if (framing === "non-transparent") {
this.documentTransformer = createNonTransparentDocumentTransformer(options.trailer, options.trailerReplacer);
}
else {
throw new Error(`Unexpected framingMethod: ${framing}`);
}
}
else {
throw new Error(`Unexpected transferMode: ${transformMode}`);
}
if (options.eps < Per100Window) {
this.timeWindow = Per1000Window;
}
else {
this.timeWindow = Per100Window;
}
this.numOfEventPerTimeWindow = Math.ceil(options.eps * (this.timeWindow / 1000));
this.numOfEventInCurrentTimeWindow = 0;
this.timeWindowIntervalTimeout = setInterval(() => {
this.__flushPendingTransformTasks();
if (debug) {
this.debugReport();
}
this.numOfEventInCurrentTimeWindow = 0;
}, this.timeWindow);
}
debugReport() {
this.totalNumOfEvent =
this.totalNumOfEvent + this.numOfEventInCurrentTimeWindow;
this.nThTimeWindow = this.nThTimeWindow + 1;
console.log(`[${this.nThTimeWindow}] => ${this.numOfEventInCurrentTimeWindow}/${this.totalNumOfEvent}`);
}
_transform(chunk, encoding, callback) {
if (!(chunk instanceof GeneratingDocument)) {
callback(new Error(`Unexpected chunk type(${typeof chunk}). Cannot process any chunk type except Document.`));
return;
}
if (this.numOfEventInCurrentTimeWindow < this.numOfEventPerTimeWindow) {
this.numOfEventInCurrentTimeWindow =
this.numOfEventInCurrentTimeWindow + 1;
this.__transformDocument(chunk, encoding, callback);
return;
}
this.pendingTransformTasks.push([chunk, encoding, callback]);
}
_flush(callback) {
this.__flushPendingTransformTasks();
this.__clearWindowInternal();
callback();
}
__transformDocument(document, encoding, callback) {
const documentTransformer = this.documentTransformer;
if (documentTransformer === undefined) {
callback(null, document);
return;
}
const bufferArray = documentTransformer(document, encoding);
bufferArray.forEach((buffer) => this.push(buffer, encoding));
callback();
}
__flushPendingTransformTasks() {
const tasks = this.pendingTransformTasks;
this.pendingTransformTasks = [];
tasks.forEach(([document, encoding, callback]) => this.__transformDocument(document, encoding, callback));
}
__clearWindowInternal() {
clearInterval(this.timeWindowIntervalTimeout);
}
}
const SP = " ";
function transformDocumentIntoOctetCountingBuffer(document, encoding) {
const outputString = document.stamp();
const messageBuf8 = Buffer.from(outputString, encoding);
const lengthCount = messageBuf8.length.toString();
const lengthBuf8 = Buffer.from(lengthCount + SP, encoding);
return [lengthBuf8, messageBuf8];
}
function createNonTransparentDocumentTransformer(trailer, trailerReplacer) {
return (document, encoding) => {
let outputString = document.stamp();
if (trailerReplacer !== undefined) {
outputString = outputString.replaceAll(trailer, trailerReplacer);
}
return [
Buffer.from(outputString, encoding),
Buffer.from(trailer, encoding),
];
};
}