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
6 changes: 4 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 @@ -122,6 +122,7 @@ export class Initialization implements IApplicationInsights {
private properties: PropertiesPlugin;
private _sender: Sender;
private _snippetVersion: string;
private _statsbeat: Statsbeat;

constructor(snippet: Snippet) {
let _self = this;
Expand All @@ -143,7 +144,8 @@ export class Initialization implements IApplicationInsights {
_self.properties = new PropertiesPlugin();
_self.dependencies = new DependenciesPlugin();
_self.core = new AppInsightsCore();
_self._sender = new Sender();
_self._statsbeat = new Statsbeat();
xiao-lix marked this conversation as resolved.
Show resolved Hide resolved
_self._sender = new Sender(_self._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, true);

extensions.push(appInsightsChannel);

Expand Down
@@ -0,0 +1,148 @@
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: () => {
this._sender.initialize(
{
instrumentationKey: 'abc',
}, new AppInsightsCore(), []
);

QUnit.assert.equal(this._statsbeat["_isEnabled"], true, "By default, statsbeat is enabled.");
}
});

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

QUnit.assert.equal(this._statsbeat["_isEnabled"], false, "Statsbeat is disabled with customer configuration.");
}
});

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(), []);
const spy = this.sandbox.spy(this._statsbeat["_sender"], "triggerSend");
// 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(spy.callCount, 1, "should call sender");
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(), []);
const spy = this.sandbox.spy(this._statsbeat["_sender"], "triggerSend");
this._statsbeat.countRequest(endpoint, 1000, true);
this._statsbeat.countRequest(endpoint, 500, false);
this._statsbeat.trackShortIntervalStatsbeats();

QUnit.assert.equal(spy.callCount, 1, "should call sender");
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(), []);
const spy = this.sandbox.spy(this._statsbeat["_sender"], "triggerSend");
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(spy.callCount, 1, "should call sender");
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
40 changes: 40 additions & 0 deletions channels/applicationinsights-channel-js/src/NetworkStatsbeat.ts
@@ -0,0 +1,40 @@
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; // what exception count? - throwinternal? or exception for the sender -> sender fail to send
xiao-lix marked this conversation as resolved.
Show resolved Hide resolved

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 = +new Date;
xiao-lix marked this conversation as resolved.
Show resolved Hide resolved
this.lastRequestCount = 0;
}
}