All files / lib canned-metrics.ts

100% Statements 35/35
100% Branches 14/14
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 1211x                                                                                             1x     169x     169x 90x 558x                 558x       558x                       169x 1x 1x 90x 113x 113x 113x       169x       169x       169x 169x 558x 558x 52x   506x     169x       506x 506x 558x 558x 558x 556x   558x    
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);
  }
}