All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.prometheus.cloudwatch.DefaultDimensionSource Maven / Gradle / Ivy

package io.prometheus.cloudwatch;

import io.prometheus.client.Counter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import software.amazon.awssdk.services.cloudwatch.CloudWatchClient;
import software.amazon.awssdk.services.cloudwatch.model.Dimension;
import software.amazon.awssdk.services.cloudwatch.model.DimensionFilter;
import software.amazon.awssdk.services.cloudwatch.model.ListMetricsRequest;
import software.amazon.awssdk.services.cloudwatch.model.ListMetricsResponse;
import software.amazon.awssdk.services.cloudwatch.model.Metric;

final class DefaultDimensionSource implements DimensionSource {

  private static final Logger LOGGER = Logger.getLogger(DefaultDimensionSource.class.getName());
  private final Counter cloudwatchRequests;
  private final CloudWatchClient cloudWatchClient;

  public DefaultDimensionSource(CloudWatchClient cloudWatchClient, Counter cloudwatchRequests) {
    this.cloudWatchClient = cloudWatchClient;
    this.cloudwatchRequests = cloudwatchRequests;
  }

  public DimensionData getDimensions(MetricRule rule, List tagBasedResourceIds) {
    if (rule.awsDimensions != null
        && rule.awsDimensionSelect != null
        && !rule.awsDimensions.isEmpty()
        && rule.awsDimensions.size() == rule.awsDimensionSelect.size()
        && rule.awsDimensionSelect.keySet().containsAll(rule.awsDimensions)
        && rule.awsTagSelect == null) {
      // The full list of dimensions is known so no need to request it from cloudwatch.
      return new DimensionData(permuteDimensions(rule.awsDimensions, rule.awsDimensionSelect));
    } else {
      return new DimensionData(listDimensions(rule, tagBasedResourceIds, cloudWatchClient));
    }
  }

  private List> permuteDimensions(
      List dimensions, Map> dimensionValues) {
    ArrayList> result = new ArrayList<>();

    if (dimensions.isEmpty()) {
      result.add(new ArrayList<>());
    } else {
      List dimensionsCopy = new ArrayList<>(dimensions);
      String dimensionName = dimensionsCopy.remove(dimensionsCopy.size() - 1);
      for (List permutation : permuteDimensions(dimensionsCopy, dimensionValues)) {
        for (String dimensionValue : dimensionValues.get(dimensionName)) {
          Dimension.Builder dimensionBuilder = Dimension.builder();
          dimensionBuilder.value(dimensionValue);
          dimensionBuilder.name(dimensionName);
          ArrayList permutationCopy = new ArrayList<>(permutation);
          permutationCopy.add(dimensionBuilder.build());
          result.add(permutationCopy);
        }
      }
    }
    return result;
  }

  private List> listDimensions(
      MetricRule rule, List tagBasedResourceIds, CloudWatchClient cloudWatchClient) {
    List> dimensions = new ArrayList<>();
    if (rule.awsDimensions == null) {
      dimensions.add(new ArrayList<>());
      return dimensions;
    }

    ListMetricsRequest.Builder requestBuilder = ListMetricsRequest.builder();
    requestBuilder.namespace(rule.awsNamespace);
    requestBuilder.metricName(rule.awsMetricName);

    // 10800 seconds is 3 hours, this setting causes metrics older than 3 hours to not be listed
    if (rule.rangeSeconds < 10800) {
      requestBuilder.recentlyActive("PT3H");
    }

    List dimensionFilters = new ArrayList<>();
    for (String dimension : rule.awsDimensions) {
      dimensionFilters.add(DimensionFilter.builder().name(dimension).build());
    }
    requestBuilder.dimensions(dimensionFilters);

    String nextToken = null;
    do {
      requestBuilder.nextToken(nextToken);
      ListMetricsResponse response = cloudWatchClient.listMetrics(requestBuilder.build());
      cloudwatchRequests.labels("listMetrics", rule.awsNamespace).inc();
      for (Metric metric : response.metrics()) {
        if (metric.dimensions().size() != dimensionFilters.size()) {
          // AWS returns all the metrics with dimensions beyond the ones we ask for,
          // so filter them out.
          continue;
        }
        if (useMetric(rule, tagBasedResourceIds, metric)) {
          dimensions.add(metric.dimensions());
        }
      }
      nextToken = response.nextToken();
    } while (nextToken != null);
    if (rule.warnOnEmptyListDimensions && dimensions.isEmpty()) {
      LOGGER.warning(
          String.format(
              "(listDimensions) ignoring metric %s:%s due to dimensions mismatch",
              rule.awsNamespace, rule.awsMetricName));
    }
    return dimensions;
  }

  /**
   * Check if a metric should be used according to `aws_dimension_select`,
   * `aws_dimension_select_regex` and dynamic `aws_tag_select`
   */
  private boolean useMetric(MetricRule rule, List tagBasedResourceIds, Metric metric) {
    if (rule.awsDimensionSelect != null && !metricsIsInAwsDimensionSelect(rule, metric)) {
      return false;
    }
    if (rule.awsDimensionSelectRegex != null && !metricIsInAwsDimensionSelectRegex(rule, metric)) {
      return false;
    }
    if (rule.awsTagSelect != null && !metricIsInAwsTagSelect(rule, tagBasedResourceIds, metric)) {
      return false;
    }
    return true;
  }

  /** Check if a metric is matched in `aws_dimension_select` */
  private boolean metricsIsInAwsDimensionSelect(MetricRule rule, Metric metric) {
    Set dimensionSelectKeys = rule.awsDimensionSelect.keySet();
    for (Dimension dimension : metric.dimensions()) {
      String dimensionName = dimension.name();
      String dimensionValue = dimension.value();
      if (dimensionSelectKeys.contains(dimensionName)) {
        List allowedDimensionValues = rule.awsDimensionSelect.get(dimensionName);
        if (!allowedDimensionValues.contains(dimensionValue)) {
          return false;
        }
      }
    }
    return true;
  }

  /** Check if a metric is matched in `aws_dimension_select_regex` */
  private boolean metricIsInAwsDimensionSelectRegex(MetricRule rule, Metric metric) {
    Set dimensionSelectRegexKeys = rule.awsDimensionSelectRegex.keySet();
    for (Dimension dimension : metric.dimensions()) {
      String dimensionName = dimension.name();
      String dimensionValue = dimension.value();
      if (dimensionSelectRegexKeys.contains(dimensionName)) {
        List allowedDimensionValues = rule.awsDimensionSelectRegex.get(dimensionName);
        if (!regexListMatch(allowedDimensionValues, dimensionValue)) {
          return false;
        }
      }
    }
    return true;
  }

  /** Check if any regex string in a list matches a given input value */
  protected static boolean regexListMatch(List regexList, String input) {
    for (String regex : regexList) {
      if (Pattern.matches(regex, input)) {
        return true;
      }
    }
    return false;
  }

  /** Check if a metric is matched in `aws_tag_select` */
  private boolean metricIsInAwsTagSelect(
      MetricRule rule, List tagBasedResourceIds, Metric metric) {
    if (rule.awsTagSelect.tagSelections == null) {
      return true;
    }
    for (Dimension dimension : metric.dimensions()) {
      String dimensionName = dimension.name();
      String dimensionValue = dimension.value();
      if (rule.awsTagSelect.resourceIdDimension.equals(dimensionName)
          && !tagBasedResourceIds.contains(dimensionValue)) {
        return false;
      }
    }
    return true;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy