Skip to content

Commit

Permalink
Enable "Pause" and "Resume" of sending of messages #1093
Browse files Browse the repository at this point in the history
- Pause sending events via `appInsights.getSender().pause();
- Resume sending events via `appInsights.getSender().resume();
  • Loading branch information
MSNev committed Oct 8, 2021
1 parent 5fdf020 commit d2611ac
Show file tree
Hide file tree
Showing 7 changed files with 286 additions and 69 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,9 @@ Most configuration fields are named such that they can be defaulted to falsey. A
| perfEvtsSendAll | boolean | false | [Optional] When _enablePerfMgr_ is enabled and the [IPerfManager](https://github.com/microsoft/ApplicationInsights-JS/blob/master/shared/AppInsightsCore/src/JavaScriptSDK.Interfaces/IPerfManager.ts) fires a [INotificationManager](https://github.com/microsoft/ApplicationInsights-JS/blob/master/shared/AppInsightsCore/src/JavaScriptSDK.Interfaces/INotificationManager.ts).perfEvent() this flag determines whether an event is fired (and sent to all listeners) for all events (true) or only for 'parent' events (false &lt;default&gt;).<br />A parent [IPerfEvent](https://github.com/microsoft/ApplicationInsights-JS/blob/master/shared/AppInsightsCore/src/JavaScriptSDK.Interfaces/IPerfEvent.ts) is an event where no other IPerfEvent is still running at the point of this event being created and it's _parent_ property is not null or undefined. Since v2.5.7
| createPerfMgr | (core: IAppInsightsCore, notificationManager: INotificationManager) => IPerfManager | undefined | Callback function that will be called to create a the IPerfManager instance when required and ```enablePerfMgr``` is enabled, this enables you to override the default creation of a PerfManager() without needing to ```setPerfMgr()``` after initialization.
| idLength | numeric | 22 | [Optional] Identifies the default length used to generate new random session and user id's. Defaults to 22, previous default value was 5 (v2.5.8 or less), if you need to keep the previous maximum length you should set this value to 5.
| customHeaders | `[{header: string, value: string}]` | undefined | [Optional] The ability for the user to provide extra headers when using a custom endpoint. customHeaders will not be added on browser shutdown moment when beacon sender is used. And adding custom headers is not supported on IE9 or ealier.
| customHeaders | `[{header: string, value: string}]` | undefined | [Optional] The ability for the user to provide extra headers when using a custom endpoint. customHeaders will not be added on browser shutdown moment when beacon sender is used. And adding custom headers is not supported on IE9 or earlier.
| convertUndefined | `any` | undefined | [Optional] Provide user an option to convert undefined field to user defined value.
| eventsLimitInMem | number | 10000 | [Optional] The number of events that can be kept in memory before the SDK starts to drop events when not using Session Storage (the default).
### ICookieMgrConfig
Expand Down
135 changes: 135 additions & 0 deletions channels/applicationinsights-channel-js/Tests/Unit/src/Sender.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1395,5 +1395,140 @@ export class SenderTests extends AITestClass {
QUnit.assert.equal("value2", baseData.properties["property2"]);
}
});

this.testCase({
name: "Channel Config: Validate pausing and resuming sending with manual flush",
useFakeTimers: true,
test: () => {
let sendNotifications = [];
let notificationManager = new NotificationManager();
notificationManager.addNotificationListener({
eventsSendRequest: (sendReason: number, isAsync?: boolean) => {
sendNotifications.push({
sendReason,
isAsync
});
}
});

let core = new AppInsightsCore();
this.sandbox.stub(core, "getNotifyMgr").returns(notificationManager);

this._sender.initialize(
{
instrumentationKey: 'abc',
maxBatchInterval: 123,
endpointUrl: 'https://example.com',
extensionConfig: {
}

}, core, []
);

const loggerSpy = this.sandbox.spy(this._sender, "triggerSend");
const telemetryItem: ITelemetryItem = {
name: 'fake item',
iKey: 'iKey',
baseType: 'some type',
baseData: {}
};
try {
this._sender.processTelemetry(telemetryItem, null);
} catch(e) {
QUnit.assert.ok(false);
}

QUnit.assert.equal(false, loggerSpy.calledOnce);
QUnit.assert.equal(0, sendNotifications.length);

this._sender.pause();
this._sender.flush();
QUnit.assert.equal(false, loggerSpy.calledOnce);
QUnit.assert.equal(0, sendNotifications.length);

this.clock.tick(1);

QUnit.assert.equal(0, sendNotifications.length);

this._sender.resume();
this._sender.flush();
QUnit.assert.equal(true, loggerSpy.calledOnce);
QUnit.assert.equal(0, sendNotifications.length);

this.clock.tick(1);

QUnit.assert.equal(1, sendNotifications.length);
QUnit.assert.equal(SendRequestReason.ManualFlush, sendNotifications[0].sendReason);
}
});

this.testCase({
name: "Channel Config: Validate pausing and resuming sending when exceeding the batch size limits",
useFakeTimers: true,
test: () => {
let sendNotifications = [];
let notificationManager = new NotificationManager();
notificationManager.addNotificationListener({
eventsSendRequest: (sendReason: number, isAsync?: boolean) => {
sendNotifications.push({
sendReason,
isAsync
});
}
});

let core = new AppInsightsCore();
this.sandbox.stub(core, "getNotifyMgr").returns(notificationManager);

this._sender.initialize(
{
instrumentationKey: 'abc',
maxBatchInterval: 123,
maxBatchSizeInBytes: 4096,
endpointUrl: 'https://example.com',
extensionConfig: {
}

}, core, []
);

const triggerSendSpy = this.sandbox.spy(this._sender, "triggerSend");
const telemetryItem: ITelemetryItem = {
name: 'fake item',
iKey: 'iKey',
baseType: 'some type',
baseData: {}
};

this._sender.pause();

// Keep sending events until the max payload size is reached
while (!triggerSendSpy.calledOnce) {
try {
this._sender.processTelemetry(telemetryItem, null);
} catch(e) {
QUnit.assert.ok(false);
}
}

QUnit.assert.equal(true, triggerSendSpy.calledOnce);
QUnit.assert.equal(0, sendNotifications.length);

this.clock.tick(1);

QUnit.assert.equal(0, sendNotifications.length);

QUnit.assert.equal(false, triggerSendSpy.calledTwice);
this._sender.resume();

QUnit.assert.equal(true, triggerSendSpy.calledTwice);
QUnit.assert.equal(0, sendNotifications.length);

this.clock.tick(1);

QUnit.assert.equal(1, sendNotifications.length);
QUnit.assert.equal(SendRequestReason.MaxBatchSize, sendNotifications[0].sendReason);
}
});
}
}
9 changes: 7 additions & 2 deletions channels/applicationinsights-channel-js/src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,19 @@ export interface ISenderConfig {
samplingPercentage: () => number;

/**
* (Opetional) The ability for the user to provide extra headers
* (Optional) The ability for the user to provide extra headers
*/
customHeaders: () => [{header: string, value: string}]

/**
* (Opetional) Provide user an option to convert undefined field to user defined value.
* (Optional) Provide user an option to convert undefined field to user defined value.
*/
convertUndefined: () => any

/**
* (Optional) The number of events that can be kept in memory before the SDK starts to drop events. By default, this is 10,000.
*/
eventsLimitInMem: () => number;
}

export interface IBackendResponse {
Expand Down
26 changes: 21 additions & 5 deletions channels/applicationinsights-channel-js/src/SendBuffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ abstract class BaseSendBuffer {
protected _get: () => string[];
protected _set: (buffer: string[]) => string[];

constructor(config: ISenderConfig) {
constructor(logger: IDiagnosticLogger, config: ISenderConfig) {
let _buffer: string[] = [];
let _bufferFullMessageSent = false;

this._get = () => {
return _buffer;
Expand All @@ -66,6 +67,20 @@ abstract class BaseSendBuffer {
dynamicProto(BaseSendBuffer, this, (_self) => {

_self.enqueue = (payload: string) => {
if (_self.count() >= config.eventsLimitInMem()) {
// sent internal log only once per page view
if (!_bufferFullMessageSent) {
logger.throwInternal(
LoggingSeverity.WARNING,
_InternalMessageId.InMemoryStorageBufferFull,
"Maximum in-memory buffer size reached: " + _self.count(),
true);
_bufferFullMessageSent = true;
}

return;
}

_buffer.push(payload);
};

Expand All @@ -88,6 +103,7 @@ abstract class BaseSendBuffer {

_self.clear = () => {
_buffer = [];
_bufferFullMessageSent = false;
};

_self.getItems = (): string[] => {
Expand Down Expand Up @@ -142,8 +158,8 @@ abstract class BaseSendBuffer {
*/
export class ArraySendBuffer extends BaseSendBuffer implements ISendBuffer {

constructor(config: ISenderConfig) {
super(config);
constructor(logger: IDiagnosticLogger, config: ISenderConfig) {
super(logger, config);

dynamicProto(ArraySendBuffer, this, (_self, _base) => {

Expand Down Expand Up @@ -177,14 +193,14 @@ export class SessionStorageSendBuffer extends BaseSendBuffer implements ISendBuf
static MAX_BUFFER_SIZE = 2000;

constructor(logger: IDiagnosticLogger, config: ISenderConfig) {
super(config);
super(logger, config);
let _bufferFullMessageSent = false;

dynamicProto(SessionStorageSendBuffer, this, (_self, _base) => {
const bufferItems = _getBuffer(SessionStorageSendBuffer.BUFFER_KEY);
const notDeliveredItems = _getBuffer(SessionStorageSendBuffer.SENT_BUFFER_KEY);

const buffer = _self._set(bufferItems.concat(notDeliveredItems));
let buffer = _self._set(bufferItems.concat(notDeliveredItems));

// If the buffer has too many items, drop items from the end.
if (buffer.length > SessionStorageSendBuffer.MAX_BUFFER_SIZE) {
Expand Down
Loading

0 comments on commit d2611ac

Please sign in to comment.