From 9ada1029a151c24b6c952bac0f7d6959e766c035 Mon Sep 17 00:00:00 2001 From: sweetim Date: Sun, 5 Mar 2023 18:51:59 +0900 Subject: [PATCH 1/9] fix lint error --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index af9f760..8f4ee15 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,8 @@ createReadStream(TOP_FILE_PATH) ### Examples There are 2 code examples shown in the [example](https://github.com/sweetim/linux-top-parser/tree/master/example) folder on how to use this package -- [read from file](https://github.com/sweetim/linux-top-parser/blob/master/example/read-from-file.ts) -- [stream from the linux `top` command output](https://github.com/sweetim/linux-top-parser/blob/master/example/stream-from-top-command.ts) +- [read from file](https://github.com/sweetim/linux-top-parser/blob/master/example/read-from-file.ts) +- [stream from the linux `top` command output](https://github.com/sweetim/linux-top-parser/blob/master/example/stream-from-top-command.ts) ## CLI @@ -90,4 +90,4 @@ Options: ``` ### Reference -- https://man7.org/linux/man-pages/man1/top.1.html +- https://man7.org/linux/man-pages/man1/top.1.html From c5ec6db4c63a4c7cfc5ca86681065d591810141a Mon Sep 17 00:00:00 2001 From: sweetim Date: Mon, 6 Mar 2023 20:44:12 +0900 Subject: [PATCH 2/9] fix sonarlint --- .vscode/settings.json | 6 ++++++ src/parser/using-regex.ts | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..42a1b82 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "sonarlint.connectedMode.project": { + "connectionId": "sweetim-github", + "projectKey": "sweetim_linux-top-parser" + } +} diff --git a/src/parser/using-regex.ts b/src/parser/using-regex.ts index 2867a6e..1bab9c0 100644 --- a/src/parser/using-regex.ts +++ b/src/parser/using-regex.ts @@ -18,7 +18,7 @@ import { import { fromDays, fromHours, fromMinutes } from "./util" export function parseUpTime_s(input: string): number { - const matcher = /(?:(\d+)\s\bday[s]?,\s)?(?:(\d+:\d+)|(\d+)\smin)/gm + const matcher = /(?:(\d+)\s\bdays?,\s)?(?:(\d+:\d+)|(\d+)\smin)/gm const tokens = Array.from(input.matchAll(matcher)).flat() if (tokens.length === 0) { @@ -40,7 +40,7 @@ export function parseUpTime_s(input: string): number { } export function parseUpTimeAndLoadAverage(line: string): UpTimeAndLoadAverage { - const matcher = /top - ([\d:]+) up (.+(?=,\s+\d \buser)),\s+(\d) \buser[s]?,\s+load average:(\s[\d.]+),(\s[\d.]+),(\s[\d.]+,?)/gm + const matcher = /top - ([\d:]+) up (.+(?=,\s+\d \buser)),\s+(\d) \busers?,\s+load average:(\s[\d.]+),(\s[\d.]+),(\s[\d.]+,?)/gm const tokens = Array.from(line.matchAll(matcher)).flat() if (tokens.length === 0) { @@ -75,7 +75,7 @@ export function parseTaskStates(str: string): TaskStates { } export function parseCpuStates(line: string): CpuStates[] { - const matcher = /^%Cpu([\d]+|\b\(s\))\s*:\s*(\d+.\d+)\sus,\s*(\d+.\d+)\ssy,\s*(\d+.\d+)\sni,\s*(\d+.\d+)\sid,\s*(\d+.\d+)\swa,\s*(\d+.\d+)\shi,\s*(\d+.\d+)\ssi,\s*(\d+.\d+)\sst/gm + const matcher = /^%Cpu(\d+|\b\(s\))\s*:\s*(\d+.\d+)\sus,\s*(\d+.\d+)\ssy,\s*(\d+.\d+)\sni,\s*(\d+.\d+)\sid,\s*(\d+.\d+)\swa,\s*(\d+.\d+)\shi,\s*(\d+.\d+)\ssi,\s*(\d+.\d+)\sst/gm const tokens = Array.from(line.matchAll(matcher)) if (tokens.length === 0) { From fcaabece3916eb28337e26dbeffbddab5ef57a1b Mon Sep 17 00:00:00 2001 From: sweetim Date: Mon, 6 Mar 2023 20:44:20 +0900 Subject: [PATCH 3/9] refactor import --- example/read-from-file.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/example/read-from-file.ts b/example/read-from-file.ts index 06165a9..61a805e 100644 --- a/example/read-from-file.ts +++ b/example/read-from-file.ts @@ -1,7 +1,6 @@ import { resolve } from "path" import { readFileSync, createReadStream } from "fs" -import { parseTopInfo } from "../src" -import { topInfoTransform } from "../src/streams" +import { parseTopInfo, topInfoTransform } from "../src" const TOP_FILE_PATH = resolve(__dirname, "..", "test", "data", "multi.txt") From a3dd3eddc3ab6d840852b5f9bca49b30aad2bc99 Mon Sep 17 00:00:00 2001 From: sweetim Date: Mon, 6 Mar 2023 20:47:55 +0900 Subject: [PATCH 4/9] change to use console log --- src/bin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin.ts b/src/bin.ts index 6415fe7..9643da4 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -53,4 +53,4 @@ process.stdin summary, filter })) - .pipe(process.stdout) + .on("data", console.log) From 54dbf18c1d1283eab66676bc43302e31d7e74655 Mon Sep 17 00:00:00 2001 From: sweetim Date: Mon, 6 Mar 2023 21:31:22 +0900 Subject: [PATCH 5/9] added timeout option --- src/bin.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/bin.ts b/src/bin.ts index 9643da4..a759c13 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -32,6 +32,7 @@ program .version(packageJson.version || "") .description("parser for linux top command") .addHelpText("after", HELPER_TEXT) + .option("-t, --timeOut_ms ", "specify the timeout value to emit the buffer", "100") .option("-s, --summary", "output summary display only", false) .option("-p, --prettify", "output top info with indentation and color", false) .option("-f, --filter", "output process that has > 0% of CPU usage only", false) @@ -41,16 +42,18 @@ type CLIOptions = { prettify: boolean summary: boolean filter: boolean + timeOut_ms: number } -const { prettify, summary, filter } = program.opts() - +const { prettify, summary, filter, timeOut_ms } = program.opts() +console.log(program.opts()) process.stdin .pipe(topInfoTransform({ stringify: { prettify }, summary, - filter + filter, + timeOut_ms: Number(timeOut_ms) })) .on("data", console.log) From 9929a7338ab6d08fd7ec02acea8e6935cca526ca Mon Sep 17 00:00:00 2001 From: sweetim Date: Mon, 6 Mar 2023 21:33:50 +0900 Subject: [PATCH 6/9] added timeout in transform --- src/streams/top-info-transform.test.ts | 37 +++++++++++++++++++++----- src/streams/top-info-transform.ts | 34 ++++++++++++++++++++--- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/streams/top-info-transform.test.ts b/src/streams/top-info-transform.test.ts index a1b5c16..f4e6a15 100644 --- a/src/streams/top-info-transform.test.ts +++ b/src/streams/top-info-transform.test.ts @@ -19,7 +19,8 @@ describe("parseTopInfoTransformOptions", () => { isStringify: false, isPrettify: false, isSummary: false, - isFilter: false + isFilter: false, + timeOut_ms: 100 } expect(parseTopInfoTransformOptions()).toStrictEqual(expected) @@ -35,7 +36,8 @@ describe("parseTopInfoTransformOptions", () => { isStringify: false, isPrettify: false, isSummary: false, - isFilter: false + isFilter: false, + timeOut_ms: 100 } expect(parseTopInfoTransformOptions(input)).toStrictEqual(expected) @@ -53,7 +55,8 @@ describe("parseTopInfoTransformOptions", () => { isStringify: true, isPrettify: false, isSummary: false, - isFilter: false + isFilter: false, + timeOut_ms: 100 } expect(parseTopInfoTransformOptions(input)).toStrictEqual(expected) @@ -70,7 +73,8 @@ describe("parseTopInfoTransformOptions", () => { isStringify: true, isPrettify: true, isSummary: false, - isFilter: false + isFilter: false, + timeOut_ms: 100 } expect(parseTopInfoTransformOptions(input)).toStrictEqual(expected) @@ -90,7 +94,8 @@ describe("parseTopInfoTransformOptions", () => { isStringify: true, isPrettify: false, isSummary: false, - isFilter: false + isFilter: false, + timeOut_ms: 100 } expect(parseTopInfoTransformOptions(input)).toStrictEqual(expected) @@ -108,7 +113,8 @@ describe("parseTopInfoTransformOptions", () => { isStringify: false, isPrettify: false, isSummary: summary, - isFilter: false + isFilter: false, + timeOut_ms: 100 } expect(parseTopInfoTransformOptions(input)).toStrictEqual(expected) @@ -126,7 +132,24 @@ describe("parseTopInfoTransformOptions", () => { isStringify: false, isPrettify: false, isSummary: false, - isFilter: filter + isFilter: filter, + timeOut_ms: 100 + } + + expect(parseTopInfoTransformOptions(input)).toStrictEqual(expected) + }) + + it("should return timeOut_ms value", () => { + const input: TopInfoTransformOptions = { + timeOut_ms: 200 + } + + const expected: TopInfoTransformConfig = { + isStringify: false, + isPrettify: false, + isSummary: false, + isFilter: false, + timeOut_ms: 200 } expect(parseTopInfoTransformOptions(input)).toStrictEqual(expected) diff --git a/src/streams/top-info-transform.ts b/src/streams/top-info-transform.ts index fe89069..bc93f09 100644 --- a/src/streams/top-info-transform.ts +++ b/src/streams/top-info-transform.ts @@ -9,6 +9,7 @@ export interface TopInfoTransformOptions { stringify?: boolean | TopInfoTransformToStringOpions summary?: boolean filter?: boolean + timeOut_ms?: number } interface TopInfoTransformToStringOpions { @@ -20,6 +21,7 @@ export interface TopInfoTransformConfig { isPrettify: boolean isSummary: boolean isFilter: boolean + timeOut_ms: number } export function parseTopInfoTransformOptions(options?: TopInfoTransformOptions): TopInfoTransformConfig { @@ -28,6 +30,7 @@ export function parseTopInfoTransformOptions(options?: TopInfoTransformOptions): isPrettify: false, isSummary: false, isFilter: false, + timeOut_ms: 100 } if (options === undefined) { @@ -59,6 +62,10 @@ export function parseTopInfoTransformOptions(options?: TopInfoTransformOptions): output.isFilter = options.filter } + if (options.timeOut_ms) { + output.timeOut_ms = options.timeOut_ms + } + return output } @@ -109,7 +116,8 @@ export function topInfoTransform(options?: TopInfoTransformOptions): Transform { return bufferTillNextHeader( /(?=^top)/gm, - topInfoMapping(config) + topInfoMapping(config), + config.timeOut_ms ) } @@ -122,9 +130,11 @@ export function topInfoTransform(options?: TopInfoTransformOptions): Transform { export function bufferTillNextHeader( header: RegExp, /* eslint-disable @typescript-eslint/no-explicit-any */ - mappingFn: (buffer: string) => any = (buffer: string) => buffer): Transform + mappingFn: (buffer: string) => any = (buffer: string) => buffer, + timeOut_ms?: number): Transform { let buffer = "" + let timeOut_id: NodeJS.Timeout | null = null return new Transform({ objectMode: true, @@ -139,8 +149,10 @@ export function bufferTillNextHeader( const isHeader = header.test(msg) if (buffer && isHeader) { - this.push(mappingFn(buffer)) - buffer = "" + if (buffer.trim().length > 0) { + this.push(mappingFn(buffer)) + buffer = "" + } } if (isHeader) { @@ -152,6 +164,20 @@ export function bufferTillNextHeader( buffer += msg }) + if (timeOut_id) { + clearTimeout(timeOut_id) + timeOut_id = null + } + + if (timeOut_ms) { + timeOut_id = setTimeout(() => { + if (buffer.trim().length > 0) { + this.push(mappingFn(buffer)) + buffer = "" + } + }, timeOut_ms) + } + cb() }, }) From 5df051dbc2bdec948e5acfaac12432f6ddceedb8 Mon Sep 17 00:00:00 2001 From: sweetim Date: Mon, 6 Mar 2023 21:59:48 +0900 Subject: [PATCH 7/9] updated readme --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8f4ee15..7261d8c 100644 --- a/README.md +++ b/README.md @@ -79,14 +79,15 @@ top -b | npx linux-top-parser | jq ".[0].summaryDisplay" ### Usage ``` -linux-top-parser [options] +Usage: linux-top-parser [options] Options: - -V, --version output the version number - -s, --summary output summary display only (default: false) - -p, --prettify output top info with indentation and color (default: false) - -f, --filter output process that has > 0% of CPU usage only (default: false) - -h, --help display help for command + -V, --version output the version number + -t, --timeOut_ms specify the timeout value to emit the buffer (default: "100") + -s, --summary output summary display only (default: false) + -p, --prettify output top info with indentation and color (default: false) + -f, --filter output process that has > 0% of CPU usage only (default: false) + -h, --help display help for command ``` ### Reference From 16fccd06f206b8e1b13c386d259cebf409874f77 Mon Sep 17 00:00:00 2001 From: sweetim Date: Mon, 6 Mar 2023 22:00:33 +0900 Subject: [PATCH 8/9] remove console log --- src/bin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin.ts b/src/bin.ts index a759c13..c30d0d1 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -46,7 +46,7 @@ type CLIOptions = { } const { prettify, summary, filter, timeOut_ms } = program.opts() -console.log(program.opts()) + process.stdin .pipe(topInfoTransform({ stringify: { From 1c3cdb4aa9ad85b8a64715e1bc28be4a51b19c9a Mon Sep 17 00:00:00 2001 From: sweetim Date: Mon, 6 Mar 2023 22:13:55 +0900 Subject: [PATCH 9/9] updated jsdoc --- README.md | 2 +- src/bin.ts | 2 +- src/streams/top-info-transform.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7261d8c..d756c2b 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Usage: linux-top-parser [options] Options: -V, --version output the version number - -t, --timeOut_ms specify the timeout value to emit the buffer (default: "100") + -t, --timeOut_ms the maximum amount of time (in milliseconds) to wait for the next header before emitting data (default: "100") -s, --summary output summary display only (default: false) -p, --prettify output top info with indentation and color (default: false) -f, --filter output process that has > 0% of CPU usage only (default: false) diff --git a/src/bin.ts b/src/bin.ts index c30d0d1..73f55c5 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -32,7 +32,7 @@ program .version(packageJson.version || "") .description("parser for linux top command") .addHelpText("after", HELPER_TEXT) - .option("-t, --timeOut_ms ", "specify the timeout value to emit the buffer", "100") + .option("-t, --timeOut_ms ", "the maximum amount of time (in milliseconds) to wait for the next header before emitting data", "100") .option("-s, --summary", "output summary display only", false) .option("-p, --prettify", "output top info with indentation and color", false) .option("-f, --filter", "output process that has > 0% of CPU usage only", false) diff --git a/src/streams/top-info-transform.ts b/src/streams/top-info-transform.ts index bc93f09..0be5b18 100644 --- a/src/streams/top-info-transform.ts +++ b/src/streams/top-info-transform.ts @@ -125,6 +125,7 @@ export function topInfoTransform(options?: TopInfoTransformOptions): Transform { * Buffers data chunks until a header is found and then applies a mapping function to the buffer. * @param {RegExp} header - The regular expression that matches the header of a new chunk. * @param {(buffer: string) => any} [mappingFn] - The function that transforms the buffer into a desired format. By default, it returns the buffer as it is. + * @param {number} timeOut_ms - The maximum amount of time (in milliseconds) to wait for the next header before emitting data. Optional. * @returns {Transform} A transform stream that buffers and maps data chunks based on the header. */ export function bufferTillNextHeader(