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

com.netflix.spectator.jvm.MappingExpr Maven / Gradle / Ivy

/*
 * Copyright 2014-2023 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.jvm;

import java.beans.Introspector;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Map;
import java.util.function.DoubleBinaryOperator;

/**
 * Helper for handling basic expressions uses as part of the mapping config.
 */
final class MappingExpr {

  private MappingExpr() {
  }

  /**
   * Substitute named variables in the pattern string with the corresponding
   * values in the variables map.
   *
   * @param pattern
   *     Pattern string with placeholders, name surounded by curly braces, e.g.:
   *     {@code {variable name}}.
   * @param vars
   *     Map of variable substitutions that are available.
   * @return
   *     String with values substituted in. If no matching key is found for a
   *     placeholder, then it will not be modified and left in place.
   */
  @SuppressWarnings("PMD.NPathComplexity")
  static String substitute(String pattern, Map vars) {
    int openBracePos = pattern.indexOf('{');
    if (openBracePos == -1) {
      return pattern;
    }

    int closeBracePos = pattern.indexOf('}', openBracePos);
    if (closeBracePos == -1) {
      return pattern;
    }

    StringBuilder builder = new StringBuilder(pattern.length());
    int startPos = 0;
    while (startPos < pattern.length()) {
      builder.append(pattern, startPos, openBracePos);
      String var = pattern.substring(openBracePos + 1, closeBracePos);
      boolean useRawValue = var.startsWith("raw:");
      String value = useRawValue
        ? vars.get(var.substring("raw:".length()))
        : vars.get(var);
      if (value == null) {
        builder.append('{').append(var).append('}');
      } else {
        builder.append(useRawValue ? value : Introspector.decapitalize(value));
      }

      startPos = closeBracePos + 1;
      openBracePos = pattern.indexOf('{', startPos);
      if (openBracePos == -1) {
        break;
      }

      closeBracePos = pattern.indexOf('}', openBracePos);
      if (closeBracePos == -1) {
        break;
      }
    }

    if (startPos < pattern.length()) {
      builder.append(pattern, startPos, pattern.length());
    }

    return builder.toString();
  }

  /**
   * Evaluate a simple stack expression for the value.
   *
   * @param expr
   *     Basic stack expression that supports placeholders, numeric constants,
   *     and basic binary operations (:add, :sub, :mul, :div).
   * @param vars
   *     Map of variable substitutions that are available.
   * @return
   *     Double value for the expression. If the expression cannot be evaluated
   *     properly, then null will be returned.
   */
  @SuppressWarnings("PMD")
  static Double eval(String expr, Map vars) {
    Deque stack = new ArrayDeque<>();
    String[] parts = expr.split("[,\\s]+");
    for (String part : parts) {
      switch (part) {
        case ":add":        binaryOp(stack, (a, b) -> a + b); break;
        case ":sub":        binaryOp(stack, (a, b) -> a - b); break;
        case ":mul":        binaryOp(stack, (a, b) -> a * b); break;
        case ":div":        binaryOp(stack, (a, b) -> a / b); break;
        case ":if-changed": ifChanged(stack);                 break;
        default:
          if (part.startsWith("{") && part.endsWith("}")) {
            Number v = vars.get(part.substring(1, part.length() - 1));
            if (v == null) v = Double.NaN;
            stack.addFirst(v.doubleValue());
          } else {
            stack.addFirst(Double.parseDouble(part));
          }
          break;
      }
    }
    return stack.removeFirst();
  }

  private static void binaryOp(Deque stack, DoubleBinaryOperator op) {
    double b = stack.removeFirst();
    double a = stack.removeFirst();
    stack.addFirst(op.applyAsDouble(a, b));
  }

  /**
   * Helper to zero out a value if there is not a change. For a stack with {@code num v1 v2},
   * if {@code v1 == v2}, then push 0.0 otherwise push {@code num}.
   *
   * For some values placed in JMX they are not regularly updated in all circumstances and
   * reporting the same value for each polling iteration gives the false impression of activity
   * when there is none. A common example is timers with the metrics library where the reservoir
   * is not rescaled during a fetch.
   *
   * https://github.com/dropwizard/metrics/issues/1030
   *
   * This operator can be used in conjunction with the previous variables to zero out the
   * misleading snapshots based on the count. For example:
   *
   * 
   *   {50thPercentile},{Count},{previous:Count},:if-changed
   * 
*/ private static void ifChanged(Deque stack) { double v2 = stack.removeFirst(); double v1 = stack.removeFirst(); double num = stack.removeFirst(); stack.addFirst((Double.compare(v1, v2) == 0) ? 0.0 : num); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy