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

Heartbeat Update #1109

Merged
merged 1 commit into from
Mar 22, 2023
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@ Tests/FunctionalTests/TestApp/package-lock.json

# Ignore log files
npm-debug.log
undefined/temp/appInsights-node/applicationinsights.log
undefined/
test/certs/server-key.pem
55 changes: 28 additions & 27 deletions AutoCollection/HeartBeat.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import crypto = require("crypto");
import os = require("os");
import Vm = require("../Library/AzureVirtualMachine");
import TelemetryClient = require("../Library/TelemetryClient");
import Constants = require("../Declarations/Constants");
import Config = require("../Library/Config");
Expand All @@ -14,7 +14,7 @@ class HeartBeat {
private _handle: NodeJS.Timer | null;
private _isEnabled: boolean;
private _isInitialized: boolean;
private _isVM: boolean;
private _uniqueProcessId: string;

constructor(client: TelemetryClient) {
if (!HeartBeat.INSTANCE) {
Expand Down Expand Up @@ -52,36 +52,37 @@ class HeartBeat {
}

public trackHeartBeat(config: Config, callback: () => void) {
let waiting: boolean = false;
let properties: { [key: string]: string } = {};
const sdkVersion = Context.sdkVersion; // "node" or "node-nativeperf"
properties["sdk"] = sdkVersion;
properties["sdkVersion"] = sdkVersion;
properties["osType"] = os.type();
if (process.env.WEBSITE_SITE_NAME) { // Web apps
properties["appSrv_SiteName"] = process.env.WEBSITE_SITE_NAME || "";
properties["appSrv_wsStamp"] = process.env.WEBSITE_HOME_STAMPNAME || "";
properties["appSrv_wsHost"] = process.env.WEBSITE_HOSTNAME || "";
} else if (process.env.FUNCTIONS_WORKER_RUNTIME) { // Function apps
properties["azfunction_appId"] = process.env.WEBSITE_HOSTNAME;
} else if (config) {
if (this._isVM === undefined) {
waiting = true;
Vm.AzureVirtualMachine.getAzureComputeMetadata(config, (vmInfo) => {
this._isVM = vmInfo.isVM;
if (this._isVM) {
properties["azInst_vmId"] = vmInfo.id;
properties["azInst_subscriptionId"] = vmInfo.subscriptionId;
properties["azInst_osType"] = vmInfo.osType;
}
this._client.trackMetric({ name: Constants.HeartBeatMetricName, value: 0, properties: properties });
callback();
});
}
properties["osVersion"] = os.release();
// Random GUID that would help in analysis when app has stopped and restarted.
if (!this._uniqueProcessId) {
this._uniqueProcessId = crypto.randomBytes(16).toString("hex");
}
properties["processSessionId"] = this._uniqueProcessId;

if (process.env.WEBSITE_SITE_NAME) {
properties["appSrv_SiteName"] = process.env.WEBSITE_SITE_NAME;
}
if (process.env.WEBSITE_HOME_STAMPNAME) {
properties["appSrv_wsStamp"] = process.env.WEBSITE_HOME_STAMPNAME;
}
if (process.env.WEBSITE_HOSTNAME) {
properties["appSrv_wsHost"] = process.env.WEBSITE_HOSTNAME;
}
if (process.env.WEBSITE_OWNER_NAME) {
properties["appSrv_wsOwner"] = process.env.WEBSITE_OWNER_NAME;
}
if (process.env.WEBSITE_RESOURCE_GROUP) {
properties["appSrv_ResourceGroup"] = process.env.WEBSITE_RESOURCE_GROUP;
}
if (!waiting) {
this._client.trackMetric({ name: Constants.HeartBeatMetricName, value: 0, properties: properties });
callback();
if (process.env.WEBSITE_SLOT_NAME) {
properties["appSrv_SlotName"] = process.env.WEBSITE_SLOT_NAME;
}
this._client.trackMetric({ name: Constants.HeartBeatMetricName, value: 0, properties: properties });
callback();
}

public dispose() {
Expand Down
2 changes: 1 addition & 1 deletion Declarations/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export const DependencyTypeName = {
QueueMessage: "Queue Message"
}

export const HeartBeatMetricName = "HeartBeat";
export const HeartBeatMetricName = "HeartbeatState";

export const StatsbeatTelemetryName = "Statsbeat";

Expand Down
54 changes: 20 additions & 34 deletions Tests/AutoCollection/Heartbeat.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,52 +56,38 @@ describe("AutoCollection/HeartBeat", () => {
env1["WEBSITE_SITE_NAME"] = "site_name";
env1["WEBSITE_HOME_STAMPNAME"] = "stamp_name";
env1["WEBSITE_HOSTNAME"] = "host_name";
env1["WEBSITE_OWNER_NAME"] = "owner_name";
env1["WEBSITE_RESOURCE_GROUP"] = "resource_group";
env1["WEBSITE_SLOT_NAME"] = "slot_name";
process.env = env1;

heartbeat1["trackHeartBeat"](client.config, () => {
assert.equal(stub1.callCount, 1, "should call trackMetric for the appSrv heartbeat metric");
assert.equal(stub1.args[0][0].name, "HeartBeat", "should use correct name for heartbeat metric");
assert.equal(stub1.args[0][0].name, "HeartbeatState", "should use correct name for heartbeat metric");
assert.equal(stub1.args[0][0].value, 0, "value should be 0");
const keys1 = Object.keys(stub1.args[0][0].properties);
assert.equal(keys1.length, 5, "should have 5 kv pairs added when resource type is appSrv");
assert.equal(keys1[0], "sdk", "sdk should be added as a key");
assert.equal(keys1.length, 10, "should have 5 kv pairs added when resource type is appSrv");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small typo in the description of this test. Should be 10 kv pairs now.

assert.equal(keys1[0], "sdkVersion", "sdk should be added as a key");
assert.equal(keys1[1], "osType", "osType should be added as a key");
assert.equal(keys1[2], "appSrv_SiteName", "appSrv_SiteName should be added as a key");
assert.equal(keys1[3], "appSrv_wsStamp", "appSrv_wsStamp should be added as a key");
assert.equal(keys1[4], "appSrv_wsHost", "appSrv_wsHost should be added as a key");
assert.equal(keys1[2], "osVersion", "osVersion should be added as a key");
assert.equal(keys1[3], "processSessionId", "processSessionId should be added as a key");
assert.equal(keys1[4], "appSrv_SiteName", "appSrv_SiteName should be added as a key");
assert.equal(keys1[5], "appSrv_wsStamp", "appSrv_wsStamp should be added as a key");
assert.equal(keys1[6], "appSrv_wsHost", "appSrv_wsHost should be added as a key");
assert.equal(keys1[7], "appSrv_wsOwner", "appSrv_wsOwner should be added as a key");
assert.equal(keys1[8], "appSrv_ResourceGroup", "appSrv_ResourceGroup should be added as a key");
assert.equal(keys1[9], "appSrv_SlotName", "appSrv_SlotName should be added as a key");
const properties1 = stub1.args[0][0].properties;
assert.equal(properties1["sdk"], Context.sdkVersion, "sdk version should be read from Context");
assert.equal(properties1["sdkVersion"], Context.sdkVersion, "sdk version should be read from Context");
assert.equal(properties1["osType"], os.type(), "osType should be read from os library");
assert.equal(properties1["osVersion"], os.release(), "osVersion should be read from envrionment variable");
assert.ok(properties1["processSessionId"], "processSessionId should be available");
assert.equal(properties1["appSrv_SiteName"], "site_name", "appSrv_SiteName should be read from envrionment variable");
assert.equal(properties1["appSrv_wsStamp"], "stamp_name", "appSrv_wsStamp should be read from envrionment variable");
assert.equal(properties1["appSrv_wsHost"], "host_name", "appSrv_wsHost should be read from envrionment variable");
done();
});
});

it("should read correct function app values from envrionment variable", (done) => {
const heartbeat2: HeartBeat = new HeartBeat(client);
heartbeat2.enable(true);
HeartBeat.INSTANCE.enable(true);
const stub2 = sandbox.stub(heartbeat2["_client"], "trackMetric");
var env2 = <{ [id: string]: string }>{};
env2["FUNCTIONS_WORKER_RUNTIME"] = "nodejs";
env2["WEBSITE_HOSTNAME"] = "host_name";
process.env = env2;

heartbeat2["trackHeartBeat"](client.config, () => {
assert.equal(stub2.callCount, 1, "should call trackMetric for the VM heartbeat metric");
assert.equal(stub2.args[0][0].name, "HeartBeat", "should use correct name for heartbeat metric");
assert.equal(stub2.args[0][0].value, 0, "value should be 0");
const keys2 = Object.keys(stub2.args[0][0].properties);
assert.equal(keys2.length, 3, "should have 3 kv pairs added when resource type is functiona app");
assert.equal(keys2[0], "sdk", "sdk should be added as a key");
assert.equal(keys2[1], "osType", "osType should be added as a key");
assert.equal(keys2[2], "azfunction_appId", "azfunction_appId should be added as a key");
const properties2 = stub2.args[0][0].properties;
assert.equal(properties2["sdk"], Context.sdkVersion, "sdk version should be read from Context");
assert.equal(properties2["osType"], os.type(), "osType should be read from os library");
assert.equal(properties2["azfunction_appId"], "host_name", "azfunction_appId should be read from envrionment variable");
assert.equal(properties1["appSrv_wsOwner"], "owner_name", "appSrv_wsOwner should be read from envrionment variable");
assert.equal(properties1["appSrv_ResourceGroup"], "resource_group", "appSrv_ResourceGroup should be read from envrionment variable");
assert.equal(properties1["appSrv_SlotName"], "slot_name", "appSrv_SlotName should be read from envrionment variable");
done();
});
});
Expand Down
82 changes: 0 additions & 82 deletions Tests/EndToEnd.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -904,86 +904,4 @@ describe("EndToEnd", () => {
}
});
});

describe("Heartbeat metrics for VM", () => {
beforeEach(() => {
JsonConfig["_instance"] = undefined;
});

afterEach(() => {
sandbox.restore();
});

it("should collect correct VM information from JSON response", (done) => {
// set up stub
const vmDataJSON = `{
"vmId": "1",
"subscriptionId": "2",
"osType": "Windows_NT"
}`;
var stub: sinon.SinonStub = sandbox.stub(http, "request", (options: any, callback: any) => {
var req = new fakeRequest(false, "http://169.254.169.254", vmDataJSON);
req.on("end", callback);
return req;
});

// set up sdk
const client = new TelemetryClient("key");
const heartbeat: HeartBeat = new HeartBeat(client);
heartbeat.enable(true);
HeartBeat.INSTANCE.enable(true);
const trackMetricStub = sandbox.stub(heartbeat["_client"], "trackMetric");

heartbeat["trackHeartBeat"](client.config, () => {
assert.equal(trackMetricStub.callCount, 1, "should call trackMetric for the VM heartbeat metric");
assert.equal(trackMetricStub.args[0][0].name, "HeartBeat", "should use correct name for heartbeat metric");
assert.equal(trackMetricStub.args[0][0].value, 0, "value should be 0");
const keys = Object.keys(trackMetricStub.args[0][0].properties);
assert.equal(keys.length, 5, "should have 4 kv pairs added when resource type is VM");
assert.equal(keys[0], "sdk", "sdk should be added as a key");
assert.equal(keys[1], "osType", "osType should be added as a key");
assert.equal(keys[2], "azInst_vmId", "azInst_vmId should be added as a key");
assert.equal(keys[3], "azInst_subscriptionId", "azInst_subscriptionId should be added as a key");
assert.equal(keys[4], "azInst_osType", "azInst_osType should be added as a key");

const properties = trackMetricStub.args[0][0].properties;
assert.equal(properties["sdk"], Context.sdkVersion, "sdk version should be read from Context");
assert.equal(properties["osType"], os.type(), "osType should be read from os library");
assert.equal(properties["azInst_vmId"], "1", "azInst_vmId should be read from response");
assert.equal(properties["azInst_subscriptionId"], "2", "azInst_subscriptionId should be read from response");
assert.equal(properties["azInst_osType"], "Windows_NT", "azInst_osType should be read from response");
done();
});
});

it("should only send name and value properties for heartbeat metric when get VM request fails", (done) => {
// set up stub
var stub: sinon.SinonStub = sandbox.stub(http, "request", (options: any, callback: any) => {
var req = new fakeRequest(true, "http://169.254.169.254");
return req;
});

// set up sdk
const client = new TelemetryClient("key");
const heartbeat: HeartBeat = new HeartBeat(client);
heartbeat.enable(true);
HeartBeat.INSTANCE.enable(true);
const trackMetricStub = sandbox.stub(heartbeat["_client"], "trackMetric");

heartbeat["trackHeartBeat"](client.config, () => {
assert.equal(trackMetricStub.callCount, 1, "should call trackMetric as heartbeat metric");
assert.equal(trackMetricStub.args[0][0].name, "HeartBeat", "should use correct name for heartbeat metric");
assert.equal(trackMetricStub.args[0][0].value, 0, "value should be 0");
const keys = Object.keys(trackMetricStub.args[0][0].properties);
assert.equal(keys.length, 2, "should have 2 kv pairs added when resource type is not web app, not function app, not VM");
assert.equal(keys[0], "sdk", "sdk should be added as a key");
assert.equal(keys[1], "osType", "osType should be added as a key");

const properties = trackMetricStub.args[0][0].properties;
assert.equal(properties["sdk"], Context.sdkVersion, "sdk version should be read from Context");
assert.equal(properties["osType"], os.type(), "osType should be read from os library");
done();
});
});
});
});
Loading