-
Notifications
You must be signed in to change notification settings - Fork 255
[main][stats beat] implement stats beat in application insights #2489
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
Merged
Merged
Changes from all commits
Commits
Show all changes
69 commits
Select commit
Hold shift + click to select a range
acc2e43
basic structure
siyuniu-ms 65be711
Merge branch 'main' into siyu/statsbeats
siyuniu-ms bfe5965
imp[ort
siyuniu-ms 5182765
test locally works good
siyuniu-ms 3f5ae6c
Update StatsBeat.ts
siyuniu-ms 0711bf7
only check statsbeat before send it
siyuniu-ms 94a090e
refactor
siyuniu-ms 5cbac35
remove statsbeatdata from function call
siyuniu-ms 9913e66
remove
siyuniu-ms 31811e7
refactor statsbeat
siyuniu-ms a6ba0fb
count in a different way
siyuniu-ms 0006c9b
call oncomplete manually
siyuniu-ms 11b7e26
Update Sender.ts
siyuniu-ms ff8e0b6
format change
siyuniu-ms 2190c12
rename var and remove set initilize
siyuniu-ms 28f2696
change class to interface
siyuniu-ms c81ea69
set up timer
siyuniu-ms eb2392b
avoid duplicate init
siyuniu-ms 6dd7528
rename exception
siyuniu-ms 96d5089
Update testVersionConflict.html
siyuniu-ms 6131e9f
add some comment
siyuniu-ms b24c9fd
add count check
siyuniu-ms ea2b96a
throttle count
siyuniu-ms 41cb9d7
fix the ugilify not compile problem
siyuniu-ms d475568
status code change
siyuniu-ms fae3295
add basic test
siyuniu-ms c94a752
add counter test
siyuniu-ms ee5faa9
Update Sender.ts
siyuniu-ms 13ec43c
Merge branch 'main' into siyu/statsbeats
siyuniu-ms 4b5ea40
signature changes
siyuniu-ms 8f0cb34
remove duplicate
siyuniu-ms 4bf7c05
refactor config
siyuniu-ms bfd5178
Update Sender.ts
siyuniu-ms 1d7fe05
Update Sender.ts
siyuniu-ms 19e3fc7
Merge branch 'siyu/statsbeats' of https://github.com/microsoft/Applic…
siyuniu-ms 9fd36fd
Merge branch 'main' into siyu/statsbeats
siyuniu-ms e79f02c
size change and a trick
siyuniu-ms bc0ff2c
Update AISKUSize.Tests.ts
siyuniu-ms 7fa1123
size change
siyuniu-ms 2dd95a5
Update AISKULightSize.Tests.ts
siyuniu-ms 50939a7
Update Sender.ts
siyuniu-ms 162a5d3
rename and change the way to call statsbeat
siyuniu-ms 4edd527
Merge branch 'main' into siyu/statsbeats
siyuniu-ms 6a65e00
Update cfgsynchelper.tests.ts
siyuniu-ms 3a6eea0
Update AppInsightsCore.ts
siyuniu-ms a60cfdb
Update StatsBeat.tests.ts
siyuniu-ms 23d4771
comment out statsbeat test, set statsbeat to null by default
siyuniu-ms 3598def
Update AppInsightsCore.ts
siyuniu-ms 1acb33f
change based on comments
siyuniu-ms b7a19da
Update StatsBeat.Tests.ts
siyuniu-ms 3c18763
Update StatsBeat.Tests.ts
siyuniu-ms afd15de
Update StatsBeat.tests.ts
siyuniu-ms b3a684a
Update Sender.ts
siyuniu-ms fafad40
Update Sender.ts
siyuniu-ms 94539c1
Update AppInsightsCore.ts
siyuniu-ms ce2070b
Update AnalyticsExtensionSize.tests.ts
siyuniu-ms 3370371
Merge branch 'main' into siyu/statsbeats
siyuniu-ms f7605df
add status code 499
siyuniu-ms dd32dc2
endpointurl update
siyuniu-ms 90bb3f1
change the way of initialize
siyuniu-ms f2a4e76
Update AppInsightsCoreSize.Tests.ts
siyuniu-ms 6161b9e
Update AppInsightsCore.ts
siyuniu-ms 1a5a97d
re-create
siyuniu-ms 0b820cb
Merge branch 'main' into siyu/statsbeats
siyuniu-ms 516d6c3
Update cfgsynchelper.tests.ts
siyuniu-ms b156b56
Update SenderPostManager.ts
siyuniu-ms 3fa8310
Update SenderPostManager.ts
siyuniu-ms 73c4a96
check statuscode
siyuniu-ms 318d41f
update response check
siyuniu-ms File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
255 changes: 255 additions & 0 deletions
255
channels/applicationinsights-channel-js/Tests/Unit/src/StatsBeat.tests.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,255 @@ | ||
| import { AITestClass, Assert, PollingAssert } from "@microsoft/ai-test-framework"; | ||
| import { AppInsightsCore, getWindow, IPayloadData, ITelemetryItem, TransportType } from "@microsoft/applicationinsights-core-js"; | ||
| import { Sender } from "../../../src/Sender"; | ||
| import { SinonSpy, SinonStub } from "sinon"; | ||
| import { ISenderConfig } from "../../../types/applicationinsights-channel-js"; | ||
| import { isBeaconApiSupported } from "@microsoft/applicationinsights-common"; | ||
|
|
||
| export class StatsbeatTests extends AITestClass { | ||
| private _core: AppInsightsCore; | ||
| private _sender: Sender; | ||
| private statsbeatCountSpy: SinonSpy; | ||
| private fetchStub: sinon.SinonStub; | ||
| private beaconStub: sinon.SinonStub; | ||
| private trackSpy: SinonSpy; | ||
|
|
||
| public testInitialize() { | ||
| this._core = new AppInsightsCore(); | ||
| this._sender = new Sender(); | ||
| } | ||
|
|
||
| public testFinishedCleanup() { | ||
| if (this._sender && this._sender.isInitialized()) { | ||
| this._sender.pause(); | ||
| this._sender._buffer.clear(); | ||
| this._sender.teardown(); | ||
| } | ||
| this._sender = null; | ||
| this._core = null; | ||
| if (this.statsbeatCountSpy) { | ||
| this.statsbeatCountSpy.restore(); | ||
| } | ||
| if (this.fetchStub) { | ||
| this.fetchStub.restore(); | ||
| } | ||
| if (this.beaconStub) { | ||
| this.beaconStub.restore(); | ||
| } | ||
| if (this.trackSpy) { | ||
| this.trackSpy.restore(); | ||
| } | ||
| } | ||
|
|
||
| private initializeCoreAndSender(config: any, instrumentationKey: string) { | ||
| const sender = new Sender(); | ||
| const core = new AppInsightsCore(); | ||
| const coreConfig = { | ||
| instrumentationKey, | ||
| _sdk: { stats: true }, | ||
| extensionConfig: { [sender.identifier]: config } | ||
| }; | ||
|
|
||
| core.initialize(coreConfig, [sender]); | ||
| this.statsbeatCountSpy = this.sandbox.spy(core.getStatsBeat(), "count"); | ||
| this.trackSpy = this.sandbox.spy(core, "track"); | ||
|
|
||
| this.onDone(() => { | ||
| sender.teardown(); | ||
| }); | ||
|
|
||
| return { core, sender }; | ||
| } | ||
|
|
||
| private createSenderConfig(transportType: TransportType) { | ||
| return { | ||
| endpointUrl: "https://test", | ||
| emitLineDelimitedJson: false, | ||
| maxBatchInterval: 15000, | ||
| maxBatchSizeInBytes: 102400, | ||
| disableTelemetry: false, | ||
| enableSessionStorageBuffer: true, | ||
| isRetryDisabled: false, | ||
| isBeaconApiDisabled: false, | ||
| disableXhr: false, | ||
| onunloadDisableFetch: false, | ||
| onunloadDisableBeacon: false, | ||
| namePrefix: "", | ||
| samplingPercentage: 100, | ||
| customHeaders: [{ header: "header", value: "val" }], | ||
| convertUndefined: "", | ||
| eventsLimitInMem: 10000, | ||
| transports: [transportType] | ||
| }; | ||
| } | ||
|
|
||
| private processTelemetryAndFlush(sender: Sender, telemetryItem: ITelemetryItem) { | ||
| try { | ||
| sender.processTelemetry(telemetryItem, null); | ||
| sender.flush(); | ||
| } catch (e) { | ||
| QUnit.assert.ok(false, "Unexpected error during telemetry processing"); | ||
| } | ||
| this.clock.tick(900000); // Simulate time passing for statsbeat to be sent | ||
| } | ||
|
|
||
| private assertStatsbeatCall(statusCode: number, eventName: string) { | ||
| Assert.equal(this.statsbeatCountSpy.callCount, 1, "Statsbeat count should be called once"); | ||
| Assert.equal(this.statsbeatCountSpy.firstCall.args[0], statusCode, `Statsbeat count should be called with status ${statusCode}`); | ||
| const data = JSON.stringify(this.statsbeatCountSpy.firstCall.args[1]); | ||
| Assert.ok(data.includes("startTime"), "Statsbeat count should be called with startTime set"); | ||
| const statsbeatEvent = this.trackSpy.firstCall.args[0]; | ||
| Assert.equal(statsbeatEvent.baseType, "MetricData", "Statsbeat event should be of type MetricData"); | ||
| Assert.equal(statsbeatEvent.baseData.name, eventName, `Statsbeat event should be of type ${eventName}`); | ||
| } | ||
|
|
||
| public registerTests() { | ||
| this.testCase({ | ||
| name: "Statsbeat initializes when stats is true", | ||
| test: () => { | ||
| const config = { | ||
| _sdk: { stats: true }, | ||
| instrumentationKey: "Test-iKey" | ||
| }; | ||
|
|
||
| this._core.initialize(config, [this._sender]); | ||
| const statsbeat = this._core.getStatsBeat(); | ||
|
|
||
| QUnit.assert.ok(statsbeat, "Statsbeat is initialized"); | ||
| QUnit.assert.ok(statsbeat.isInitialized(), "Statsbeat is marked as initialized"); | ||
| } | ||
| }); | ||
|
|
||
| this.testCaseAsync({ | ||
| name: "Statsbeat increments success count when fetch sender is called once", | ||
| useFakeTimers: true, | ||
| useFakeServer: true, | ||
| stepDelay: 100, | ||
| steps: [ | ||
| () => { | ||
| this.fetchStub = this.sandbox.stub(window, "fetch").callsFake(() => { // only fetch is supported to stub, why? | ||
| return Promise.resolve(new Response("{}", { status: 200, statusText: "OK" })); | ||
| }); | ||
|
|
||
| const config = this.createSenderConfig(TransportType.Fetch); | ||
| const { sender } = this.initializeCoreAndSender(config, "000e0000-e000-0000-a000-000000000000"); | ||
|
|
||
| const telemetryItem: ITelemetryItem = { | ||
| name: "fake item", | ||
| iKey: "testIkey2;ingestionendpoint=testUrl1", | ||
| baseType: "some type", | ||
| baseData: {} | ||
| }; | ||
|
|
||
| this.processTelemetryAndFlush(sender, telemetryItem); | ||
|
|
||
| } | ||
| ].concat(PollingAssert.createPollingAssert(() => { | ||
| if (this.statsbeatCountSpy.called && this.fetchStub.called) { | ||
| this.assertStatsbeatCall(200, "Request_Success_Count"); | ||
| return true; | ||
| } | ||
| return false; | ||
| }, "Waiting for fetch sender and Statsbeat count to be called") as any) | ||
| }); | ||
|
|
||
| this.testCaseAsync({ | ||
| name: "Statsbeat increments throttle count when fetch sender is called with status 439", | ||
| useFakeTimers: true, | ||
| stepDelay: 100, | ||
| steps: [ | ||
| () => { | ||
| this.fetchStub = this.sandbox.stub(window, "fetch").callsFake(() => { | ||
| return Promise.resolve(new Response("{}", { status: 439, statusText: "Too Many Requests" })); | ||
| }); | ||
|
|
||
| const config = this.createSenderConfig(TransportType.Fetch); | ||
| const { sender } = this.initializeCoreAndSender(config, "000e0000-e000-0000-a000-000000000000"); | ||
|
|
||
| const telemetryItem: ITelemetryItem = { | ||
| name: "fake item", | ||
| iKey: "testIkey2;ingestionendpoint=testUrl1", | ||
| baseType: "some type", | ||
| baseData: {} | ||
| }; | ||
|
|
||
| this.processTelemetryAndFlush(sender, telemetryItem); | ||
| } | ||
| ].concat(PollingAssert.createPollingAssert(() => { | ||
| if (this.statsbeatCountSpy.called && this.fetchStub.called) { | ||
| this.assertStatsbeatCall(439, "Throttle_Count"); | ||
| return true; | ||
| } | ||
| return false; | ||
| }, "Waiting for fetch sender and Statsbeat count to be called") as any) | ||
| }); | ||
|
|
||
| this.testCaseAsync({ | ||
| name: "Statsbeat increments success count for beacon sender", | ||
| useFakeTimers: true, | ||
| stepDelay: 100, | ||
| steps: [ | ||
| () => { | ||
| const config = this.createSenderConfig(TransportType.Beacon); | ||
| const { sender } = this.initializeCoreAndSender(config, "000e0000-e000-0000-a000-000000000000"); | ||
|
|
||
| const telemetryItem: ITelemetryItem = { | ||
| name: "fake item", | ||
| iKey: "testIkey2;ingestionendpoint=testUrl1", | ||
| baseType: "some type", | ||
| baseData: {} | ||
| }; | ||
| let sendBeaconCalled = false; | ||
| this.hookSendBeacon((url: string) => { | ||
| sendBeaconCalled = true; | ||
| return true; | ||
| }); | ||
| QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported"); | ||
| this.processTelemetryAndFlush(sender, telemetryItem); | ||
| } | ||
| ].concat(PollingAssert.createPollingAssert(() => { | ||
| if (this.statsbeatCountSpy.called) { | ||
| this.assertStatsbeatCall(200, "Request_Success_Count"); | ||
| return true; | ||
| } | ||
| return false; | ||
| }, "Waiting for beacon sender and Statsbeat count to be called") as any) | ||
| }); | ||
|
|
||
|
|
||
| this.testCaseAsync({ | ||
| name: "Statsbeat increments success count for xhr sender", | ||
| useFakeTimers: true, | ||
| useFakeServer: true, | ||
| stepDelay: 100, | ||
| fakeServerAutoRespond: true, | ||
| steps: [ | ||
| () => { | ||
| let window = getWindow(); | ||
| let fakeXMLHttpRequest = (window as any).XMLHttpRequest; // why we do this? | ||
| let config = this.createSenderConfig(TransportType.Xhr) && {disableSendBeaconSplit: true}; | ||
| const { sender } = this.initializeCoreAndSender(config, "000e0000-e000-0000-a000-000000000000"); | ||
| console.log("xhr sender called", this._getXhrRequests().length); | ||
|
|
||
| const telemetryItem: ITelemetryItem = { | ||
| name: "fake item", | ||
| iKey: "testIkey2;ingestionendpoint=testUrl1", | ||
| baseType: "some type", | ||
| baseData: {} | ||
| }; | ||
| this.processTelemetryAndFlush(sender, telemetryItem); | ||
| QUnit.assert.equal(1, this._getXhrRequests().length, "xhr sender is called"); | ||
| console.log("xhr sender is called", this._getXhrRequests().length); | ||
| (window as any).XMLHttpRequest = fakeXMLHttpRequest; | ||
|
|
||
| } | ||
| ].concat(PollingAssert.createPollingAssert(() => { | ||
| if (this.statsbeatCountSpy.called) { | ||
| this.assertStatsbeatCall(200, "Request_Success_Count"); | ||
| console.log("Statsbeat count called with success count for xhr sender"); | ||
| return true; | ||
| } | ||
| return false; | ||
| }, "Waiting for xhr sender and Statsbeat count to be called", 60, 1000) as any) | ||
| }); | ||
| } | ||
| } |
2 changes: 2 additions & 0 deletions
2
channels/applicationinsights-channel-js/Tests/Unit/src/aichannel.tests.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,11 @@ | ||
| import { SenderTests } from "./Sender.tests"; | ||
| import { SampleTests } from "./Sample.tests"; | ||
| import { GlobalTestHooks } from "./GlobalTestHooks.Test"; | ||
| import { StatsbeatTests } from "./StatsBeat.tests"; | ||
|
|
||
| export function runTests() { | ||
| new GlobalTestHooks().registerTests(); | ||
| new SenderTests().registerTests(); | ||
| new SampleTests().registerTests(); | ||
| // new StatsbeatTests().registerTests(); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.