sm-utility 2.4.30 → 2.4.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,14 +10,14 @@ const logger_1 = require("../../logger");
10
10
  class ExcelCreator {
11
11
  create(sheets) {
12
12
  try {
13
- logger_1.logger.info("ExcelCreator initiated", { context: sheets });
13
+ logger_1.logger.info("ExcelCreator initiated", { context: { sheets_count: sheets.length } });
14
14
  const builderSheets = sheets.map((sheet) => new sheetExcelCreator_1.SheetExcelCreator().create(sheet));
15
15
  const buffer = node_xlsx_1.default.build(builderSheets);
16
- logger_1.logger.info("ExcelCreator finished", { context: sheets });
16
+ logger_1.logger.info("ExcelCreator finished", { context: { sheets_count: sheets.length } });
17
17
  return buffer;
18
18
  }
19
19
  catch (error) {
20
- logger_1.logger.error("ExcelCreator failed", { context: sheets, error });
20
+ logger_1.logger.error("ExcelCreator failed", { context: { sheets_count: sheets.length }, error });
21
21
  throw error;
22
22
  }
23
23
  }
package/index.js CHANGED
@@ -27,3 +27,4 @@ __exportStar(require("./thread"), exports);
27
27
  __exportStar(require("./excel"), exports);
28
28
  __exportStar(require("./errors"), exports);
29
29
  __exportStar(require("./tracing"), exports);
30
+ __exportStar(require("./profiler"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sm-utility",
3
- "version": "2.4.30",
3
+ "version": "2.4.32",
4
4
  "description": "reusable utility codes for sm projects",
5
5
  "main": "index.js",
6
6
  "types": "./index.d.ts",
@@ -0,0 +1,3 @@
1
+ export { Profiler } from "./profiler";
2
+ export { ProfilingPathBuilder, ProfilingType, } from "./profilingPath.builder";
3
+ export { IFileUploader, ProfilingSignalsManager, } from "./profilingSignalsManager";
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProfilingSignalsManager = exports.ProfilingType = exports.ProfilingPathBuilder = exports.Profiler = void 0;
4
+ var profiler_1 = require("./profiler");
5
+ Object.defineProperty(exports, "Profiler", { enumerable: true, get: function () { return profiler_1.Profiler; } });
6
+ var profilingPath_builder_1 = require("./profilingPath.builder");
7
+ Object.defineProperty(exports, "ProfilingPathBuilder", { enumerable: true, get: function () { return profilingPath_builder_1.ProfilingPathBuilder; } });
8
+ Object.defineProperty(exports, "ProfilingType", { enumerable: true, get: function () { return profilingPath_builder_1.ProfilingType; } });
9
+ var profilingSignalsManager_1 = require("./profilingSignalsManager");
10
+ Object.defineProperty(exports, "ProfilingSignalsManager", { enumerable: true, get: function () { return profilingSignalsManager_1.ProfilingSignalsManager; } });
@@ -0,0 +1,15 @@
1
+ import { Writable } from "stream";
2
+ type Params = {
3
+ outputStream: Writable;
4
+ };
5
+ type ProfileParams = Params & {
6
+ /** duration in milliseconds for the profiling period */
7
+ duration?: number;
8
+ };
9
+ export declare class Profiler {
10
+ static takeHeapSnapshot({ outputStream, }: Params): Promise<void>;
11
+ static takeCpuProfile({ outputStream, duration, }: ProfileParams): Promise<void>;
12
+ static takeHeapProfile({ outputStream, duration, }: ProfileParams): Promise<void>;
13
+ private static post;
14
+ }
15
+ export {};
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Profiler = void 0;
4
+ const inspector_1 = require("inspector");
5
+ const logger_1 = require("../logger");
6
+ class Profiler {
7
+ static async takeHeapSnapshot({ outputStream, }) {
8
+ const session = new inspector_1.Session();
9
+ session.connect();
10
+ try {
11
+ await this.post(session, "HeapProfiler.enable");
12
+ session.on("HeapProfiler.addHeapSnapshotChunk", (m) => {
13
+ outputStream.write(m.params.chunk);
14
+ });
15
+ await this.post(session, "HeapProfiler.takeHeapSnapshot");
16
+ await this.post(session, "HeapProfiler.disable");
17
+ }
18
+ catch (err) {
19
+ logger_1.logger.error(err);
20
+ }
21
+ finally {
22
+ session.disconnect();
23
+ }
24
+ }
25
+ static async takeCpuProfile({ outputStream, duration = 10000, }) {
26
+ const session = new inspector_1.Session();
27
+ session.connect();
28
+ try {
29
+ await this.post(session, "Profiler.enable");
30
+ await this.post(session, "Profiler.start");
31
+ await new Promise((resolve) => {
32
+ setTimeout(resolve, duration);
33
+ });
34
+ const { profile } = await this.post(session, "Profiler.stop");
35
+ outputStream.write(JSON.stringify(profile));
36
+ }
37
+ catch (err) {
38
+ logger_1.logger.error(err);
39
+ }
40
+ finally {
41
+ session.disconnect();
42
+ }
43
+ }
44
+ static async takeHeapProfile({ outputStream, duration = 10000, }) {
45
+ const session = new inspector_1.Session();
46
+ session.connect();
47
+ try {
48
+ await this.post(session, "HeapProfiler.enable");
49
+ await this.post(session, "HeapProfiler.startSampling");
50
+ await new Promise((resolve) => {
51
+ setTimeout(resolve, duration);
52
+ });
53
+ const { profile } = await this.post(session, "HeapProfiler.stopSampling");
54
+ outputStream.write(JSON.stringify(profile));
55
+ await this.post(session, "HeapProfiler.disable");
56
+ }
57
+ catch (err) {
58
+ logger_1.logger.error(err);
59
+ }
60
+ finally {
61
+ session.disconnect();
62
+ }
63
+ }
64
+ static post(session, method, params) {
65
+ return new Promise((resolve, reject) => {
66
+ session.post(method, params || {}, (err, result) => {
67
+ if (err) {
68
+ reject(err);
69
+ return;
70
+ }
71
+ resolve(result);
72
+ });
73
+ });
74
+ }
75
+ }
76
+ exports.Profiler = Profiler;
@@ -0,0 +1,27 @@
1
+ export declare enum ProfilingType {
2
+ HEAP_SNAPSHOT = "HEAP_SNAPSHOT",
3
+ HEAP_PROFILE = "HEAP_PROFILE",
4
+ CPU_PROFILE = "CPU_PROFILE"
5
+ }
6
+ export declare class ProfilingPathBuilder {
7
+ /**
8
+ * Build the filename with the format:
9
+ * YYYY-MM-DD_HH-MM-SS_{env}_{serviceName}_{profileType}.{extension}
10
+ */
11
+ static buildFileName({ type, serviceName, env, timestamp, }: {
12
+ type: ProfilingType;
13
+ serviceName: string;
14
+ env: string;
15
+ timestamp?: Date;
16
+ }): string;
17
+ /**
18
+ * Build the S3 key based on the structure:
19
+ * {env}/{serviceName}/{profileType-folder}/{filename}
20
+ */
21
+ static buildS3Key({ env, serviceName, type, fileName, }: {
22
+ env: string;
23
+ serviceName: string;
24
+ type: ProfilingType;
25
+ fileName: string;
26
+ }): string;
27
+ }
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProfilingPathBuilder = exports.ProfilingType = void 0;
4
+ var ProfilingType;
5
+ (function (ProfilingType) {
6
+ ProfilingType["HEAP_SNAPSHOT"] = "HEAP_SNAPSHOT";
7
+ ProfilingType["HEAP_PROFILE"] = "HEAP_PROFILE";
8
+ ProfilingType["CPU_PROFILE"] = "CPU_PROFILE";
9
+ })(ProfilingType || (exports.ProfilingType = ProfilingType = {}));
10
+ const FileExtension = {
11
+ HEAP_SNAPSHOT: "heapsnapshot",
12
+ HEAP_PROFILE: "heapprofile",
13
+ CPU_PROFILE: "cpuprofile",
14
+ };
15
+ class ProfilingPathBuilder {
16
+ /**
17
+ * Build the filename with the format:
18
+ * YYYY-MM-DD_HH-MM-SS_{env}_{serviceName}_{profileType}.{extension}
19
+ */
20
+ static buildFileName({ type, serviceName, env, timestamp = new Date(), }) {
21
+ const ext = FileExtension[type];
22
+ const formattedDate = timestamp
23
+ .toISOString()
24
+ .replace("T", "_")
25
+ .replace(/:/g, "-")
26
+ .replace(/\..+/, "");
27
+ return `${formattedDate}_${env}_${serviceName}.${ext}`;
28
+ }
29
+ /**
30
+ * Build the S3 key based on the structure:
31
+ * {env}/{serviceName}/{profileType-folder}/{filename}
32
+ */
33
+ static buildS3Key({ env, serviceName, type, fileName, }) {
34
+ return `${env}/${serviceName}/${FileExtension[type]}/${fileName}`;
35
+ }
36
+ }
37
+ exports.ProfilingPathBuilder = ProfilingPathBuilder;
@@ -0,0 +1,51 @@
1
+ type Options = {
2
+ /**
3
+ * If true, the heap snapshot will be taken when the SIGUSR1 signal is received.
4
+ */
5
+ heapSnapshot?: boolean;
6
+ /**
7
+ * If true, the sampling profiles for CPU and Heap will be taken when the SIGUSR2 signal is received.
8
+ * To configure the sampling profiles, the configured instructions file must be created with the following format:
9
+ * ```
10
+ * cpu;15000
11
+ * heap;15000
12
+ * ```
13
+ * The format is `mode;duration` where `mode` is `cpu` or `heap` and `duration` is the duration in milliseconds.
14
+ */
15
+ samplingProfiles?: boolean;
16
+ };
17
+ type ProfilingLogger = {
18
+ info(message: unknown, ...meta: unknown[]): void;
19
+ error(message: unknown, ...meta: unknown[]): void;
20
+ };
21
+ export interface IFileUploader {
22
+ uploadFile(destBucket: string, destKey: string, body: Buffer, contentType?: string): Promise<string | void>;
23
+ }
24
+ type ProfilingSignalsManagerParams = {
25
+ serviceName: string;
26
+ environment: string;
27
+ fileUploader?: IFileUploader;
28
+ bucket?: string;
29
+ profilingInstructionsPath?: string;
30
+ outputDirectory?: string;
31
+ shouldUpload?: () => boolean;
32
+ logger?: ProfilingLogger;
33
+ };
34
+ export declare class ProfilingSignalsManager {
35
+ private readonly params;
36
+ private readonly bucket;
37
+ private readonly profilingInstructionsPath;
38
+ private readonly outputDirectory;
39
+ private readonly shouldUpload;
40
+ private readonly logger;
41
+ constructor(params: ProfilingSignalsManagerParams);
42
+ registerHandlers(options: Options): void;
43
+ private registerHeapSnapshotSignalListener;
44
+ private handleHeapSnapshot;
45
+ private registerSamplingProfilesSignalListener;
46
+ private handleHeapProfiling;
47
+ private handleCpuProfiling;
48
+ private handleProfileOutput;
49
+ private removeFile;
50
+ }
51
+ export {};
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ProfilingSignalsManager = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const logger_1 = require("../logger");
10
+ const profiler_1 = require("./profiler");
11
+ const profilingPath_builder_1 = require("./profilingPath.builder");
12
+ class ProfilingSignalsManager {
13
+ constructor(params) {
14
+ this.params = params;
15
+ this.bucket = params.bucket || "smartrips-profiling";
16
+ this.profilingInstructionsPath =
17
+ params.profilingInstructionsPath || "/tmp/profiling";
18
+ this.outputDirectory = params.outputDirectory || process.cwd();
19
+ this.shouldUpload =
20
+ params.shouldUpload || (() => process.env.NODE_ENV !== "dev");
21
+ this.logger = params.logger || logger_1.logger;
22
+ }
23
+ registerHandlers(options) {
24
+ if (options.heapSnapshot) {
25
+ this.registerHeapSnapshotSignalListener();
26
+ }
27
+ if (options.samplingProfiles) {
28
+ this.registerSamplingProfilesSignalListener();
29
+ }
30
+ }
31
+ registerHeapSnapshotSignalListener() {
32
+ process.on("SIGUSR1", () => {
33
+ try {
34
+ this.handleHeapSnapshot();
35
+ }
36
+ catch (err) {
37
+ this.logger.error(err);
38
+ }
39
+ });
40
+ }
41
+ handleHeapSnapshot() {
42
+ this.logger.info("Heap snapshot signal received");
43
+ const fileName = profilingPath_builder_1.ProfilingPathBuilder.buildFileName({
44
+ type: profilingPath_builder_1.ProfilingType.HEAP_SNAPSHOT,
45
+ serviceName: this.params.serviceName,
46
+ env: this.params.environment,
47
+ });
48
+ const filePath = path_1.default.resolve(this.outputDirectory, fileName);
49
+ const writeStream = fs_1.default.createWriteStream(filePath);
50
+ writeStream.on("finish", async () => {
51
+ await this.handleProfileOutput({
52
+ filePath,
53
+ fileName,
54
+ type: profilingPath_builder_1.ProfilingType.HEAP_SNAPSHOT,
55
+ contentType: "application/octet-stream",
56
+ profileName: "Heap snapshot",
57
+ });
58
+ });
59
+ profiler_1.Profiler.takeHeapSnapshot({ outputStream: writeStream })
60
+ .then(() => writeStream.end())
61
+ .catch((err) => this.logger.error(err));
62
+ }
63
+ registerSamplingProfilesSignalListener() {
64
+ process.on("SIGUSR2", () => {
65
+ try {
66
+ const samplingInstructions = fs_1.default
67
+ .readFileSync(this.profilingInstructionsPath, "utf-8")
68
+ .trim();
69
+ const [mode, duration] = samplingInstructions.split(";");
70
+ const durationInMs = Number(duration);
71
+ if (!mode || Number.isNaN(durationInMs)) {
72
+ this.logger.error("Invalid sampling instructions. Use format cpu;15000");
73
+ return;
74
+ }
75
+ if (mode === "cpu") {
76
+ this.handleCpuProfiling({ duration: durationInMs });
77
+ }
78
+ else if (mode === "heap") {
79
+ this.handleHeapProfiling({ duration: durationInMs });
80
+ }
81
+ else {
82
+ this.logger.error("Unsupported mode. Use cpu or heap.");
83
+ }
84
+ }
85
+ catch (err) {
86
+ this.logger.error(err);
87
+ }
88
+ });
89
+ }
90
+ handleHeapProfiling({ duration }) {
91
+ this.logger.info(`Heap profile signal received for ${duration}ms`);
92
+ const fileName = profilingPath_builder_1.ProfilingPathBuilder.buildFileName({
93
+ type: profilingPath_builder_1.ProfilingType.HEAP_PROFILE,
94
+ serviceName: this.params.serviceName,
95
+ env: this.params.environment,
96
+ });
97
+ const filePath = path_1.default.resolve(this.outputDirectory, fileName);
98
+ const writeStream = fs_1.default.createWriteStream(filePath);
99
+ writeStream.on("finish", async () => {
100
+ await this.handleProfileOutput({
101
+ filePath,
102
+ fileName,
103
+ type: profilingPath_builder_1.ProfilingType.HEAP_PROFILE,
104
+ profileName: "Heap profile",
105
+ });
106
+ });
107
+ profiler_1.Profiler.takeHeapProfile({ outputStream: writeStream, duration })
108
+ .then(() => writeStream.end())
109
+ .catch((err) => this.logger.error(err));
110
+ }
111
+ handleCpuProfiling({ duration }) {
112
+ this.logger.info(`CPU profile signal received for ${duration}ms`);
113
+ const type = profilingPath_builder_1.ProfilingType.CPU_PROFILE;
114
+ const fileName = profilingPath_builder_1.ProfilingPathBuilder.buildFileName({
115
+ type,
116
+ serviceName: this.params.serviceName,
117
+ env: this.params.environment,
118
+ });
119
+ const filePath = path_1.default.resolve(this.outputDirectory, fileName);
120
+ const writeStream = fs_1.default.createWriteStream(filePath);
121
+ writeStream.on("finish", async () => {
122
+ await this.handleProfileOutput({
123
+ filePath,
124
+ fileName,
125
+ type,
126
+ profileName: "CPU profile",
127
+ });
128
+ });
129
+ profiler_1.Profiler.takeCpuProfile({ outputStream: writeStream, duration })
130
+ .then(() => writeStream.end())
131
+ .catch((err) => this.logger.error(err));
132
+ }
133
+ async handleProfileOutput({ filePath, fileName, type, contentType, profileName, }) {
134
+ try {
135
+ if (this.shouldUpload()) {
136
+ const s3Key = profilingPath_builder_1.ProfilingPathBuilder.buildS3Key({
137
+ fileName,
138
+ type,
139
+ serviceName: this.params.serviceName,
140
+ env: this.params.environment,
141
+ });
142
+ if (!this.params.fileUploader) {
143
+ throw new Error("Profiling fileUploader is required.");
144
+ }
145
+ const uploadedTo = await this.params.fileUploader.uploadFile(this.bucket, s3Key, fs_1.default.readFileSync(filePath), contentType);
146
+ this.logger.info(`${profileName} saved to ${uploadedTo || s3Key}`);
147
+ this.removeFile(filePath);
148
+ }
149
+ else {
150
+ this.logger.info(`${profileName} saved to ${filePath}`);
151
+ }
152
+ }
153
+ catch (err) {
154
+ this.logger.error(err);
155
+ this.removeFile(filePath);
156
+ }
157
+ }
158
+ removeFile(filePath) {
159
+ if (fs_1.default.existsSync(filePath)) {
160
+ fs_1.default.unlinkSync(filePath);
161
+ }
162
+ }
163
+ }
164
+ exports.ProfilingSignalsManager = ProfilingSignalsManager;