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

com.groupon.lex.metrics.processors.wavefront.WavefrontStrings Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2016, Groupon, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * Neither the name of GROUPON nor the names of its contributors may be
 * used to endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.groupon.lex.metrics.processors.wavefront;

import com.groupon.lex.metrics.GroupName;
import com.groupon.lex.metrics.MetricName;
import com.groupon.lex.metrics.MetricValue;
import com.groupon.lex.metrics.SimpleGroupPath;
import com.groupon.lex.metrics.Tags;
import com.groupon.lex.metrics.lib.SimpleMapEntry;
import com.groupon.lex.metrics.timeseries.TimeSeriesValue;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.joda.time.DateTime;

public class WavefrontStrings {
    public final static int MAX_TAG_KEY_VAL_CHARS = 254;  // Max of 255 chars, but need to exclude the '=' sign between tag name and tag value.
    public final static int TRUNCATE_TAG_NAME = 127;  // We truncate tag names to 128 chars, so we don't have to truncate values so much.

    private WavefrontStrings() {
    }

    /**
     * Convert a name to a name that wavefront will accept.
     *
     * Wavefront documentation specifies a metric consists of the characters
     * [-a-zA-Z_0-9.] Any disallowed characters are replaced with underscores.
     */
    public static final String name(String name) {
        return name
                .toLowerCase(Locale.ROOT)
                .replaceAll("[^-a-zA-Z_0-9.]", "_");
    }

    /**
     * Convert a group+metric to a wavefront name.
     *
     * Concatenates the paths of a Group and a Metric, separating each path
     * element with a dot ('.').
     *
     * Example: - group path: [ 'example', 'group', 'path' ] - and metric: [
     * 'metric', 'name' ] are concatenated into
     * "example.group.path.metric.name".
     *
     * The concatenated name is cleaned to only contain characters allowed by
     * wavefront. (See the WavefrontString.name(String) function.)
     */
    public static final String name(SimpleGroupPath group, MetricName metric) {
        return name(Stream.concat(group.getPath().stream(), metric.getPath().stream())
                .collect(Collectors.joining(".")));
    }

    /**
     * Does the escaping of tag values.
     *
     * This function assumes you'll put double quotes ('"') around your tag
     * value.
     */
    private static String escapeTagValue(String tag_value) {
        return tag_value.replace("\"", "\\\"");
    }

    /**
     * Transform a tag entry into a wavefront tag.
     *
     * Double quotes in the tag value will be escaped.
     */
    private static Optional> createTagEntry(Map.Entry tag_entry) {
        final Optional opt_tag_value = tag_entry.getValue().asString();
        return opt_tag_value
                .map(tag_value -> SimpleMapEntry.create(name(tag_entry.getKey()), escapeTagValue(tag_value)));
    }

    /**
     * Truncate tag keys and values, to prevent them from exceeding the max
     * length of a tag entry.
     */
    private static Map.Entry maybeTruncateTagEntry(Map.Entry tag_entry) {
        String k = tag_entry.getKey();
        String v = tag_entry.getValue();
        if (k.length() + v.length() <= MAX_TAG_KEY_VAL_CHARS - 2) // 2 chars for the quotes around the value
            return tag_entry;

        if (k.length() > TRUNCATE_TAG_NAME)
            k = k.substring(0, TRUNCATE_TAG_NAME);
        if (k.length() + v.length() > MAX_TAG_KEY_VAL_CHARS - 2)
            v = v.substring(0, MAX_TAG_KEY_VAL_CHARS - 2 - k.length());
        return SimpleMapEntry.create(k, v);
    }

    /**
     * Create a map of tags for wavefront.
     *
     * The tag values are escaped and should be surrounded by double quotes.
     * This function does not put the surrounding quotes around the tag values.
     */
    public static Map tags(Tags tags) {
        return tags.stream()
                .map(WavefrontStrings::createTagEntry)
                .flatMap(opt -> opt.map(Stream::of).orElseGet(Stream::empty))
                .map(WavefrontStrings::maybeTruncateTagEntry)
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    }

    /**
     * Create a wavefront compatible string representation of the metric value.
     *
     * If the metric value is empty or not representable in wavefront, an empty
     * optional will be returned.
     */
    public static Optional wavefrontValue(MetricValue mv) {
        // Omit NaN and Inf.
        if (mv.isInfiniteOrNaN()) return Optional.empty();
        return mv.value().map(Number::toString);
    }

    /**
     * Convert a timestamp to the string representation that wavefront will
     * accept.
     */
    public static String timestamp(DateTime ts) {
        return Long.toString(ts.getMillis() / 1000);
    }

    /**
     * Extract the 'source' tag from the tag_map.
     *
     * Wavefront requires the 'source' tag to be the first tag on the line,
     * hence the special handling. It also *must* be present, so we can never
     * return null.
     */
    private static String extractTagSource(Map tag_map) {
        return Optional.ofNullable(tag_map.remove("source"))
                .orElseGet(() -> {
                    return Optional.ofNullable(tag_map.get("cluster"))
                            .map(WavefrontStrings::escapeTagValue)
                            .orElseGet(() -> tag_map.getOrDefault("moncluster", "monsoon"));
                });
    }

    /**
     * Build the wavefront line from its parts.
     */
    private static String wavefrontLine(DateTime ts, SimpleGroupPath group, MetricName metric, String value, String source, Map tag_map) {
        return new StringBuilder()
                .append(name(group, metric))
                .append(' ')
                .append(value)
                .append(' ')
                .append(timestamp(ts))
                .append(' ')
                .append("source=").append(source)
                .append(' ')
                .append(tag_map.entrySet().stream()
                        .map(entry -> entry.getKey() + "=\"" + entry.getValue() + '\"')
                        .collect(Collectors.joining(" "))
                )
                .toString();
    }

    /**
     * Convert a metric to a wavefront string.
     *
     * Empty metrics and histograms do not emit a value.
     *
     * Note: the line is not terminated with a newline.
     */
    public static Optional wavefrontLine(DateTime ts, GroupName group, MetricName metric, MetricValue metric_value) {
        return wavefrontValue(metric_value)
                .map(value -> {
                    final Map tag_map = tags(group.getTags());
                    final String source = extractTagSource(tag_map);  // Modifies tag_map.
                    return wavefrontLine(ts, group.getPath(), metric, value, source, tag_map);
                });
    }

    private static Stream wavefrontLineForMetric(DateTime ts, GroupName group, Map.Entry metricEntry) {
        return wavefrontLine(ts, group, metricEntry.getKey(), metricEntry.getValue())
                .map(Stream::of)
                .orElseGet(Stream::empty);
    }

    /**
     * Convert a time series value into the string entries for wavefront.
     *
     * Note: the line is not terminated with a newline.
     */
    public static Stream wavefrontLine(DateTime ts, TimeSeriesValue tsv) {
        final GroupName group = tsv.getGroup();

        return tsv.getMetrics().entrySet().stream()
                .flatMap(metricEntry -> wavefrontLineForMetric(ts, group, metricEntry));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy