org.apache.druid.emitter.graphite.WhiteListBasedConverter.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.druid.emitter.graphite.WhiteListBasedConverter.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.druid.emitter.graphite;

import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.io.Files;
import com.google.common.io.Resources;
import org.apache.druid.java.util.common.ISE;
import org.apache.druid.java.util.common.logger.Logger;
import org.apache.druid.java.util.emitter.service.ServiceMetricEvent;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.concurrent.TimeUnit;

@JsonTypeName("whiteList")
public class WhiteListBasedConverter implements DruidToGraphiteEventConverter {
    private static final Logger LOGGER = new Logger(WhiteListBasedConverter.class);
    /**
     * @code whiteListDimsMapper is a white list of metric->dimensions mappings.
     * Key is the metric name or the metric's prefix.
     * Value is a list of metric's dimensions names.
     * The order of the dimension name is important, it will be used to build the graphite metric path.
     * For instance we have dimension type is nested under dimension dataSource -> prefix.dataSource.queryType.metricName
     */
    private final ImmutableSortedMap<String, ImmutableSet<String>> whiteListDimsMapper;

    @JsonProperty
    private final boolean ignoreHostname;

    @JsonProperty
    private final boolean ignoreServiceName;

    @JsonProperty
    private final String namespacePrefix;

    @JsonProperty
    private final boolean replaceSlashWithDot;

    @JsonProperty
    private final String mapPath;

    private final ObjectMapper mapper;

    @JsonCreator
    public WhiteListBasedConverter(@JsonProperty("namespacePrefix") String namespacePrefix,
            @JsonProperty("ignoreHostname") Boolean ignoreHostname,
            @JsonProperty("ignoreServiceName") Boolean ignoreServiceName,
            @JsonProperty("replaceSlashWithDot") Boolean replaceSlashWithDot,
            @JsonProperty("mapPath") String mapPath, @JacksonInject ObjectMapper mapper) {
        this.mapper = Preconditions.checkNotNull(mapper);
        this.mapPath = mapPath;
        this.whiteListDimsMapper = readMap(this.mapPath);
        this.ignoreHostname = ignoreHostname == null ? false : ignoreHostname;
        this.ignoreServiceName = ignoreServiceName == null ? false : ignoreServiceName;
        this.replaceSlashWithDot = replaceSlashWithDot == null ? false : replaceSlashWithDot;
        this.namespacePrefix = Preconditions.checkNotNull(namespacePrefix, "namespace prefix can not be null");
    }

    @JsonProperty
    public boolean isIgnoreHostname() {
        return ignoreHostname;
    }

    @JsonProperty
    public boolean isIgnoreServiceName() {
        return ignoreServiceName;
    }

    @JsonProperty
    public String getNamespacePrefix() {
        return namespacePrefix;
    }

    @JsonProperty
    public boolean replaceSlashWithDot() {
        return replaceSlashWithDot;
    }

    /**
     * @param event Event subject to filtering
     *
     * @return true if and only if the event prefix key is in the {@code whiteListDimsMapper}
     */
    private boolean isInWhiteList(ServiceMetricEvent event) {
        return getPrefixKey(event.getMetric(), whiteListDimsMapper) != null;
    }

    /**
     * @param key       the metric name to lookup
     * @param whiteList
     *
     * @return <tt>null</tt> if the key does not match with any of the prefixes keys in @code metricsWhiteList,
     * or the prefix in @code whiteListDimsMapper
     */
    private String getPrefixKey(String key, SortedMap<String, ?> whiteList) {
        String prefixKey = null;
        if (whiteList.containsKey(key)) {
            return key;
        }
        SortedMap<String, ?> headMap = whiteList.headMap(key);
        if (!headMap.isEmpty() && key.startsWith(headMap.lastKey())) {
            prefixKey = headMap.lastKey();
        }
        return prefixKey;
    }

    /**
     * Returns a {@link List} of the white-listed dimension's values to send.
     * The list is order is the same as the order of dimensions {@code whiteListDimsMapper}
     *
     * @param event the event for which will filter dimensions
     *
     * @return {@link List}  of the filtered dimension values to send or <tt>null<tt/> if the event is not in the white list
     */
    private List<String> getOrderedDimValues(ServiceMetricEvent event) {
        String prefixKey = getPrefixKey(event.getMetric(), whiteListDimsMapper);
        if (prefixKey == null) {
            return null;
        }
        ImmutableList.Builder<String> outputList = new ImmutableList.Builder<>();
        Set<String> dimensions = whiteListDimsMapper.get(prefixKey);
        if (dimensions == null) {
            return Collections.emptyList();
        }
        for (String dimKey : dimensions) {
            Object rawValue = event.getUserDims().get(dimKey);
            String value = null;

            if (rawValue instanceof String) {
                value = (String) rawValue;
            } else if (rawValue instanceof Collection) {
                Collection values = (Collection) rawValue;
                if (!values.isEmpty()) {
                    value = (String) values.iterator().next();
                }
            }

            if (value != null) {
                outputList.add(GraphiteEmitter.sanitize(value));
            }
        }
        return outputList.build();
    }

    /**
     * @param serviceMetricEvent druid metric event to convert
     *
     * @return <tt>null</tt> if the event is not white listed, otherwise return {@link GraphiteEvent}
     * <p>
     * The metric path of the graphite event is:
     * <namespacePrefix>.[<druid service name>].[<druid hostname>].<white-listed dimensions>.<metric>
     * <p/>
     * The order of the dimension is the order returned by {@code getOrderedDimValues()}
     * Note that this path will be sanitized by replacing all the `.` or space by `_` {@link GraphiteEmitter#sanitize(String)}
     * </p>
     */

    @Override
    public GraphiteEvent druidEventToGraphite(ServiceMetricEvent serviceMetricEvent) {
        if (!this.isInWhiteList(serviceMetricEvent)) {
            return null;
        }
        final ImmutableList.Builder<String> metricPathBuilder = new ImmutableList.Builder<>();
        metricPathBuilder.add(this.getNamespacePrefix());
        if (!this.isIgnoreServiceName()) {
            metricPathBuilder.add(GraphiteEmitter.sanitize(serviceMetricEvent.getService()));
        }
        if (!this.isIgnoreHostname()) {
            metricPathBuilder.add(GraphiteEmitter.sanitize(serviceMetricEvent.getHost()));
        }
        List<String> dimValues = getOrderedDimValues(serviceMetricEvent);
        if (dimValues != null) {
            metricPathBuilder.addAll(dimValues);
        }
        metricPathBuilder.add(GraphiteEmitter.sanitize(serviceMetricEvent.getMetric(), this.replaceSlashWithDot()));

        return new GraphiteEvent(Joiner.on(".").join(metricPathBuilder.build()),
                String.valueOf(serviceMetricEvent.getValue()),
                TimeUnit.MILLISECONDS.toSeconds(serviceMetricEvent.getCreatedTime().getMillis()));
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof WhiteListBasedConverter)) {
            return false;
        }

        WhiteListBasedConverter that = (WhiteListBasedConverter) o;

        if (isIgnoreHostname() != that.isIgnoreHostname()) {
            return false;
        }
        if (isIgnoreServiceName() != that.isIgnoreServiceName()) {
            return false;
        }
        if (replaceSlashWithDot() != that.replaceSlashWithDot()) {
            return false;
        }
        if (!getNamespacePrefix().equals(that.getNamespacePrefix())) {
            return false;
        }
        return mapPath != null ? mapPath.equals(that.mapPath) : that.mapPath == null;

    }

    @Override
    public int hashCode() {
        int result = (isIgnoreHostname() ? 1 : 0);
        result = 31 * result + (isIgnoreServiceName() ? 1 : 0);
        result = 31 * result + (replaceSlashWithDot() ? 1 : 0);
        result = 31 * result + getNamespacePrefix().hashCode();
        result = 31 * result + (mapPath != null ? mapPath.hashCode() : 0);
        return result;
    }

    private ImmutableSortedMap<String, ImmutableSet<String>> readMap(final String mapPath) {
        String fileContent;
        String actualPath = mapPath;
        try {
            if (Strings.isNullOrEmpty(mapPath)) {
                URL resource = this.getClass().getClassLoader().getResource("defaultWhiteListMap.json");
                actualPath = resource.getFile();
                LOGGER.info("using default whiteList map located at [%s]", actualPath);
                fileContent = Resources.toString(resource, Charset.defaultCharset());
            } else {
                fileContent = Files.asCharSource(new File(mapPath), Charset.forName("UTF-8")).read();
            }
            return mapper.reader(new TypeReference<ImmutableSortedMap<String, ImmutableSet<String>>>() {
            }).readValue(fileContent);
        } catch (IOException e) {
            throw new ISE(e, "Got an exception while parsing file [%s]", actualPath);
        }
    }
}