Skip to content

Commit e874950

Browse files
authored
Add 15 Second Delay to Long Interval Statsbeat & Fix Tests (#1440)
* Add 15 second delay to long interval statsbeat. * Update timer. * Attempt to make backward compatible. * Update and fix tests. * Remove node 12 * Update package.json * Update backcompat.yml * Remove unneeded removals. * Update AzureFunctionsHook.ts * Update AzureFunctionsHook.ts * Update integration.yml
1 parent b89bd08 commit e874950

File tree

9 files changed

+261
-55
lines changed

9 files changed

+261
-55
lines changed

.github/workflows/backcompat.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313

1414
strategy:
1515
matrix:
16-
node-version: [12.x]
16+
node-version: [14.x]
1717

1818
steps:
1919
- uses: actions/checkout@v2

.github/workflows/integration.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313

1414
strategy:
1515
matrix:
16-
node-version: [12.x]
16+
node-version: [16.x]
1717

1818
steps:
1919
- uses: actions/checkout@v2

.github/workflows/node.js.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
strategy:
1313
matrix:
1414
os: [ubuntu-latest]
15-
node-version: [8, 10, 12, 14, 16, 17, 18]
15+
node-version: [14, 16, 17, 18]
1616

1717
steps:
1818
- uses: actions/checkout@v2

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
strategy:
1212
matrix:
1313
os: [ubuntu-latest]
14-
node-version: [12, 14, 16, 18]
14+
node-version: [14, 16, 18]
1515

1616
steps:
1717
- uses: actions/checkout@v2

AutoCollection/Statsbeat.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class Statsbeat {
1818
public static EU_CONNECTION_STRING = "InstrumentationKey=7dc56bab-3c0c-4e9f-9ebb-d1acadee8d0f;IngestionEndpoint=https://westeurope-5.in.applicationinsights.azure.com";
1919
public static STATS_COLLECTION_SHORT_INTERVAL: number = 900000; // 15 minutes
2020
public static STATS_COLLECTION_LONG_INTERVAL: number = 86400000; // 1 day
21+
public static STATS_COLLECTION_INITIAL_DELAY: number = 15000; // 15 seconds
2122

2223
private static TAG = "Statsbeat";
2324

@@ -26,6 +27,7 @@ class Statsbeat {
2627
private _context: Context;
2728
private _handle: NodeJS.Timer | null;
2829
private _longHandle: NodeJS.Timer | null;
30+
private _longHandleTimeout: NodeJS.Timer | null;
2931
private _isEnabled: boolean;
3032
private _isInitialized: boolean;
3133
private _config: Config;
@@ -51,6 +53,7 @@ class Statsbeat {
5153
this._networkStatsbeatCollection = [];
5254
this._config = config;
5355
this._context = context || new Context();
56+
this._longHandleTimeout = null;
5457
let statsbeatConnectionString = this._getConnectionString(config);
5558
this._statsbeatConfig = new Config(statsbeatConnectionString);
5659
this._statsbeatConfig.samplingPercentage = 100; // Do not sample
@@ -71,8 +74,13 @@ class Statsbeat {
7174
this._handle.unref(); // Allow the app to terminate even while this loop is going on
7275
}
7376
if (!this._longHandle) {
74-
// On first enablement
75-
this.trackLongIntervalStatsbeats();
77+
// Add 15 second delay before first long interval statsbeat
78+
this._longHandleTimeout = setTimeout(() => {
79+
if (this.isEnabled()) {
80+
this.trackLongIntervalStatsbeats();
81+
}
82+
}, Statsbeat.STATS_COLLECTION_INITIAL_DELAY);
83+
this._longHandleTimeout.unref(); // Allow the app to terminate even while this loop is going on
7684
this._longHandle = setInterval(() => {
7785
this.trackLongIntervalStatsbeats();
7886
}, Statsbeat.STATS_COLLECTION_LONG_INTERVAL);
@@ -87,6 +95,10 @@ class Statsbeat {
8795
clearInterval(this._longHandle);
8896
this._longHandle = null;
8997
}
98+
if (this._longHandleTimeout) {
99+
clearTimeout(this._longHandleTimeout);
100+
this._longHandleTimeout = null;
101+
}
90102
}
91103
}
92104

Library/Sender.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import fs = require("fs");
1+
import fs = require("fs");
22
import http = require("http");
33
import os = require("os");
44
import path = require("path");

Tests/AutoCollection/NativePerformance.tests.ts

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,27 @@ describe("AutoCollection/NativePerformance", () => {
2424

2525
describe("#init and #dispose()", () => {
2626
it("init should enable and dispose should stop autocollection interval", () => {
27+
var client = new TelemetryClient("1aa11111-bbbb-1ccc-8ddd-eeeeffff3333");
2728
var setIntervalSpy = sandbox.spy(global, "setInterval");
2829
var clearIntervalSpy = sandbox.spy(global, "clearInterval");
29-
const statsAddSpy = sandbox.spy(AutoCollectNativePerformance.INSTANCE["_statsbeat"], "addFeature");
30-
const statsRemoveSpy = sandbox.spy(AutoCollectNativePerformance.INSTANCE["_statsbeat"], "removeFeature");
31-
32-
AppInsights.setup("1aa11111-bbbb-1ccc-8ddd-eeeeffff3333")
33-
.setAutoCollectHeartbeat(false)
34-
.setAutoCollectPerformance(false, true)
35-
.setAutoCollectPreAggregatedMetrics(false)
36-
.start();
30+
var native = new AutoCollectNativePerformance(client);
31+
32+
// Enable auto collection
33+
native.enable(true);
34+
35+
if (AutoCollectNativePerformance["_metricsAvailable"]) {
36+
assert.equal(setIntervalSpy.callCount, 1, "setInterval should be called when enabling");
37+
} else {
38+
assert.equal(setIntervalSpy.callCount, 0, "setInterval should not be called if native metrics package is not installed");
39+
}
40+
41+
// Dispose should stop the interval
42+
native.dispose();
43+
3744
if (AutoCollectNativePerformance["_metricsAvailable"]) {
38-
assert.ok(statsAddSpy.calledOnce);
39-
assert.strictEqual(AutoCollectNativePerformance.INSTANCE["_statsbeat"]["_feature"], Constants.StatsbeatFeature.NATIVE_METRICS + Constants.StatsbeatFeature.DISK_RETRY);
40-
assert.equal(setIntervalSpy.callCount, 3, "setInteval should be called three times as part of NativePerformance initialization as well as Statsbeat");
41-
AppInsights.dispose();
42-
assert.ok(statsRemoveSpy.calledOnce);
43-
assert.strictEqual(AutoCollectNativePerformance.INSTANCE["_statsbeat"]["_feature"], Constants.StatsbeatFeature.DISK_RETRY);
44-
assert.equal(clearIntervalSpy.callCount, 1, "clearInterval should be called once as part of NativePerformance shutdown");
45+
assert.equal(clearIntervalSpy.callCount, 1, "clearInterval should be called when disposing");
4546
} else {
46-
assert.equal(setIntervalSpy.callCount, 2, "setInterval should not be called if NativePerformance package is not available, Statsbeat will be called");
47-
AppInsights.dispose();
48-
assert.equal(clearIntervalSpy.callCount, 0, "clearInterval should not be called if NativePerformance package is not available");
47+
assert.equal(clearIntervalSpy.callCount, 0, "clearInterval should not be called if native metrics package is not installed");
4948
}
5049
});
5150

Tests/AutoCollection/Statsbeat.tests.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,4 +356,102 @@ describe("AutoCollection/Statsbeat", () => {
356356
}).catch((error) => { done(error); });
357357
});
358358
});
359+
360+
describe("#enable() with long interval delay", () => {
361+
let clock: sinon.SinonFakeTimers;
362+
363+
beforeEach(() => {
364+
clock = sinon.useFakeTimers();
365+
});
366+
367+
afterEach(() => {
368+
clock.restore();
369+
});
370+
371+
it("should delay first long interval statsbeat by 15 seconds", (done) => {
372+
const trackLongIntervalSpy = sandbox.spy(statsBeat, "trackLongIntervalStatsbeats");
373+
374+
// Enable statsbeat
375+
statsBeat.enable(true);
376+
377+
// Immediately after enabling, trackLongIntervalStatsbeats should not have been called
378+
assert.equal(trackLongIntervalSpy.callCount, 0, "trackLongIntervalStatsbeats should not be called immediately");
379+
380+
// Fast-forward 10 seconds - still should not be called
381+
clock.tick(10000);
382+
assert.equal(trackLongIntervalSpy.callCount, 0, "trackLongIntervalStatsbeats should not be called after 10 seconds");
383+
384+
// Fast-forward to 15 seconds - now it should be called
385+
clock.tick(5000);
386+
assert.equal(trackLongIntervalSpy.callCount, 1, "trackLongIntervalStatsbeats should be called after 15 seconds");
387+
388+
done();
389+
});
390+
391+
it("should call trackLongIntervalStatsbeats on regular interval after initial delay", (done) => {
392+
const trackLongIntervalSpy = sandbox.spy(statsBeat, "trackLongIntervalStatsbeats");
393+
394+
// Enable statsbeat
395+
statsBeat.enable(true);
396+
397+
// Fast-forward to the first call (15 seconds)
398+
clock.tick(15000);
399+
assert.equal(trackLongIntervalSpy.callCount, 1, "First call should happen after 15 seconds");
400+
401+
// Fast-forward by the long interval (24 hours)
402+
clock.tick(Statsbeat.STATS_COLLECTION_LONG_INTERVAL);
403+
assert.equal(trackLongIntervalSpy.callCount, 2, "Second call should happen after the interval");
404+
405+
// Fast-forward by another long interval
406+
clock.tick(Statsbeat.STATS_COLLECTION_LONG_INTERVAL);
407+
assert.equal(trackLongIntervalSpy.callCount, 3, "Third call should happen after another interval");
408+
409+
done();
410+
});
411+
412+
it("should not delay if enable is called multiple times", (done) => {
413+
const trackLongIntervalSpy = sandbox.spy(statsBeat, "trackLongIntervalStatsbeats");
414+
415+
// Enable statsbeat first time
416+
statsBeat.enable(true);
417+
418+
// Fast-forward 5 seconds
419+
clock.tick(5000);
420+
assert.equal(trackLongIntervalSpy.callCount, 0, "Should not be called yet");
421+
422+
// Disable and re-enable (simulating multiple enable calls)
423+
statsBeat.enable(false);
424+
statsBeat.enable(true);
425+
426+
// The second enable should start a new 15-second timer
427+
clock.tick(10000); // Total 15 seconds from first enable, but only 10 from second
428+
assert.equal(trackLongIntervalSpy.callCount, 0, "Should not be called yet after re-enable");
429+
430+
// Fast-forward another 5 seconds (15 seconds from second enable)
431+
clock.tick(5000);
432+
assert.equal(trackLongIntervalSpy.callCount, 1, "Should be called 15 seconds after re-enable");
433+
434+
done();
435+
});
436+
437+
it("should handle disable before initial delay completes", (done) => {
438+
const trackLongIntervalSpy = sandbox.spy(statsBeat, "trackLongIntervalStatsbeats");
439+
440+
// Enable statsbeat
441+
statsBeat.enable(true);
442+
443+
// Fast-forward 10 seconds (before the 15-second delay completes)
444+
clock.tick(10000);
445+
assert.equal(trackLongIntervalSpy.callCount, 0, "Should not be called yet");
446+
447+
// Disable before the delay completes
448+
statsBeat.enable(false);
449+
450+
// Fast-forward past where the call would have happened
451+
clock.tick(10000); // Total 20 seconds
452+
assert.equal(trackLongIntervalSpy.callCount, 0, "Should not be called after disable");
453+
454+
done();
455+
});
456+
});
359457
});

0 commit comments

Comments
 (0)