// Copyright 2016 Twitter. All rights reserved. // // 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.twitter.heron.metricsmgr.executor; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.Collections; import java.util.HashMap; import java.util.Map; import com.twitter.heron.common.basics.Communicator; import com.twitter.heron.common.basics.SlaveLooper; import com.twitter.heron.common.basics.SysUtils; import com.twitter.heron.common.basics.TypeUtils; import com.twitter.heron.metricsmgr.MetricsSinksConfig; import com.twitter.heron.spi.metricsmgr.metrics.MetricsRecord; import com.twitter.heron.spi.metricsmgr.sink.IMetricsSink; import com.twitter.heron.spi.metricsmgr.sink.SinkContext; /** * SinkExecutor is a Runnable, running in a specific thread to drive the IMetricsSink. * The IMetricsSink does not need to care about thread-safe since: * 1. It is always running in the same specific thread * 2. All arguments passed to IMetricsSink are immutable * <p> * The thread of SinkExecutor would be blocked to save resources except: * 1. New MetricsRecord comes and notify the SinkExecutor to wake up to process it * 2. The time interval to invoke flush() is met so SinkExecutor would wake up to invoke flush() */ public class SinkExecutor implements Runnable, AutoCloseable { private final IMetricsSink metricsSink; private final SlaveLooper slaveLooper; // Communicator to read MetricsRecord private final Communicator<MetricsRecord> metricsInSinkQueue; // An unmodifiable Map to store config private final Map<String, Object> sinkConfig; // Context for a sink to init() private final SinkContext sinkContext; // The name of Executor, would be used as the name of running thread private final String executorName; /** * Construct a SinkExecutor, which is a Runnable * * @param executorName the name of this executor used as the name of running thread * @param metricsSink the class implementing IMetricsSink * @param slaveLooper the SlaveLoop to bind with * @param metricsInSinkQueue the queue to read MetricsRecord from */ public SinkExecutor(String executorName, IMetricsSink metricsSink, SlaveLooper slaveLooper, Communicator<MetricsRecord> metricsInSinkQueue, SinkContext sinkContext) { this.executorName = executorName; this.metricsSink = metricsSink; this.slaveLooper = slaveLooper; this.metricsInSinkQueue = metricsInSinkQueue; this.sinkContext = sinkContext; this.sinkConfig = new HashMap<String, Object>(); } public void setProperty(String key, Object value) { sinkConfig.put(key, value); } public void setPropertyMap(Map<? extends String, Object> configMap) { sinkConfig.putAll(configMap); } public Communicator<MetricsRecord> getCommunicator() { return metricsInSinkQueue; } @Override public void close() { SysUtils.closeIgnoringExceptions(metricsSink); } @Override public void run() { // Set current running thread's name as executorName Thread.currentThread().setName(executorName); // Add task to invoke processRecord method when the WakeableLooper is waken up addSinkTasks(); // Invoke flush method at a interval flushSinkAtInterval(); metricsSink.init(Collections.unmodifiableMap(sinkConfig), sinkContext); slaveLooper.loop(); } // Add task to invoke processRecord method when the WakeableLooper is waken up private void addSinkTasks() { Runnable sinkTasks = new Runnable() { @Override public void run() { while (!metricsInSinkQueue.isEmpty()) { metricsSink.processRecord(metricsInSinkQueue.poll()); } } }; slaveLooper.addTasksOnWakeup(sinkTasks); } // Add TimerTask to invoke flush() in IMetricsSink private void flushSinkAtInterval() { Object flushIntervalObj = sinkConfig.get(MetricsSinksConfig.CONFIG_KEY_FLUSH_FREQUENCY_MS); // If the config is not set, we consider the flush() would never be invoked if (flushIntervalObj != null) { final Duration flushInterval = TypeUtils.getDuration(flushIntervalObj, ChronoUnit.MILLIS); Runnable flushSink = new Runnable() { @Override public void run() { metricsSink.flush(); //Plan itself in future slaveLooper.registerTimerEvent(flushInterval, this); } }; // Plan the runnable explicitly at the first time slaveLooper.registerTimerEvent(flushInterval, flushSink); } } }