Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable "Pause" and "Resume" of sending of messages #1093 #1688

Merged
merged 1 commit into from
Oct 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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