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

com.netflix.spectator.ipc.IpcMetric Maven / Gradle / Ivy

There is a newer version: 1.8.2
Show newest version
/*
 * Copyright 2014-2019 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.netflix.spectator.ipc;

import com.netflix.spectator.api.Counter;
import com.netflix.spectator.api.DistributionSummary;
import com.netflix.spectator.api.Gauge;
import com.netflix.spectator.api.Id;
import com.netflix.spectator.api.Registry;
import com.netflix.spectator.api.Tag;
import com.netflix.spectator.api.Timer;
import com.netflix.spectator.api.Utils;

import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collectors;

/**
 * IPC metric names and associated metadata.
 */
public enum IpcMetric {
  /**
   * Timer recording the number and latency of outbound requests.
   */
  clientCall(
      "ipc.client.call",
      EnumSet.of(
          IpcTagKey.attempt,
          IpcTagKey.attemptFinal,
          IpcTagKey.owner,
          IpcTagKey.result,
          IpcTagKey.status
      ),
      EnumSet.of(
          IpcTagKey.endpoint,
          IpcTagKey.failureInjected,
          IpcTagKey.httpMethod,
          IpcTagKey.httpStatus,
          IpcTagKey.id,
          IpcTagKey.protocol,
          IpcTagKey.serverApp,
          IpcTagKey.serverCluster,
          IpcTagKey.serverAsg,
          IpcTagKey.statusDetail,
          IpcTagKey.vip
      )
  ),

  /**
   * Timer recording the number and latency of inbound requests.
   */
  serverCall(
      "ipc.server.call",
      EnumSet.of(
          IpcTagKey.owner,
          IpcTagKey.result,
          IpcTagKey.status
      ),
      EnumSet.of(
          IpcTagKey.endpoint,
          IpcTagKey.clientApp,
          IpcTagKey.clientCluster,
          IpcTagKey.clientAsg,
          IpcTagKey.failureInjected,
          IpcTagKey.httpMethod,
          IpcTagKey.httpStatus,
          IpcTagKey.id,
          IpcTagKey.protocol,
          IpcTagKey.statusDetail,
          IpcTagKey.vip
      )
  ),

  /**
   * Distribution summary recording the size of the entity for client responses.
   */
  clientCallSizeInbound(
      "ipc.client.call.size.inbound",
      EnumSet.of(
          IpcTagKey.attempt,
          IpcTagKey.attemptFinal,
          IpcTagKey.owner,
          IpcTagKey.result,
          IpcTagKey.status
      ),
      EnumSet.of(
          IpcTagKey.endpoint,
          IpcTagKey.failureInjected,
          IpcTagKey.httpMethod,
          IpcTagKey.httpStatus,
          IpcTagKey.id,
          IpcTagKey.protocol,
          IpcTagKey.serverApp,
          IpcTagKey.serverCluster,
          IpcTagKey.serverAsg,
          IpcTagKey.statusDetail,
          IpcTagKey.vip
      )
  ),

  /**
   * Distribution summary recording the size of the entity for client requests.
   */
  clientCallSizeOutbound(
      "ipc.client.call.size.outbound",
      EnumSet.of(
          IpcTagKey.attempt,
          IpcTagKey.attemptFinal,
          IpcTagKey.owner,
          IpcTagKey.result,
          IpcTagKey.status
      ),
      EnumSet.of(
          IpcTagKey.endpoint,
          IpcTagKey.failureInjected,
          IpcTagKey.httpMethod,
          IpcTagKey.httpStatus,
          IpcTagKey.id,
          IpcTagKey.protocol,
          IpcTagKey.serverApp,
          IpcTagKey.serverCluster,
          IpcTagKey.serverAsg,
          IpcTagKey.statusDetail,
          IpcTagKey.vip
      )
  ),

  /**
   * Distribution summary recording the size of the entity for server requests.
   */
  serverCallSizeInbound(
      "ipc.server.call.size.inbound",
      EnumSet.of(
          IpcTagKey.owner,
          IpcTagKey.result,
          IpcTagKey.status
      ),
      EnumSet.of(
          IpcTagKey.endpoint,
          IpcTagKey.clientApp,
          IpcTagKey.clientCluster,
          IpcTagKey.clientAsg,
          IpcTagKey.failureInjected,
          IpcTagKey.httpMethod,
          IpcTagKey.httpStatus,
          IpcTagKey.id,
          IpcTagKey.protocol,
          IpcTagKey.statusDetail,
          IpcTagKey.vip
      )
  ),

  /**
   * Distribution summary recording the size of the entity for server responses.
   */
  serverCallSizeOutbound(
      "ipc.server.call.size.outbound",
      EnumSet.of(
          IpcTagKey.owner,
          IpcTagKey.result,
          IpcTagKey.status
      ),
      EnumSet.of(
          IpcTagKey.endpoint,
          IpcTagKey.clientApp,
          IpcTagKey.clientCluster,
          IpcTagKey.clientAsg,
          IpcTagKey.failureInjected,
          IpcTagKey.httpMethod,
          IpcTagKey.httpStatus,
          IpcTagKey.id,
          IpcTagKey.protocol,
          IpcTagKey.statusDetail,
          IpcTagKey.vip
      )
  ),

  /**
   * Number of outbound requests that are currently in flight.
   */
  clientInflight(
      "ipc.client.inflight",
      EnumSet.of(
          IpcTagKey.owner
      ),
      EnumSet.of(
          IpcTagKey.endpoint,
          IpcTagKey.id,
          IpcTagKey.protocol,
          IpcTagKey.serverApp,
          IpcTagKey.serverCluster,
          IpcTagKey.serverAsg,
          IpcTagKey.vip
      )
  ),

  /**
   * Number of inbound requests that are currently in flight.
   */
  serverInflight(
      "ipc.server.inflight",
      EnumSet.of(
          IpcTagKey.owner
      ),
      EnumSet.of(
          IpcTagKey.clientApp,
          IpcTagKey.clientCluster,
          IpcTagKey.clientAsg,
          IpcTagKey.endpoint,
          IpcTagKey.id,
          IpcTagKey.protocol,
          IpcTagKey.vip
      )
  );

  private final String metricName;
  private final EnumSet requiredDimensions;
  private final EnumSet optionalDimensions;

  /** Create a new instance. */
  IpcMetric(
      String metricName,
      EnumSet requiredDimensions,
      EnumSet optionalDimensions) {
    this.metricName = metricName;
    this.requiredDimensions = requiredDimensions;
    this.optionalDimensions = optionalDimensions;
  }

  /** Returns the metric name to use in the meter id. */
  public String metricName() {
    return metricName;
  }

  /** Returns the set of dimensions that are required for this metrics. */
  public EnumSet requiredDimensions() {
    return requiredDimensions;
  }

  private static final Class[] METER_TYPES = {
      Counter.class,
      Timer.class,
      DistributionSummary.class,
      Gauge.class
  };

  private static final SortedSet ATTEMPT_FINAL_VALUES = new TreeSet<>();

  static {
    ATTEMPT_FINAL_VALUES.add(Boolean.TRUE.toString());
    ATTEMPT_FINAL_VALUES.add(Boolean.FALSE.toString());
  }

  private static void assertTrue(boolean condition, String description, Object... args) {
    if (!condition) {
      throw new IllegalArgumentException(String.format(description, args));
    }
  }

  private static String getName(Class cls) {
    for (Class c : METER_TYPES) {
      if (c.isAssignableFrom(cls)) {
        return c.getSimpleName();
      }
    }
    return cls.getSimpleName();
  }

  private static boolean isPercentile(Id id) {
    final String stat = Utils.getTagValue(id, "statistic");
    return "percentile".equals(stat);
  }

  private static void validateIpcMeter(
      Registry registry, IpcMetric metric, Class type, boolean strict) {
    final String name = metric.metricName();
    registry.stream()
        .filter(m -> name.equals(m.id().name()) && !isPercentile(m.id()))
        .forEach(m -> {
          assertTrue(type.isAssignableFrom(m.getClass()),
              "[%s] has the wrong type, expected %s but found %s",
              m.id(), type.getSimpleName(), getName(m.getClass()));
          metric.validate(m.id(), strict);
        });
  }

  /**
   * Validate all of the common IPC metrics contained within the specified registry.
   *
   * @param registry
   *     Registry to query for IPC metrics.
   */
  public static void validate(Registry registry) {
    validate(registry, false);
  }

  /**
   * Validate all of the common IPC metrics contained within the specified registry.
   *
   * @param registry
   *     Registry to query for IPC metrics.
   * @param strict
   *     If true, then checks if the ids are strictly compliant with the spec without any
   *     additions. Otherwise, just checks for minimal compliance. Integrations should be
   *     strictly compliant to ensure consistency for users.
   */
  public static void validate(Registry registry, boolean strict) {
    validateIpcMeter(registry, IpcMetric.clientCall, Timer.class, strict);
    validateIpcMeter(registry, IpcMetric.serverCall, Timer.class, strict);
    validateIpcMeter(registry, IpcMetric.clientCallSizeInbound, DistributionSummary.class, strict);
    validateIpcMeter(registry, IpcMetric.clientCallSizeOutbound, DistributionSummary.class, strict);
    validateIpcMeter(registry, IpcMetric.serverCallSizeInbound, DistributionSummary.class, strict);
    validateIpcMeter(registry, IpcMetric.serverCallSizeOutbound, DistributionSummary.class, strict);
    validateIpcMeter(registry, IpcMetric.clientInflight, DistributionSummary.class, strict);
    validateIpcMeter(registry, IpcMetric.serverInflight, DistributionSummary.class, strict);
  }

  @SuppressWarnings("PMD.PreserveStackTrace")
  private > void validateValues(Id id, String key, Class cls) {
    String value = Utils.getTagValue(id, key);
    if (value != null) {
      try {
        Enum.valueOf(cls, value);
      } catch (Exception e) {
        String values = Arrays.stream(cls.getEnumConstants())
            .map(Enum::name)
            .collect(Collectors.joining(", "));
        throw new IllegalArgumentException(String.format(
            "[%s] invalid value for dimension %s, acceptable values are (%s)",
            id, key, values));
      }
    }
  }

  private void validateValues(Id id, String key, SortedSet allowedValues) {
    String value = Utils.getTagValue(id, key);
    if (value != null && !allowedValues.contains(value)) {
      String values = allowedValues.stream()
          .collect(Collectors.joining(", "));
      throw new IllegalArgumentException(String.format(
          "[%s] invalid value for dimension %s, acceptable values are (%s)",
          id, key, values));
    }
  }

  /**
   * Validate that the specified id has the correct tagging for this IPC metric.
   *
   * @param id
   *     Meter identifier to validate.
   */
  public void validate(Id id) {
    validate(id, false);
  }

  /**
   * Validate that the specified id has the correct tagging for this IPC metric.
   *
   * @param id
   *     Meter identifier to validate.
   * @param strict
   *     If true, then checks if the ids are strictly compliant with the spec without any
   *     additions. Otherwise, just checks for minimal compliance. Integrations should be
   *     strictly compliant to ensure consistency for users.
   */
  public void validate(Id id, boolean strict) {
    assertTrue(metricName.equals(id.name()), "%s != %s", metricName, id.name());

    // Check that required dimensions are present
    EnumSet dimensions = requiredDimensions.clone();
    dimensions.forEach(k -> {
      String value = Utils.getTagValue(id, k.key());
      assertTrue(value != null, "[%s] is missing required dimension %s", id, k.key());
    });

    // Check the values are correct for enum keys
    validateValues(id, IpcTagKey.attemptFinal.key(), ATTEMPT_FINAL_VALUES);
    validateValues(id, IpcTagKey.attempt.key(), IpcAttempt.class);
    validateValues(id, IpcTagKey.result.key(), IpcResult.class);
    validateValues(id, IpcTagKey.status.key(), IpcStatus.class);
    validateValues(id, IpcTagKey.failureInjected.key(), IpcFailureInjection.class);

    // Check that result and status are consistent
    String status = Utils.getTagValue(id, IpcTagKey.status.key());
    if (status != null) {
      IpcResult expected = IpcStatus.valueOf(status).result();
      IpcResult actual = IpcResult.valueOf(Utils.getTagValue(id, IpcTagKey.result.key()));
      assertTrue(actual == expected, "[%s] result is inconsistent with status", id);
    }

    // Check that only allowed dimensions are used on the id
    if (strict) {
      Set allowedDimensions = new HashSet<>();
      allowedDimensions.add("statistic");
      allowedDimensions.add("percentile");
      requiredDimensions.forEach(d -> allowedDimensions.add(d.key()));
      optionalDimensions.forEach(d -> allowedDimensions.add(d.key()));
      for (Tag tag : id.tags()) {
        assertTrue(allowedDimensions.contains(tag.key()),
            "[%s] has unspecified dimension %s",
            id, tag.key());
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy