All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.rabbitmq.tools.Tracer Maven / Gradle / Ivy

//  The contents of this file are subject to the Mozilla Public License
//  Version 1.1 (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.mozilla.org/MPL/
//
//  Software distributed under the License is distributed on an "AS IS"
//  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
//  the License for the specific language governing rights and
//  limitations under the License.
//
//  The Original Code is RabbitMQ.
//
//  The Initial Developer of the Original Code is GoPivotal, Inc.
//  Copyright (c) 2007-2014 GoPivotal, Inc.  All rights reserved.
//

package com.rabbitmq.tools;

import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.impl.AMQCommand;
import com.rabbitmq.client.impl.AMQContentHeader;
import com.rabbitmq.client.impl.AMQImpl;
import com.rabbitmq.client.impl.Frame;
import com.rabbitmq.utility.BlockingCell;
import com.rabbitmq.utility.Utility;

/**
 * AMQP Protocol Analyzer program. Listens on a port (in-port) and when a
 * connection arrives, makes an outbound connection to a host and
 * port (out-port). Relays frames from the in-port to the out-port.
 * Commands are decoded and printed to a supplied {@link Logger}.
 * 

* The stand-alone program ({@link #main(String[])}) prints to System.out, * using a private {@link AsyncLogger} instance. When the connection closes * the program listens for a subsequent connection and traces that to the same {@link Logger}. * This continues until the program is interrupted. *

* Options for controlling, for example, whether command bodies are decoded, * are obtained from System.properties, and are reported to the console * before starting the trace. *

* A {@link Tracer} object may be instantiated, using one of the constructors *

    *
  • Tracer(int listenPort, String id, String host, int port, Logger logger, Properties props)
  • *
  • Tracer(String id)
  • *
  • Tracer(String id, Properties props) *

    where the missing parameters default as follows: *

      *
    • listenPort defaults to 5673
    • *
    • host defaults to localhost
    • *
    • port defaults to 5672
    • *
    • logger defaults to new AsyncLogger(System.out)
    • and *
    • props defaults to System.getProperties()
    • *
    *
  • *
*

* These constructors block waiting for a connection to arrive on the listenPort. * Tracing does not begin until the tracer is {@link #start()}ed which {@link Logger#start()}s * the supplied logger and creates and starts a {@link Thread} for relaying and deconstructing the frames. *

* The properties specified in props are used at {@link Tracer#start()} time and may be modified * before this call. * @see Tracer.Logger * @see Tracer.AsyncLogger */ public class Tracer implements Runnable { private static final int DEFAULT_LISTEN_PORT = 5673; private static final String DEFAULT_CONNECT_HOST = "localhost"; private static final int DEFAULT_CONNECT_PORT = 5672; private static final String PROP_PREFIX = "com.rabbitmq.tools.Tracer."; private static boolean getBoolProperty(String propertyName, Properties props) { return Boolean.parseBoolean(props.getProperty(PROP_PREFIX + propertyName)); } private static void printBoolProperty(String propName, Properties props) { StringBuilder sb = new StringBuilder(100); System.out.println(sb.append(PROP_PREFIX).append(propName) .append(" = ").append(getBoolProperty(propName, props)).toString()); } public static void main(String[] args) { int listenPort = args.length > 0 ? Integer.parseInt(args[0]) : DEFAULT_LISTEN_PORT; String connectHost = args.length > 1 ? args[1] : DEFAULT_CONNECT_HOST; int connectPort = args.length > 2 ? Integer.parseInt(args[2]) : DEFAULT_CONNECT_PORT; System.out.println("Usage: Tracer [ [ []]]"); System.out.println(" Serially traces connections on the , logging\n" + " frames received and passing them to the connect host and port."); System.out.println("Invoked as: Tracer " + listenPort + " " + connectHost + " " + connectPort); Properties props = System.getProperties(); printBoolProperty("WITHHOLD_INBOUND_HEARTBEATS", props); printBoolProperty("WITHHOLD_OUTBOUND_HEARTBEATS", props); printBoolProperty("NO_ASSEMBLE_FRAMES", props); printBoolProperty("NO_DECODE_FRAMES", props); printBoolProperty("SUPPRESS_COMMAND_BODIES", props); Logger logger = new AsyncLogger(System.out); // initially stopped try { ServerSocket server = new ServerSocket(listenPort); int counter = 0; while (true) { Socket conn = server.accept(); new Tracer( conn , "Tracer-" + (counter++) , connectHost, connectPort , logger ).start(); } } catch (Exception e) { logger.stop(); // will stop shared logger thread e.printStackTrace(); System.exit(1); } } private final Properties props; private final Socket inSock; private final Socket outSock; private final String idLabel; private final DataInputStream iis; private final DataOutputStream ios; private final DataInputStream ois; private final DataOutputStream oos; private final Logger logger; private final BlockingCell reportEnd; private final AtomicBoolean started; private Tracer(Socket sock, String id, String host, int port, Logger logger, BlockingCell reportEnd, Properties props) throws IOException { this.props = props; this.inSock = sock; this.outSock = new Socket(host, port); this.idLabel = ": <" + id + "> "; this.iis = new DataInputStream(this.inSock.getInputStream()); this.ios = new DataOutputStream(this.inSock.getOutputStream()); this.ois = new DataInputStream(this.outSock.getInputStream()); this.oos = new DataOutputStream(this.outSock.getOutputStream()); this.logger = logger; this.reportEnd = reportEnd; this.started = new AtomicBoolean(false); } private Tracer(Socket sock, String id, String host, int port, Logger logger) throws IOException { this(sock, id, host, port, logger, new BlockingCell(), System.getProperties()); } private Tracer(int listenPort, String id, String host, int port, Logger logger, BlockingCell reportEnd, Properties props) throws IOException { this(new ServerSocket(listenPort).accept(), id, host, port, logger, reportEnd, props); } public Tracer(int listenPort, String id, String host, int port, Logger logger, Properties props) throws IOException { this(listenPort, id, host, port, logger, new BlockingCell(), props); } public Tracer(String id) throws IOException { this(DEFAULT_LISTEN_PORT, id, DEFAULT_CONNECT_HOST, DEFAULT_CONNECT_PORT, new AsyncLogger(System.out), new BlockingCell(), System.getProperties()); } public Tracer(String id, Properties props) throws IOException { this(DEFAULT_LISTEN_PORT, id, DEFAULT_CONNECT_HOST, DEFAULT_CONNECT_PORT, new AsyncLogger(System.out), new BlockingCell(), props); } public void start() { if (this.started.compareAndSet(false, true)) { this.logger.start(); new Thread(this).start(); } } public void run() { try { byte[] handshake = new byte[8]; this.iis.readFully(handshake); this.oos.write(handshake); BlockingCell wio = new BlockingCell(); new Thread(new DirectionHandler(wio, true, this.iis, this.oos, this.props)) .start(); new Thread(new DirectionHandler(wio, false, this.ois, this.ios, this.props)) .start(); waitAndLogException(wio); // either stops => we stop } catch (Exception e) { reportAndLogNonNullException(e); } finally { try { this.inSock.close(); } catch (Exception e) { logException(e); } try { this.outSock.close(); } catch (Exception e) { logException(e); } this.reportEnd.setIfUnset(null); this.logger.stop(); } } private void waitAndLogException(BlockingCell bc) { reportAndLogNonNullException(bc.uninterruptibleGet()); } private void reportAndLogNonNullException(Exception e) { if (e!=null) { this.reportEnd.setIfUnset(e); logException(e); } } public void log(String message) { StringBuilder sb = new StringBuilder(); this.logger.log(sb.append(System.currentTimeMillis()) .append(this.idLabel) .append(message) .toString()); } public void logException(Exception e) { log("uncaught " + Utility.makeStackTrace(e)); } private class DirectionHandler implements Runnable { private final BlockingCell waitCell; private final boolean silentMode; private final boolean noDecodeFrames; private final boolean noAssembleFrames; private final boolean suppressCommandBodies; private final boolean writeHeartBeats; private final String directionIndicator; private final DataInputStream inStream; private final DataOutputStream outStream; private final Map commands; public DirectionHandler(BlockingCell waitCell, boolean inBound, DataInputStream inStream, DataOutputStream outStream, Properties props) { this.waitCell = waitCell; this.silentMode = getBoolProperty("SILENT_MODE", props); this.noDecodeFrames = getBoolProperty("NO_DECODE_FRAMES", props); this.noAssembleFrames = getBoolProperty("NO_ASSEMBLE_FRAMES", props); this.suppressCommandBodies = getBoolProperty("SUPPRESS_COMMAND_BODIES", props); this.writeHeartBeats = ( inBound && !getBoolProperty("WITHHOLD_INBOUND_HEARTBEATS", props)) || (!inBound && !getBoolProperty("WITHHOLD_OUTBOUND_HEARTBEATS", props)); this.directionIndicator = (inBound ? " -> " : " <- "); this.inStream = inStream; this.outStream = outStream; this.commands = new HashMap(); } private Frame readFrame() throws IOException { return Frame.readFrom(this.inStream); } private void report(int channel, Object object) { StringBuilder sb = new StringBuilder("ch#").append(channel) .append(this.directionIndicator).append(object); Tracer.this.log(sb.toString()); } private void reportFrame(Frame frame) throws IOException { switch (frame.type) { case AMQP.FRAME_METHOD: { report(frame.channel, AMQImpl.readMethodFrom(frame.getInputStream())); break; } case AMQP.FRAME_HEADER: { AMQContentHeader contentHeader = AMQImpl .readContentHeaderFrom(frame.getInputStream()); long remainingBodyBytes = contentHeader.getBodySize(); StringBuilder sb = new StringBuilder("Expected body size: ") .append(remainingBodyBytes).append("; ") .append(contentHeader); report(frame.channel, sb); break; } default: report(frame.channel, frame); } } private void doFrame() throws IOException { Frame frame = readFrame(); if (frame != null) { if (this.silentMode) { frame.writeTo(this.outStream); return; } if (frame.type == AMQP.FRAME_HEARTBEAT) { if (this.writeHeartBeats) { frame.writeTo(this.outStream); report(frame.channel, frame); } else { report(frame.channel, "(withheld) " + frame.toString()); } } else { frame.writeTo(this.outStream); if (this.noDecodeFrames) { report(frame.channel, frame); } else if (this.noAssembleFrames) { reportFrame(frame); } else { AMQCommand cmd = this.commands.get(frame.channel); if (cmd == null) { cmd = new AMQCommand(); this.commands.put(frame.channel, cmd); } if (cmd.handleFrame(frame)) { report(frame.channel, cmd.toString(this.suppressCommandBodies)); commands.remove(frame.channel); } } } } } public void run() { try { while (true) { doFrame(); } } catch (Exception e) { this.waitCell.setIfUnset(e); } finally { this.waitCell.setIfUnset(null); } } } /** * Logging strings to an outputStream. Logging may be started and stopped. */ public interface Logger { /** * Start logging, that is, printing log entries written using * {@link #log(String)}. Multiple successive starts are equivalent to a * single start. * * @return true if start actually started the logger; * false otherwise. */ boolean start(); /** * Stop logging, that is, stop printing log entries written using * {@link #log(String)}. Flush preceding writes. The logger can only be * stopped if started. Multiple successive stops are equivalent to a * single stop. * * @return true if stop actually stopped the logger; * false otherwise. */ boolean stop(); /** * Write msg to the log. This may block, and may block indefinitely if * the logger is stopped. * * @param msg */ void log(String msg); } /** * A {@link Tracer.Logger} designed to print {@link String}s to a designated {@link OutputStream} * on a private thread. *

{@link String}s are read from a private queue and printed to a buffered {@link PrintStream} * which is periodically flushed. *

* When instantiated the private queue is created (an in-memory {@link ArrayBlockingQueue} in this * implementation) and when {@link #start()}ed the private thread is created and started unless it is * already present. An {@link AsyncLogger} may be started many times, but only one thread is created. *

* When {@link #stop()}ed either the number of starts is decremented, or, if this count reaches zero, * a special element is queued which causes the private thread to end when encountered. *

* If the private thread is interrupted, the thread will also end, and the count set to zero, * This will cause subsequent {@link #stop()}s to be ignored, and the next {@link #start()} will create a new thread. *

* {@link #log(String)} never blocks unless the private queue is full; this may never un-block if the {@link Logger} is stopped. */ public static class AsyncLogger implements Logger { private static final int MIN_FLUSH_INTERVAL = 100; private static final int ONE_SECOND_INTERVAL = 1000; private static final int LOG_QUEUE_SIZE = 1024 * 1024; private static final int BUFFER_SIZE = 10 * 1024 * 1024; private final Runnable loggerRunnable; private final SafeCounter countStarted; private volatile Thread loggerThread = null; /** * Simple pair class for queue elements. * @param type of left item * @param type of right item */ private static class Pr { private final L left; private final R right; public L left() { return this.left; } public R right() { return this.right; } public Pr(L left, R right) { this.left=left; this.right=right; } } private enum LogCmd { STOP, PRINT } private final BlockingQueue > queue = new ArrayBlockingQueue >( LOG_QUEUE_SIZE, true); /** * Same as {@link #Tracer.AsyncLogger(OutputStream, int)} with a one-second flush interval. * @param os OutputStream to print to. */ public AsyncLogger(OutputStream os) { this(os, ONE_SECOND_INTERVAL); } /** * Start/stoppable logger that prints to an {@link OutputStream} with flushes every flushInterval milliseconds. * @param os OutputStream to print to. * @param flushInterval in milliseconds, time between flushes. */ public AsyncLogger(OutputStream os, int flushInterval) { if (flushInterval < MIN_FLUSH_INTERVAL) throw new IllegalArgumentException("Flush interval (" + flushInterval + "ms) must be positive and at least " + MIN_FLUSH_INTERVAL + "ms."); this.countStarted = new SafeCounter(); PrintStream printStream = new PrintStream(new BufferedOutputStream( os, BUFFER_SIZE), false); this.loggerRunnable = new AsyncLoggerRunnable(printStream, flushInterval, this.queue); } public void log(String message) { if (message != null) { try { this.queue.put(new Pr(message, LogCmd.PRINT)); } catch (InterruptedException ie) { throw new RuntimeException("Interrupted while logging.", ie); } } } public boolean start() { if (this.countStarted.testZeroAndIncrement()) { this.loggerThread = new Thread(this.loggerRunnable); this.loggerThread.start(); return true; } return false; // meaning already started } public boolean stop() { if (this.countStarted.decrementAndTestZero()) { if (this.loggerThread != null) { try { this.queue.put(new Pr(null, LogCmd.STOP)); } catch (InterruptedException ie) { this.loggerThread.interrupt(); //try harder throw new RuntimeException("Interrupted while stopping.", ie); } this.loggerThread = null; } return true; } return false; // meaning already stopped } private class AsyncLoggerRunnable implements Runnable { private final int flushInterval; private final PrintStream ps; private final BlockingQueue > queue; public AsyncLoggerRunnable(PrintStream ps, int flushInterval, BlockingQueue > queue) { this.flushInterval = flushInterval; this.ps = ps; this.queue = queue; } public void run() { try { long timeOfNextFlush = System.currentTimeMillis() + this.flushInterval; boolean printedSinceLastFlush = false; while (true) { long timeToNextFlush; while (0 >= (timeToNextFlush = timeOfNextFlush - System.currentTimeMillis())) { if (printedSinceLastFlush) { this.ps.flush(); printedSinceLastFlush = false; } timeOfNextFlush += this.flushInterval; } Pr item = this.queue.poll(timeToNextFlush, TimeUnit.MILLISECONDS); if (item != null) { if (item.left() != null) { this.ps.println(item.left()); printedSinceLastFlush = true; } if (item.right() == LogCmd.STOP) break; } } drainCurrentQueue(); this.ps.println("Stopped."); this.ps.flush(); } catch (InterruptedException ie) { AsyncLogger.this.countStarted.reset(); drainCurrentQueue(); this.ps.println("Interrupted."); this.ps.flush(); } } private void drainCurrentQueue() { int currentSize = this.queue.size(); while (currentSize-- > 0) { Pr item = this.queue.poll(); if (item != null && item.left() != null) this.ps.println(item.left()); } } } } private static class SafeCounter { private final Object countMonitor = new Object(); private int count; public SafeCounter() { this.count = 0; } public boolean testZeroAndIncrement() { synchronized (this.countMonitor) { int val = this.count; this.count++; return (val == 0); } } public boolean decrementAndTestZero() { synchronized (this.countMonitor) { if (this.count == 0) return false; --this.count; return (0 == this.count); } } public void reset() { synchronized (this.countMonitor) { this.count = 0; } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy