/*
* 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
*
* 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.streams.monitoring.tasks;
import org.apache.streams.config.ComponentConfigurator;
import org.apache.streams.config.StreamsConfiguration;
import org.apache.streams.config.StreamsConfigurator;
import org.apache.streams.jackson.DatumStatusCounterDeserializer;
import org.apache.streams.jackson.MemoryUsageDeserializer;
import org.apache.streams.jackson.StreamsJacksonMapper;
import org.apache.streams.jackson.StreamsTaskCounterDeserializer;
import org.apache.streams.jackson.ThroughputQueueDeserializer;
import org.apache.streams.local.monitoring.MonitoringConfiguration;
import org.apache.streams.monitoring.persist.MessagePersister;
import org.apache.streams.monitoring.persist.impl.BroadcastMessagePersister;
import org.apache.streams.monitoring.persist.impl.LogstashUdpMessagePersister;
import org.apache.streams.monitoring.persist.impl.Slf4jMessagePersister;
import org.apache.streams.pojo.json.Broadcast;
import org.apache.streams.pojo.json.DatumStatusCounterBroadcast;
import org.apache.streams.pojo.json.MemoryUsageBroadcast;
import org.apache.streams.pojo.json.StreamsTaskCounterBroadcast;
import org.apache.streams.pojo.json.ThroughputQueueBroadcast;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.slf4j.Logger;
import java.lang.management.ManagementFactory;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.management.MBeanServer;
import javax.management.NotificationBroadcasterSupport;
import javax.management.ObjectName;
/**
* This thread runs inside of a Streams runtime and periodically persists information
* from relevant JMX beans.
*/
public class BroadcastMonitorThread extends NotificationBroadcasterSupport implements Runnable {
private static final Logger LOGGER = org.slf4j.LoggerFactory.getLogger(BroadcastMonitorThread.class);
private static MBeanServer server;
private MonitoringConfiguration configuration;
private URI broadcastUri = null;
private MessagePersister messagePersister;
private volatile boolean keepRunning;
private static ObjectMapper objectMapper = StreamsJacksonMapper.getInstance();
/**
* DEPRECATED
* Please initialize logging with monitoring object via typesafe.
* @param streamConfig streamConfig map.
*/
@Deprecated
public BroadcastMonitorThread(Map<String, Object> streamConfig) {
this(objectMapper.convertValue(streamConfig, MonitoringConfiguration.class));
}
public BroadcastMonitorThread(StreamsConfiguration streamConfig) {
this(objectMapper.convertValue(streamConfig.getAdditionalProperties().get("monitoring"), MonitoringConfiguration.class));
}
/**
* BroadcastMonitorThread constructor - uses supplied MonitoringConfiguration.
* @param configuration MonitoringConfiguration
*/
public BroadcastMonitorThread(MonitoringConfiguration configuration) {
this.configuration = configuration;
if ( this.configuration == null ) {
this.configuration = new ComponentConfigurator<>(MonitoringConfiguration.class).detectConfiguration(StreamsConfigurator.getConfig().atPath("monitoring"));
}
LOGGER.info("BroadcastMonitorThread created");
initializeObjectMapper();
prepare();
LOGGER.info("BroadcastMonitorThread initialized");
}
/**
* Initialize our object mapper with all of our bean's custom deserializers.
* This way we can convert them to and from Strings dictated by our
* POJOs which are generated from JSON schemas.
*/
private void initializeObjectMapper() {
SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(MemoryUsageBroadcast.class, new MemoryUsageDeserializer());
simpleModule.addDeserializer(ThroughputQueueBroadcast.class, new ThroughputQueueDeserializer());
simpleModule.addDeserializer(StreamsTaskCounterBroadcast.class, new StreamsTaskCounterDeserializer());
simpleModule.addDeserializer(DatumStatusCounterBroadcast.class, new DatumStatusCounterDeserializer());
objectMapper.registerModule(simpleModule);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
/**
* Get all relevant JMX beans, convert their values to strings, and then persist them.
*/
@Override
public void run() {
LOGGER.info("BroadcastMonitorThread running");
while (keepRunning) {
try {
List<String> messages = new ArrayList<>();
Set<ObjectName> beans = server.queryNames(null, null);
for (ObjectName name : beans) {
String item = objectMapper.writeValueAsString(name);
Broadcast broadcast = null;
if (name.getKeyPropertyList().get("type") != null) {
if (name.getKeyPropertyList().get("type").equals("ThroughputQueue")) {
broadcast = objectMapper.readValue(item, ThroughputQueueBroadcast.class);
} else if (name.getKeyPropertyList().get("type").equals("StreamsTaskCounter")) {
broadcast = objectMapper.readValue(item, StreamsTaskCounterBroadcast.class);
} else if (name.getKeyPropertyList().get("type").equals("DatumStatusCounter")) {
broadcast = objectMapper.readValue(item, DatumStatusCounterBroadcast.class);
} else if (name.getKeyPropertyList().get("type").equals("Memory")) {
broadcast = objectMapper.readValue(item, MemoryUsageBroadcast.class);
}
if (broadcast != null) {
messages.add(objectMapper.writeValueAsString(broadcast));
}
}
}
messagePersister.persistMessages(messages);
Thread.sleep(configuration.getMonitoringBroadcastIntervalMs());
} catch (InterruptedException ex) {
LOGGER.debug("Broadcast Monitor Interrupted!");
Thread.currentThread().interrupt();
this.keepRunning = false;
} catch (Exception ex) {
LOGGER.error("Exception: {}", ex);
this.keepRunning = false;
}
}
}
/**
* prepare for execution.
*/
public void prepare() {
keepRunning = true;
LOGGER.info("BroadcastMonitorThread setup " + this.configuration);
server = ManagementFactory.getPlatformMBeanServer();
if (this.configuration != null && this.configuration.getBroadcastURI() != null) {
try {
broadcastUri = new URI(configuration.getBroadcastURI());
} catch (Exception ex) {
LOGGER.error("invalid URI: ", ex);
}
if (broadcastUri != null) {
switch (broadcastUri.getScheme()) {
case "http":
messagePersister = new BroadcastMessagePersister(broadcastUri.toString());
break;
case "udp":
messagePersister = new LogstashUdpMessagePersister(broadcastUri.toString());
break;
default:
LOGGER.error("You need to specify a broadcast URI with either a HTTP or UDP protocol defined.");
throw new RuntimeException();
}
} else {
messagePersister = new Slf4jMessagePersister();
}
} else {
messagePersister = new Slf4jMessagePersister();
}
}
public void shutdown() {
this.keepRunning = false;
LOGGER.debug("Shutting down BroadcastMonitor Thread");
}
public String getBroadcastUri() {
return configuration.getBroadcastURI();
}
public long getWaitTime() {
return configuration.getMonitoringBroadcastIntervalMs();
}
}