const {
MetricsAdvisorKeyCredential,
MetricsAdvisorClient,
MetricsAdvisorAdministrationClient
} = require("@azure/ai-metrics-advisor");
const credential = new MetricsAdvisorKeyCredential("<subscription Key>", "<API key>");
const client = new MetricsAdvisorClient("<endpoint>", credential);
const adminClient = new MetricsAdvisorAdministrationClient("<endpoint>", credential);
const metrics: DataFeedMetric[] = [
{
name: "revenue",
displayName: "Revenue",
description: "Revenue description"
},
{
name: "cost",
displayName: "Cost",
description: "Cost description"
}
];
const dimensions: DataFeedDimension[] = [
{ name: "city", displayName: "City display name" },
{ name: "category", displayName: "Category display name" }
];
const schema: DataFeedSchema = {
metrics,
dimensions,
timestampColumn: "timestamp"
};
const ingestionSettings: DataFeedIngestionSettings = {
ingestionStartTime: new Date(Date.UTC(2020, 6, 1)),
stopRetryAfterInSeconds: 10
};
const granularity: DataFeedGranularity = {
granularityType: "Daily"
};
const source: DataFeedSource = {
dataSourceType: "SqlServer",
dataSourceParameter: {
connectionString: "<SQL Server Connection String>",
query: "<SQL Server Query>"
}
};
const options: DataFeedOptions = {
rollupSettings: {
rollupType: "AutoRollup",
rollupMethod: "Sum",
autoRollupGroupByColumnNames: [ "category" ]
rollupIdentificationValue: "__SUM__"
},
missingDataPointFillSettings: {
fillType: "SmartFilling"
},
accessMode: "Private",
adminEmails: ["xyz@example.com"]
};
const result = await adminClient.createDataFeed({
name: "SQL Server data feed",
source,
granularity,
schema,
ingestionSettings,
options
});
const costMetric = result.schema.metrics.filter(m => m.name === "cost")[0];
const costMetricId = costMetric.id;
const detectionConfigId = "<Default detection config id>"; // auto-added by service
for await (const anomaly of client.listAnomaliesForDetectionConfiguration(
detectionConfigId,
new Date("09/06/2020"),
new Date("09/11/2020"),
{
severityFilter: { min: "Medium", max: "High" }
}
)) {
console.log("Anomaly");
console.log(` metric id: ${anomaly.metricId}`);
console.log(` detection config id: ${anomaly.detectionConfigurationId}`);
console.log(` created on: ${anomaly.createdOn}`);
console.log(` modified on: ${anomaly.modifiedOn}`);
console.log(` severity: ${anomaly.severity}`);
console.log(` status: ${anomaly.status}`);
console.log(` series key: ${anomaly.seriesKey}`);
}
for await (const incident of client.listIncidentsForDetectionConfiguration(
"<Default detection config id>",
new Date("09/06/2020"),
new Date("09/11/2020"),
{
dimensionFilter: [{ dimension: { city: "Manila", category: "Shoes Handbags & Sunglasses" } }]
}
)) {
console.log(" Incident");
console.log(` id: ${incident.id}`);
console.log(` severity: ${incident.severity}`); // "Low" | "Medium" | "High"
console.log(` status: ${incident.status}`); // "Active" | "Resolved"
console.log(` startTime: ${incident.rootDimensionKey}`);
console.log(` startTime: ${incident.startTime}`);
console.log(` last occured: ${incident.lastOccuredTime}`);
console.log(` detection config id: ${incident.detectionConfigurationId}`);
}
const result = await client.getIncidentRootCauses(detectionConfigId, incidentId);
for (const rootcause of result.rootCauses) {
console.log(`Root cause`);
console.log(` Trace the path for the incident root cause ${rootcause.path.join(" => ")}`);
console.log(` Series key: ${rootcause.seriesKey}`);
console.log(` Description: ${rootcause.description}`);
console.log(` Contribution score: ${rootcause.score}`);
}
const wholeSeriesDetectionCondition: MetricDetectionCondition = {
conditionOperator: "AND",
smartDetectionCondition: {
sensitivity: 50,
anomalyDetectorDirection: "Both",
suppressCondition: {
minNumber: 3,
minRatio: 50
}
},
changeThresholdCondition: {
anomalyDetectorDirection: "Both",
shiftPoint: 1,
changePercentage: 33,
withinRange: true,
suppressCondition: { minNumber: 2, minRatio: 50 }
},
hardThresholdCondition: {
anomalyDetectorDirection: "Up",
upperBound: 400,
suppressCondition: { minNumber: 2, minRatio: 70 }
}
};
const seriesGroupDetectionConditions : MetricSeriesGroupDetectionCondition[] = [
{
group: { dimension: { city: "Bellevue" } },
conditionOperator: "AND",
smartDetectionCondition: { ... },
hardThresholdCondition: { ... },
changeThresholdCondition: { ... }
}
];
const seriesDetectionConditions : MetricSingleSeriesDetectionCondition[] = [
{
series: { dimension: { city: "Redmond", category: "Cell Phones" } },
conditionOperator: "AND",
smartDetectionCondition: { ... },
hardThresholdCondition: { ... },
changeThresholdCondition: { ... }
}
],
const config: Omit<AnomalyDetectionConfiguration, "id"> = {
name: "Custom detection" + new Date().getTime().toString(),
description: "Custom detection description",
costMetricId, // the cost metric id we have earlier
wholeSeriesDetectionCondition,
seriesGroupDetectionConditions,
seriesDetectionConditions,
};
const created = await adminClient.createMetricAnomalyDetectionConfiguration(config);
const hook: WebNotificationHook = {
hookType: "Webhook",
name: "web hook example " + new Date().getTime().toFixed(),
description: "alert us!",
hookParameter: {
endpoint: "https://contoso.org/post",
username: "user",
password: "pass",
headers: {
name1: "value1",
name2: "value2"
}
// certificateKey: "k",
// certificatePassword: "kp"
}
};
const created = await client.createHook(hook);
console.log(` hook created: ${created.id}`);
const metricAlertingConfig1: MetricAlertConfiguration = {
detectionConfigurationId: detectionConfigId,
alertScope: {
scopeType: "All"
}
};
const metricAlertingConfig2: MetricAlertConfiguration = {
detectionConfigurationId: detectionConfigId,
alertScope: {
scopeType: "Dimension",
dimensionAnomalyScope: { dimension: { city: "Redmond", category: "Handmade" } }
}
};
const created = await adminClient.createAnomalyAlertConfiguration({
name: "Alert config name " + new Date().getTime().toString(),
crossMetricsOperator: "AND",
metricAlertConfigurations: [metricAlertingConfig1, metricAlertingConfig2],
hookIds: ["<webHookId>", "emailHookId"],
description: "Alert config description"
});
for await (const alert of client.listAlertsForAlertConfiguration(
alertConfigId,
new Date("01/01/2020"),
new Date("09/09/2020"),
"AnomalyTime" // or "CreatedTime"/"ModifiedTime"
)) {
console.log("Alert");
console.log(` id: ${alert.id}`);
console.log(` timestamp: ${alert.timestamp}`);
console.log(` created on: ${alert.createdOn}`);
}
for await (const incident of client.listIncidentsForAlert(alertConfigId, alertId)) {
console.log("Incident");
console.log(` id: ${incident.id}`);
console.log(` severity: ${incident.severity}`);
console.log(` status: ${incident.status}`);
console.log(` startTime: ${incident.startTime}`);
console.log(` last occured: ${incident.lastOccuredTime}`);
console.log(` detection config id: ${incident.detectionConfigurationId}`);
}
(same as previous one)
In addition to listing incidents associated with an anomaly alert, it is also possible to list all the anomalies that caused those incidents.
for await (const anomaly of client.listAnomaliesForAlert(alertConfigId, alertId)) {
console.log("Anomaly");
console.log(` metric id: ${anomaly.metricId}`);
console.log(` detection config id: ${anomaly.detectionConfigurationId}`);
console.log(` created on: ${anomaly.createdOn}`);
console.log(` modified on: ${anomaly.modifiedOn}`);
console.log(` severity: ${anomaly.severity}`);
console.log(` status: ${anomaly.status}`);
console.log(` series key: ${anomaly.seriesKey}`);
}
Suppose some of the anomaly detection results provided by Metrics Monitor are incorrect or less accurate. In that case, the user can manually add feedback that will affect the data's model. The service will incorporate the feedback and adjust model for future detection.
const anomalyFeedback: MetricAnomalyFeedback = {
metricId,
feedbackType: "Anomaly",
startTime: new Date("2020/08/05"),
endTime: new Date("2020/08/07"),
value: "NotAnomaly",
dimensionFilter: { dimension: { Dim1: "Common Lime", Dim2: "Ant" } }
};
await client.createMetricFeedback(anomalyFeedback);
Other types of feedback include MetricChangePointFeedback
, MetricPeriodFeedback
, and MetricCommentFeedback
.
const result = await client.getMetricSeriesData(
metricId,
new Date("09/01/2020"),
new Date("09/12/2020"),
[
{ dimension: { city: "Manila", category: "Shoes Handbags & Sunglasses" } },
{ dimension: { city: "Redmond", category: "Cell Phones" } }
]
);
let i = 0;
for (const series of result.metricSeriesDataList || []) {
const { definition, timestamps, values } = series;
lineChart[i].setTitle(toDisplayString(definition.dimension));
lineChart[i++].setData({
x: timestamps,
y: [ values ]
});
}
const result = await client.getMetricEnrichedSeriesData(
detectionConfigId,
new Date("09/01/2020"),
new Date("09/12/2020"),
[
{ dimension: { city: "Manila", category: "Shoes Handbags & Sunglasses" } },
{ dimension: { city: "Redmond", category: "Cell Phones" } }
]
);
let i = 0;
for (const enriched of result.results || []) {
const { seriesKey, timestamps, values, lowerBounds, upperBounds, isAnomaly } = enriched;
lineChart[i].setTitle(toDisplayString(seriesKey.dimension));
lineChart[i++].setData({
x: timestamps,
y: [ values, lowerBounds, upperBounds ],
dataLabels: [ isAnomaly.map((v) => v === true? "A" : undefined) ]
});
}
const startTime = new Date("07/22/2020");
const endTime = new Date("07/24/2020");
for await (const ingestion of adminClient.listDataFeedIngestionStatus(
"4957a2f7-a0f4-4fc0-b8d7-d866c1df0f4c",
startTime,
endTime
)) {
console.log(` ${ingestion.timestamp} ${ingestion.status} ${ingestion.message}`);
}
for await (const status of client.listMetricEnrichmentStatus(
metricId,
new Date("01/01/2020"),
new Date("09/09/2020")
)) {
console.log("Metric enrichment status");
console.log(` status: ${status.status}`);
console.log(` message: ${status.message}`);
console.log(` timestamp: ${status.timestamp}`);
}