/*
* 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.hadoop.chukwa.dataloader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.hadoop.chukwa.Chunk;
import org.apache.hadoop.chukwa.ChunkImpl;
import org.apache.hadoop.chukwa.conf.ChukwaConfiguration;
import org.apache.hadoop.chukwa.datacollection.DataFactory;
import org.apache.hadoop.chukwa.datacollection.writer.SocketTeeWriter;
import org.apache.hadoop.chukwa.util.ExceptionUtil;
import org.apache.log4j.Logger;
/**
* Socket Data Loader, also known as the SDL, is a framework for allowing direct
* access to log data under the Chukwa Collector in a safe and efficient manner.
* Subscribe to chukwaCollector.tee.port for data streaming.
* Defaults socket tee port is 9094.
*/
public class SocketDataLoader implements Runnable {
private String hostname = "localhost";
private int port = 9094;
private static Logger log = Logger.getLogger(SocketDataLoader.class);
private Socket s = null;
private DataInputStream dis = null;
private DataOutputStream dos = null;
private Queue<Chunk> q = new LinkedList<Chunk>();
private String recordType = null;
private boolean running = false;
private static final int QUEUE_MAX = 10;
private Iterator<String> collectors = null;
private static Pattern pattern = Pattern.compile("(.+?)\\://(.+?)\\:(.+?)");
/*
* Create and start an instance of SocketDataLoader.
* @param Record Type
*/
public SocketDataLoader(String recordType) {
this.recordType = recordType;
try {
collectors = DataFactory.getInstance().getCollectorURLs(new ChukwaConfiguration());
} catch (IOException e) {
log.error(ExceptionUtil.getStackTrace(e));
}
Matcher m = pattern.matcher(collectors.next());
// Socket data loader only supports to stream data from a single collector.
// For large deployment, it may require to setup multi-tiers of collectors to
// channel data into a single collector for display.
if(m.matches()) {
hostname = m.group(2);
}
start();
}
/*
* Establish a connection to chukwa collector and filter data stream
* base on record type.
*/
public synchronized void start() {
try {
running = true;
s = new Socket(hostname, port);
try {
s.setSoTimeout(120000);
dos = new DataOutputStream (s.getOutputStream());
StringBuilder output = new StringBuilder();
output.append(SocketTeeWriter.WRITABLE);
if(recordType.toLowerCase().intern()!="all".intern()) {
output.append(" datatype=");
output.append(recordType);
} else {
output.append(" all");
}
output.append("\n");
dos.write((output.toString()).getBytes());
} catch (SocketException e) {
log.warn("Error while settin soTimeout to 120000");
}
dis = new DataInputStream(s
.getInputStream());
dis.readFully(new byte[3]); //read "OK\n"
StringBuilder sb = new StringBuilder();
sb.append("Subscribe to ");
sb.append(hostname);
sb.append(":");
sb.append(port);
sb.append(" for record type: ");
sb.append(recordType);
log.info(sb.toString());
Thread t=new Thread (this);
t.start();
} catch (IOException e) {
log.error(ExceptionUtil.getStackTrace(e));
stop();
}
}
/*
* Read the current chunks in the SDL queue.
* @return List of chunks in the SDL queue.
*/
public synchronized Collection<Chunk> read() throws NoSuchElementException {
Collection<Chunk> list = Collections.synchronizedCollection(q);
return list;
}
/*
* Unsubscribe from Chukwa collector and stop streaming.
*/
public void stop() {
if(s!=null) {
try {
dis.close();
dos.close();
s.close();
StringBuilder sb = new StringBuilder();
sb.append("Unsubscribe from ");
sb.append(hostname);
sb.append(":");
sb.append(port);
sb.append(" for data type: ");
sb.append(recordType);
log.info(sb.toString());
running = false;
} catch (IOException e) {
log.debug("Unable to close Socket Tee client socket.");
}
}
}
/*
* Check if streaming is currently happening for the current instance of SDL.
* @return running state of the SDL,
*/
public boolean running() {
return running;
}
/*
* Background thread for reading data from SocketTeeWriter, and add new data
* into SDL queue.
*/
@Override
public void run() {
try {
Chunk c;
while ((c = ChunkImpl.read(dis)) != null) {
StringBuilder sb = new StringBuilder();
sb.append("Chunk received, recordType:");
sb.append(c.getDataType());
log.debug(sb);
if(q.size()>QUEUE_MAX) {
q.poll();
}
q.offer(c);
}
} catch (IOException e) {
log.error(ExceptionUtil.getStackTrace(e));
stop();
}
}
}