You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by ti...@apache.org on 2020/08/25 17:26:25 UTC

[maven-surefire] 01/01: 300 - 400 nanos per decoding std-out (100 chars/line)

This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch base64
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit 390cf0b294bf4a94fe2fb1639d7c643098e56bfa
Author: tibordigana <ti...@apache.org>
AuthorDate: Sat Aug 1 23:09:34 2020 +0200

    300 - 400 nanos per decoding std-out (100 chars/line)
---
 .../surefire/extensions/EventConsumerThread.java   | 805 +++++++++++++++------
 .../extensions/EventConsumerThreadTest.java        | 492 +++++++++++++
 .../api/booter/ForkedProcessEventType.java         | 230 +++++-
 3 files changed, 1300 insertions(+), 227 deletions(-)

diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
index 1d5686b..b74cb5e 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
@@ -44,28 +44,37 @@ import org.apache.maven.surefire.api.event.TestStartingEvent;
 import org.apache.maven.surefire.api.event.TestSucceededEvent;
 import org.apache.maven.surefire.api.event.TestsetCompletedEvent;
 import org.apache.maven.surefire.api.event.TestsetStartingEvent;
+import org.apache.maven.surefire.api.report.RunMode;
+import org.apache.maven.surefire.api.report.StackTraceWriter;
+import org.apache.maven.surefire.api.report.TestSetReportEntry;
 import org.apache.maven.surefire.extensions.CloseableDaemonThread;
 import org.apache.maven.surefire.extensions.EventHandler;
 import org.apache.maven.surefire.extensions.ForkNodeArguments;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
-import org.apache.maven.surefire.api.report.RunMode;
-import org.apache.maven.surefire.api.report.StackTraceWriter;
-import org.apache.maven.surefire.api.report.TestSetReportEntry;
-import org.apache.maven.surefire.shared.codec.binary.Base64;
 
 import javax.annotation.Nonnull;
 import java.io.File;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
 import java.nio.channels.ReadableByteChannel;
 import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
-import java.util.Iterator;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
+import static java.lang.Math.max;
+import static java.nio.charset.CodingErrorAction.REPLACE;
 import static java.nio.charset.StandardCharsets.US_ASCII;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.MAGIC_NUMBER;
+import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.StreamReadStatus.EOF;
+import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.StreamReadStatus.OVERFLOW;
+import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.StreamReadStatus.UNDERFLOW;
+import static org.apache.maven.surefire.api.booter.Constants.MAGIC_NUMBER;
+import static org.apache.maven.surefire.api.booter.Constants.STREAM_ENCODING;
 import static org.apache.maven.surefire.api.report.CategorizedReportEntry.reportEntry;
 import static org.apache.maven.surefire.api.report.RunMode.MODES;
 
@@ -83,7 +92,10 @@ public class EventConsumerThread extends CloseableDaemonThread
             "java.lang.module.findexception" // JPMS errors
         };
     private static final String PRINTABLE_JVM_NATIVE_STREAM = "Listening for transport dt_socket at address:";
-    private static final Base64 BASE64 = new Base64();
+    private static final byte[] MAGIC_NUMBER_BYTES = MAGIC_NUMBER.getBytes( US_ASCII );
+    private static final int DELIMINATOR_LENGTH = 1;
+    private static final int BYTE_LENGTH = 1;
+    private static final int INT_LENGTH = 4;
 
     private final ReadableByteChannel channel;
     private final EventHandler<Event> eventHandler;
@@ -133,153 +145,307 @@ public class EventConsumerThread extends CloseableDaemonThread
     @SuppressWarnings( "checkstyle:innerassignment" )
     private void decode() throws IOException
     {
-        List<String> tokens = new ArrayList<>();
-        StringBuilder line = new StringBuilder();
-        StringBuilder token = new StringBuilder( MAGIC_NUMBER.length() );
-        ByteBuffer buffer = ByteBuffer.allocate( 1024 );
-        buffer.position( buffer.limit() );
-        boolean streamContinues;
+        Map<Segment, ForkedProcessEventType> events = mapEventTypes();
+        Memento memento = new Memento();
+        BufferedStream line = new BufferedStream( 32 );
+        memento.bb.limit( 0 );
+        boolean streamContinues = true;
 
         start:
         do
         {
-            line.setLength( 0 );
-            tokens.clear();
-            token.setLength( 0 );
-            FrameCompletion completion = null;
-            for ( boolean frameStarted = false; streamContinues = read( buffer ); completion = null )
+            if ( !streamContinues )
             {
-                char c = (char) buffer.get();
+                printExistingLine( line );
+                return;
+            }
 
-                if ( c == '\n' || c == '\r' )
-                {
-                    printExistingLine( line );
-                    continue start;
-                }
+            line.reset();
+            memento.segment.reset();
 
-                line.append( c );
+            int readCount =
+                DELIMINATOR_LENGTH + MAGIC_NUMBER_BYTES.length + DELIMINATOR_LENGTH + BYTE_LENGTH + DELIMINATOR_LENGTH;
+            streamContinues = read( memento.bb, readCount ) != EOF;
+            if ( memento.bb.remaining() < readCount )
+            {
+                //todo throw exception - broken stream
+            }
+            checkHeader( memento.bb, memento.segment );
 
-                if ( !frameStarted )
+            memento.eventType = events.get( readSegment( memento.bb ) );
+
+            for ( SegmentType segmentType : nextSegmentType( memento ) )
+            {
+                if ( segmentType == null )
                 {
-                    if ( c == ':' )
-                    {
-                        frameStarted = true;
-                        token.setLength( 0 );
-                        tokens.clear();
-                    }
+                    break;
                 }
-                else
+
+                switch ( segmentType )
                 {
-                    if ( c == ':' )
-                    {
-                        tokens.add( token.toString() );
-                        token.setLength( 0 );
-                        completion = frameCompleteness( tokens );
-                        if ( completion == FrameCompletion.COMPLETE )
+                    case RUN_MODE:
+                        memento.runMode = MODES.get( readSegment( memento ) );
+                        break;
+                    case STRING_ENCODING:
+                        //todo handle exceptions
+                        memento.charset = Charset.forName( readSegment( memento ) );
+                        break;
+                    case BYTES_INT_COUNTER:
+                        memento.bytesCounter = readInt( memento.bb );
+                        break;
+                    case DATA_STRING:
+                        memento.cb.clear();
+                        int bytesCounter = memento.bytesCounter;
+                        if ( bytesCounter == 0 )
+                        {
+                            memento.data.add( "" );
+                        }
+                        else if ( bytesCounter == 1 )
                         {
-                            line.setLength( 0 );
-                            break;
+                            // handle the returned boolean
+                            read( memento.bb, 1 );
+                            byte oneChar = memento.bb.get();
+                            memento.data.add( oneChar == 0 ? null : String.valueOf( (char) oneChar ) );
                         }
-                        else if ( completion == FrameCompletion.MALFORMED )
+                        else
                         {
-                            printExistingLine( line );
-                            continue start;
+                            memento.data.add( readString( memento ) );
                         }
-                    }
-                    else
-                    {
-                        token.append( c );
-                    }
+                        memento.bytesCounter = 0;
+                        break;
+                    case DATA_INT:
+                        memento.data.add( readInt( memento.bb ) );
+                        break;
+                    case END_OF_FRAME:
+                        eventHandler.handleEvent( toEvent( memento ) );
+                        continue start;
+                    default:
+                        throw new IllegalArgumentException( "Unknown enum " + segmentType );
                 }
-            }
 
-            if ( completion == FrameCompletion.COMPLETE )
-            {
-                Event event = toEvent( tokens );
-                if ( !disabled && event != null )
+                memento.cb.clear();
+
+                read( memento.bb, 1 );
+                b = 0xff & memento.bb.get();
+                if ( Character.isDefined( b ) && b != ':' )
                 {
-                    eventHandler.handleEvent( event );
+                    //memento.segmentCompletion = SegmentCompletion.;
+                    //MalformedStreamException
+                    continue start;
                 }
             }
 
-            if ( !streamContinues )
+            memento.bb.flip();
+            line.write( memento.bb );
+            memento.reset();
+        }
+        while ( true );
+    }
+
+    @Nonnull
+    private static Segment readSegment( ByteBuffer bb )
+    {
+        int readCount = bb.get() & 0xff;
+        Segment segment = new Segment( bb.array(), bb.arrayOffset() + bb.position(), readCount );
+        bb.position( bb.position() + readCount );
+        checkDelimiter( bb );
+        return segment;
+    }
+
+    private static void checkHeader( ByteBuffer bb, BufferedStream segment )
+    {
+        checkDelimiter( bb );
+
+        segment.reset();
+
+        int shift = 0;
+        for ( int start = bb.arrayOffset() + bb.position(), length = MAGIC_NUMBER_BYTES.length;
+              shift < length; shift++ )
+        {
+            if ( bb.array()[shift + start] != MAGIC_NUMBER_BYTES[shift] )
             {
-                printExistingLine( line );
-                return;
+                //todo throw exception - broken stream
             }
         }
-        while ( true );
+        bb.position( bb.position() + shift );
+
+        checkDelimiter( bb );
     }
 
-    private boolean read( ByteBuffer buffer ) throws IOException
+    private static void checkDelimiter( ByteBuffer bb )
     {
-        if ( buffer.hasRemaining() && buffer.position() > 0 )
+        if ( ( 0xff & bb.get() ) != ':' )
         {
-            return true;
+            //todo throw exception - broken stream
         }
-        else
+    }
+
+    private static SegmentType[] nextSegmentType( Memento memento )
+    {
+        switch ( memento.eventType )
         {
-            buffer.clear();
-            boolean isEndOfStream = channel.read( buffer ) == -1;
-            buffer.flip();
-            return !isEndOfStream;
+            case BOOTERCODE_BYE:
+            case BOOTERCODE_STOP_ON_NEXT_TEST:
+            case BOOTERCODE_NEXT_TEST:
+                return new SegmentType[] {SegmentType.END_OF_FRAME};
+            case BOOTERCODE_CONSOLE_ERROR:
+            case BOOTERCODE_JVM_EXIT_ERROR:
+                return new SegmentType[] {
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.STRING_ENCODING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.END_OF_FRAME
+                };
+            case BOOTERCODE_CONSOLE_INFO:
+            case BOOTERCODE_CONSOLE_DEBUG:
+            case BOOTERCODE_CONSOLE_WARNING:
+                return new SegmentType[] {
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.STRING_ENCODING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.END_OF_FRAME
+                };
+            case BOOTERCODE_STDOUT:
+            case BOOTERCODE_STDOUT_NEW_LINE:
+            case BOOTERCODE_STDERR:
+            case BOOTERCODE_STDERR_NEW_LINE:
+                return new SegmentType[] {
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.RUN_MODE,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.STRING_ENCODING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.END_OF_FRAME
+                };
+            case BOOTERCODE_SYSPROPS:
+                return new SegmentType[] {
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.RUN_MODE,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.STRING_ENCODING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.END_OF_FRAME
+                };
+            case BOOTERCODE_TESTSET_STARTING:
+            case BOOTERCODE_TESTSET_COMPLETED:
+            case BOOTERCODE_TEST_STARTING:
+            case BOOTERCODE_TEST_SUCCEEDED:
+            case BOOTERCODE_TEST_FAILED:
+            case BOOTERCODE_TEST_SKIPPED:
+            case BOOTERCODE_TEST_ERROR:
+            case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
+                return new SegmentType[] {
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.RUN_MODE,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.STRING_ENCODING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.DATA_INT,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.BYTES_INT_COUNTER,
+                    SegmentType.DATA_STRING,
+                    SegmentType.END_OF_FRAME
+                };
+            default:
+                throw new IllegalArgumentException( "Unknown enum " + memento.eventType );
         }
     }
 
-    private void printExistingLine( StringBuilder line )
+    private StreamReadStatus read( ByteBuffer buffer, int recommendedCount ) throws IOException
     {
-        if ( line.length() != 0 )
+        if ( buffer.remaining() >= recommendedCount && buffer.position() != 0 )
+        {
+            return OVERFLOW;
+        }
+        else
         {
-            ConsoleLogger logger = arguments.getConsoleLogger();
-            String s = line.toString().trim();
-            if ( s.contains( PRINTABLE_JVM_NATIVE_STREAM ) )
+            if ( buffer.position() != 0 && recommendedCount > buffer.capacity() - buffer.position() )
             {
-                if ( logger.isDebugEnabled() )
-                {
-                    logger.debug( s );
-                }
-                else if ( logger.isInfoEnabled() )
-                {
-                    logger.info( s );
-                }
-                else
-                {
-                    // In case of debugging forked JVM, see PRINTABLE_JVM_NATIVE_STREAM.
-                    System.out.println( s );
-                }
+                buffer.compact().flip();
             }
-            else
+            int mark = buffer.position();
+            buffer.position( buffer.limit() );
+            buffer.limit( buffer.capacity() );
+            boolean isEnd = false;
+            while ( !isEnd && buffer.position() - mark < recommendedCount && buffer.position() != buffer.limit() )
             {
-                if ( isJvmError( s ) )
-                {
-                    logger.error( s );
-                }
-                String msg = "Corrupted STDOUT by directly writing to native stream in forked JVM "
-                    + arguments.getForkChannelId() + ".";
-                File dumpFile = arguments.dumpStreamText( msg + " Stream '" + s + "'." );
-                arguments.logWarningAtEnd( msg + " See FAQ web page and the dump file " + dumpFile.getAbsolutePath() );
-
-                if ( logger.isDebugEnabled() )
-                {
-                    logger.debug( s );
-                }
+                isEnd = channel.read( buffer ) == -1;
             }
+            buffer.limit( buffer.position() );
+            buffer.position( mark );
+            return isEnd ? EOF : ( buffer.remaining() >= recommendedCount ? OVERFLOW : UNDERFLOW );
         }
     }
 
-    private Event toEvent( List<String> tokensInFrame )
+    private void printExistingLine( BufferedStream line )
     {
-        Iterator<String> tokens = tokensInFrame.iterator();
-        String header = tokens.next();
-        assert header != null;
-
-        ForkedProcessEventType event = ForkedProcessEventType.byOpcode( tokens.next() );
-
-        if ( event == null )
+        if ( line.isEmpty() )
+        {
+            return;
+        }
+        ConsoleLogger logger = arguments.getConsoleLogger();
+        String s = line.toString( STREAM_ENCODING ).trim();
+        if ( s.contains( PRINTABLE_JVM_NATIVE_STREAM ) )
+        {
+            if ( logger.isDebugEnabled() )
+            {
+                logger.debug( s );
+            }
+            else if ( logger.isInfoEnabled() )
+            {
+                logger.info( s );
+            }
+            else
+            {
+                // In case of debugging forked JVM, see PRINTABLE_JVM_NATIVE_STREAM.
+                System.out.println( s );
+            }
+        }
+        else
         {
-            return null;
+            if ( isJvmError( s ) )
+            {
+                logger.error( s );
+            }
+            String msg = "Corrupted STDOUT by directly writing to native stream in forked JVM "
+                + arguments.getForkChannelId() + ".";
+            File dumpFile = arguments.dumpStreamText( msg + " Stream '" + s + "'." );
+            arguments.logWarningAtEnd( msg + " See FAQ web page and the dump file " + dumpFile.getAbsolutePath() );
+
+            if ( logger.isDebugEnabled() )
+            {
+                logger.debug( s );
+            }
         }
+    }
 
+    private Event toEvent( Memento memento )
+    {
+        ForkedProcessEventType event = memento.eventType;
         if ( event.isControlCategory() )
         {
             switch ( event )
@@ -296,16 +462,17 @@ public class EventConsumerThread extends CloseableDaemonThread
         }
         else if ( event.isConsoleErrorCategory() || event.isJvmExitError() )
         {
-            Charset encoding = Charset.forName( tokens.next() );
-            StackTraceWriter stackTraceWriter = decodeTrace( encoding, tokens.next(), tokens.next(), tokens.next() );
+            String traceMessage = (String) memento.data.get( 0 );
+            String smartTrimmedStackTrace = (String) memento.data.get( 1 );
+            String stackTrace = (String) memento.data.get( 2 );
+            StackTraceWriter stackTraceWriter = newTrace( traceMessage, smartTrimmedStackTrace, stackTrace );
             return event.isConsoleErrorCategory()
                 ? new ConsoleErrorEvent( stackTraceWriter )
                 : new JvmExitErrorEvent( stackTraceWriter );
         }
         else if ( event.isConsoleCategory() )
         {
-            Charset encoding = Charset.forName( tokens.next() );
-            String msg = decode( tokens.next(), encoding );
+            String msg = (String) memento.data.get( 0 );
             switch ( event )
             {
                 case BOOTERCODE_CONSOLE_INFO:
@@ -320,57 +487,62 @@ public class EventConsumerThread extends CloseableDaemonThread
         }
         else if ( event.isStandardStreamCategory() )
         {
-            RunMode mode = MODES.get( tokens.next() );
-            Charset encoding = Charset.forName( tokens.next() );
-            String output = decode( tokens.next(), encoding );
+            String output = (String) memento.data.get( 0 );
             switch ( event )
             {
                 case BOOTERCODE_STDOUT:
-                    return new StandardStreamOutEvent( mode, output );
+                    return new StandardStreamOutEvent( memento.runMode, output );
                 case BOOTERCODE_STDOUT_NEW_LINE:
-                    return new StandardStreamOutWithNewLineEvent( mode, output );
+                    return new StandardStreamOutWithNewLineEvent( memento.runMode, output );
                 case BOOTERCODE_STDERR:
-                    return new StandardStreamErrEvent( mode, output );
+                    return new StandardStreamErrEvent( memento.runMode, output );
                 case BOOTERCODE_STDERR_NEW_LINE:
-                    return new StandardStreamErrWithNewLineEvent( mode, output );
+                    return new StandardStreamErrWithNewLineEvent( memento.runMode, output );
                 default:
                     throw new IllegalStateException( "Unknown enum " + event );
             }
         }
         else if ( event.isSysPropCategory() )
         {
-            RunMode mode = MODES.get( tokens.next() );
-            Charset encoding = Charset.forName( tokens.next() );
-            String key = decode( tokens.next(), encoding );
-            String value = decode( tokens.next(), encoding );
-            return new SystemPropertyEvent( mode, key, value );
+            String key = (String) memento.data.get( 0 );
+            String value = (String) memento.data.get( 1 );
+            return new SystemPropertyEvent( memento.runMode, key, value );
         }
         else if ( event.isTestCategory() )
         {
-            RunMode mode = MODES.get( tokens.next() );
-            Charset encoding = Charset.forName( tokens.next() );
-            TestSetReportEntry reportEntry =
-                decodeReportEntry( encoding, tokens.next(), tokens.next(), tokens.next(), tokens.next(),
-                    tokens.next(), tokens.next(), tokens.next(), tokens.next(), tokens.next(), tokens.next() );
+            // ReportEntry:
+            String source = (String) memento.data.get( 0 );
+            String sourceText = (String) memento.data.get( 1 );
+            String name = (String) memento.data.get( 2 );
+            String nameText = (String) memento.data.get( 3 );
+            String group = (String) memento.data.get( 4 );
+            String message = (String) memento.data.get( 5 );
+            Integer timeElapsed = (Integer) memento.data.get( 6 );
+            // StackTraceWriter:
+            String traceMessage = (String) memento.data.get( 7 );
+            String smartTrimmedStackTrace = (String) memento.data.get( 8 );
+            String stackTrace = (String) memento.data.get( 9 );
+            TestSetReportEntry reportEntry = newReportEntry( source, sourceText, name, nameText, group, message,
+                timeElapsed, traceMessage, smartTrimmedStackTrace, stackTrace );
 
             switch ( event )
             {
                 case BOOTERCODE_TESTSET_STARTING:
-                    return new TestsetStartingEvent( mode, reportEntry );
+                    return new TestsetStartingEvent( memento.runMode, reportEntry );
                 case BOOTERCODE_TESTSET_COMPLETED:
-                    return new TestsetCompletedEvent( mode, reportEntry );
+                    return new TestsetCompletedEvent( memento.runMode, reportEntry );
                 case BOOTERCODE_TEST_STARTING:
-                    return new TestStartingEvent( mode, reportEntry );
+                    return new TestStartingEvent( memento.runMode, reportEntry );
                 case BOOTERCODE_TEST_SUCCEEDED:
-                    return new TestSucceededEvent( mode, reportEntry );
+                    return new TestSucceededEvent( memento.runMode, reportEntry );
                 case BOOTERCODE_TEST_FAILED:
-                    return new TestFailedEvent( mode, reportEntry );
+                    return new TestFailedEvent( memento.runMode, reportEntry );
                 case BOOTERCODE_TEST_SKIPPED:
-                    return new TestSkippedEvent( mode, reportEntry );
+                    return new TestSkippedEvent( memento.runMode, reportEntry );
                 case BOOTERCODE_TEST_ERROR:
-                    return new TestErrorEvent( mode, reportEntry );
+                    return new TestErrorEvent( memento.runMode, reportEntry );
                 case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
-                    return new TestAssumptionFailureEvent( mode, reportEntry );
+                    return new TestAssumptionFailureEvent( memento.runMode, reportEntry );
                 default:
                     throw new IllegalStateException( "Unknown enum " + event );
             }
@@ -379,116 +551,145 @@ public class EventConsumerThread extends CloseableDaemonThread
         throw new IllegalStateException( "Missing a branch for the event type " + event );
     }
 
-    private static FrameCompletion frameCompleteness( List<String> tokens )
+    private static StackTraceWriter newTrace( String traceMessage, String smartTrimmedStackTrace, String stackTrace )
     {
-        if ( !tokens.isEmpty() && !MAGIC_NUMBER.equals( tokens.get( 0 ) ) )
-        {
-            return FrameCompletion.MALFORMED;
-        }
+        boolean exists = traceMessage != null || stackTrace != null || smartTrimmedStackTrace != null;
+        return exists ? new DeserializedStacktraceWriter( traceMessage, smartTrimmedStackTrace, stackTrace ) : null;
+    }
+
+    static TestSetReportEntry newReportEntry( // ReportEntry:
+                                              String source, String sourceText, String name,
+                                              String nameText, String group, String message,
+                                              Integer timeElapsed,
+                                              // StackTraceWriter:
+                                              String traceMessage,
+                                              String smartTrimmedStackTrace, String stackTrace )
+        throws NumberFormatException
+    {
+        StackTraceWriter stackTraceWriter = newTrace( traceMessage, smartTrimmedStackTrace, stackTrace );
+        return reportEntry( source, sourceText, name, nameText, group, stackTraceWriter, timeElapsed, message,
+            Collections.<String, String>emptyMap() );
+    }
 
-        if ( tokens.size() >= 2 )
+    private static boolean isJvmError( String line )
+    {
+        String lineLower = line.toLowerCase();
+        for ( String errorPattern : JVM_ERROR_PATTERNS )
         {
-            String opcode = tokens.get( 1 );
-            ForkedProcessEventType event = ForkedProcessEventType.byOpcode( opcode );
-            if ( event == null )
-            {
-                return FrameCompletion.MALFORMED;
-            }
-            else if ( event.isControlCategory() )
-            {
-                return FrameCompletion.COMPLETE;
-            }
-            else if ( event.isConsoleErrorCategory() )
-            {
-                return tokens.size() == 6 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
-            }
-            else if ( event.isConsoleCategory() )
-            {
-                return tokens.size() == 4 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
-            }
-            else if ( event.isStandardStreamCategory() )
-            {
-                return tokens.size() == 5 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
-            }
-            else if ( event.isSysPropCategory() )
-            {
-                return tokens.size() == 6 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
-            }
-            else if ( event.isTestCategory() )
-            {
-                return tokens.size() == 14 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
-            }
-            else if ( event.isJvmExitError() )
+            if ( lineLower.contains( errorPattern ) )
             {
-                return tokens.size() == 6 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
+                return true;
             }
         }
-        return FrameCompletion.NOT_COMPLETE;
+        return false;
     }
 
-    static String decode( String line, Charset encoding )
+    private String readSegment( Memento memento ) throws IOException
     {
-        // ForkedChannelEncoder is encoding the stream with US_ASCII
-        return line == null || "-".equals( line )
-            ? null
-            : new String( BASE64.decode( line.getBytes( US_ASCII ) ), encoding );
+        int startsWithPosition = memento.bb.position();
+        boolean streamContinues;
+
+        do
+        {
+            streamContinues = read( memento.bb, 1 ) != EOF;
+        }
+        while ( ( 0xff & memento.bb.get() ) != ':' );
+
+        int endsWithLimit = memento.bb.limit();
+        memento.bb.limit( memento.bb.position() - 1 );
+        memento.bb.position( startsWithPosition );
+
+        memento.cb.clear();
+
+        memento.decoder
+            .reset()
+            .decode( memento.bb, memento.cb, true );
+
+        memento.bb.position( memento.bb.position() + 1 );
+        memento.bb.limit( endsWithLimit );
+
+        String segment = memento.cb.flip().toString();
+        memento.cb.clear();
+        return segment;
     }
 
-    private static StackTraceWriter decodeTrace( Charset encoding, String encTraceMessage,
-                                                 String encSmartTrimmedStackTrace, String encStackTrace )
+    String readString( Memento memento ) throws IOException
     {
-        String traceMessage = decode( encTraceMessage, encoding );
-        String stackTrace = decode( encStackTrace, encoding );
-        String smartTrimmedStackTrace = decode( encSmartTrimmedStackTrace, encoding );
-        boolean exists = traceMessage != null || stackTrace != null || smartTrimmedStackTrace != null;
-        return exists ? new DeserializedStacktraceWriter( traceMessage, smartTrimmedStackTrace, stackTrace ) : null;
+        final CharBuffer chars = memento.cb;
+        final ByteBuffer buffer = memento.bb;
+        final int totalBytes = memento.bytesCounter;
+
+        int countDecodedBytes = 0;
+        int countReadBytes = 0;
+
+        buffer.clear();
+
+        int positionChars = chars.position();
+        int startPosition;
+        List<String> strings = new ArrayList<>();
+        do
+        {
+            startPosition = buffer.position();
+            buffer.limit( startPosition );
+            read( buffer, totalBytes - countReadBytes );
+            memento.decoder
+                .reset()
+                .decode( buffer, chars, countDecodedBytes + buffer.remaining() >= totalBytes );
+            final boolean hasDecodedNewBytes = chars.position() != positionChars;
+            if ( hasDecodedNewBytes )
+            {
+                countDecodedBytes += buffer.position() - startPosition;
+                positionChars = chars.position();
+            }
+            countReadBytes += buffer.limit() - startPosition;
+            if ( buffer.hasRemaining() )
+            {
+                buffer.compact();
+            }
+            strings.add( chars.flip().toString() );
+            chars.clear();
+        }
+        while ( countReadBytes < totalBytes );
+
+        return toString( strings );
     }
 
-    static TestSetReportEntry decodeReportEntry( Charset encoding,
-                                                 // ReportEntry:
-                                                 String encSource, String encSourceText, String encName,
-                                                 String encNameText, String encGroup, String encMessage,
-                                                 String encTimeElapsed,
-                                                 // StackTraceWriter:
-                                                 String encTraceMessage,
-                                                 String encSmartTrimmedStackTrace, String encStackTrace )
-        throws NumberFormatException
+    int readInt( ByteBuffer bb ) throws IOException
     {
-        if ( encoding == null )
-        {
-            // corrupted or incomplete stream
-            return null;
-        }
-
-        String source = decode( encSource, encoding );
-        String sourceText = decode( encSourceText, encoding );
-        String name = decode( encName, encoding );
-        String nameText = decode( encNameText, encoding );
-        String group = decode( encGroup, encoding );
-        StackTraceWriter stackTraceWriter =
-            decodeTrace( encoding, encTraceMessage, encSmartTrimmedStackTrace, encStackTrace );
-        Integer elapsed = decodeToInteger( encTimeElapsed );
-        String message = decode( encMessage, encoding );
-        return reportEntry( source, sourceText, name, nameText,
-            group, stackTraceWriter, elapsed, message, Collections.<String, String>emptyMap() );
+        read( bb, 4 );
+        return bb.getInt();
     }
 
-    static Integer decodeToInteger( String line )
+    private static String toString( List<String> strings )
     {
-        return line == null || "-".equals( line ) ? null : Integer.decode( line );
+        if ( strings.size() == 1 )
+        {
+            return strings.get( 0 );
+        }
+        StringBuilder concatenated = new StringBuilder();
+        for ( String s : strings )
+        {
+            concatenated.append( s );
+        }
+        return concatenated.toString();
     }
 
-    private static boolean isJvmError( String line )
+    static Map<Segment, ForkedProcessEventType> mapEventTypes()
     {
-        String lineLower = line.toLowerCase();
-        for ( String errorPattern : JVM_ERROR_PATTERNS )
+        Map<Segment, ForkedProcessEventType> map = new HashMap<>();
+        for (ForkedProcessEventType e : ForkedProcessEventType.values() )
         {
-            if ( lineLower.contains( errorPattern ) )
-            {
-                return true;
-            }
+            byte[] array = e.getOpcode().getBytes();
+            map.put( new Segment( array, 0, array.length ), e );
         }
-        return false;
+        return map;
+    }
+
+    enum StreamReadStatus
+    {
+        UNDERFLOW,
+        OVERFLOW,
+        EOF
     }
 
     /**
@@ -500,4 +701,160 @@ public class EventConsumerThread extends CloseableDaemonThread
         COMPLETE,
         MALFORMED
     }
+
+    private enum SegmentType
+    {
+        RUN_MODE,
+        STRING_ENCODING,
+        BYTES_INT_COUNTER,
+        DATA_STRING,
+        DATA_INT,
+        END_OF_FRAME
+    }
+
+    /**
+     * This class avoids locking which gains the performance of this decoder.
+     */
+    private static class BufferedStream
+    {
+        private byte[] buffer;
+        private int count;
+
+        public BufferedStream( int capacity )
+        {
+            this.buffer = new byte[capacity];
+        }
+
+        void write( int b )
+        {
+            ensureCapacity( 1 );
+            buffer[count++] = (byte) b;
+        }
+
+        void write( ByteBuffer bb )
+        {
+            int size = bb.remaining();
+            if ( size > 0 )
+            {
+                ensureCapacity( size );
+                byte[] b = bb.array();
+                int pos = bb.arrayOffset() + bb.position();
+                System.arraycopy( b, pos, buffer, count, size );
+                count+= size;
+                bb.position( bb.position() + size );
+            }
+        }
+
+        boolean isEmpty()
+        {
+            return count != 0;
+        }
+
+        void reset()
+        {
+            count = 0;
+        }
+
+        String toString( Charset charset )
+        {
+            return new String( buffer, 0, count, charset );
+        }
+
+        private void ensureCapacity( int addCapacity )
+        {
+            int oldCapacity = buffer.length;
+            int exactCapacity = count + addCapacity;
+            if ( exactCapacity < 0 )
+            {
+                throw new OutOfMemoryError();
+            }
+
+            if ( oldCapacity < exactCapacity )
+            {
+                int newCapacity = oldCapacity << 1;
+                buffer = Arrays.copyOf( buffer, max( newCapacity, exactCapacity ) );
+            }
+        }
+    }
+
+    static class Memento
+    {
+        final CharsetDecoder decoder;
+        final BufferedStream segment = new BufferedStream( MAGIC_NUMBER.length() );
+        final List<Object> data = new ArrayList<>();
+        final CharBuffer cb = CharBuffer.allocate( 1024 );
+        final ByteBuffer bb = ByteBuffer.allocate( 1024 );
+        FrameCompletion frameCompletion;
+        ForkedProcessEventType eventType;
+        RunMode runMode;
+        Charset charset;
+        int bytesCounter;
+
+        Memento()
+        {
+            decoder = STREAM_ENCODING.newDecoder()
+                .onMalformedInput( REPLACE )
+                .onUnmappableCharacter( REPLACE );
+        }
+
+        void reset()
+        {
+            segment.reset();
+            frameCompletion = null;
+        }
+    }
+
+    static class Segment
+    {
+        private final byte[] array;
+        private final int fromIndex;
+        private final int length;
+        private final int hashCode;
+
+        public Segment( byte[] array, int fromIndex, int length )
+        {
+            this.array = array;
+            this.fromIndex = fromIndex;
+            this.length = length;
+
+            int hashCode = 0;
+            int i = fromIndex;
+            for (int loops = length >> 1; loops-- != 0; )
+            {
+                hashCode = 31 * hashCode + array[i++];
+                hashCode = 31 * hashCode + array[i++];
+            }
+            this.hashCode = i == fromIndex + length ? hashCode : 31 * hashCode + array[i];
+        }
+
+        @Override
+        public int hashCode()
+        {
+            return hashCode;
+        }
+
+        @Override
+        public boolean equals( Object obj )
+        {
+            if ( !( obj instanceof Segment ) )
+            {
+                return false;
+            }
+
+            Segment that = (Segment) obj;
+            if ( that.length != length )
+            {
+                return false;
+            }
+
+            for ( int i = 0; i < length; i++ )
+            {
+                if ( that.array[that.fromIndex + i] != array[fromIndex + i] )
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java
new file mode 100644
index 0000000..0979399
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java
@@ -0,0 +1,492 @@
+package org.apache.maven.plugin.surefire.extensions;
+
+import org.apache.maven.plugin.surefire.extensions.EventConsumerThread.Memento;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
+import org.apache.maven.surefire.extensions.EventHandler;
+import org.apache.maven.surefire.extensions.ForkNodeArguments;
+import org.apache.maven.surefire.extensions.util.CountdownCloseable;
+import org.fest.assertions.Assertions;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.mockito.Mockito;
+
+import javax.annotation.Nonnull;
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.channels.Channel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.TimeUnit;
+
+import static java.nio.charset.CodingErrorAction.REPLACE;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.maven.plugin.surefire.extensions.EventConsumerThreadTest.StreamReadStatus.EOF;
+import static org.apache.maven.plugin.surefire.extensions.EventConsumerThreadTest.StreamReadStatus.OVERFLOW;
+import static org.apache.maven.plugin.surefire.extensions.EventConsumerThreadTest.StreamReadStatus.UNDERFLOW;
+import static org.apache.maven.surefire.api.booter.Constants.STREAM_ENCODING;
+import static org.fest.assertions.Assertions.assertThat;
+//import static org.mockito.Mockito.mock;
+
+//@RunWith( Parameterized.class )
+public class EventConsumerThreadTest
+{
+    @Parameters
+    public static Iterable<Object> channels()
+    {
+        return Arrays.asList( (Object) complexEncodings() );
+    }
+
+    @Parameter
+    public Channel channel;
+
+    private static Channel complexEncodings()
+    {
+        byte[] bytes = new byte[]{(byte) -30, (byte) -126, (byte) -84, 'a', 'b', (byte) 0xc2, (byte) 0xa9, 'c'};
+        bytes = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789".getBytes( UTF_8 );
+        return new Channel( bytes, 1 );
+    }
+
+    private static Channel complexEncodings( int chunkSize )
+    {
+        byte[] bytes = new byte[]{(byte) -30, (byte) -126, (byte) -84, 'a', 'b', (byte) 0xc2, (byte) 0xa9, 'c'};
+        bytes = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789".getBytes( UTF_8 );
+        return new Channel( bytes, chunkSize );
+    }
+
+    @Test
+    public void test5() throws IOException, InterruptedException
+    {
+        /*final CharsetDecoder decoder = STREAM_ENCODING.newDecoder()
+            .onMalformedInput( REPLACE )
+            .onUnmappableCharacter( REPLACE );
+        final CharBuffer chars = CharBuffer.allocate(5);
+
+        final int totalBytes = 7;
+
+        int countDecodedBytes = 0;
+        int countReadBytes = 0;
+        final ByteBuffer buffer = ByteBuffer.wrap(new byte[4]);
+
+        buffer.clear();
+
+        int positionChars = chars.position();
+        int startPosition;
+        List<String> strings = new ArrayList<>();
+        do
+        {
+            startPosition = buffer.position();
+            buffer.limit( startPosition );
+            read( buffer, totalBytes - countReadBytes );
+            decoder.decode(buffer, chars, countDecodedBytes >= totalBytes );
+            final boolean hasDecodedNewBytes = chars.position() != positionChars;
+            if ( hasDecodedNewBytes )
+            {
+                countDecodedBytes += buffer.position() - startPosition;
+                positionChars = chars.position();
+            }
+            countReadBytes += buffer.limit() - startPosition;
+            buffer.compact();
+            strings.add( chars.flip().toString() );
+            chars.clear();
+        }
+        while ( countReadBytes < totalBytes );
+        decoder.reset();
+
+        String s = toString( strings );*/
+        channel = complexEncodings(100);
+
+        Closeable c = new Closeable()
+        {
+            @Override
+            public void close() throws IOException
+            {
+
+            }
+        };
+
+        EventHandler eh = new EventHandler()
+        {
+            @Override
+            public void handleEvent( @Nonnull Object event )
+            {
+
+            }
+        };
+
+        ForkNodeArguments forkNodeArguments = new ForkNodeArguments()
+        {
+            @Nonnull
+            @Override
+            public String getSessionId()
+            {
+                return null;
+            }
+
+            @Override
+            public int getForkChannelId()
+            {
+                return 0;
+            }
+
+            @Nonnull
+            @Override
+            public File dumpStreamText( @Nonnull String text )
+            {
+                return null;
+            }
+
+            @Override
+            public void logWarningAtEnd( @Nonnull String text )
+            {
+
+            }
+
+            @Nonnull
+            @Override
+            public ConsoleLogger getConsoleLogger()
+            {
+                return null;
+            }
+        };
+
+        CountdownCloseable closeable = new CountdownCloseable( c, 0 );
+        EventConsumerThread thread = new EventConsumerThread( "t", channel, eh, closeable,
+            forkNodeArguments );
+        Memento memento = new Memento();
+        TimeUnit.SECONDS.sleep( 2 );
+        System.gc();
+        TimeUnit.SECONDS.sleep( 5 );
+        long l1 = System.currentTimeMillis();
+        for (int i = 0; i < 10_000_000; i++) {
+            //memento.cb.clear();
+            memento.bb.position(0);
+            memento.bb.limit(memento.bb.capacity());
+            memento.bytesCounter = 100;//7
+            memento.data.clear();
+            channel.reset();
+            thread.readString( memento );
+        }
+
+        long l2 = System.currentTimeMillis();
+        System.out.println(l2 - l1);
+        /*assertThat( s )
+            .isEqualTo( "€ab©" );*/
+    }
+
+    @Test
+    public void test6() throws InterruptedException
+    {
+        CharsetDecoder decoder = STREAM_ENCODING.newDecoder()
+            .onMalformedInput( REPLACE )
+            .onUnmappableCharacter( REPLACE );
+        // CharsetDecoder + ByteBuffer.allocate( 0 ) makes 11.5 nanos
+        // CharsetDecoder + ByteBuffer.allocate( 0 ) + toString() makes 16.1 nanos
+        ByteBuffer buffer = ByteBuffer.wrap( "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789".getBytes( UTF_8 ) );
+        CharBuffer chars = CharBuffer.allocate( 100 );
+        TimeUnit.SECONDS.sleep( 2 );
+        System.gc();
+        TimeUnit.SECONDS.sleep( 5 );
+        long l1 = System.currentTimeMillis();
+        for ( int i = 0; i < 10_000_000; i++ )
+        {
+            decoder
+                .reset()
+                .decode( buffer, chars, true ); // CharsetDecoder 71 nanos
+            chars.flip().toString();//CharsetDecoder + toString = 91 nanos
+            buffer.clear();
+            chars.clear();
+        }
+        long l2 = System.currentTimeMillis();
+        System.out.println(l2 - l1);
+    }
+
+    @Test
+    public void test7() throws Exception {
+        byte[] b = {};
+        TimeUnit.SECONDS.sleep( 2 );
+        System.gc();
+        TimeUnit.SECONDS.sleep( 5 );
+        String s = null;
+        long l1 = System.currentTimeMillis();
+        for (int i = 0; i < 10_000_000; i++) {
+            s = new String(b, UTF_8);
+        }
+
+        long l2 = System.currentTimeMillis();
+        System.out.println(l2 - l1);
+        System.out.println(s);
+    }
+
+    @Test
+    public void test8() {
+        ForkedProcessEventType[] enums = ForkedProcessEventType.values();
+        SortedSet<Integer> s = new TreeSet<>();
+        for (ForkedProcessEventType e : enums) {
+            if (s.contains( e.getOpcode().length() )) {
+                System.out.println("obsahuje "  + e + " s dlzkou " + e.getOpcode().length() + " s has codom " + e.getOpcode().hashCode());
+            }
+            s.add( e.getOpcode().length() );
+        }
+    }
+
+    @Test
+    public void test9() throws InterruptedException
+    {
+        ForkedProcessEventType[] enums = ForkedProcessEventType.values();
+        TreeMap<Integer, ForkedProcessEventType> map = new TreeMap<>();
+        for (ForkedProcessEventType e : enums) {
+            map.put( e.getOpcode().hashCode(), e );
+        }
+        TimeUnit.SECONDS.sleep( 2 );
+        System.gc();
+        TimeUnit.SECONDS.sleep( 5 );
+        ForkedProcessEventType s = null;
+        int hash = ForkedProcessEventType.BOOTERCODE_STDOUT.hashCode();
+        long l1 = System.currentTimeMillis();
+        for (int i = 0; i < 10_000_000; i++) {
+            s = map.get( hash );
+        }
+        long l2 = System.currentTimeMillis();
+        System.out.println(l2 - l1);
+        System.out.println(s);
+    }
+
+    @Test
+    public void test10() throws InterruptedException
+    {
+        ForkedProcessEventType[] enums = ForkedProcessEventType.values();
+        Map<Segment, ForkedProcessEventType> map = new HashMap<>();
+        for (ForkedProcessEventType e : enums) {
+            byte[] array = e.getOpcode().getBytes();
+            map.put( new Segment( array, 0, array.length ), e );
+        }
+        TimeUnit.SECONDS.sleep( 2 );
+        System.gc();
+        TimeUnit.SECONDS.sleep( 5 );
+        ForkedProcessEventType s = null;
+        byte[] array = ForkedProcessEventType.BOOTERCODE_STDOUT.getOpcode().getBytes();
+        Segment segment = new Segment( array, 0, array.length );
+        long l1 = System.currentTimeMillis();
+        for (int i = 0; i < 10_000_000; i++) {
+            s = map.get( new Segment( array, 0, array.length ) ); // 33.7 nanos
+        }
+        long l2 = System.currentTimeMillis();
+        System.out.println(l2 - l1);
+        System.out.println(s);
+    }
+
+    @Test
+    public void test11() throws InterruptedException
+    {
+        CharsetDecoder decoder = STREAM_ENCODING.newDecoder()
+            .onMalformedInput( REPLACE )
+            .onUnmappableCharacter( REPLACE );
+        ByteBuffer buffer = ByteBuffer.wrap( ForkedProcessEventType.BOOTERCODE_STDOUT.getOpcode().getBytes( UTF_8 ) );
+        CharBuffer chars = CharBuffer.allocate( 100 );
+        TimeUnit.SECONDS.sleep( 2 );
+        System.gc();
+        TimeUnit.SECONDS.sleep( 5 );
+        long l1 = System.currentTimeMillis();
+        for ( int i = 0; i < 10_000_000; i++ )
+        {
+            decoder
+                .reset()
+                .decode( buffer, chars, true );
+            String s = chars.flip().toString(); // 37 nanos = CharsetDecoder + toString
+            buffer.clear();
+            chars.clear();
+            ForkedProcessEventType.byOpcode( s ); // 65 nanos = CharsetDecoder + toString + byOpcode
+        }
+        long l2 = System.currentTimeMillis();
+        System.out.println(l2 - l1);
+    }
+
+    @Test
+    public void test12() throws InterruptedException
+    {
+        StringBuilder builder = new StringBuilder( 256 );
+        TimeUnit.SECONDS.sleep( 2 );
+        System.gc();
+        TimeUnit.SECONDS.sleep( 5 );
+        byte[] array = ForkedProcessEventType.BOOTERCODE_STDOUT.getOpcode().getBytes();
+        long l1 = System.currentTimeMillis();
+        for (int i = 0; i < 10_000_000; i++) {
+            builder.setLength( 0 );
+            for ( byte b : array )
+            {
+                char c = (char) b;
+                if ( c == ':' ) break;
+                builder.append( c );
+            }
+        }
+        long l2 = System.currentTimeMillis();
+        System.out.println(l2 - l1);
+        System.out.println( builder.toString() );
+    }
+
+    private static class Segment {
+        private final byte[] array;
+        private final int fromIndex;
+        private final int length;
+        private final int hashCode;
+
+        public Segment( byte[] array, int fromIndex, int length )
+        {
+            this.array = array;
+            this.fromIndex = fromIndex;
+            this.length = length;
+
+            int hashCode = 0;
+            int i = fromIndex;
+            for (int loops = length >> 1; loops-- != 0; )
+            {
+                hashCode = 31 * hashCode + array[i++];
+                hashCode = 31 * hashCode + array[i++];
+            }
+            this.hashCode = i == fromIndex + length ? hashCode : 31 * hashCode + array[i];
+        }
+
+        @Override
+        public int hashCode()
+        {
+            return hashCode;
+        }
+
+        @Override
+        public boolean equals( Object obj )
+        {
+            if ( !( obj instanceof Segment ) )
+            {
+                return false;
+            }
+
+            Segment that = (Segment) obj;
+            if ( that.length != length )
+            {
+                return false;
+            }
+
+            for ( int i = 0; i < length; i++ )
+            {
+                if ( that.array[that.fromIndex + i] != array[fromIndex + i] )
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+
+    private static String toString( List<String> strings )
+    {
+        if ( strings.size() == 1 )
+        {
+            return strings.get( 0 );
+        }
+        StringBuilder concatenated = new StringBuilder();
+        for ( String s : strings )
+        {
+            concatenated.append( s );
+        }
+        return concatenated.toString();
+    }
+
+    private StreamReadStatus read( ByteBuffer buffer, int recommendedCount ) throws IOException
+    {
+        if ( buffer.remaining() >= recommendedCount && buffer.position() != 0 )
+        {
+            return OVERFLOW;
+        }
+        else
+        {
+            int mark = buffer.position();
+            buffer.position( buffer.limit() );
+            buffer.limit( buffer.capacity() );
+            boolean isEnd = false;
+            while ( !isEnd && buffer.position() - mark < recommendedCount && buffer.position() != buffer.limit() )
+            {
+                isEnd = channel.read( buffer ) == -1;
+            }
+            buffer.limit( buffer.position() );
+            buffer.position( mark );
+            return isEnd ? EOF : ( buffer.remaining() >= recommendedCount ? OVERFLOW : UNDERFLOW );
+        }
+    }
+
+    private static class Channel implements ReadableByteChannel
+    {
+        private final byte[] bytes;
+        private final int chunkSize;
+        private int i;
+
+        public Channel( byte[] bytes, int chunkSize )
+        {
+            this.bytes = bytes;
+            this.chunkSize = chunkSize;
+        }
+
+        public void reset()
+        {
+            i = 0;
+        }
+
+        @Override
+        public int read( ByteBuffer dst )
+        {
+            if ( i == bytes.length )
+            {
+                return -1;
+            }
+            else if ( dst.hasRemaining() )
+            {
+                int length = Math.min( chunkSize, bytes.length - i );
+                dst.put( bytes, i, length );
+                i += length;
+                return length;
+            }
+            else
+            {
+                return 0;
+            }
+        }
+
+        @Override
+        public boolean isOpen()
+        {
+            return false;
+        }
+
+        @Override
+        public void close() throws IOException
+        {
+        }
+    }
+
+    enum StreamReadStatus
+    {
+        UNDERFLOW,
+        OVERFLOW,
+        EOF
+    }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkedProcessEventType.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkedProcessEventType.java
index e84e5bc..ea81458 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkedProcessEventType.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkedProcessEventType.java
@@ -31,37 +31,261 @@ import static java.util.Collections.unmodifiableMap;
  * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
  * @since 3.0.0-M4
  */
+@SuppressWarnings( "checkstyle:linelength" )
 public enum ForkedProcessEventType
 {
-    BOOTERCODE_SYSPROPS( "sys-prop" ),
+    /**
+     * This is the opcode "sys-prop". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:sys-prop:RunMode:UTF-8:0xFFFFFFFF:key:0xFFFFFFFF:value:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "sys-prop"
+     * </ul>
+     */
+    BOOTERCODE_SYSPROPS( "sys-prop"  ),
 
+    /**
+     * This is the opcode "testset-starting". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:testset-starting:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "testset-starting"
+     * </ul>
+     */
     BOOTERCODE_TESTSET_STARTING( "testset-starting" ),
+
+    /**
+     * This is the opcode "testset-completed". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:testset-completed:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "testset-completed"
+     * </ul>
+     */
     BOOTERCODE_TESTSET_COMPLETED( "testset-completed" ),
+
+    /**
+     * This is the opcode "test-starting". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:test-starting:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "test-starting"
+     * </ul>
+     */
     BOOTERCODE_TEST_STARTING( "test-starting" ),
+
+    /**
+     * This is the opcode "test-succeeded". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:test-succeeded:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "test-succeeded"
+     * </ul>
+     */
     BOOTERCODE_TEST_SUCCEEDED( "test-succeeded" ),
+
+    /**
+     * This is the opcode "test-failed". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:test-failed:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "test-failed"
+     * </ul>
+     */
     BOOTERCODE_TEST_FAILED( "test-failed" ),
+
+    /**
+     * This is the opcode "test-skipped". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:test-skipped:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "test-skipped"
+     * </ul>
+     */
     BOOTERCODE_TEST_SKIPPED( "test-skipped" ),
+
+    /**
+     * This is the opcode "test-error". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:test-error:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "test-error"
+     * </ul>
+     */
     BOOTERCODE_TEST_ERROR( "test-error" ),
+
+    /**
+     * This is the opcode "test-assumption-failure". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:test-assumption-failure:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "test-assumption-failure"
+     * </ul>
+     */
     BOOTERCODE_TEST_ASSUMPTIONFAILURE( "test-assumption-failure" ),
 
+    /**
+     * This is the opcode "std-out-stream". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:std-out-stream:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "std-out-stream"
+     * </ul>
+     */
     BOOTERCODE_STDOUT( "std-out-stream" ),
+
+    /**
+     * This is the opcode "std-out-stream-new-line". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:std-out-stream-new-line:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "std-out-stream-new-line"
+     * </ul>
+     */
     BOOTERCODE_STDOUT_NEW_LINE( "std-out-stream-new-line" ),
+
+    /**
+     * This is the opcode "std-err-stream". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:std-err-stream:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "std-err-stream"
+     * </ul>
+     */
     BOOTERCODE_STDERR( "std-err-stream" ),
+
+    /**
+     * This is the opcode "std-err-stream-new-line". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:std-err-stream-new-line:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "std-err-stream-new-line"
+     * </ul>
+     */
     BOOTERCODE_STDERR_NEW_LINE( "std-err-stream-new-line" ),
 
+    /**
+     * This is the opcode "console-info-log". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:console-info-log:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "console-info-log"
+     * </ul>
+     */
     BOOTERCODE_CONSOLE_INFO( "console-info-log" ),
+
+    /**
+     * This is the opcode "console-debug-log". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:console-debug-log:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "console-debug-log"
+     * </ul>
+     */
     BOOTERCODE_CONSOLE_DEBUG( "console-debug-log" ),
+
+    /**
+     * This is the opcode "console-warning-log". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:console-warning-log:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "console-warning-log"
+     * </ul>
+     */
     BOOTERCODE_CONSOLE_WARNING( "console-warning-log" ),
+
+    /**
+     * This is the opcode "console-error-log". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:console-error-log:RunMode:UTF-8:0xFFFFFFFF:line:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "console-error-log"
+     * </ul>
+     */
     BOOTERCODE_CONSOLE_ERROR( "console-error-log" ),
 
+    /**
+     * This is the opcode "bye". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:bye:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "bye"
+     * </ul>
+     */
     BOOTERCODE_BYE( "bye" ),
+
+    /**
+     * This is the opcode "stop-on-next-test". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:stop-on-next-test:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "stop-on-next-test"
+     * </ul>
+     */
     BOOTERCODE_STOP_ON_NEXT_TEST( "stop-on-next-test" ),
+
+    /**
+     * This is the opcode "next-test". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:next-test:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "next-test"
+     * </ul>
+     */
     BOOTERCODE_NEXT_TEST( "next-test" ),
 
+    /**
+     * This is the opcode "jvm-exit-error". The frame is composed of segments and the separator characters ':'
+     * <pre>
+     * :maven-surefire-event:jvm-exit-error:
+     * </pre>
+     * The constructor with one argument:
+     * <ul>
+     *     <li>the opcode is "jvm-exit-error"
+     * </ul>
+     */
     BOOTERCODE_JVM_EXIT_ERROR( "jvm-exit-error" );
 
-    public static final String MAGIC_NUMBER = "maven-surefire-event";
-
     private static final Map<String, ForkedProcessEventType> EVENTS = events();
 
     private static Map<String, ForkedProcessEventType> events()