/*
* 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.ganglia;
import com.yammer.metrics.core.Gauge;
import com.yammer.metrics.core.MetricName;
import com.yammer.metrics.core.MetricsRegistry;
import com.yammer.metrics.reporting.GangliaReporter;
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.PropertyDescriptor;
import org.apache.nifi.controller.ConfigurationContext;
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.InitializationException;
import org.apache.nifi.reporting.ReportingContext;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* Configuration of this reporting task requires a "host" property that points
* to the Ganglia server and optionally allows a "port" property (default
* otherwise is 8649)
*/
@Tags({"ganglia", "stats"})
@CapabilityDescription("Reports metrics to Ganglia so that Ganglia can be used for external monitoring of the application. Metrics"
+ " reported include JVM Metrics (optional); the following 5-minute NiFi statistics: FlowFiles Received, Bytes Received,"
+ " FlowFiles Sent, Bytes Sent, Bytes Read, Bytes Written, Total Task Duration; and the current values for"
+ " FlowFiles Queued, Bytes Queued, and number of Active Threads.")
public class StandardGangliaReporter extends AbstractReportingTask {
public static final PropertyDescriptor HOSTNAME = new PropertyDescriptor.Builder()
.name("Hostname")
.description("The fully-qualified name of the host on which Ganglia is running")
.required(true)
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
.defaultValue("localhost")
.build();
public static final PropertyDescriptor PORT = new PropertyDescriptor.Builder()
.name("Port")
.description("The Port on which Ganglia is listening for incoming connections")
.required(true)
.addValidator(StandardValidators.PORT_VALIDATOR)
.defaultValue("8649")
.build();
public static final PropertyDescriptor SEND_JVM_METRICS = new PropertyDescriptor.Builder()
.name("Send JVM Metrics")
.description("Specifies whether or not JVM Metrics should be gathered and sent, in addition to NiFi-specific metrics")
.required(true)
.allowableValues("true", "false")
.defaultValue("false")
.build();
public static final String METRICS_GROUP = "NiFi";
private MetricsRegistry metricsRegistry;
private GangliaReporter gangliaReporter;
private final AtomicReference<ProcessGroupStatus> latestStatus = new AtomicReference<>();
@Override
protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
final List<PropertyDescriptor> properties = new ArrayList<>();
properties.add(HOSTNAME);
properties.add(PORT);
properties.add(SEND_JVM_METRICS);
return properties;
}
@OnScheduled
public void onConfigure(final ConfigurationContext config) throws InitializationException {
metricsRegistry = new MetricsRegistry();
metricsRegistry.newGauge(new MetricName(METRICS_GROUP, "int32", "FlowFiles Received Last 5 mins"), new Gauge<Integer>() {
@Override
public Integer value() {
final ProcessGroupStatus status = latestStatus.get();
if (status == null) {
return 0;
}
final Integer value = status.getFlowFilesReceived();
return (value == null) ? 0 : value;
}
});
metricsRegistry.newGauge(new MetricName(METRICS_GROUP, "int64", "Bytes Received Last 5 mins"), new Gauge<Long>() {
@Override
public Long value() {
final ProcessGroupStatus status = latestStatus.get();
if (status == null) {
return 0L;
}
return status.getBytesReceived();
}
});
metricsRegistry.newGauge(new MetricName(METRICS_GROUP, "int32", "FlowFiles Sent Last 5 mins"), new Gauge<Integer>() {
@Override
public Integer value() {
final ProcessGroupStatus status = latestStatus.get();
if (status == null) {
return 0;
}
return status.getFlowFilesSent();
}
});
metricsRegistry.newGauge(new MetricName(METRICS_GROUP, "int64", "Bytes Sent Last 5 mins"), new Gauge<Long>() {
@Override
public Long value() {
final ProcessGroupStatus status = latestStatus.get();
if (status == null) {
return 0L;
}
return status.getBytesSent();
}
});
metricsRegistry.newGauge(new MetricName(METRICS_GROUP, "int32", "FlowFiles Queued"), new Gauge<Integer>() {
@Override
public Integer value() {
final ProcessGroupStatus status = latestStatus.get();
if (status == null) {
return 0;
}
final Integer value = status.getQueuedCount();
return (value == null) ? 0 : value;
}
});
metricsRegistry.newGauge(new MetricName(METRICS_GROUP, "int64", "Bytes Queued"), new Gauge<Long>() {
@Override
public Long value() {
final ProcessGroupStatus status = latestStatus.get();
if (status == null) {
return 0L;
}
final Long value = status.getQueuedContentSize();
return (value == null) ? 0L : value;
}
});
metricsRegistry.newGauge(new MetricName(METRICS_GROUP, "int64", "Bytes Read (5 mins)"), new Gauge<Long>() {
@Override
public Long value() {
final ProcessGroupStatus status = latestStatus.get();
if (status == null) {
return 0L;
}
final Long value = status.getBytesRead();
return (value == null) ? 0L : value;
}
});
metricsRegistry.newGauge(new MetricName(METRICS_GROUP, "int64", "Bytes Written (5 mins)"), new Gauge<Long>() {
@Override
public Long value() {
final ProcessGroupStatus status = latestStatus.get();
if (status == null) {
return 0L;
}
final Long value = status.getBytesWritten();
return (value == null) ? 0L : value;
}
});
metricsRegistry.newGauge(new MetricName(METRICS_GROUP, "int32", "Active Threads"), new Gauge<Integer>() {
@Override
public Integer value() {
final ProcessGroupStatus status = latestStatus.get();
if (status == null) {
return 0;
}
final Integer value = status.getActiveThreadCount();
return (value == null) ? 0 : value;
}
});
metricsRegistry.newGauge(new MetricName(METRICS_GROUP, "int32", "Total Task Duration Seconds"), new Gauge<Integer>() {
@Override
public Integer value() {
final ProcessGroupStatus status = latestStatus.get();
if (status == null) {
return 0;
}
final long nanos = calculateProcessingNanos(status);
return (int) TimeUnit.NANOSECONDS.toSeconds(nanos);
}
});
final String gangliaHost = config.getProperty(HOSTNAME).getValue();
final int port = config.getProperty(PORT).asInteger();
try {
gangliaReporter = new GangliaReporter(metricsRegistry, gangliaHost, port, METRICS_GROUP) {
@Override
protected String sanitizeName(MetricName name) {
return name.getName();
}
};
gangliaReporter.printVMMetrics = config.getProperty(SEND_JVM_METRICS).asBoolean();
} catch (final IOException e) {
throw new InitializationException(e);
}
}
@Override
public void onTrigger(final ReportingContext context) {
final ProcessGroupStatus rootGroupStatus = context.getEventAccess().getControllerStatus();
this.latestStatus.set(rootGroupStatus);
gangliaReporter.run();
getLogger().info("{} Sent metrics to Ganglia", new Object[] {this});
}
private long calculateProcessingNanos(final ProcessGroupStatus status) {
long nanos = 0L;
for (final ProcessorStatus procStats : status.getProcessorStatus()) {
nanos += procStats.getProcessingNanos();
}
for (final ProcessGroupStatus childGroupStatus : status.getProcessGroupStatus()) {
nanos += calculateProcessingNanos(childGroupStatus);
}
return nanos;
}
}