# All files / lib canned-metrics.ts

100% Statements 35/35
100% Branches 12/12
100% Functions 11/11
100% Lines 31/31

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    1218x

    8x

    183x

    183x
    232x
    1377x

    1377x

    1377x

    183x
    1x
    1x
    105x
    206x
    206x
    206x

    183x

    183x

    183x
    183x
    1377x
    1377x
    547x

    830x

    183x

    830x
    830x
    1377x
    1377x
    1377x
    1276x

    1377x

     import { loadCannedMetricsFile, MetricTemplate } from './canned-metrics/canned-metrics-schema';

    export type NonEmptyArray<T> = [T, ...T[]];

    /**
     * A single canned service metric
     *
     * These are kindly provided to us by the good people of CloudWatch Explorer.
     */
    export interface CannedMetric {
      /**
       * Metric namespace
       */
      readonly namespace: string;

      /**
       * Metric name
       */
      readonly metricName: string;

      /**
       * List of all possible dimension permutations for this metric
       *
       * Most metrics will have a single list of strings as their one set of
       * allowed dimensions, but some metrics are emitted under multiple
       * combinations of dimensions.
       */
      readonly dimensions: NonEmptyArray<string[]>;

      /**
       * Suggested default aggregration statistic
       *
       * Not always the most appropriate one to use! These defaults have
       * been classified by people and they generally just pick "Average"
       * as the default, even if it doesn't make sense.
       *
       * For example: for event-based metrics that only ever emit `1`
       * (and never `0`) the better statistic would be `Sum`.
       *
       * Use your judgement based on the type of metric this is.
       */
      readonly defaultStat: string;
    }

    /**
     * Return the list of canned metrics for the given service
     */
    export function cannedMetricsForService(cloudFormationNamespace: string): CannedMetric[] {
      // One metricTemplate has a single set of dimensions, but the same metric NAME
      // may occur in multiple metricTemplates (if it has multiple sets of dimensions)
      const metricTemplates = cannedMetricsIndex()[cloudFormationNamespace] ?? [];

      // First construct almost what we need, but with a single dimension per metric
      const metricsWithDuplicates = flatMap(metricTemplates, metricSet => {
        const dimensions = metricSet.dimensions.map(d => d.dimensionName);
        return metricSet.metrics.map(metric => ({
          namespace: metricSet.namespace,
          dimensions,
          metricName: metric.name,
          defaultStat: metric.defaultStat,
        }));
      });

      // Then combine the dimensions for the same metrics into a single list
      return groupBy(metricsWithDuplicates, m => `${m.namespace}/${m.metricName}`).map(metrics => ({
        namespace: metrics[0].namespace,
        metricName: metrics[0].metricName,
        defaultStat: metrics[0].defaultStat,
        dimensions: Array.from(dedupeStringLists(metrics.map(m => m.dimensions))) as any,
      }));
    }

    type CannedMetricsIndex = Record<string, MetricTemplate[]>;

    let cannedMetricsCache: CannedMetricsIndex | undefined;

    /**
     * Load the canned metrics file and process it into an index, grouped by service namespace
     */
    function cannedMetricsIndex() {
      if (cannedMetricsCache === undefined) {
        cannedMetricsCache = {};
        for (const group of loadCannedMetricsFile()) {
          for (const metricTemplate of group.metricTemplates) {
            const [aws, service] = metricTemplate.resourceType.split('::');
            const serviceKey = [aws, service].join('::');
            (cannedMetricsCache[serviceKey] ?? (cannedMetricsCache[serviceKey] = [])).push(metricTemplate);
          }
        }
      }
      return cannedMetricsCache;
    }

    function flatMap<A, B>(xs: A[], fn: (x: A) => B[]): B[] {
      return Array.prototype.concat.apply([], xs.map(fn));
    }

    function groupBy<A>(xs: A[], keyFn: (x: A) => string): Array<NonEmptyArray<A>> {
      const obj: Record<string, NonEmptyArray<A>> = {};
      for (const x of xs) {
        const key = keyFn(x);
        if (key in obj) {
          obj[key].push(x);
        } else {
          obj[key] = [x];
        }
      }
      return Object.values(obj);
    }

    function* dedupeStringLists(xs: string[][]): IterableIterator<string[]> {
      const seen = new Set<string>();
      for (const x of xs) {
        x.sort();
        const key = `${x.join(',')}`;
        if (!seen.has(key)) {
          yield x;
        }
        seen.add(key);
      }
    }