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

release 1.3.0 #502

Merged
merged 3 commits into from
Apr 3, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
160 changes: 143 additions & 17 deletions AutoCollection/Performance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import http = require("http");
import os = require("os");

import TelemetryClient = require("../Library/TelemetryClient");
import Contracts = require("../Declarations/Contracts");
import Logging = require("../Library/Logging");
import Constants = require("../Declarations/Constants");

class AutoCollectPerformance {

Expand All @@ -12,27 +12,41 @@ class AutoCollectPerformance {
private static _totalRequestCount: number = 0;
private static _totalFailedRequestCount: number = 0;
private static _lastRequestExecutionTime: number = 0;

private static _totalDependencyCount: number = 0;
private static _totalFailedDependencyCount: number = 0;
private static _lastDependencyExecutionTime: number = 0;
private static _totalExceptionCount: number = 0;
private static _intervalDependencyExecutionTime: number = 0;
private static _intervalRequestExecutionTime: number = 0;

private _enableLiveMetricsCounters: boolean;
private _collectionInterval: number;
private _client: TelemetryClient;
private _handle: NodeJS.Timer;
private _isEnabled: boolean;
private _isInitialized: boolean;
private _lastAppCpuUsage: { user: number, system: number };
private _lastHrtime: number[];
private _lastCpus: { model: string; speed: number; times: { user: number; nice: number; sys: number; idle: number; irq: number; }; }[];
private _lastDependencies: { totalDependencyCount: number; totalFailedDependencyCount: number; time: number; };
private _lastRequests: { totalRequestCount: number; totalFailedRequestCount: number; time: number; };

constructor(client: TelemetryClient) {
if (!!AutoCollectPerformance.INSTANCE) {
throw new Error("Performance tracking should be configured from the applicationInsights object");
private _lastExceptions: { totalExceptionCount: number, time: number };

/**
* @param enableLiveMetricsCounters - enable sending additional live metrics information (dependency metrics, exception metrics, committed memory)
*/
constructor(client: TelemetryClient, collectionInterval = 60000, enableLiveMetricsCounters = false) {
if (!AutoCollectPerformance.INSTANCE) {
AutoCollectPerformance.INSTANCE = this;
}

AutoCollectPerformance.INSTANCE = this;
this._isInitialized = false;
this._client = client;
this._collectionInterval = collectionInterval;
this._enableLiveMetricsCounters = enableLiveMetricsCounters;
}

public enable(isEnabled: boolean) {
public enable(isEnabled: boolean, collectionInterval?: number) {
this._isEnabled = isEnabled;
if (this._isEnabled && !this._isInitialized) {
this._isInitialized = true;
Expand All @@ -46,12 +60,22 @@ class AutoCollectPerformance {
totalFailedRequestCount: AutoCollectPerformance._totalFailedRequestCount,
time: +new Date
};
this._lastDependencies = {
totalDependencyCount: AutoCollectPerformance._totalDependencyCount,
totalFailedDependencyCount: AutoCollectPerformance._totalFailedDependencyCount,
time: +new Date
};
this._lastExceptions = {
totalExceptionCount: AutoCollectPerformance._totalExceptionCount,
time: +new Date
};

if (typeof (process as any).cpuUsage === 'function'){
this._lastAppCpuUsage = (process as any).cpuUsage();
}
this._lastHrtime = process.hrtime();

this._handle = setInterval(() => this.trackPerformance(), 60000);
this._collectionInterval = collectionInterval || this._collectionInterval;
this._handle = setInterval(() => this.trackPerformance(), this._collectionInterval);
this._handle.unref(); // Allow the app to terminate even while this loop is going on
}
} else {
Expand All @@ -77,7 +101,7 @@ class AutoCollectPerformance {
if (typeof response.once === "function") {
response.once("finish", () => {
var end = +new Date;
this._lastRequestExecutionTime = end - start;
AutoCollectPerformance._intervalRequestExecutionTime += this._lastRequestExecutionTime = end - start;
AutoCollectPerformance._totalRequestCount++;
if (response.statusCode >= 400) {
AutoCollectPerformance._totalFailedRequestCount++;
Expand All @@ -86,6 +110,32 @@ class AutoCollectPerformance {
}
}

public static countException() {
AutoCollectPerformance._totalExceptionCount++;
}

public static countDependency(duration: number | string, success: boolean) {
let durationMs: number;
if (!AutoCollectPerformance.isEnabled()) {
return;
}

if (typeof duration === 'string') {
// dependency duration is passed in as "00:00:00.123" by autocollectors
durationMs = +new Date('1970-01-01T' + duration + 'Z'); // convert to num ms, returns NaN if wrong
} else if (typeof duration === 'number') {
durationMs = duration;
} else {
return;
}

AutoCollectPerformance._intervalDependencyExecutionTime += durationMs;
if (success === false) {
AutoCollectPerformance._totalFailedDependencyCount++;
}
AutoCollectPerformance._totalDependencyCount++;
}

public isInitialized() {
return this._isInitialized;
}
Expand All @@ -98,6 +148,8 @@ class AutoCollectPerformance {
this._trackCpu();
this._trackMemory();
this._trackNetwork();
this._trackDependencyRate();
this._trackExceptionRate();
}

private _trackCpu() {
Expand Down Expand Up @@ -162,8 +214,8 @@ class AutoCollectPerformance {

var combinedTotal = (totalUser + totalSys + totalNice + totalIdle + totalIrq) || 1;

this._client.trackMetric({name: "\\Processor(_Total)\\% Processor Time", value: ((combinedTotal - totalIdle) / combinedTotal) * 100});
this._client.trackMetric({name: "\\Process(??APP_WIN32_PROC??)\\% Processor Time", value: appCpuPercent || ((totalUser / combinedTotal) * 100)});
this._client.trackMetric({name: Constants.PerformanceCounter.PROCESSOR_TIME, value: ((combinedTotal - totalIdle) / combinedTotal) * 100});
this._client.trackMetric({name: Constants.PerformanceCounter.PROCESS_TIME, value: appCpuPercent || ((totalUser / combinedTotal) * 100)});
}

this._lastCpus = cpus;
Expand All @@ -172,8 +224,14 @@ class AutoCollectPerformance {
private _trackMemory() {
var freeMem = os.freemem();
var usedMem = process.memoryUsage().rss;
this._client.trackMetric({name:"\\Process(??APP_WIN32_PROC??)\\Private Bytes", value: usedMem});
this._client.trackMetric({name: "\\Memory\\Available Bytes", value: freeMem});
var committedMemory = os.totalmem() - freeMem;
this._client.trackMetric({name: Constants.PerformanceCounter.PRIVATE_BYTES, value: usedMem});
this._client.trackMetric({name: Constants.PerformanceCounter.AVAILABLE_BYTES, value: freeMem});

// Only supported by quickpulse service
if (this._enableLiveMetricsCounters) {
this._client.trackMetric({name: Constants.QuickPulseCounter.COMMITTED_BYTES, value: committedMemory});
}
}

private _trackNetwork() {
Expand All @@ -189,18 +247,86 @@ class AutoCollectPerformance {
var intervalFailedRequests = (requests.totalFailedRequestCount - lastRequests.totalFailedRequestCount) || 0;
var elapsedMs = requests.time - lastRequests.time;
var elapsedSeconds = elapsedMs / 1000;
var averageRequestExecutionTime = AutoCollectPerformance._intervalRequestExecutionTime / intervalRequests;
AutoCollectPerformance._intervalRequestExecutionTime = 0; // reset

if (elapsedMs > 0) {
var requestsPerSec = intervalRequests / elapsedSeconds;
var failedRequestsPerSec = intervalFailedRequests / elapsedSeconds;

this._client.trackMetric({ name: "\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Requests/Sec", value: requestsPerSec });
this._client.trackMetric({ name: "\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Request Execution Time", value: AutoCollectPerformance._lastRequestExecutionTime });
this._client.trackMetric({ name: Constants.PerformanceCounter.REQUEST_RATE, value: requestsPerSec });

// Only send duration to live metrics if it has been updated!
if (!this._enableLiveMetricsCounters || intervalRequests > 0) {
this._client.trackMetric({ name: Constants.PerformanceCounter.REQUEST_DURATION, value: averageRequestExecutionTime });
}

// Only supported by quickpulse service
if (this._enableLiveMetricsCounters) {
this._client.trackMetric({name: Constants.QuickPulseCounter.REQUEST_FAILURE_RATE, value: failedRequestsPerSec});
}
}

this._lastRequests = requests;
}

// Static counter is accumulated externally. Report the rate to client here
// Note: This is currently only used with QuickPulse client
private _trackDependencyRate() {
if (this._enableLiveMetricsCounters) {
var lastDependencies = this._lastDependencies;
var dependencies = {
totalDependencyCount: AutoCollectPerformance._totalDependencyCount,
totalFailedDependencyCount: AutoCollectPerformance._totalFailedDependencyCount,
time: +new Date
};

var intervalDependencies = (dependencies.totalDependencyCount - lastDependencies.totalDependencyCount) || 0;
var intervalFailedDependencies = (dependencies.totalFailedDependencyCount - lastDependencies.totalFailedDependencyCount) || 0;
var elapsedMs = dependencies.time - lastDependencies.time;
var elapsedSeconds = elapsedMs / 1000;
var averageDependencyExecutionTime = AutoCollectPerformance._intervalDependencyExecutionTime / intervalDependencies;
AutoCollectPerformance._intervalDependencyExecutionTime = 0; // reset

if (elapsedMs > 0) {
var dependenciesPerSec = intervalDependencies / elapsedSeconds;
var failedDependenciesPerSec = intervalFailedDependencies / elapsedSeconds;

this._client.trackMetric({ name: Constants.QuickPulseCounter.DEPENDENCY_RATE, value: dependenciesPerSec});
this._client.trackMetric({ name: Constants.QuickPulseCounter.DEPENDENCY_FAILURE_RATE, value: failedDependenciesPerSec});

// redundant check for livemetrics, but kept for consistency w/ requests
// Only send duration to live metrics if it has been updated!
if (!this._enableLiveMetricsCounters || intervalDependencies > 0) {
this._client.trackMetric({ name: Constants.QuickPulseCounter.DEPENDENCY_DURATION, value: averageDependencyExecutionTime});
}
}
this._lastDependencies = dependencies;
}
}

// Static counter is accumulated externally. Report the rate to client here
// Note: This is currently only used with QuickPulse client
private _trackExceptionRate() {
if (this._enableLiveMetricsCounters) {
var lastExceptions = this._lastExceptions;
var exceptions = {
totalExceptionCount: AutoCollectPerformance._totalExceptionCount,
time: +new Date
};

var intervalExceptions = (exceptions.totalExceptionCount - lastExceptions.totalExceptionCount) || 0;
var elapsedMs = exceptions.time - lastExceptions.time;
var elapsedSeconds = elapsedMs / 1000;

if (elapsedMs > 0) {
var exceptionsPerSec = intervalExceptions / elapsedSeconds;
this._client.trackMetric({ name: Constants.QuickPulseCounter.EXCEPTION_RATE, value: exceptionsPerSec});
}
this._lastExceptions = exceptions;
}
}

public dispose() {
AutoCollectPerformance.INSTANCE = null;
this.enable(false);
Expand Down
94 changes: 94 additions & 0 deletions Declarations/Constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import Contracts = require("./Contracts")

export enum QuickPulseCounter {
// Memory
COMMITTED_BYTES= "\\Memory\\Committed Bytes",

// CPU
PROCESSOR_TIME= "\\Processor(_Total)\\% Processor Time",

// Request
REQUEST_RATE= "\\ApplicationInsights\\Requests\/Sec",
REQUEST_FAILURE_RATE= "\\ApplicationInsights\\Requests Failed\/Sec",
REQUEST_DURATION= "\\ApplicationInsights\\Request Duration",

// Dependency
DEPENDENCY_RATE= "\\ApplicationInsights\\Dependency Calls\/Sec",
DEPENDENCY_FAILURE_RATE= "\\ApplicationInsights\\Dependency Calls Failed\/Sec",
DEPENDENCY_DURATION= "\\ApplicationInsights\\Dependency Call Duration",

// Exception
EXCEPTION_RATE= "\\ApplicationInsights\\Exceptions\/Sec"
}

export enum PerformanceCounter {
// Memory
PRIVATE_BYTES= "\\Process(??APP_WIN32_PROC??)\\Private Bytes",
AVAILABLE_BYTES= "\\Memory\\Available Bytes",

// CPU
PROCESSOR_TIME= "\\Processor(_Total)\\% Processor Time",
PROCESS_TIME= "\\Process(??APP_WIN32_PROC??)\\% Processor Time",

// Requests
REQUEST_RATE= "\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Requests/Sec",
REQUEST_DURATION= "\\ASP.NET Applications(??APP_W3SVC_PROC??)\\Request Execution Time"
};

/**
* Map a PerformanceCounter/QuickPulseCounter to a QuickPulseCounter. If no mapping exists, mapping is *undefined*
*/
export const PerformanceToQuickPulseCounter: {[key: string]: QuickPulseCounter} = {
[PerformanceCounter.PROCESSOR_TIME]: QuickPulseCounter.PROCESSOR_TIME,
[PerformanceCounter.REQUEST_RATE]: QuickPulseCounter.REQUEST_RATE,
[PerformanceCounter.REQUEST_DURATION]: QuickPulseCounter.REQUEST_DURATION,

// Remap quick pulse only counters
[QuickPulseCounter.COMMITTED_BYTES]: QuickPulseCounter.COMMITTED_BYTES,
[QuickPulseCounter.REQUEST_FAILURE_RATE]: QuickPulseCounter.REQUEST_FAILURE_RATE,
[QuickPulseCounter.DEPENDENCY_RATE]: QuickPulseCounter.DEPENDENCY_RATE,
[QuickPulseCounter.DEPENDENCY_FAILURE_RATE]: QuickPulseCounter.DEPENDENCY_FAILURE_RATE,
[QuickPulseCounter.DEPENDENCY_DURATION]: QuickPulseCounter.DEPENDENCY_DURATION,
[QuickPulseCounter.EXCEPTION_RATE]: QuickPulseCounter.EXCEPTION_RATE
};

// Note: Explicitly define these types instead of using enum due to
// potential 'export enum' issues with typescript < 2.0.
export type QuickPulseDocumentType = "Event" | "Exception" | "Trace" | "Metric" | "Request" | "RemoteDependency";
export type QuickPulseType = "EventTelemetryDocument" | "ExceptionTelemetryDocument" | "TraceTelemetryDocument" | "MetricTelemetryDocument" | "RequestTelemetryDocument" | "DependencyTelemetryDocument";

export const QuickPulseDocumentType: {[key in Contracts.TelemetryTypeKeys]: QuickPulseDocumentType} = {
Event: "Event",
Exception: "Exception",
Trace: "Trace",
Metric: "Metric",
Request: "Request",
Dependency: "RemoteDependency"
};

export const QuickPulseType: {[key in Contracts.TelemetryTypeKeys]: QuickPulseType} = {
Event: "EventTelemetryDocument",
Exception: "ExceptionTelemetryDocument",
Trace: "TraceTelemetryDocument",
Metric: "MetricTelemetryDocument",
Request: "RequestTelemetryDocument",
Dependency: "DependencyTelemetryDocument"
};

export const TelemetryTypeStringToQuickPulseType: {[key in Contracts.TelemetryTypeValues]: QuickPulseType} = {
EventData: QuickPulseType.Event,
ExceptionData: QuickPulseType.Exception,
MessageData: QuickPulseType.Trace,
MetricData: QuickPulseType.Metric,
RequestData: QuickPulseType.Request,
RemoteDependencyData: QuickPulseType.Dependency
};

export const TelemetryTypeStringToQuickPulseDocumentType: {[key in Contracts.TelemetryTypeValues]: QuickPulseDocumentType} = {
EventData: QuickPulseDocumentType.Event,
ExceptionData: QuickPulseDocumentType.Exception,
MessageData: QuickPulseDocumentType.Trace,
MetricData: QuickPulseDocumentType.Metric,
RequestData: QuickPulseDocumentType.Request,
RemoteDependencyData: QuickPulseDocumentType.Dependency
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { DocumentQuickPulse } from "./DocumentQuickPulse";

export interface DependencyDocumentQuickPulse extends DocumentQuickPulse {
Name: string;
Target: string;
Success?: boolean;
Duration: string;
ResultCode: string,
CommandName: string;
DependencyTypeName: string;
OperationName: string;
}
16 changes: 16 additions & 0 deletions Declarations/Contracts/QuickPulseTypes/DocumentQuickPulse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export interface DocumentQuickPulse {
__type: string;

DocumentType: string;

Version: string;

OperationId: string;

Properties: IDocumentProperty[];
}

export interface IDocumentProperty {
key: string;
value: string;
};