@instana/shared-metrics 1.132.2 → 1.136.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instana/shared-metrics",
3
- "version": "1.132.2",
3
+ "version": "1.136.0",
4
4
  "description": "Internal metrics plug-in package for Node.js monitoring with Instana",
5
5
  "author": {
6
6
  "name": "Bastian Krol",
@@ -59,13 +59,14 @@
59
59
  },
60
60
  "license": "MIT",
61
61
  "dependencies": {
62
- "@instana/core": "1.132.2",
62
+ "@instana/core": "1.136.0",
63
63
  "detect-libc": "^1.0.3",
64
64
  "event-loop-lag": "^1.4.0",
65
65
  "recursive-copy": "^2.0.13",
66
- "tar": "^5.0.9"
66
+ "tar": "^5.0.11"
67
67
  },
68
68
  "devDependencies": {
69
+ "@types/tar": "^4.0.5",
69
70
  "eslint": "^7.30.0",
70
71
  "eslint-config-airbnb-base": "^14.2.1",
71
72
  "eslint-plugin-import": "^2.23.4",
@@ -75,8 +76,7 @@
75
76
  },
76
77
  "optionalDependencies": {
77
78
  "event-loop-stats": "1.3.0",
78
- "gcstats.js": "1.0.0",
79
- "node-gyp": "^7.1.2"
79
+ "gcstats.js": "1.0.0"
80
80
  },
81
- "gitHead": "05a4a392340c90788ed075cc205e5b5858ebb815"
81
+ "gitHead": "2a2aaf4aa675a10ba0534c50e9bd2c34ab104d78"
82
82
  }
@@ -7,7 +7,6 @@
7
7
 
8
8
  const path = require('path');
9
9
  const fs = require('fs');
10
-
11
10
  const { applicationUnderMonitoring } = require('@instana/core').util;
12
11
 
13
12
  let logger = require('@instana/core').logger.getLogger('metrics');
@@ -32,6 +31,7 @@ exports.payloadPrefix = 'dependencies';
32
31
  const preliminaryPayload = {};
33
32
 
34
33
  /** @type {Object.<string, *>} */
34
+ // @ts-ignore: Cannot redeclare exported variable 'currentPayload'
35
35
  exports.currentPayload = {};
36
36
 
37
37
  exports.MAX_ATTEMPTS = 20;
@@ -43,14 +43,14 @@ exports.activate = function activate() {
43
43
  attempts++;
44
44
 
45
45
  const started = Date.now();
46
- applicationUnderMonitoring.getMainPackageJsonPathStartingAtMainModule((err, packageJsonPath) => {
46
+ applicationUnderMonitoring.getMainPackageJsonPathStartingAtMainModule((err, mainPackageJsonPath) => {
47
47
  if (err) {
48
48
  return logger.warn('Failed to determine main package.json. Reason: %s %s ', err.message, err.stack);
49
- } else if (!packageJsonPath && attempts < exports.MAX_ATTEMPTS) {
49
+ } else if (!mainPackageJsonPath && attempts < exports.MAX_ATTEMPTS) {
50
50
  logger.debug('Main package.json could not be found. Will try again later.');
51
51
  setTimeout(exports.activate, DELAY).unref();
52
52
  return;
53
- } else if (!packageJsonPath) {
53
+ } else if (!mainPackageJsonPath) {
54
54
  logger.info(
55
55
  `Main package.json could not be found after ${attempts} retries. Looking for node_modules folder now.`
56
56
  );
@@ -70,11 +70,11 @@ exports.activate = function activate() {
70
70
 
71
71
  let dependencyDir;
72
72
  if (applicationUnderMonitoring.isAppInstalledIntoNodeModules()) {
73
- dependencyDir = path.join(path.dirname(packageJsonPath), '..', '..', 'node_modules');
73
+ dependencyDir = path.join(path.dirname(mainPackageJsonPath), '..', '..', 'node_modules');
74
74
  } else {
75
- dependencyDir = path.join(path.dirname(packageJsonPath), 'node_modules');
75
+ dependencyDir = path.join(path.dirname(mainPackageJsonPath), 'node_modules');
76
76
  }
77
- addAllDependencies(dependencyDir, started, packageJsonPath);
77
+ addAllDependencies(dependencyDir, started, mainPackageJsonPath);
78
78
  });
79
79
  };
80
80
 
@@ -89,6 +89,7 @@ exports.activate = function activate() {
89
89
  function addAllDependencies(dependencyDir, started, packageJsonPath) {
90
90
  addDependenciesFromDir(dependencyDir, () => {
91
91
  if (Object.keys(preliminaryPayload).length <= exports.MAX_DEPENDENCIES) {
92
+ // @ts-ignore: Cannot redeclare exported variable 'currentPayload'
92
93
  exports.currentPayload = preliminaryPayload;
93
94
  logger.debug(`Collection of dependencies took ${Date.now() - started} ms.`);
94
95
  return;
@@ -238,6 +239,7 @@ function limitAndSet(distances = {}) {
238
239
  for (let i = 0; i < keys.length - exports.MAX_DEPENDENCIES; i++) {
239
240
  delete preliminaryPayload[keys[i]];
240
241
  }
242
+ // @ts-ignore: Cannot redeclare exported variable 'currentPayload'
241
243
  exports.currentPayload = preliminaryPayload;
242
244
  }
243
245
 
@@ -13,7 +13,6 @@ const fs = require('fs');
13
13
  const os = require('os');
14
14
  const path = require('path');
15
15
  const tar = require('tar');
16
- const { fork } = require('child_process');
17
16
  const detectLibc = require('detect-libc');
18
17
 
19
18
  /**
@@ -26,20 +25,9 @@ const detectLibc = require('detect-libc');
26
25
  * @property {string} [loadFrom]
27
26
  */
28
27
 
29
- /** @type {Array.<string>} */
30
- const retryMechanisms = [];
31
- if (
32
- !process.env.INSTANA_COPY_PRECOMPILED_NATIVE_ADDONS ||
33
- process.env.INSTANA_COPY_PRECOMPILED_NATIVE_ADDONS.toLowerCase() !== 'false'
34
- ) {
35
- retryMechanisms.push('copy-precompiled');
36
- }
37
- if (
38
- process.env.INSTANA_REBUILD_NATIVE_ADDONS_ON_DEMAND &&
39
- process.env.INSTANA_REBUILD_NATIVE_ADDONS_ON_DEMAND.toLowerCase() === 'true'
40
- ) {
41
- retryMechanisms.push('rebuild');
42
- }
28
+ const copyPrecompiledDisabled =
29
+ process.env.INSTANA_COPY_PRECOMPILED_NATIVE_ADDONS &&
30
+ process.env.INSTANA_COPY_PRECOMPILED_NATIVE_ADDONS.toLowerCase() === 'false';
43
31
 
44
32
  const platform = os.platform();
45
33
  const arch = process.arch;
@@ -60,18 +48,16 @@ function loadNativeAddOn(opts) {
60
48
  // Give clients a chance to register event listeners on the emitter that we return by attempting to load the module
61
49
  // asynchronously on the next tick.
62
50
  opts.loadFrom = opts.nativeModuleName;
63
- process.nextTick(loadNativeAddOnInternal.bind(null, opts, loaderEmitter, 0));
51
+ process.nextTick(loadNativeAddOnInternal.bind(null, opts, loaderEmitter));
64
52
  return loaderEmitter;
65
53
  }
66
54
 
67
55
  /**
68
56
  * @param {InstanaSharedMetricsOptions} opts
69
57
  * @param {EventEmitter} loaderEmitter
70
- * @param {number} retryIndex
71
- * @param {boolean} skipAttempt
72
- * @returns
58
+ * @returns {boolean}
73
59
  */
74
- function loadNativeAddOnInternal(opts, loaderEmitter, retryIndex, skipAttempt) {
60
+ function loadNativeAddOnInternal(opts, loaderEmitter) {
75
61
  try {
76
62
  const { isMainThread } = require('worker_threads');
77
63
  if (!isMainThread) {
@@ -83,21 +69,32 @@ function loadNativeAddOnInternal(opts, loaderEmitter, retryIndex, skipAttempt) {
83
69
  // worker threads are not available, so we know that this is the main thread
84
70
  }
85
71
 
86
- if (skipAttempt) {
87
- // The logic of the previous retry mechanism figured out that it cannot complete successfully, so there is no reason
88
- // to try to require the module again. Skip directly to the next retry.
89
- logger.debug(`Skipping attempt ${retryIndex + 1} to load native add-on ${opts.nativeModuleName}.`);
90
- prepareNextRetry(opts, loaderEmitter, retryIndex);
91
- } else {
92
- logger.debug(`Attempt ${retryIndex + 1} to load native add-on ${opts.nativeModuleName} from ${opts.loadFrom}.`);
93
- try {
94
- // Try to actually require the native add-on module.
95
- const nativeModule = require(opts.loadFrom);
96
- loaderEmitter.emit('loaded', nativeModule);
97
- logger.debug(`Attempt ${retryIndex + 1} to load native add-on ${opts.nativeModuleName} has been successful.`);
98
- } catch (e) {
99
- logger.debug(`Attempt ${retryIndex + 1} to load native add-on ${opts.nativeModuleName} has failed.`, e);
100
- prepareNextRetry(opts, loaderEmitter, retryIndex);
72
+ let nativeModuleHasBeenRequiredSuccessfully = attemptRequire(opts, loaderEmitter, 'directly');
73
+ if (!nativeModuleHasBeenRequiredSuccessfully) {
74
+ if (!copyPrecompiledDisabled) {
75
+ copyPrecompiled(opts, loaderEmitter, success => {
76
+ if (success) {
77
+ // The initial attempt to require the native add-on directly has failed but copying the precompiled add-on
78
+ // binaries has been successful. Try to require the precompiled add-on now.
79
+ nativeModuleHasBeenRequiredSuccessfully = attemptRequire(
80
+ opts,
81
+ loaderEmitter,
82
+ 'after copying precompiled binaries'
83
+ );
84
+ if (!nativeModuleHasBeenRequiredSuccessfully) {
85
+ // Requiring the precompiled add-on has failed after successfully copying them.
86
+ giveUp(opts, loaderEmitter);
87
+ }
88
+ } else {
89
+ // The initial attempt to require the native add-on directly has failed and copying the precompiled add-on
90
+ // binaries has also failed.
91
+ giveUp(opts, loaderEmitter);
92
+ }
93
+ });
94
+ } else {
95
+ // The initial attempt to require the native add-on directly has failed and copying the precompiled add-on
96
+ // binaries has been explicitly disabled via INSTANA_COPY_PRECOMPILED_NATIVE_ADDONS=false.
97
+ giveUp(opts, loaderEmitter);
101
98
  }
102
99
  }
103
100
  }
@@ -105,49 +102,50 @@ function loadNativeAddOnInternal(opts, loaderEmitter, retryIndex, skipAttempt) {
105
102
  /**
106
103
  * @param {InstanaSharedMetricsOptions} opts
107
104
  * @param {EventEmitter} loaderEmitter
108
- * @param {number} retryIndex
105
+ * @param {string} mechanism
109
106
  */
110
- function prepareNextRetry(opts, loaderEmitter, retryIndex) {
111
- // The first pre-condition for all retry mechanisms is that we can find the path to the native add-on that can not be
112
- // required.
113
- if (!opts.nativeModulePath || !opts.nativeModuleParentPath) {
114
- findNativeModulePath(opts);
115
- if (!opts.nativeModulePath || !opts.nativeModuleParentPath) {
116
- logger.warn(opts.message + ' (No retry attempted.)');
117
- loaderEmitter.emit('failed');
118
- return;
119
- }
107
+ function attemptRequire(opts, loaderEmitter, mechanism) {
108
+ try {
109
+ // Try to actually require the native add-on module.
110
+ const nativeModule = require(opts.loadFrom);
111
+ loaderEmitter.emit('loaded', nativeModule);
112
+ logger.debug(`Attempt to load native add-on ${opts.nativeModuleName} ${mechanism} has been successful.`);
113
+ return true;
114
+ } catch (e) {
115
+ logger.debug(`Attempt to load native add-on ${opts.nativeModuleName} ${mechanism} has failed.`, e);
116
+ return false;
120
117
  }
118
+ }
121
119
 
122
- const nextRetryMechanism = retryMechanisms[retryIndex];
123
- if (!nextRetryMechanism) {
124
- // We have exhausted all possible mechanisms to cope with the failure to load the native add-on.
125
- logger.warn(opts.message);
126
- loaderEmitter.emit('failed');
127
- } else if (nextRetryMechanism === 'copy-precompiled') {
128
- copyPrecompiled(opts, loaderEmitter, retryIndex);
129
- } else if (nextRetryMechanism === 'rebuild') {
130
- rebuildOnDemand(opts, loaderEmitter, retryIndex);
131
- } else {
132
- logger.error(
133
- `Unknown retry mechanism for loading the native module ${opts.nativeModuleName}: ${nextRetryMechanism}.`
134
- );
135
- process.nextTick(loadNativeAddOnInternal.bind(null, opts, loaderEmitter, ++retryIndex, true));
136
- }
120
+ /**
121
+ * @param {InstanaSharedMetricsOptions} opts
122
+ * @param {EventEmitter} loaderEmitter
123
+ */
124
+ function giveUp(opts, loaderEmitter) {
125
+ logger.warn(opts.message);
126
+ loaderEmitter.emit('failed');
137
127
  }
138
128
 
139
129
  /**
140
130
  * @param {InstanaSharedMetricsOptions} opts
141
131
  * @param {EventEmitter} loaderEmitter
142
- * @param {number} retryIndex
132
+ * @param {(success: boolean) => void} callback
143
133
  */
144
- function copyPrecompiled(opts, loaderEmitter, retryIndex) {
134
+ function copyPrecompiled(opts, loaderEmitter, callback) {
145
135
  logger.debug(`Trying to copy precompiled version of ${opts.nativeModuleName} for Node.js ${process.version}.`);
146
136
 
137
+ if (!opts.nativeModulePath || !opts.nativeModuleParentPath) {
138
+ if (!findNativeModulePath(opts)) {
139
+ logger.warn(`Unable to find or construct a path for native add-on ${opts.nativeModuleName}.`);
140
+ process.nextTick(callback.bind(false));
141
+ return;
142
+ }
143
+ }
144
+
147
145
  const abi = process.versions.modules;
148
146
  if (!abi) {
149
147
  logger.warn(`Could not determine ABI version for Node.js version ${process.version}.`);
150
- process.nextTick(loadNativeAddOnInternal.bind(null, opts, loaderEmitter, ++retryIndex, true));
148
+ process.nextTick(callback.bind(false));
151
149
  return;
152
150
  }
153
151
 
@@ -163,14 +161,11 @@ function copyPrecompiled(opts, loaderEmitter, retryIndex) {
163
161
  logger.info(
164
162
  `A precompiled version for ${opts.nativeModuleName} is not available ${label} (at ${precompiledTarGzPath}).`
165
163
  );
166
- process.nextTick(loadNativeAddOnInternal.bind(null, opts, loaderEmitter, ++retryIndex, true));
164
+ callback(false);
167
165
  return;
168
- // Note: We could combine copying and recompiling for cases where we do not have a precompiled version but
169
- // node-gyp is available. That is, copy the precompiled package (from an arbitrary architecture/ABI version), then
170
- // rebuild that.
171
166
  } else if (statsErr) {
172
167
  logger.warn(`Looking for a precompiled version for ${opts.nativeModuleName} ${label} failed.`, statsErr);
173
- process.nextTick(loadNativeAddOnInternal.bind(null, opts, loaderEmitter, ++retryIndex, true));
168
+ callback(false);
174
169
  return;
175
170
  }
176
171
 
@@ -197,7 +192,7 @@ function copyPrecompiled(opts, loaderEmitter, retryIndex) {
197
192
  cpErr => {
198
193
  if (cpErr) {
199
194
  logger.warn(`Copying the precompiled build for ${opts.nativeModuleName} ${label} failed.`, cpErr);
200
- process.nextTick(loadNativeAddOnInternal.bind(null, opts, loaderEmitter, ++retryIndex, true));
195
+ callback(false);
201
196
  return;
202
197
  }
203
198
 
@@ -205,7 +200,7 @@ function copyPrecompiled(opts, loaderEmitter, retryIndex) {
205
200
  // dependency should work.
206
201
  //
207
202
  // However, we must not use any of the paths from which Node.js has tried to load the module before (that
208
- // is, node_modules/${opts.nativeModuleName}). Node.js has module loading infrastructure
203
+ // is, node_modules/${opts.nativeModuleName}). Node.js' module loading infrastructure
209
204
  // (lib/internal/modules/cjs/loader.js and lib/internal/modules/package_json_reader.js) have built-in
210
205
  // caching on multiple levels (for example, package.json locations and package.json contents). If Node.js
211
206
  // has tried unsuccessfully to load a module or read a package.json from a particular path, it will remember
@@ -213,78 +208,17 @@ function copyPrecompiled(opts, loaderEmitter, retryIndex) {
213
208
  // key). Instead, we force a new path, by adding precompiled to the module path and use the absolute path to
214
209
  // the module to load it.
215
210
  opts.loadFrom = targetDir;
216
- process.nextTick(loadNativeAddOnInternal.bind(null, opts, loaderEmitter, ++retryIndex));
211
+ callback(true);
217
212
  }
218
213
  );
219
214
  })
220
215
  .catch(tarErr => {
221
216
  logger.warn(`Unpacking the precompiled build for ${opts.nativeModuleName} ${label} failed.`, tarErr);
222
- process.nextTick(loadNativeAddOnInternal.bind(null, opts, loaderEmitter, ++retryIndex, true));
217
+ callback(false);
223
218
  });
224
219
  });
225
220
  }
226
221
 
227
- /**
228
- * This is mainly for the scenario where @instana/collector and its dependencies has been provided by an external
229
- * mechanism (like copying a bundled version contained in the Instana agent package) without running an npm install on
230
- * the target system. Native addons are present, but might have been build for a different operating system/Node ABI,
231
- * libc familiy, ...). Thus, they need to be rebuilt.
232
- *
233
- * @param {InstanaSharedMetricsOptions} opts
234
- * @param {EventEmitter} loaderEmitter
235
- * @param {number} retryIndex
236
- */
237
- function rebuildOnDemand(opts, loaderEmitter, retryIndex) {
238
- /** @type {string} */
239
- let nodeGypExecutable;
240
- try {
241
- const nodeGypPath = require.resolve('node-gyp');
242
- if (!nodeGypPath) {
243
- logger.warn(
244
- // eslint-disable-next-line max-len
245
- `Could not find node-gyp (require.resolve didn't return anything) to rebuild ${opts.nativeModuleName} on demand.`
246
- );
247
- process.nextTick(loadNativeAddOnInternal.bind(null, opts, loaderEmitter, ++retryIndex, true));
248
- return;
249
- }
250
- nodeGypExecutable = path.join(nodeGypPath, '..', '..', 'bin', 'node-gyp.js');
251
- } catch (e) {
252
- logger.warn(`Could not load node-gyp to rebuild ${opts.nativeModuleName} on demand.`, e);
253
- process.nextTick(loadNativeAddOnInternal.bind(null, opts, loaderEmitter, ++retryIndex, true));
254
- return;
255
- }
256
-
257
- logger.info(`Rebuilding ${opts.nativeModulePath} via ${nodeGypExecutable}.`);
258
- const nodeGyp = fork(nodeGypExecutable, ['rebuild'], {
259
- cwd: opts.nativeModulePath
260
- });
261
- nodeGyp.on('error', err => {
262
- logger.warn(
263
- // eslint-disable-next-line max-len
264
- `Attempt to rebuild ${opts.nativeModulePath} via ${nodeGypExecutable} has failed with an error.`,
265
- err
266
- );
267
- });
268
- nodeGyp.on('close', code => {
269
- if (code === 0) {
270
- logger.info(
271
- // eslint-disable-next-line max-len
272
- `Attempt to rebuild ${opts.nativeModulePath} via ${nodeGypExecutable} has finished, will try to load the module again.`
273
- );
274
- // In contrast to copyPrecompiled we do not need to force a different module path here, since in this scenario the
275
- // package contents (including the package.json) was present in the node_modules folder, it was only the binary
276
- // that didn't match the platform/ABI version.
277
- opts.loadFrom = opts.nativeModuleName;
278
- process.nextTick(loadNativeAddOnInternal.bind(null, opts, loaderEmitter, ++retryIndex));
279
- } else {
280
- logger.warn(
281
- `Attempt to rebuild ${opts.nativeModulePath} via ${nodeGypExecutable} has failed with exit code ${code}.`
282
- );
283
- process.nextTick(loadNativeAddOnInternal.bind(null, opts, loaderEmitter, ++retryIndex, true));
284
- }
285
- });
286
- }
287
-
288
222
  /**
289
223
  * @param {InstanaSharedMetricsOptions} opts
290
224
  */
@@ -297,24 +231,24 @@ function findNativeModulePath(opts) {
297
231
  `Could not find location for ${opts.nativeModuleName} (require.resolve didn't return anything). ` +
298
232
  'Will create a path for it.'
299
233
  );
300
- createNativeModulePath(opts);
301
- return;
234
+ return createNativeModulePath(opts);
302
235
  }
303
236
  // We found a path to the module in node_modules, that means the directory exist (and we will reuse it) but the
304
237
  // module installation is incomplete and it could not be loaded earlier (otherwise we wouldn't have gotten here).
305
238
  const idx = nativeModulePath.lastIndexOf('node_modules');
306
239
  if (idx < 0) {
307
240
  logger.warn(`Could not find node_modules substring in ${nativeModulePath}.`);
308
- return;
241
+ return false;
309
242
  }
310
243
  opts.nativeModulePath = nativeModulePath.substring(
311
244
  0,
312
245
  idx + 'node_modules'.length + opts.nativeModuleName.length + 2
313
246
  );
314
247
  opts.nativeModuleParentPath = path.join(opts.nativeModulePath, '..');
248
+ return true;
315
249
  } catch (e) {
316
250
  logger.debug(`Could not find location for ${opts.nativeModuleName}. Will create a path for it.`, e);
317
- createNativeModulePath(opts);
251
+ return createNativeModulePath(opts);
318
252
  }
319
253
  }
320
254
 
@@ -332,7 +266,7 @@ function createNativeModulePath(opts) {
332
266
  logger.warn(
333
267
  `Could not find node_modules substring in ${selfPath}. Will give up loading ${opts.nativeModuleName}.`
334
268
  );
335
- return;
269
+ return false;
336
270
  }
337
271
 
338
272
  // cut off everything after module path
@@ -343,6 +277,7 @@ function createNativeModulePath(opts) {
343
277
  // we need to go up two directory levels.
344
278
  opts.nativeModuleParentPath = loadNativeAddOn.selfNodeModulesPath;
345
279
  opts.nativeModulePath = path.join(loadNativeAddOn.selfNodeModulesPath, opts.nativeModuleName);
280
+ return true;
346
281
  }
347
282
 
348
283
  loadNativeAddOn.setLogger = setLogger;