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

[Main][Task]26681220: Better handle timers in offline channel #2267

Merged
merged 3 commits into from
Feb 12, 2024
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
20 changes: 20 additions & 0 deletions channels/offline-channel-js/Tests/Unit/src/TestHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export class TestChannel extends BaseTelemetryPlugin implements IChannelControls
public identifier = BreezeChannelIdentifier;
public priority: number = 1001;
public endpoint: string = DEFAULT_BREEZE_ENDPOINT + DEFAULT_BREEZE_PATH;
public isIdle: boolean = true;

lastEventAdded: ITelemetryItem;
eventsAdded: ITelemetryItem[] = [];
Expand Down Expand Up @@ -40,6 +41,13 @@ export class TestChannel extends BaseTelemetryPlugin implements IChannelControls
this.flushCalled = true;
}

setIsIdle(val) {
this.isIdle = val;
}
isCompletelyIdle() {
return this.isIdle;
}

getOfflineSupport() {
return {
serialize: (evt) => {
Expand Down Expand Up @@ -70,5 +78,17 @@ export class TestChannel extends BaseTelemetryPlugin implements IChannelControls
}
}

export function mockTelemetryItem(): ITelemetryItem {
let evt = {
ver: "testVer" + Math.random(),
name:"testName",
time: "testTime",
iKey:"testKey",
baseData: {pro1: "prop1"},
baseType: "testType"
} as ITelemetryItem;
return evt;
}



18 changes: 3 additions & 15 deletions channels/offline-channel-js/Tests/Unit/src/channel.tests.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AITestClass, Assert, PollingAssert } from "@microsoft/ai-test-framework";
import { DEFAULT_BREEZE_ENDPOINT, DEFAULT_BREEZE_PATH, IConfig } from "@microsoft/applicationinsights-common";
import { AppInsightsCore, IConfiguration, ITelemetryItem, getGlobal, getGlobalInst } from "@microsoft/applicationinsights-core-js";
import { TestChannel } from "./TestHelper";
import { AppInsightsCore, IConfiguration, getGlobal, getGlobalInst } from "@microsoft/applicationinsights-core-js";
import { TestChannel, mockTelemetryItem } from "./TestHelper";
import { OfflineChannel } from "../../../src/OfflineChannel"
import { IOfflineChannelConfiguration, eStorageProviders } from "../../../src/applicationinsights-offlinechannel-js";

Expand Down Expand Up @@ -123,7 +123,7 @@ export class ChannelTests extends AITestClass {

let storageObj = JSON.parse(storageStr);
let evts = storageObj.evts;
Assert.deepEqual(Object.keys(evts).length, 1, "storgae should have one event");
Assert.deepEqual(Object.keys(evts).length, 1, "storage should have one event");

this.clock.tick(10);

Expand Down Expand Up @@ -292,15 +292,3 @@ export class ChannelTests extends AITestClass {
});
}
}

function mockTelemetryItem(): ITelemetryItem {
let evt = {
ver: "testVer" + Math.random(),
name:"testName",
time: "testTime",
iKey:"testKey",
baseData: {pro1: "prop1"},
baseType: "testType"
} as ITelemetryItem;
return evt;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { OfflineDbProviderTests } from "./dbprovider.tests"
import { OfflineInMemoryBatchTests } from "./inmemorybatch.tests";
import { OfflineBatchHandlerTests } from "./offlinebatchhandler.tests";
import { ChannelTests } from "./channel.tests";
import { Offlinetimer } from "./offlinetimer.tests";

export function runTests() {
new OfflineIndexedDBTests().registerTests();
Expand All @@ -12,4 +13,5 @@ export function runTests() {
new OfflineInMemoryBatchTests().registerTests();
new OfflineBatchHandlerTests().registerTests();
new ChannelTests().registerTests();
new Offlinetimer().registerTests();
}
227 changes: 227 additions & 0 deletions channels/offline-channel-js/Tests/Unit/src/offlinetimer.tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import { AITestClass, Assert } from "@microsoft/ai-test-framework";
import { DEFAULT_BREEZE_ENDPOINT, DEFAULT_BREEZE_PATH, EventPersistence, IConfig } from "@microsoft/applicationinsights-common";
import { AppInsightsCore, IConfiguration, IPayloadData, OnCompleteCallback } from "@microsoft/applicationinsights-core-js";
import { TestChannel, mockTelemetryItem } from "./TestHelper";
import { OfflineChannel } from "../../../src/OfflineChannel";
import { IOfflineChannelConfiguration, eStorageProviders } from "../../../src/Interfaces/IOfflineProvider";
import { IPostTransmissionTelemetryItem } from "../../../src/applicationinsights-offlinechannel-js";

export class Offlinetimer extends AITestClass {
private core: AppInsightsCore;
private coreConfig: IConfig & IConfiguration;

public testInitialize() {
super.testInitialize();
AITestClass.orgLocalStorage.clear();

this.coreConfig = {
instrumentationKey: "testIkey",
endpointUrl: DEFAULT_BREEZE_ENDPOINT + DEFAULT_BREEZE_PATH
};
this.core = new AppInsightsCore();

}

public testCleanup() {
super.testCleanup();
AITestClass.orgLocalStorage.clear();
this.onDone(() => {
this.core.unload();
});
this.core = null as any;
this.coreConfig = null as any;

}

public registerTests() {

this.testCase({
name: "InMemo Timer: Handle in memory timer",
useFakeTimers: true,
test: () => {
this.coreConfig.extensionConfig = {["OfflineChannel"]: {providers:[eStorageProviders.LocalStorage], inMemoMaxTime: 2000, minPersistenceLevel: EventPersistence.Critical, eventsLimitInMem: 2 } as IOfflineChannelConfiguration};
let channel = new OfflineChannel();
let onlineChannel = new TestChannel();
this.core.initialize(this.coreConfig,[channel, onlineChannel]);
let offlineListener = channel.getOfflineListener() as any;

// online, processTelemetry is not called
offlineListener.setOnlineState(1);
let inMemoTimer = channel["_getDbgPlgTargets"]()[3];
Assert.ok(!inMemoTimer, "in memo timer should be null");

// offline, processTelemetry is not called
offlineListener.setOnlineState(2);
inMemoTimer = channel["_getDbgPlgTargets"]()[3];
Assert.ok(!inMemoTimer, "in memo timer should be null test1");

// online, processTelemetry is called
offlineListener.setOnlineState(1);
let evt = mockTelemetryItem();
channel.processTelemetry(evt);
inMemoTimer = channel["_getDbgPlgTargets"]()[3];
Assert.ok(!inMemoTimer, "in memo timer should be null test2");

// offline, processTelemetry is called with event that should not be sent
offlineListener.setOnlineState(2);
evt = mockTelemetryItem();
channel.processTelemetry(evt);
inMemoTimer = channel["_getDbgPlgTargets"]()[3];
Assert.ok(!inMemoTimer, "in memo timer should be null test3");

// offline, processTelemetry is called with event that should be sent
offlineListener.setOnlineState(2);
let validEvt = mockTelemetryItem() as IPostTransmissionTelemetryItem;
validEvt.persistence = 2;
channel.processTelemetry(validEvt);
channel.processTelemetry(validEvt);
inMemoTimer = channel["_getDbgPlgTargets"]()[3];
Assert.ok(inMemoTimer, "in memo timer should be created");
Assert.ok(inMemoTimer.enabled, "in memo timer should be enabled");
let inMemoBatch = channel["_getDbgPlgTargets"]()[1];
Assert.equal(inMemoBatch && inMemoBatch.count(), 2, "should have two events");

// offline, flush all events in memory, and processTelemetry is not called again
this.clock.tick(2000);
inMemoBatch = channel["_getDbgPlgTargets"]()[1];
Assert.equal(inMemoBatch && inMemoBatch.count(), 0, "should have no event left");
inMemoTimer = channel["_getDbgPlgTargets"]()[3];
Assert.ok(!inMemoTimer.enabled, "in memo timer enabled should be false with no events in memory");

this.clock.tick(2000);
inMemoBatch = channel["_getDbgPlgTargets"]()[1];
Assert.equal(inMemoBatch && inMemoBatch.count(), 0, "should have no event left");
inMemoTimer = channel["_getDbgPlgTargets"]()[3];
Assert.ok(!inMemoTimer.enabled, "in memo timer enabled should be false with no events in memory and no processTelemtry is called");

// offline, flush all events and with one event left in memory, and processTelemetry is not called again
channel.processTelemetry(validEvt);
channel.processTelemetry(validEvt);
channel.processTelemetry(validEvt);
inMemoTimer = channel["_getDbgPlgTargets"]()[3];
Assert.ok(inMemoTimer.enabled, "in memo timer should be enabled");
inMemoBatch = channel["_getDbgPlgTargets"]()[1];
Assert.equal(inMemoBatch && inMemoBatch.count(), 1, "should have one event left after flush");

this.clock.tick(2000);
inMemoBatch = channel["_getDbgPlgTargets"]()[1];
Assert.equal(inMemoBatch && inMemoBatch.count(), 0, "should have no event left");
inMemoTimer = channel["_getDbgPlgTargets"]()[3];
Assert.ok(!inMemoTimer.enabled, "in memo timer should be canceld with no events in memory test1");

// offline with one event saved in memory, and then online with processTelemetry called
channel.processTelemetry(validEvt);
inMemoTimer = channel["_getDbgPlgTargets"]()[3];
Assert.ok(inMemoTimer.enabled, "in memo timer should be enabled");
inMemoBatch = channel["_getDbgPlgTargets"]()[1];
Assert.equal(inMemoBatch && inMemoBatch.count(), 1, "should have one event left after flush");

offlineListener.setOnlineState(1);
this.clock.tick(2000);
inMemoBatch = channel["_getDbgPlgTargets"]()[1];
Assert.equal(inMemoBatch && inMemoBatch.count(), 0, "should have no event left");
inMemoTimer = channel["_getDbgPlgTargets"]()[3];
Assert.ok(!inMemoTimer.enabled, "in memo timer should be canceld with no events in memory test2");

channel.processTelemetry(validEvt);
inMemoTimer = channel["_getDbgPlgTargets"]()[3];
Assert.ok(!inMemoTimer.enabled, "in memo timer should be canceld when back online");


channel.teardown();

}
});

this.testCase({
name: "SendNextBatch Timer: Handle sendNextBatch timer",
useFakeTimers: true,
test: () => {
let called = 0;
let sendPost = (payload: IPayloadData, oncomplete: OnCompleteCallback, sync?: boolean) => {
// first call, return complete, data null;
called ++;
console.log("test")
oncomplete(200, {});
return;

}

let xhrOverride = {sendPOST: sendPost}
this.coreConfig.extensionConfig = {["OfflineChannel"]: {providers:[eStorageProviders.LocalStorage], inMemoMaxTime: 2000, eventsLimitInMem: 2, maxSentBatchInterval: 10000,
senderCfg: {httpXHROverride: xhrOverride, alwaysUseXhrOverride: true}
} as IOfflineChannelConfiguration};
let channel = new OfflineChannel();
let onlineChannel = new TestChannel();
this.core.initialize(this.coreConfig,[channel, onlineChannel]);
let offlineListener = channel.getOfflineListener() as any;

// online, processTelemetry is not called
offlineListener.setOnlineState(1);
let sendBatchTimer = channel["_getDbgPlgTargets"]()[4];
Assert.ok(sendBatchTimer, "sendBatch timer should be created after init");
Assert.ok(sendBatchTimer.enabled, "sendBatch timer should be enabled");

// online, processTelemetry is not called, no previous stored events returned
this.clock.tick(10000);
sendBatchTimer = channel["_getDbgPlgTargets"]()[4];
Assert.ok(!sendBatchTimer.enabled, "sendBatch timer should not start again with no events in storage");

// online, processTelemetry called, no previous stored events returned
this.clock.tick(10000);
let evt = mockTelemetryItem() as IPostTransmissionTelemetryItem;
evt.persistence = 2;
channel.processTelemetry(evt);
sendBatchTimer = channel["_getDbgPlgTargets"]()[4];
Assert.ok(!sendBatchTimer.enabled, "sendBatch timer should not start again with no events in storage test1");
Assert.equal(called, 0 , "no data is sent");

// offline, processTelemetry is not called
offlineListener.setOnlineState(2);
this.clock.tick(10000);
sendBatchTimer = channel["_getDbgPlgTargets"]()[4];
Assert.ok(!sendBatchTimer.enabled, "sendBatch timer should not start again with no events in storage test2");
Assert.equal(called, 0 , "no data is sent");

// offline, processTelemetry is called, in Memory flush called
channel.processTelemetry(evt);
channel.processTelemetry(evt);
this.clock.tick(10000);
sendBatchTimer = channel["_getDbgPlgTargets"]()[4];
Assert.ok(!sendBatchTimer.enabled, "sendBatch timer should not start again with no events in storage test3");
Assert.equal(called, 0 , "no data is sent");

// online, with online sender is not idle
offlineListener.setOnlineState(1);
onlineChannel.setIsIdle(false);
channel.processTelemetry(evt);
this.clock.tick(10000);
sendBatchTimer = channel["_getDbgPlgTargets"]()[4];
Assert.ok(sendBatchTimer.enabled, "sendBatch timer be refreshed when it is online and not idle");
Assert.equal(called, 0 , "no data is sent");

// online, with online sender is idle
offlineListener.setOnlineState(1);
onlineChannel.setIsIdle(true);
channel.processTelemetry(evt);
this.clock.tick(10000);
sendBatchTimer = channel["_getDbgPlgTargets"]()[4];
Assert.ok(sendBatchTimer.enabled, "sendBatch timer be refreshed when it is online and idle");
Assert.equal(called, 1 , "data is sent");

// online, with processTelemtry called again
offlineListener.setOnlineState(1);
channel.processTelemetry(evt);
this.clock.tick(10000);
sendBatchTimer = channel["_getDbgPlgTargets"]()[4];
Assert.ok(!sendBatchTimer.enabled, "sendBatch timer not be refreshed when it is online and no stored events");
Assert.equal(called, 1 , "no data is sent again");



channel.teardown();

}
});
}
}
4 changes: 2 additions & 2 deletions channels/offline-channel-js/src/InMemoryBatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ export class InMemoryBatch implements IInMemoryBatch {
return new InMemoryBatch(logger, endpoint, theEvts, evtsLimitInMem);
};

_self.createNew = (newEndpoint: string, evts?: IPostTransmissionTelemetryItem[] | ITelemetryItem[], evtsLimitInMem?: number) => {
return new InMemoryBatch(logger, newEndpoint, evts,evtsLimitInMem);
_self.createNew = (newEndpoint: string, evts?: IPostTransmissionTelemetryItem[] | ITelemetryItem[], newEvtsLimitInMem?: number) => {
return new InMemoryBatch(logger, newEndpoint, evts, newEvtsLimitInMem);
}

});
Expand Down
Loading
Loading