/* * 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.nifi.reporting.datadog; import com.codahale.metrics.Gauge; import com.codahale.metrics.MetricRegistry; import com.google.common.base.Optional; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.util.concurrent.AtomicDouble; import com.yammer.metrics.core.VirtualMachineMetrics; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.annotation.lifecycle.OnScheduled; import org.apache.nifi.components.AllowableValue; import org.apache.nifi.components.PropertyDescriptor; import org.apache.nifi.controller.ConfigurationContext; import org.apache.nifi.controller.status.ConnectionStatus; import org.apache.nifi.controller.status.PortStatus; import org.apache.nifi.controller.status.ProcessGroupStatus; import org.apache.nifi.controller.status.ProcessorStatus; import org.apache.nifi.processor.util.StandardValidators; import org.apache.nifi.reporting.AbstractReportingTask; import org.apache.nifi.reporting.ReportingContext; import org.apache.nifi.reporting.datadog.metrics.MetricsService; import org.coursera.metrics.datadog.DynamicTagsCallback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Map; import java.util.HashMap; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; @Tags({"reporting", "datadog", "metrics"}) @CapabilityDescription("Publishes metrics from NiFi to datadog. For accurate and informative reporting, components should have unique names.") public class DataDogReportingTask extends AbstractReportingTask { static final AllowableValue DATADOG_AGENT = new AllowableValue("Datadog Agent", "Datadog Agent", "Metrics will be sent via locally installed Datadog agent. " + "Datadog agent needs to be installed manually before using this option"); static final AllowableValue DATADOG_HTTP = new AllowableValue("Datadog HTTP", "Datadog HTTP", "Metrics will be sent via HTTP transport with no need of Agent installed. " + "Datadog API key needs to be set"); static final PropertyDescriptor DATADOG_TRANSPORT = new PropertyDescriptor.Builder() .name("Datadog transport") .description("Transport through which metrics will be sent to Datadog") .required(true) .allowableValues(DATADOG_AGENT, DATADOG_HTTP) .defaultValue(DATADOG_HTTP.getValue()) .build(); static final PropertyDescriptor API_KEY = new PropertyDescriptor.Builder() .name("API key") .description("Datadog API key. If specified value is 'agent', local Datadog agent will be used.") .expressionLanguageSupported(false) .required(false) .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .build(); static final PropertyDescriptor METRICS_PREFIX = new PropertyDescriptor.Builder() .name("Metrics prefix") .description("Prefix to be added before every metric") .required(true) .expressionLanguageSupported(true) .defaultValue("nifi") .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .build(); static final PropertyDescriptor ENVIRONMENT = new PropertyDescriptor.Builder() .name("Environment") .description("Environment, dataflow is running in. " + "This property will be included as metrics tag.") .required(true) .expressionLanguageSupported(true) .defaultValue("dev") .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .build(); private MetricsService metricsService; private DDMetricRegistryBuilder ddMetricRegistryBuilder; private MetricRegistry metricRegistry; private String metricsPrefix; private String environment; private String statusId; private ConcurrentHashMap<String, AtomicDouble> metricsMap; private Map<String, String> defaultTags; private volatile VirtualMachineMetrics virtualMachineMetrics; private Logger logger = LoggerFactory.getLogger(getClass().getName()); @OnScheduled public void setup(final ConfigurationContext context) { metricsService = getMetricsService(); ddMetricRegistryBuilder = getMetricRegistryBuilder(); metricRegistry = getMetricRegistry(); metricsMap = getMetricsMap(); metricsPrefix = METRICS_PREFIX.getDefaultValue(); environment = ENVIRONMENT.getDefaultValue(); virtualMachineMetrics = VirtualMachineMetrics.getInstance(); ddMetricRegistryBuilder.setMetricRegistry(metricRegistry) .setTags(metricsService.getAllTagsList()); } @Override protected List<PropertyDescriptor> getSupportedPropertyDescriptors() { final List<PropertyDescriptor> properties = new ArrayList<>(); properties.add(METRICS_PREFIX); properties.add(ENVIRONMENT); properties.add(API_KEY); properties.add(DATADOG_TRANSPORT); return properties; } @Override public void onTrigger(ReportingContext context) { final ProcessGroupStatus status = context.getEventAccess().getControllerStatus(); metricsPrefix = context.getProperty(METRICS_PREFIX).evaluateAttributeExpressions().getValue(); environment = context.getProperty(ENVIRONMENT).evaluateAttributeExpressions().getValue(); statusId = status.getId(); defaultTags = ImmutableMap.of("env", environment, "dataflow_id", statusId); try { updateDataDogTransport(context); } catch (IOException e) { e.printStackTrace(); } updateAllMetricGroups(status); ddMetricRegistryBuilder.getDatadogReporter().report(); } protected void updateMetrics(Map<String, Double> metrics, Optional<String> processorName, Map<String, String> tags) { for (Map.Entry<String, Double> entry : metrics.entrySet()) { final String metricName = buildMetricName(processorName, entry.getKey()); logger.debug(metricName + ": " + entry.getValue()); //if metric is not registered yet - register it if (!metricsMap.containsKey(metricName)) { metricsMap.put(metricName, new AtomicDouble(entry.getValue())); metricRegistry.register(metricName, new MetricGauge(metricName, tags)); } //set real time value to metrics map metricsMap.get(metricName).set(entry.getValue()); } } private void updateAllMetricGroups(ProcessGroupStatus processGroupStatus) { final List<ProcessorStatus> processorStatuses = new ArrayList<>(); populateProcessorStatuses(processGroupStatus, processorStatuses); for (final ProcessorStatus processorStatus : processorStatuses) { updateMetrics(metricsService.getProcessorMetrics(processorStatus), Optional.of(processorStatus.getName()), defaultTags); } final List<ConnectionStatus> connectionStatuses = new ArrayList<>(); populateConnectionStatuses(processGroupStatus, connectionStatuses); for (ConnectionStatus connectionStatus: connectionStatuses) { Map<String, String> connectionStatusTags = new HashMap<>(defaultTags); connectionStatusTags.putAll(metricsService.getConnectionStatusTags(connectionStatus)); updateMetrics(metricsService.getConnectionStatusMetrics(connectionStatus), Optional.<String>absent(), connectionStatusTags); } final List<PortStatus> inputPortStatuses = new ArrayList<>(); populateInputPortStatuses(processGroupStatus, inputPortStatuses); for (PortStatus portStatus: inputPortStatuses) { Map<String, String> portTags = new HashMap<>(defaultTags); portTags.putAll(metricsService.getPortStatusTags(portStatus)); updateMetrics(metricsService.getPortStatusMetrics(portStatus), Optional.<String>absent(), portTags); } final List<PortStatus> outputPortStatuses = new ArrayList<>(); populateOutputPortStatuses(processGroupStatus, outputPortStatuses); for (PortStatus portStatus: outputPortStatuses) { Map<String, String> portTags = new HashMap<>(defaultTags); portTags.putAll(metricsService.getPortStatusTags(portStatus)); updateMetrics(metricsService.getPortStatusMetrics(portStatus), Optional.<String>absent(), portTags); } updateMetrics(metricsService.getJVMMetrics(virtualMachineMetrics), Optional.<String>absent(), defaultTags); updateMetrics(metricsService.getDataFlowMetrics(processGroupStatus), Optional.<String>absent(), defaultTags); } private class MetricGauge implements Gauge, DynamicTagsCallback { private Map<String, String> tags; private String metricName; public MetricGauge(String metricName, Map<String, String> tagsMap) { this.tags = tagsMap; this.metricName = metricName; } @Override public Object getValue() { return metricsMap.get(metricName).get(); } @Override public List<String> getTags() { List<String> tagsList = Lists.newArrayList(); for (Map.Entry<String, String> entry : tags.entrySet()) { tagsList.add(entry.getKey() + ":" + entry.getValue()); } return tagsList; } } private void updateDataDogTransport(ReportingContext context) throws IOException { String dataDogTransport = context.getProperty(DATADOG_TRANSPORT).getValue(); if (dataDogTransport.equalsIgnoreCase(DATADOG_AGENT.getValue())) { ddMetricRegistryBuilder.build("agent"); } else if (dataDogTransport.equalsIgnoreCase(DATADOG_HTTP.getValue()) && context.getProperty(API_KEY).isSet()) { ddMetricRegistryBuilder.build(context.getProperty(API_KEY).getValue()); } } private void populateProcessorStatuses(final ProcessGroupStatus groupStatus, final List<ProcessorStatus> statuses) { statuses.addAll(groupStatus.getProcessorStatus()); for (final ProcessGroupStatus childGroupStatus : groupStatus.getProcessGroupStatus()) { populateProcessorStatuses(childGroupStatus, statuses); } } private void populateConnectionStatuses(final ProcessGroupStatus groupStatus, final List<ConnectionStatus> statuses) { statuses.addAll(groupStatus.getConnectionStatus()); for (final ProcessGroupStatus childGroupStatus : groupStatus.getProcessGroupStatus()) { populateConnectionStatuses(childGroupStatus, statuses); } } private void populateInputPortStatuses(final ProcessGroupStatus groupStatus, final List<PortStatus> statuses) { statuses.addAll(groupStatus.getInputPortStatus()); for (final ProcessGroupStatus childGroupStatus : groupStatus.getProcessGroupStatus()) { populateInputPortStatuses(childGroupStatus, statuses); } } private void populateOutputPortStatuses(final ProcessGroupStatus groupStatus, final List<PortStatus> statuses) { statuses.addAll(groupStatus.getOutputPortStatus()); for (final ProcessGroupStatus childGroupStatus : groupStatus.getProcessGroupStatus()) { populateOutputPortStatuses(childGroupStatus, statuses); } } private String buildMetricName(Optional<String> processorName, String metricName) { return metricsPrefix + "." + processorName.or("flow") + "." + metricName; } protected MetricsService getMetricsService() { return new MetricsService(); } protected DDMetricRegistryBuilder getMetricRegistryBuilder() { return new DDMetricRegistryBuilder(); } protected MetricRegistry getMetricRegistry() { return new MetricRegistry(); } protected ConcurrentHashMap<String, AtomicDouble> getMetricsMap() { return new ConcurrentHashMap<>(); } }