@tramvai/module-performance-profile 6.81.11

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.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # @tramvai/module-performance-profile
2
+
3
+ [Actual documentation](guides/server-optimization/runtime-profiling.md)
@@ -0,0 +1,4 @@
1
+ export { PERFORMANCE_PROFILE_OPTIONS_TOKEN } from './tokens';
2
+ export declare class PerformanceProfileModule {
3
+ }
4
+ //# sourceMappingURL=browser.d.ts.map
package/lib/browser.js ADDED
@@ -0,0 +1,14 @@
1
+ import { __decorate } from 'tslib';
2
+ import { Module } from '@tramvai/core';
3
+ export { PERFORMANCE_PROFILE_OPTIONS_TOKEN } from './tokens.browser.js';
4
+
5
+ let PerformanceProfileModule = class PerformanceProfileModule {
6
+ };
7
+ PerformanceProfileModule = __decorate([
8
+ Module({
9
+ imports: [],
10
+ providers: [],
11
+ })
12
+ ], PerformanceProfileModule);
13
+
14
+ export { PerformanceProfileModule };
@@ -0,0 +1,4 @@
1
+ export { PERFORMANCE_PROFILE_OPTIONS_TOKEN } from './tokens';
2
+ export declare class PerformanceProfileModule {
3
+ }
4
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1,211 @@
1
+ import { __decorate } from 'tslib';
2
+ import path from 'node:path';
3
+ import fs from 'node:fs';
4
+ import { finished } from 'node:stream/promises';
5
+ import { Session } from 'node:inspector';
6
+ import { createPapiMethod } from '@tramvai/papi';
7
+ import { Module, provide } from '@tramvai/core';
8
+ import { SERVER_MODULE_PAPI_PRIVATE_ROUTE } from '@tramvai/tokens-server';
9
+ import { LOGGER_TOKEN } from '@tramvai/module-common';
10
+ import { FASTIFY_RESPONSE } from '@tramvai/tokens-server-private';
11
+ import { PERFORMANCE_PROFILE_OPTIONS_TOKEN, PERFORMANCE_PROFILE_STATE_TOKEN } from './tokens.es.js';
12
+ export { PERFORMANCE_PROFILE_OPTIONS_TOKEN } from './tokens.es.js';
13
+
14
+ const REQUEST_TIMEOUT = 120000;
15
+ const DEFAULT_DURATION = 60000;
16
+ const papiTakeMemorySnapshotUrl = '/takeMemorySnapshot';
17
+ const papiGetMemorySnapshotUrl = '/getMemorySnapshot';
18
+ const papiCpuProfileUrl = '/getCpuProfile';
19
+ const post = (session, method) => new Promise((resolve, reject) => {
20
+ session.post(method, undefined, (err, result) => {
21
+ if (err) {
22
+ reject(err);
23
+ }
24
+ else {
25
+ resolve(result);
26
+ }
27
+ });
28
+ });
29
+ let PerformanceProfileModule = class PerformanceProfileModule {
30
+ };
31
+ PerformanceProfileModule = __decorate([
32
+ Module({
33
+ imports: [],
34
+ providers: [
35
+ provide({
36
+ provide: SERVER_MODULE_PAPI_PRIVATE_ROUTE,
37
+ multi: true,
38
+ useFactory: ({ logger, options, state }) => {
39
+ const log = logger('performance-profile:inspect-memory');
40
+ const tmpDir = options?.tmpDir ?? '/tmp';
41
+ return createPapiMethod({
42
+ method: 'get',
43
+ options: {
44
+ timeout: REQUEST_TIMEOUT,
45
+ },
46
+ path: papiTakeMemorySnapshotUrl,
47
+ async handler() {
48
+ if (state.inProgress) {
49
+ this.deps.res.code(409).send(`Snapshot already generating`);
50
+ return;
51
+ }
52
+ state.inProgress = true;
53
+ const fileName = `heapdump-app.heapsnapshot`;
54
+ const filePath = path.join(tmpDir, fileName);
55
+ this.deps.res.send(`Creating snapshot for app`);
56
+ const session = new Session();
57
+ session.connect();
58
+ try {
59
+ if (fs.existsSync(filePath)) {
60
+ await fs.promises.unlink(filePath);
61
+ }
62
+ const writeStream = fs.createWriteStream(filePath);
63
+ session.on('HeapProfiler.addHeapSnapshotChunk', ({ params: { chunk } }) => {
64
+ writeStream.write(chunk);
65
+ });
66
+ await post(session, 'HeapProfiler.takeHeapSnapshot');
67
+ log.info('Snapshot created successfully');
68
+ writeStream.end();
69
+ await finished(writeStream);
70
+ }
71
+ catch (error) {
72
+ log.error({
73
+ event: 'snapshot-creation-failed',
74
+ message: 'Failed to create snapshot',
75
+ error,
76
+ });
77
+ }
78
+ finally {
79
+ session.disconnect();
80
+ state.inProgress = false;
81
+ }
82
+ },
83
+ deps: {
84
+ res: FASTIFY_RESPONSE,
85
+ },
86
+ });
87
+ },
88
+ deps: {
89
+ logger: LOGGER_TOKEN,
90
+ options: { token: PERFORMANCE_PROFILE_OPTIONS_TOKEN, optional: true },
91
+ state: PERFORMANCE_PROFILE_STATE_TOKEN,
92
+ },
93
+ }),
94
+ provide({
95
+ provide: SERVER_MODULE_PAPI_PRIVATE_ROUTE,
96
+ multi: true,
97
+ useFactory: ({ logger, options, state }) => {
98
+ const log = logger('performance-profile:inspect-memory');
99
+ const tmpDir = options?.tmpDir ?? '/tmp';
100
+ return createPapiMethod({
101
+ method: 'get',
102
+ options: {
103
+ timeout: REQUEST_TIMEOUT,
104
+ },
105
+ path: papiGetMemorySnapshotUrl,
106
+ async handler({ responseManager }) {
107
+ if (state.inProgress) {
108
+ responseManager.setStatus(425);
109
+ responseManager.setBody({
110
+ resultCode: 'Error',
111
+ errorMessage: 'Snapshot not ready yet',
112
+ });
113
+ return;
114
+ }
115
+ const fileName = `heapdump-app.heapsnapshot`;
116
+ const filePath = path.join(tmpDir, fileName);
117
+ if (!fs.existsSync(filePath)) {
118
+ const error = {
119
+ resultCode: 'Error',
120
+ errorMessage: 'Snapshot not found',
121
+ };
122
+ responseManager.setStatus(404);
123
+ responseManager.setBody(error);
124
+ return;
125
+ }
126
+ const stream = fs.createReadStream(filePath);
127
+ stream.on('close', () => {
128
+ fs.unlink(filePath, (error) => {
129
+ if (error) {
130
+ log.error({
131
+ event: 'snapshot-deletion-failed',
132
+ message: 'Failed to delete snapshot',
133
+ error,
134
+ });
135
+ }
136
+ });
137
+ });
138
+ this.deps.res.header('Content-Type', 'application/octet-stream');
139
+ this.deps.res.header('Content-Disposition', `attachment; filename="${fileName}"`);
140
+ await this.deps.res.send(stream);
141
+ },
142
+ deps: {
143
+ res: FASTIFY_RESPONSE,
144
+ },
145
+ });
146
+ },
147
+ deps: {
148
+ logger: LOGGER_TOKEN,
149
+ options: { token: PERFORMANCE_PROFILE_OPTIONS_TOKEN, optional: true },
150
+ state: PERFORMANCE_PROFILE_STATE_TOKEN,
151
+ },
152
+ }),
153
+ provide({
154
+ provide: SERVER_MODULE_PAPI_PRIVATE_ROUTE,
155
+ multi: true,
156
+ useFactory: ({ logger }) => {
157
+ const log = logger('performance-profile:inspect-cpu');
158
+ return createPapiMethod({
159
+ method: 'get',
160
+ options: {
161
+ timeout: REQUEST_TIMEOUT,
162
+ },
163
+ path: papiCpuProfileUrl,
164
+ async handler({ parsedUrl, responseManager }) {
165
+ const { duration } = parsedUrl.query;
166
+ let parsedDuration = DEFAULT_DURATION;
167
+ if (duration && !Number.isNaN(Number(duration))) {
168
+ parsedDuration = Math.max(100, Math.min(Number(duration), REQUEST_TIMEOUT));
169
+ }
170
+ const session = new Session();
171
+ session.connect();
172
+ try {
173
+ await post(session, 'Profiler.enable');
174
+ await post(session, 'Profiler.start');
175
+ await new Promise((resolve) => setTimeout(resolve, parsedDuration));
176
+ const { profile } = await post(session, 'Profiler.stop');
177
+ responseManager.setHeader('Content-Type', 'application/octet-stream');
178
+ responseManager.setHeader('Content-Disposition', `attachment; filename="cpuprofile-${Date.now()}.cpuprofile"`);
179
+ responseManager.setBody(Buffer.from(JSON.stringify(profile, null, 2)));
180
+ }
181
+ catch (error) {
182
+ log.error({
183
+ event: 'profile-creation-failed',
184
+ message: 'Failed to create profile',
185
+ error,
186
+ });
187
+ responseManager.setStatus(500);
188
+ responseManager.setBody({
189
+ resultCode: 'Error',
190
+ errorMessage: `Failed to create profile. ${error.toString()}`,
191
+ });
192
+ }
193
+ finally {
194
+ session.disconnect();
195
+ }
196
+ },
197
+ });
198
+ },
199
+ deps: {
200
+ logger: LOGGER_TOKEN,
201
+ },
202
+ }),
203
+ provide({
204
+ provide: PERFORMANCE_PROFILE_STATE_TOKEN,
205
+ useValue: {},
206
+ }),
207
+ ],
208
+ })
209
+ ], PerformanceProfileModule);
210
+
211
+ export { PerformanceProfileModule };
package/lib/server.js ADDED
@@ -0,0 +1,219 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var tslib = require('tslib');
6
+ var path = require('node:path');
7
+ var fs = require('node:fs');
8
+ var promises = require('node:stream/promises');
9
+ var node_inspector = require('node:inspector');
10
+ var papi = require('@tramvai/papi');
11
+ var core = require('@tramvai/core');
12
+ var tokensServer = require('@tramvai/tokens-server');
13
+ var moduleCommon = require('@tramvai/module-common');
14
+ var tokensServerPrivate = require('@tramvai/tokens-server-private');
15
+ var tokens = require('./tokens.js');
16
+
17
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
18
+
19
+ var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
20
+ var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
21
+
22
+ const REQUEST_TIMEOUT = 120000;
23
+ const DEFAULT_DURATION = 60000;
24
+ const papiTakeMemorySnapshotUrl = '/takeMemorySnapshot';
25
+ const papiGetMemorySnapshotUrl = '/getMemorySnapshot';
26
+ const papiCpuProfileUrl = '/getCpuProfile';
27
+ const post = (session, method) => new Promise((resolve, reject) => {
28
+ session.post(method, undefined, (err, result) => {
29
+ if (err) {
30
+ reject(err);
31
+ }
32
+ else {
33
+ resolve(result);
34
+ }
35
+ });
36
+ });
37
+ exports.PerformanceProfileModule = class PerformanceProfileModule {
38
+ };
39
+ exports.PerformanceProfileModule = tslib.__decorate([
40
+ core.Module({
41
+ imports: [],
42
+ providers: [
43
+ core.provide({
44
+ provide: tokensServer.SERVER_MODULE_PAPI_PRIVATE_ROUTE,
45
+ multi: true,
46
+ useFactory: ({ logger, options, state }) => {
47
+ const log = logger('performance-profile:inspect-memory');
48
+ const tmpDir = options?.tmpDir ?? '/tmp';
49
+ return papi.createPapiMethod({
50
+ method: 'get',
51
+ options: {
52
+ timeout: REQUEST_TIMEOUT,
53
+ },
54
+ path: papiTakeMemorySnapshotUrl,
55
+ async handler() {
56
+ if (state.inProgress) {
57
+ this.deps.res.code(409).send(`Snapshot already generating`);
58
+ return;
59
+ }
60
+ state.inProgress = true;
61
+ const fileName = `heapdump-app.heapsnapshot`;
62
+ const filePath = path__default["default"].join(tmpDir, fileName);
63
+ this.deps.res.send(`Creating snapshot for app`);
64
+ const session = new node_inspector.Session();
65
+ session.connect();
66
+ try {
67
+ if (fs__default["default"].existsSync(filePath)) {
68
+ await fs__default["default"].promises.unlink(filePath);
69
+ }
70
+ const writeStream = fs__default["default"].createWriteStream(filePath);
71
+ session.on('HeapProfiler.addHeapSnapshotChunk', ({ params: { chunk } }) => {
72
+ writeStream.write(chunk);
73
+ });
74
+ await post(session, 'HeapProfiler.takeHeapSnapshot');
75
+ log.info('Snapshot created successfully');
76
+ writeStream.end();
77
+ await promises.finished(writeStream);
78
+ }
79
+ catch (error) {
80
+ log.error({
81
+ event: 'snapshot-creation-failed',
82
+ message: 'Failed to create snapshot',
83
+ error,
84
+ });
85
+ }
86
+ finally {
87
+ session.disconnect();
88
+ state.inProgress = false;
89
+ }
90
+ },
91
+ deps: {
92
+ res: tokensServerPrivate.FASTIFY_RESPONSE,
93
+ },
94
+ });
95
+ },
96
+ deps: {
97
+ logger: moduleCommon.LOGGER_TOKEN,
98
+ options: { token: tokens.PERFORMANCE_PROFILE_OPTIONS_TOKEN, optional: true },
99
+ state: tokens.PERFORMANCE_PROFILE_STATE_TOKEN,
100
+ },
101
+ }),
102
+ core.provide({
103
+ provide: tokensServer.SERVER_MODULE_PAPI_PRIVATE_ROUTE,
104
+ multi: true,
105
+ useFactory: ({ logger, options, state }) => {
106
+ const log = logger('performance-profile:inspect-memory');
107
+ const tmpDir = options?.tmpDir ?? '/tmp';
108
+ return papi.createPapiMethod({
109
+ method: 'get',
110
+ options: {
111
+ timeout: REQUEST_TIMEOUT,
112
+ },
113
+ path: papiGetMemorySnapshotUrl,
114
+ async handler({ responseManager }) {
115
+ if (state.inProgress) {
116
+ responseManager.setStatus(425);
117
+ responseManager.setBody({
118
+ resultCode: 'Error',
119
+ errorMessage: 'Snapshot not ready yet',
120
+ });
121
+ return;
122
+ }
123
+ const fileName = `heapdump-app.heapsnapshot`;
124
+ const filePath = path__default["default"].join(tmpDir, fileName);
125
+ if (!fs__default["default"].existsSync(filePath)) {
126
+ const error = {
127
+ resultCode: 'Error',
128
+ errorMessage: 'Snapshot not found',
129
+ };
130
+ responseManager.setStatus(404);
131
+ responseManager.setBody(error);
132
+ return;
133
+ }
134
+ const stream = fs__default["default"].createReadStream(filePath);
135
+ stream.on('close', () => {
136
+ fs__default["default"].unlink(filePath, (error) => {
137
+ if (error) {
138
+ log.error({
139
+ event: 'snapshot-deletion-failed',
140
+ message: 'Failed to delete snapshot',
141
+ error,
142
+ });
143
+ }
144
+ });
145
+ });
146
+ this.deps.res.header('Content-Type', 'application/octet-stream');
147
+ this.deps.res.header('Content-Disposition', `attachment; filename="${fileName}"`);
148
+ await this.deps.res.send(stream);
149
+ },
150
+ deps: {
151
+ res: tokensServerPrivate.FASTIFY_RESPONSE,
152
+ },
153
+ });
154
+ },
155
+ deps: {
156
+ logger: moduleCommon.LOGGER_TOKEN,
157
+ options: { token: tokens.PERFORMANCE_PROFILE_OPTIONS_TOKEN, optional: true },
158
+ state: tokens.PERFORMANCE_PROFILE_STATE_TOKEN,
159
+ },
160
+ }),
161
+ core.provide({
162
+ provide: tokensServer.SERVER_MODULE_PAPI_PRIVATE_ROUTE,
163
+ multi: true,
164
+ useFactory: ({ logger }) => {
165
+ const log = logger('performance-profile:inspect-cpu');
166
+ return papi.createPapiMethod({
167
+ method: 'get',
168
+ options: {
169
+ timeout: REQUEST_TIMEOUT,
170
+ },
171
+ path: papiCpuProfileUrl,
172
+ async handler({ parsedUrl, responseManager }) {
173
+ const { duration } = parsedUrl.query;
174
+ let parsedDuration = DEFAULT_DURATION;
175
+ if (duration && !Number.isNaN(Number(duration))) {
176
+ parsedDuration = Math.max(100, Math.min(Number(duration), REQUEST_TIMEOUT));
177
+ }
178
+ const session = new node_inspector.Session();
179
+ session.connect();
180
+ try {
181
+ await post(session, 'Profiler.enable');
182
+ await post(session, 'Profiler.start');
183
+ await new Promise((resolve) => setTimeout(resolve, parsedDuration));
184
+ const { profile } = await post(session, 'Profiler.stop');
185
+ responseManager.setHeader('Content-Type', 'application/octet-stream');
186
+ responseManager.setHeader('Content-Disposition', `attachment; filename="cpuprofile-${Date.now()}.cpuprofile"`);
187
+ responseManager.setBody(Buffer.from(JSON.stringify(profile, null, 2)));
188
+ }
189
+ catch (error) {
190
+ log.error({
191
+ event: 'profile-creation-failed',
192
+ message: 'Failed to create profile',
193
+ error,
194
+ });
195
+ responseManager.setStatus(500);
196
+ responseManager.setBody({
197
+ resultCode: 'Error',
198
+ errorMessage: `Failed to create profile. ${error.toString()}`,
199
+ });
200
+ }
201
+ finally {
202
+ session.disconnect();
203
+ }
204
+ },
205
+ });
206
+ },
207
+ deps: {
208
+ logger: moduleCommon.LOGGER_TOKEN,
209
+ },
210
+ }),
211
+ core.provide({
212
+ provide: tokens.PERFORMANCE_PROFILE_STATE_TOKEN,
213
+ useValue: {},
214
+ }),
215
+ ],
216
+ })
217
+ ], exports.PerformanceProfileModule);
218
+
219
+ exports.PERFORMANCE_PROFILE_OPTIONS_TOKEN = tokens.PERFORMANCE_PROFILE_OPTIONS_TOKEN;
@@ -0,0 +1,13 @@
1
+ import { createToken } from '@tinkoff/dippy';
2
+
3
+ /**
4
+ * @public
5
+ * @description Provides performance profile module options
6
+ */
7
+ const PERFORMANCE_PROFILE_OPTIONS_TOKEN = createToken('performance profile options');
8
+ /**
9
+ * @private
10
+ */
11
+ createToken('performance profile state');
12
+
13
+ export { PERFORMANCE_PROFILE_OPTIONS_TOKEN };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @public
3
+ * @description Provides performance profile module options
4
+ */
5
+ export declare const PERFORMANCE_PROFILE_OPTIONS_TOKEN: {
6
+ tmpDir?: string | undefined;
7
+ } & {
8
+ __type?: "base token" | undefined;
9
+ };
10
+ /**
11
+ * @private
12
+ */
13
+ export declare const PERFORMANCE_PROFILE_STATE_TOKEN: {
14
+ inProgress?: boolean | undefined;
15
+ } & {
16
+ __type?: "base token" | undefined;
17
+ };
18
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1,13 @@
1
+ import { createToken } from '@tinkoff/dippy';
2
+
3
+ /**
4
+ * @public
5
+ * @description Provides performance profile module options
6
+ */
7
+ const PERFORMANCE_PROFILE_OPTIONS_TOKEN = createToken('performance profile options');
8
+ /**
9
+ * @private
10
+ */
11
+ const PERFORMANCE_PROFILE_STATE_TOKEN = createToken('performance profile state');
12
+
13
+ export { PERFORMANCE_PROFILE_OPTIONS_TOKEN, PERFORMANCE_PROFILE_STATE_TOKEN };
package/lib/tokens.js ADDED
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var dippy = require('@tinkoff/dippy');
6
+
7
+ /**
8
+ * @public
9
+ * @description Provides performance profile module options
10
+ */
11
+ const PERFORMANCE_PROFILE_OPTIONS_TOKEN = dippy.createToken('performance profile options');
12
+ /**
13
+ * @private
14
+ */
15
+ const PERFORMANCE_PROFILE_STATE_TOKEN = dippy.createToken('performance profile state');
16
+
17
+ exports.PERFORMANCE_PROFILE_OPTIONS_TOKEN = PERFORMANCE_PROFILE_OPTIONS_TOKEN;
18
+ exports.PERFORMANCE_PROFILE_STATE_TOKEN = PERFORMANCE_PROFILE_STATE_TOKEN;
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@tramvai/module-performance-profile",
3
+ "version": "6.81.11",
4
+ "description": "The module provides debug papi handlers for collecting cpu and memory usage profiles on the server",
5
+ "browser": "lib/browser.js",
6
+ "main": "lib/server.js",
7
+ "module": "lib/server.es.js",
8
+ "typings": "lib/server.d.ts",
9
+ "files": [
10
+ "lib"
11
+ ],
12
+ "sideEffects": false,
13
+ "license": "Apache-2.0",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+ssh://git@github.com/tramvaijs/tramvai.git"
17
+ },
18
+ "scripts": {
19
+ "build": "tramvai-build --forPublish --preserveModules",
20
+ "watch": "tsc -w"
21
+ },
22
+ "publishConfig": {
23
+ "registry": "https://registry.npmjs.org/"
24
+ },
25
+ "peerDependencies": {
26
+ "@tinkoff/dippy": "^1.0.0",
27
+ "@tramvai/core": "6.81.11",
28
+ "@tramvai/module-common": "6.81.11",
29
+ "@tramvai/papi": "6.81.11",
30
+ "@tramvai/tokens-server": "6.81.11",
31
+ "@tramvai/tokens-server-private": "6.81.11",
32
+ "tslib": "^2.4.0"
33
+ }
34
+ }