Skip to content

Commit

Permalink
[Azure Monitor Exporter] Add support for Application Insights Standar…
Browse files Browse the repository at this point in the history
…d Metrics (Azure#23591)

* Add support for AI Standard Metrics

* Addressing comments
  • Loading branch information
hectorhdzg authored Oct 25, 2022
1 parent aba1e0a commit c9d9360
Show file tree
Hide file tree
Showing 8 changed files with 301 additions and 129 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class FileSystemPersist implements PersistentStorage {
static TEMPDIR_PREFIX = "ot-azure-exporter-";
static FILENAME_SUFFIX = ".ai.json";

fileRetemptionPeriod = 7 * 24 * 60 * 60 * 1000; // 7 days
fileRetemptionPeriod = 2 * 24 * 60 * 60 * 1000; // 2 days
cleanupTimeOut = 60 * 60 * 1000; // 1 hour
maxBytesOnDisk: number = 50_000_000; // ~50MB

Expand Down
114 changes: 114 additions & 0 deletions sdk/monitor/monitor-opentelemetry-exporter/src/utils/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import os from "os";
import {
SemanticResourceAttributes,
SemanticAttributes,
DbSystemValues,
} from "@opentelemetry/semantic-conventions";
import { Tags } from "../types";
import { getInstance } from "../platform";
import { KnownContextTagKeys } from "../generated";
import { Resource } from "@opentelemetry/resources";
import { Attributes } from "@opentelemetry/api";

export function createTagsFromResource(resource: Resource): Tags {
const context = getInstance();
const tags: Tags = { ...context.tags };
if (resource && resource.attributes) {
const serviceName = resource.attributes[SemanticResourceAttributes.SERVICE_NAME];
const serviceNamespace = resource.attributes[SemanticResourceAttributes.SERVICE_NAMESPACE];
if (serviceName) {
if (serviceNamespace) {
tags[KnownContextTagKeys.AiCloudRole] = `${serviceNamespace}.${serviceName}`;
} else {
tags[KnownContextTagKeys.AiCloudRole] = String(serviceName);
}
}
const serviceInstanceId = resource.attributes[SemanticResourceAttributes.SERVICE_INSTANCE_ID];
if (serviceInstanceId) {
tags[KnownContextTagKeys.AiCloudRoleInstance] = String(serviceInstanceId);
} else {
tags[KnownContextTagKeys.AiCloudRoleInstance] = os && os.hostname();
}
const endUserId = resource.attributes[SemanticAttributes.ENDUSER_ID];
if (endUserId) {
tags[KnownContextTagKeys.AiUserId] = String(endUserId);
}
}
return tags;
}

export function isSqlDB(dbSystem: string) {
return (
dbSystem === DbSystemValues.DB2 ||
dbSystem === DbSystemValues.DERBY ||
dbSystem === DbSystemValues.MARIADB ||
dbSystem === DbSystemValues.MSSQL ||
dbSystem === DbSystemValues.ORACLE ||
dbSystem === DbSystemValues.SQLITE ||
dbSystem === DbSystemValues.OTHER_SQL ||
dbSystem === DbSystemValues.HSQLDB ||
dbSystem === DbSystemValues.H2
);
}

export function getUrl(attributes: Attributes): string {
if (!attributes) {
return "";
}
const httpMethod = attributes[SemanticAttributes.HTTP_METHOD];
if (httpMethod) {
const httpUrl = attributes[SemanticAttributes.HTTP_URL];
if (httpUrl) {
return String(httpUrl);
} else {
const httpScheme = attributes[SemanticAttributes.HTTP_SCHEME];
const httpTarget = attributes[SemanticAttributes.HTTP_TARGET];
if (httpScheme && httpTarget) {
const httpHost = attributes[SemanticAttributes.HTTP_HOST];
if (httpHost) {
return `${httpScheme}://${httpHost}${httpTarget}`;
} else {
const netPeerPort = attributes[SemanticAttributes.NET_PEER_PORT];
if (netPeerPort) {
const netPeerName = attributes[SemanticAttributes.NET_PEER_NAME];
if (netPeerName) {
return `${httpScheme}://${netPeerName}:${netPeerPort}${httpTarget}`;
} else {
const netPeerIp = attributes[SemanticAttributes.NET_PEER_IP];
if (netPeerIp) {
return `${httpScheme}://${netPeerIp}:${netPeerPort}${httpTarget}`;
}
}
}
}
}
}
}
return "";
}

export function getDependencyTarget(attributes: Attributes): string {
if (!attributes) {
return "";
}
const peerService = attributes[SemanticAttributes.PEER_SERVICE];
const httpHost = attributes[SemanticAttributes.HTTP_HOST];
const httpUrl = attributes[SemanticAttributes.HTTP_URL];
const netPeerName = attributes[SemanticAttributes.NET_PEER_NAME];
const netPeerIp = attributes[SemanticAttributes.NET_PEER_IP];
if (peerService) {
return String(peerService);
} else if (httpHost) {
return String(httpHost);
} else if (httpUrl) {
return String(httpUrl);
} else if (netPeerName) {
return String(netPeerName);
} else if (netPeerIp) {
return String(netPeerIp);
}
return "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,47 @@ export enum DependencyTypes {
}

export const AzureMonitorSampleRate = "_MS.sampleRate";

export enum StandardMetrics {
HTTP_REQUEST_DURATION = "azureMonitor.http.requestDuration",
HTTP_DEPENDENCY_DURATION = "azureMonitor.http.dependencyDuration",
EXCEPTION_COUNT = "azureMonitor.exceptionCount",
TRACE_COUNT = "azureMonitor.traceCount",
}

export enum StandardMetricIds {
REQUEST_DURATION = "requests/duration",
DEPENDENCY_DURATION = "dependencies/duration",
EXCEPTION_COUNT = "exceptions/count",
TRACE_COUNT = "traces/count",
}

export type MetricDimensionTypeKeys =
| "cloudRoleInstance"
| "cloudRoleName"
| "requestSuccess"
| "requestResultCode"
| "dependencyType"
| "dependencyTarget"
| "dependencySuccess"
| "dependencyResultCode"
| "traceSeverityLevel"
| "operationSynthetic"
| "metricId"
| "IsAutocollected";

// Names expected in Breeze side for dimensions
export const PreAggregatedMetricPropertyNames: { [key in MetricDimensionTypeKeys]: string } = {
cloudRoleInstance: "cloud/roleInstance",
cloudRoleName: "cloud/roleName",
operationSynthetic: "operation/synthetic",
requestSuccess: "Request.Success",
requestResultCode: "request/resultCode",
dependencyType: "Dependency.Type",
dependencyTarget: "dependency/target",
dependencySuccess: "Dependency.Success",
dependencyResultCode: "dependency/resultCode",
traceSeverityLevel: "trace/severityLevel",
metricId: "_MS.MetricId",
IsAutocollected: "_MS.IsAutocollected",
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,19 @@

import { MetricAttributes } from "@opentelemetry/api-metrics";
import { DataPointType, Histogram, ResourceMetrics } from "@opentelemetry/sdk-metrics";
import { TelemetryItem as Envelope, MetricsData, MetricDataPoint } from "../generated";
import { createTagsFromResource } from "./resourceUtils";
import {
TelemetryItem as Envelope,
MetricsData,
MetricDataPoint,
KnownContextTagKeys,
} from "../generated";
import { Tags } from "../types";
import {
PreAggregatedMetricPropertyNames,
StandardMetricIds,
StandardMetrics,
} from "./constants/applicationinsights";
import { createTagsFromResource, getDependencyTarget } from "./common";

function createPropertiesFromMetricAttributes(attributes?: MetricAttributes): {
[propertyName: string]: string;
Expand All @@ -30,13 +41,22 @@ export function resourceMetricsToEnvelope(metrics: ResourceMetrics, ikey: string

metrics.scopeMetrics.forEach((scopeMetric) => {
scopeMetric.metrics.forEach((metric) => {
const isStandardMetric = metric.descriptor?.name?.startsWith("azureMonitor.");
metric.dataPoints.forEach((dataPoint) => {
let baseData: MetricsData = {
metrics: [],
version: 2,
properties: {},
};
baseData.properties = createPropertiesFromMetricAttributes(dataPoint.attributes);
if (isStandardMetric) {
baseData.properties = createStandardMetricsProperties(
metric.descriptor.name,
dataPoint.attributes,
tags
);
} else {
baseData.properties = createPropertiesFromMetricAttributes(dataPoint.attributes);
}
var metricDataPoint: MetricDataPoint = {
name: metric.descriptor.name,
value: 0,
Expand Down Expand Up @@ -76,3 +96,40 @@ export function resourceMetricsToEnvelope(metrics: ResourceMetrics, ikey: string

return envelopes;
}

function createStandardMetricsProperties(
name: string,
attributes: MetricAttributes,
tags: Tags
): {
[propertyName: string]: string;
} {
const properties: { [propertyName: string]: string } = {};
properties[PreAggregatedMetricPropertyNames.IsAutocollected] = "True";
properties[PreAggregatedMetricPropertyNames.cloudRoleInstance] =
tags[KnownContextTagKeys.AiCloudRoleInstance];
properties[PreAggregatedMetricPropertyNames.cloudRoleName] =
tags[KnownContextTagKeys.AiCloudRole];

if (name == StandardMetrics.HTTP_REQUEST_DURATION) {
properties[PreAggregatedMetricPropertyNames.metricId] = StandardMetricIds.REQUEST_DURATION;
let statusCode = String(attributes["http.status_code"]);
properties[PreAggregatedMetricPropertyNames.requestResultCode] = statusCode;
properties[PreAggregatedMetricPropertyNames.requestSuccess] =
statusCode == "200" ? "True" : "False";
} else if (name == StandardMetrics.HTTP_DEPENDENCY_DURATION) {
properties[PreAggregatedMetricPropertyNames.metricId] = StandardMetricIds.DEPENDENCY_DURATION;
let statusCode = String(attributes["http.status_code"]);
properties[PreAggregatedMetricPropertyNames.dependencyTarget] = getDependencyTarget(attributes);
properties[PreAggregatedMetricPropertyNames.dependencyResultCode] = statusCode;
properties[PreAggregatedMetricPropertyNames.dependencyType] = "http";
properties[PreAggregatedMetricPropertyNames.dependencySuccess] =
statusCode == "200" ? "True" : "False";
} else if (name == StandardMetrics.TRACE_COUNT) {
properties[PreAggregatedMetricPropertyNames.metricId] = StandardMetricIds.TRACE_COUNT;
} else if (name == StandardMetrics.EXCEPTION_COUNT) {
properties[PreAggregatedMetricPropertyNames.metricId] = StandardMetricIds.EXCEPTION_COUNT;
}

return properties;
}

This file was deleted.

Loading

0 comments on commit c9d9360

Please sign in to comment.