Skip to content

Instantly share code, notes, and snippets.

@jeremymeng
Last active October 14, 2020 20:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeremymeng/7eee97dfb65db46569ae16ef4f9195fd to your computer and use it in GitHub Desktop.
Save jeremymeng/7eee97dfb65db46569ae16ef4f9195fd to your computer and use it in GitHub Desktop.

Metrics Advisor champion scenarios

Creating Metrics Advisor clients

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);

Scenario 1: DataFeed creation

1.a Create data feed

  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;

Scenario 2: Query anomalies detected, incidents and their root causes

2.a Enumerating detected anomalies

  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}`);
  }

2.b Enumerating incidents caused by anomalies

  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}`);
  }

2.c Root cause analysis for an incident

  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}`);
  }

2.d Applying/tuning anomaly detection

  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);

Scenario 3: Configure alerts and get incidents notification using a hook

4.a Create a WebNotificationHook (used later for alerting)

  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}`);

4.b Create an alerting configuration

  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"
  });

Scenario 4: Enumerate alerts, alerted incidents, and anomalies

4.a Enumerating alerts created by an alert configuration

  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}`);
  }

4.b Enumerating incidents in an alert

  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}`);
  }

4.c Root cause an incident

(same as previous one)

4.d Listing anomalies that produced the alert

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}`);
  }

Scenario 5: Provide Feedback to service

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.

6.a Providing a feedback for anomaly state of data points

  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.

Scenario 3: Plot time series and enriched time series

3.a Retrieving time series for plotting

  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 ]
    });
  }

3.b Retrieving enriched time series for plotting

  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) ]
    });
  }

Additional scenarios

A. Check ingestion status of a data feed

  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}`);
  }

B. Checking time-series enrichment status

  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}`);
  }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment