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

[Feature] Add Statsbeat Network data collection #1645

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
4 changes: 2 additions & 2 deletions AISKU/src/Initialization.ts
Expand Up @@ -7,7 +7,7 @@ import {
arrForEach, isString, isFunction, isNullOrUndefined, addEventHandler, isArray, throwError, ICookieMgr, safeGetCookieMgr
} from "@microsoft/applicationinsights-core-js";
import { ApplicationInsights } from "@microsoft/applicationinsights-analytics-js";
import { Sender } from "@microsoft/applicationinsights-channel-js";
import { Sender, Statsbeat } from "@microsoft/applicationinsights-channel-js";
import { PropertiesPlugin } from "@microsoft/applicationinsights-properties-js";
import { AjaxPlugin as DependenciesPlugin, IDependenciesPlugin } from '@microsoft/applicationinsights-dependencies-js';
import {
Expand Down Expand Up @@ -143,7 +143,7 @@ export class Initialization implements IApplicationInsights {
_self.properties = new PropertiesPlugin();
_self.dependencies = new DependenciesPlugin();
_self.core = new AppInsightsCore();
_self._sender = new Sender();
_self._sender = new Sender(new Statsbeat());

_self.snippet = snippet;
_self.config = config;
Expand Down
5 changes: 3 additions & 2 deletions AISKULight/index.ts
Expand Up @@ -13,7 +13,7 @@ import {
throwError
} from "@microsoft/applicationinsights-core-js";
import { IConfig } from "@microsoft/applicationinsights-common";
import { Sender } from "@microsoft/applicationinsights-channel-js";
import { Sender, Statsbeat } from "@microsoft/applicationinsights-channel-js";

"use strict";

Expand Down Expand Up @@ -52,7 +52,8 @@ export class ApplicationInsights {
public initialize(): void {
this.core = new AppInsightsCore();
const extensions = [];
const appInsightsChannel: Sender = new Sender();
const statsbeat = new Statsbeat();
const appInsightsChannel: Sender = new Sender(statsbeat);

extensions.push(appInsightsChannel);

Expand Down
@@ -0,0 +1,145 @@
import { AITestClass } from "@microsoft/ai-test-framework";
import { Sender } from "../../../src/Sender";
import { Metric, IMetricTelemetry } from "@microsoft/applicationinsights-common";
import { AppInsightsCore, _InternalMessageId } from "@microsoft/applicationinsights-core-js";
import { Statsbeat } from "../../../src/Statsbeat";
import { StatsbeatCounter } from "../../../src/Constants";

Statsbeat.INSTRUMENTATION_KEY = "2aa22222-bbbb-1ccc-8ddd-eeeeffff3333";
var strEmpty = "";
const endpoint = "https://dc.services.visualstudio.com/v2/track";
export class StatsbeatTests extends AITestClass {
private _sender: Sender;
private _statsbeat: Statsbeat;

public testInitialize() {
this._statsbeat = new Statsbeat();
this._sender = new Sender(this._statsbeat);
}

public testCleanup() {
this._statsbeat = null;
this._sender = null;
}

public registerTests() {
this.testCase({
name: "Statsbeat by default is enabled and is initialized while sender is initialized.",
test: () => {
const spy = this.sandbox.spy(this._statsbeat, "initialize");
this._sender.initialize(
{
instrumentationKey: 'abc',
}, new AppInsightsCore(), []
);

QUnit.assert.equal(true, spy.calledOnce, "Statsbeat is enabled by default.");
QUnit.assert.equal(true, this._statsbeat.isInitialized());
}
});

this.testCase({
name: "Statsbeat is disabled if customer configured to disable statsbeat.",
test: () => {
this._sender.initialize(
{
instrumentationKey: 'abc',
disableStatsbeat: true,
}, new AppInsightsCore(), []
);

const spy = this.sandbox.spy(this._statsbeat, "initialize");
QUnit.assert.equal(false, spy.calledOnce, "Statsbeat is disabled with customer configuration. When disableStatsbeat sets to true, statsbeat is not initialized thus initialize method is not called.");
}
});

this.testCase({
name: "It adds correct network properties to custom metric.",
test: () => {
// the first xhr gets created when _sender calls initialize; the second xhr gest created when statsbeat's sender calls initialize
this._sender.initialize({ instrumentationKey: "1aa11111-bbbb-1ccc-8ddd-eeeeffff3333" }, new AppInsightsCore(), []);
// the third xhr gets created when track is called and the current _sender creates a xhr to send data
this._statsbeat.trackShortIntervalStatsbeats();

QUnit.assert.equal(1, this._getXhrRequests().length, "xhr sender is called");

// we only care the last object that's about to send
const arr = JSON.parse(this._getXhrRequests()[0].requestBody);
let fakeReq = arr[arr.length - 1];
console.log(fakeReq);
QUnit.assert.equal(fakeReq.name, "Microsoft.ApplicationInsights." + Statsbeat.INSTRUMENTATION_KEY.replace(/-/g, strEmpty) + ".Metric");
QUnit.assert.equal(fakeReq.iKey, Statsbeat.INSTRUMENTATION_KEY);
QUnit.assert.equal(fakeReq.data.baseType, Metric.dataType);

let baseData: IMetricTelemetry = fakeReq.data.baseData;
QUnit.assert.equal(baseData.properties["cikey"], "1aa11111-bbbb-1ccc-8ddd-eeeeffff3333");
QUnit.assert.equal(baseData.properties["host"], "https://dc.services.visualstudio.com/v2/track");
}
});

this.testCase({
name: "Track duration.",
test: () => {
this._sender.initialize({ instrumentationKey: "1aa11111-bbbb-1ccc-8ddd-eeeeffff3333" }, new AppInsightsCore(), []);
this._statsbeat.countRequest(endpoint, 1000, true);
this._statsbeat.countRequest(endpoint, 500, false);
this._statsbeat.trackShortIntervalStatsbeats();

QUnit.assert.equal(1, this._getXhrRequests().length, "xhr sender is called");

// we only care the last object that's about to send
const arr = JSON.parse(this._getXhrRequests()[0].requestBody);
let fakeReq = arr[arr.length - 1];
console.log(fakeReq);
QUnit.assert.equal(fakeReq.name, "Microsoft.ApplicationInsights." + Statsbeat.INSTRUMENTATION_KEY.replace(/-/g, strEmpty) + ".Metric");
QUnit.assert.equal(fakeReq.iKey, Statsbeat.INSTRUMENTATION_KEY);
QUnit.assert.equal(fakeReq.data.baseType, Metric.dataType);

let baseData: IMetricTelemetry = fakeReq.data.baseData;
QUnit.assert.equal(baseData.properties["cikey"], "1aa11111-bbbb-1ccc-8ddd-eeeeffff3333");
QUnit.assert.equal(baseData.properties["host"], endpoint);
QUnit.assert.equal(baseData.properties[StatsbeatCounter.REQUEST_DURATION], "750");
QUnit.assert.equal(baseData.properties[StatsbeatCounter.REQUEST_SUCCESS], "1");
}
});

this.testCase({
name: "Track counts.",
test: () => {
this._sender.initialize({ instrumentationKey: "1aa11111-bbbb-1ccc-8ddd-eeeeffff3333" }, new AppInsightsCore(), []);
this._statsbeat.countRequest(endpoint, 1, true);
this._statsbeat.countRequest(endpoint, 1, true);
this._statsbeat.countRequest(endpoint, 1, true);
this._statsbeat.countRequest(endpoint, 1, true);
this._statsbeat.countRequest(endpoint, 1, false);
this._statsbeat.countRequest(endpoint, 1, false);
this._statsbeat.countRequest(endpoint, 1, false);
this._statsbeat.countRetry(endpoint);
this._statsbeat.countRetry(endpoint);
this._statsbeat.countThrottle(endpoint);
this._statsbeat.countException(endpoint);
this._statsbeat.trackShortIntervalStatsbeats();

QUnit.assert.equal(1, this._getXhrRequests().length, "xhr sender is called");

// we only care the last object that's about to send
const arr = JSON.parse(this._getXhrRequests()[0].requestBody);
let fakeReq = arr[arr.length - 1];
console.log(fakeReq);
QUnit.assert.equal(fakeReq.name, "Microsoft.ApplicationInsights." + Statsbeat.INSTRUMENTATION_KEY.replace(/-/g, strEmpty) + ".Metric");
QUnit.assert.equal(fakeReq.iKey, Statsbeat.INSTRUMENTATION_KEY);
QUnit.assert.equal(fakeReq.data.baseType, Metric.dataType);

let baseData: IMetricTelemetry = fakeReq.data.baseData;
QUnit.assert.equal(baseData.properties["cikey"], "1aa11111-bbbb-1ccc-8ddd-eeeeffff3333");
QUnit.assert.equal(baseData.properties["host"], endpoint);
QUnit.assert.equal(baseData.properties[StatsbeatCounter.REQUEST_DURATION], "1");
QUnit.assert.equal(baseData.properties[StatsbeatCounter.REQUEST_SUCCESS], "4");
QUnit.assert.equal(baseData.properties[StatsbeatCounter.REQUEST_FAILURE], "3");
QUnit.assert.equal(baseData.properties[StatsbeatCounter.RETRY_COUNT], "2");
QUnit.assert.equal(baseData.properties[StatsbeatCounter.THROTTLE_COUNT], "1");
QUnit.assert.equal(baseData.properties[StatsbeatCounter.EXCEPTION_COUNT], "1");
}
});
}
}
@@ -1,7 +1,9 @@
import { SenderTests } from "./Sender.tests";
import { SampleTests } from "./Sample.tests";
import { StatsbeatTests } from "./Statsbeat.tests";

export function runTests() {
new SenderTests().registerTests();
new SampleTests().registerTests();
new StatsbeatTests().registerTests();
}
8 changes: 8 additions & 0 deletions channels/applicationinsights-channel-js/src/Constants.ts
@@ -0,0 +1,8 @@
export const StatsbeatCounter = {
REQUEST_SUCCESS: "Request Success Count",
REQUEST_FAILURE: "Requests Failure Count",
REQUEST_DURATION: "Request Duration",
RETRY_COUNT: "Retry Count",
THROTTLE_COUNT: "Throttle Count",
EXCEPTION_COUNT: "Exception Count",
}
Expand Up @@ -266,7 +266,7 @@ export class MetricEnvelopeCreator extends EnvelopeCreator {
Create(logger: IDiagnosticLogger, telemetryItem: ITelemetryItem, customUndefinedValue?: any): IEnvelope {
super.Init(logger, telemetryItem);

const baseData = telemetryItem[strBaseData];
const baseData = telemetryItem[strBaseData] || {};
xiao-lix marked this conversation as resolved.
Show resolved Hide resolved
const props = baseData[strProperties] || {};
const measurements = baseData.measurements || {};
EnvelopeCreator.extractPropsAndMeasurements(telemetryItem.data, props, measurements);
Expand Down
41 changes: 41 additions & 0 deletions channels/applicationinsights-channel-js/src/NetworkStatsbeat.ts
@@ -0,0 +1,41 @@
import { dateNow } from "@microsoft/applicationinsights-core-js";
export class NetworkStatsbeat {

public time: number;

public lastTime: number;

public host: string;

public totalRequestCount: number;

public lastRequestCount: number;

public totalSuccesfulRequestCount: number;

public totalFailedRequestCount: number;

public retryCount: number;

public exceptionCount: number;

public throttleCount: number;

public intervalRequestExecutionTime: number;

public lastIntervalRequestExecutionTime: number;

constructor(host: string) {
this.host = host;
this.totalRequestCount = 0;
this.totalSuccesfulRequestCount = 0;
this.totalFailedRequestCount = 0;
this.retryCount = 0;
this.exceptionCount = 0;
this.throttleCount = 0;
this.intervalRequestExecutionTime = 0;
this.lastIntervalRequestExecutionTime = 0;
this.lastTime = dateNow();
this.lastRequestCount = 0;
}
}