From 6f3f509b17acd84069ef4f75edbd38d7be612c31 Mon Sep 17 00:00:00 2001 From: Witold Pruchnicki Date: Mon, 11 Dec 2023 11:45:09 +0000 Subject: [PATCH] revert to commit before deleting mgr-sequence --- typescript/mgr-sequence/README.md | 77 ++++++++++++ typescript/mgr-sequence/seq-config.json | 9 ++ typescript/mgr-sequence/shh-config.json | 21 ++++ .../mgr-sequence/timestamp-manager/index.ts | 112 ++++++++++++++++++ .../timestamp-manager/package.json | 35 ++++++ .../timestamp-manager/real-hrtime.node | Bin 0 -> 45896 bytes .../timestamp-manager/tsconfig.json | 10 ++ .../mgr-sequence/timestamp-producer/index.ts | 44 +++++++ .../timestamp-producer/package.json | 37 ++++++ .../timestamp-producer/real-hrtime.node | Bin 0 -> 45896 bytes .../timestamp-producer/tsconfig.json | 10 ++ 11 files changed, 355 insertions(+) create mode 100644 typescript/mgr-sequence/README.md create mode 100644 typescript/mgr-sequence/seq-config.json create mode 100644 typescript/mgr-sequence/shh-config.json create mode 100644 typescript/mgr-sequence/timestamp-manager/index.ts create mode 100644 typescript/mgr-sequence/timestamp-manager/package.json create mode 100644 typescript/mgr-sequence/timestamp-manager/real-hrtime.node create mode 100644 typescript/mgr-sequence/timestamp-manager/tsconfig.json create mode 100644 typescript/mgr-sequence/timestamp-producer/index.ts create mode 100644 typescript/mgr-sequence/timestamp-producer/package.json create mode 100644 typescript/mgr-sequence/timestamp-producer/real-hrtime.node create mode 100644 typescript/mgr-sequence/timestamp-producer/tsconfig.json diff --git a/typescript/mgr-sequence/README.md b/typescript/mgr-sequence/README.md new file mode 100644 index 0000000..c83b00b --- /dev/null +++ b/typescript/mgr-sequence/README.md @@ -0,0 +1,77 @@ +# Sequence deployment together with starting Hubs + +## Sequence description + +### timestamp-manager + +This Sequence starts multiple instances of the '@scramjet/timestamp-producer' sequence, calculates the time differences between received timestamps and local timestamps, and returns the overall average difference in milliseconds to the Instance output and stdout. + +### timestamp-producer + +This Sequence generates and sends timestamps to the output stream. Upon receiving the 'start' event, the Sequence starts generating the specified number of timestamps at the given interval and pushes them to the output stream. It uses the 'real-hrtime' module to generate timestamps. After generating the desired number of timestamps, it pushes an end indicator to signify the end of data. + +## STH deployment command + +>**Build each Sequence before deployment (`npm run build`)** + +```bash +sth -S typescript/mgr-sequence/seq-config.json -E -D typescript/mgr-sequence --runtime-adapter process --config typescript/mgr-sequence/shh-config.json +``` + +> Optionally you can use `DEVELOPMENT=1` env to see Runner logs. + +Options used in the command: + +* `-S ` or `--startup-config ` - option that points at configuration JSON file, which contains metadata needed for starting up the sequence(s). It works only with process adapter. As a `` argument you should provide the location of a config file with the list of Sequences to be started along with the sth. +The example of a startup-config.json file: + +```json +{ + "sequences": [ + { + "id": "sequence-name", + "args": [], + "instanceId": "11111111-2222-3333-4444-555555555555" + } + ] +} +``` + +As the example above shows, you should pass in your own Sequence ID and Instance ID in your config file. + +Sequence id given in the config file should be exactly the same as the same if the Sequence directory. Otherwise you will get a WARN like this in STH logs: + +```bash +2023-06-16T13:58:54.067Z WARN Host Sequence id not found for startup config [ { id: 'sequence-name', args: [], instanceId: '11111111-2222-3333-4444-555555555555' } ] +``` + +* `-E` or `--identify-existing` - this option scans the catalog and looks for Sequences, when there are any found they are added to STH and started right after. + +· `-D ` or `--sequences-root ` - this option points at the location of the Sequences that will be started together with sth. This is also where ProcessSequenceAdapter saves new Sequences. + +Optionally: + +* `-X` or `--exit-with-last-instance` - thanks to this option STH will exit when no more Instances exist. + +## Output + +The results will be provided by the instance of the `timestamp-manager` Sequence. +Read `stdout` stream (`si inst stdout `), or check out STH logs, you should see something similar to this: + +```bash +Channel averages (nanoseconds): +┌─────────┬──────────────┐ +│ (index) │ Values │ +├─────────┼──────────────┤ +│ 0 │ ' 422940' │ +│ 1 │ ' 423649' │ +│ 2 │ ' 422948' │ +│ 3 │ ' 434039' │ +│ 4 │ ' 412757' │ +└─────────┴──────────────┘ +Average from 5 output streams: 423266 (nanoseconds) +Average of 5 averages: 0.2116 (milliseconds) +``` + +To find out what each Sequence does look into the comments included in the code. +___ diff --git a/typescript/mgr-sequence/seq-config.json b/typescript/mgr-sequence/seq-config.json new file mode 100644 index 0000000..07e1e62 --- /dev/null +++ b/typescript/mgr-sequence/seq-config.json @@ -0,0 +1,9 @@ +{ + "sequences": [ + { + "id": "timestamp-manager", + "args": [], + "instanceId": "11111111-2222-3333-4444-555555555555" + } + ] +} diff --git a/typescript/mgr-sequence/shh-config.json b/typescript/mgr-sequence/shh-config.json new file mode 100644 index 0000000..9691387 --- /dev/null +++ b/typescript/mgr-sequence/shh-config.json @@ -0,0 +1,21 @@ +{ + "host": { + "hostname":"0.0.0.0", + "port":9002, + "instancesServerPort":9003, + "id":"shh-1" + }, + "description": "My awesome Self-Hosted Hub <3", + "tags": [ + "scramjet", + "data streaming" + ], + "timings": { + "instanceLifetimeExtensionDelay": 360000 + }, + "platform": { + "apiKey":"", + "api":"https://api-shh.scramjet.cloud", + "space":":-manager" + } +} diff --git a/typescript/mgr-sequence/timestamp-manager/index.ts b/typescript/mgr-sequence/timestamp-manager/index.ts new file mode 100644 index 0000000..6e1f991 --- /dev/null +++ b/typescript/mgr-sequence/timestamp-manager/index.ts @@ -0,0 +1,112 @@ +/* eslint-disable no-loop-func */ +import { AppConfig, AppContext } from "@scramjet/types"; +import { PassThrough, Readable } from "stream"; + +const rht = require("./real-hrtime.node"); + +// Default configuration values +let CONFIG = { + tsPerInstance: 1000, + tsInterval: 100, + instancesCount: 5 +}; + +/** + * This Sequence finds the Sequence with the name '@scramjet/timestamp-producer' on the Sequence list and starts it multiple times. + * Each Instance of the '@scramjet/timestamp-producer' Sequence will send a 'start' event, triggering the data flow. The Instances will write + * timestamps to the output stream based on the provided number and interval arguments. + * The Sequence reads the output from each Instance, calculates the time differences, and returns the overall average to the output stream. + * + * @param {AppContext} this - Application context + * @param {any}_stream - A dummy input stream + * @param {number} instancesCount - The number of instances to start + * @param {number} tsPerInstance - The number of timestamps to be sent to the output + * @param {number} tsInterval - The interval at which the timestamps are sent + * @returns {PassThrough} A PassThrough stream containing the overall average + */ + +export default [ + async function (this: AppContext, _stream: any, instancesCount: number, tsPerInstance: number, tsInterval: number) { + // Wait for 5 seconds + await new Promise((res) => setTimeout(res, 5000)); + + // Update the configuration values if provided, otherwise use default values + CONFIG.instancesCount ??= instancesCount; + CONFIG.tsInterval ??= tsInterval; + CONFIG.tsPerInstance ??= tsPerInstance; + + // Retrieve the Sequence ID by its name and create Sequence client + const sequenceName = "@scramjet/timestamp-producer"; + const hostClient = this.hub!; + const seqList = await hostClient.listSequences(); + const producerSequence = seqList.find((seq) => seq.config.name === sequenceName); + const seqClient = hostClient.getSequenceClient(producerSequence!.id); + + // Start multiple instances of the sequence with specified arguments in CONFIG + const instances = await Promise.all( + new Array(CONFIG.instancesCount).fill(null) + .map((_e, i) => { + return new Promise((res) => setTimeout(res, i * 1000)) + .then(__ => seqClient.start({ args: [CONFIG.tsPerInstance, CONFIG.tsInterval], appConfig: {} })); + }) + ); + + // Get the "output" stream from each instance + const outputs = await Promise.all( + instances.map((instanceClient, i) => + new Promise((res) => setTimeout(res, i * 16)) + .then(() => instanceClient.getStream("output") + ) + ) + ) as Readable[]; + + // Send "start" event to each instance + instances.forEach(async (ic, i) => { + await new Promise((res) => setTimeout(res, i * 8)) + ic.sendEvent("start", ""); + }); + + // Calculate the differences between timestamps received from instances and the local timestamps + const outputDiffs = await Promise.all(outputs.map((o, i) => + new Promise(res => { + const diffs: BigInt[] = []; // Array to store the differences between timestamps + + // Event-based processing of data from the output stream + o.on("data", (d) => { + const rx = d.toString().replace("\n", ""); // Convert received data to string and remove "\n" + + // If end indicator received, resolve with the differences array + if (rx === "0") { + res(diffs as bigint[]); + return; + } + + const rxTs = BigInt(rx); // Convert received timestamp to BigInt + const ts = rht.bigint() as bigint; // Get local timestamp using rht module + + diffs.push((ts - rxTs) as bigint); // Calculate difference and push it in the diffs array + }); + }) + )); + + // Calculate the average difference for each output + const outputDiffsAvg = outputDiffs.map((d: bigint[]) => d.reduce((partialSum: bigint, a: bigint) => partialSum + a, BigInt(0)) / BigInt(d.length)); + + console.log("Channel averages (nanoseconds):"); + console.table(outputDiffsAvg.map(a => Number(a).toFixed(0).toString().padStart(10, " "))); + + // Calculate the overall average difference in nanoseconds + const avg = outputDiffsAvg.reduce((partialSum: bigint, a: bigint) => partialSum + a, BigInt(0)) / BigInt(outputDiffsAvg.length); + console.log(`Average from ${CONFIG.instancesCount} output streams:`, Number(avg), "(nanoseconds)"); + + // Calculate the ultimate average in milliseconds + const ultimateAvg = (Number(avg) / 1000000 / 2).toFixed(4); + console.log(`Average of ${outputDiffsAvg.length} averages:`, ultimateAvg, "(milliseconds)\n"); + + // Create a new PassThrough stream and write the overall average in ms + const ps = new PassThrough({ encoding: "utf-8" }); + ps.write(ultimateAvg.toString()+ "\n"); + + return ps; + } +]; diff --git a/typescript/mgr-sequence/timestamp-manager/package.json b/typescript/mgr-sequence/timestamp-manager/package.json new file mode 100644 index 0000000..d031653 --- /dev/null +++ b/typescript/mgr-sequence/timestamp-manager/package.json @@ -0,0 +1,35 @@ +{ + "name": "@scramjet/timestamp-manager", + "version": "0.1.0", + "main": "dist/index.js", + "author": "Scramjet ", + "license": "ISC", + "description": "This package contains a Scramjet Sequence that starts multiple instances of the '@scramjet/timestamp-producer' sequence, calculates the time differences between received timestamps and local timestamps, and returns the overall average difference in milliseconds to the Instance output and stdout.", + "keywords": [ + "sample", + "easy", + "streaming", + "Data Producer" + ], + "repository": { + "type": "git", + "url": "https://github.com/scramjetorg/platform-samples/tree/main/typescript/mgr-sequence" + }, + "assets": [ + "real-hrtime.node" + ], + "scripts": { + "build": "tsc -p tsconfig.json", + "postbuild": "cp package.json dist/ && cp -r real-hrtime.node dist/ && (cd dist && npm i --omit=dev)", + "pack": "si seq pack ./dist/", + "clean": "rm -rf ./dist ./*.tar.gz" + }, + "devDependencies": { + "@scramjet/types": "^0.34.0", + "@types/node": "15.12.5" + }, + "dependencies": { + "@scramjet/api-client": "^0.34.0", + "scramjet": "^4.36.9" + } +} diff --git a/typescript/mgr-sequence/timestamp-manager/real-hrtime.node b/typescript/mgr-sequence/timestamp-manager/real-hrtime.node new file mode 100644 index 0000000000000000000000000000000000000000..ec5b2149c0a6e0c433d3deebdc8e0ccce2db8aaa GIT binary patch literal 45896 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, _stream: any, tsQuantity: number = 128, delay: number = 100) { + this.logger.info(`Sending ${tsQuantity} timestamps to output`); + const p = new Readable({ read: () => {}}); + let l = 0; + + // Event listener for the "start" event + this.on("start", async () => { + while (l++ < tsQuantity) { + await new Promise(res => setTimeout(res, delay)); + + if (!p.push(`${rht.stringified()}\n`)) { + await new Promise(res => p.once("drain", res)); + } + } + + await new Promise(res => setTimeout(res, 10)); + + p.push("0\n"); + }); + + return p; + } +]; diff --git a/typescript/mgr-sequence/timestamp-producer/package.json b/typescript/mgr-sequence/timestamp-producer/package.json new file mode 100644 index 0000000..f8a1070 --- /dev/null +++ b/typescript/mgr-sequence/timestamp-producer/package.json @@ -0,0 +1,37 @@ +{ + "name": "@scramjet/timestamp-producer", + "version": "0.1.0", + "main": "dist/index.js", + "author": "Scramjet ", + "license": "ISC", + "description": "This package contains a Scramjet Sequence that generates and sends timestamps to the output stream. Upon receiving the 'start' event, the Sequence starts generating the specified number of timestamps at the given interval and pushes them to the output stream. It uses the 'real-hrtime' module to generate timestamps. After generating the desired number of timestamps, it pushes an end indicator to signify the end of data.", + "keywords": [ + "sample", + "easy", + "streaming", + "Data Producer" + ], + "repository": { + "type": "git", + "url": "https://github.com/scramjetorg/platform-samples/tree/main/typescript/mgr-sequence" + }, + "assets": [ + "real-hrtime.node" + ], "scripts": { + "build": "tsc -p tsconfig.json", + "postbuild": "cp package.json dist/ && cp -r real-hrtime.node dist/ && (cd dist && npm i --omit=dev)", + "pack": "si seq pack ./dist/", + "clean": "rm -rf ./dist ./*.tar.gz" + }, + "engines": { + "node": ">=14" + }, + "devDependencies": { + "@scramjet/types": "^0.34.0", + "@types/node": "15.12.5" + }, + "dependencies": { + "@scramjet/api-client": "^0.34.0", + "scramjet": "^4.36.9" + } +} diff --git a/typescript/mgr-sequence/timestamp-producer/real-hrtime.node b/typescript/mgr-sequence/timestamp-producer/real-hrtime.node new file mode 100644 index 0000000000000000000000000000000000000000..ec5b2149c0a6e0c433d3deebdc8e0ccce2db8aaa GIT binary patch literal 45896 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