diff --git a/community/io/src/test/java/org/neo4j/io/pagecache/PageCacheSlowTest.java b/community/io/src/test/java/org/neo4j/io/pagecache/PageCacheSlowTest.java index 576c37e476e7d..f3c552f58d364 100644 --- a/community/io/src/test/java/org/neo4j/io/pagecache/PageCacheSlowTest.java +++ b/community/io/src/test/java/org/neo4j/io/pagecache/PageCacheSlowTest.java @@ -40,9 +40,9 @@ import org.neo4j.io.fs.FileSystemAbstraction; import org.neo4j.io.pagecache.tracing.PageCacheTracer; import org.neo4j.io.pagecache.tracing.cursor.PageCursorTracerSupplier; -import org.neo4j.test.LinearHistoryPageCacheTracer; +import org.neo4j.io.pagecache.tracing.linear.LinearHistoryTracerFactory; +import org.neo4j.io.pagecache.tracing.linear.LinearTracers; import org.neo4j.test.rule.RepeatRule; -import org.neo4j.test.LinearHistoryPageCursorTracer; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; @@ -454,9 +454,9 @@ public void pageCacheMustRemainInternallyConsistentWhenGettingRandomFailures() t // Because our test failures are non-deterministic, we use this tracer to capture a full history of the // events leading up to any given failure. - LinearHistoryPageCacheTracer tracer = new LinearHistoryPageCacheTracer(); - //TODO:sdfasdf - getPageCache( fs, maxPages, pageCachePageSize, tracer, LinearHistoryPageCursorTracer::new ); + LinearTracers linearTracers = LinearHistoryTracerFactory.pageCacheTracer(); + getPageCache( fs, maxPages, pageCachePageSize, linearTracers.getPageCacheTracer(), + linearTracers.getCursorTracerSupplier() ); PagedFile pfA = pageCache.map( existingFile( "a" ), filePageSize ); PagedFile pfB = pageCache.map( existingFile( "b" ), filePageSize / 2 + 1 ); @@ -525,7 +525,7 @@ public void pageCacheMustRemainInternallyConsistentWhenGettingRandomFailures() t } catch ( Throwable e ) { - tracer.printHistory( System.err ); + linearTracers.printHistory( System.err ); throw e; } } diff --git a/community/io/src/test/java/org/neo4j/io/pagecache/randomharness/RandomPageCacheTestHarness.java b/community/io/src/test/java/org/neo4j/io/pagecache/randomharness/RandomPageCacheTestHarness.java index 5b9bda02f8966..099a53446487d 100644 --- a/community/io/src/test/java/org/neo4j/io/pagecache/randomharness/RandomPageCacheTestHarness.java +++ b/community/io/src/test/java/org/neo4j/io/pagecache/randomharness/RandomPageCacheTestHarness.java @@ -44,7 +44,10 @@ import org.neo4j.io.pagecache.impl.SingleFilePageSwapperFactory; import org.neo4j.io.pagecache.impl.muninn.MuninnPageCache; import org.neo4j.io.pagecache.tracing.PageCacheTracer; +import org.neo4j.io.pagecache.tracing.cursor.DefaultPageCursorTracerSupplier; import org.neo4j.io.pagecache.tracing.cursor.PageCursorTracerSupplier; +import org.neo4j.io.pagecache.tracing.linear.LinearHistoryPageCacheTracerTest; +import org.neo4j.io.pagecache.tracing.linear.LinearTracers; /** * The RandomPageCacheTestHarness can plan and run random page cache tests, repeatably if necessary, and verify that @@ -54,7 +57,7 @@ * before and after executing the planned test respectively, and it can integrate with the adversarial file system * for fault injection, and arbitrary PageCacheTracers. * - * See {@link org.neo4j.test.LinearHistoryPageCacheTracerTest} for an example of how to configure and use the harness. + * See {@link LinearHistoryPageCacheTracerTest} for an example of how to configure and use the harness. */ public class RandomPageCacheTestHarness implements Closeable { @@ -92,7 +95,7 @@ public RandomPageCacheTestHarness() filePageCount = cachePageCount * 10; filePageSize = cachePageSize; tracer = PageCacheTracer.NULL; - cursorTracerSupplier = PageCursorTracerSupplier.NULL; + cursorTracerSupplier = DefaultPageCursorTracerSupplier.INSTANCE; commandCount = 1000; Command[] commands = Command.values(); @@ -150,6 +153,14 @@ public void setTracer( PageCacheTracer tracer ) this.tracer = tracer; } + /** + * Set the page cursor tracers supplier. + */ + public void setCursorTracerSupplier( PageCursorTracerSupplier cursorTracerSupplier ) + { + this.cursorTracerSupplier = cursorTracerSupplier; + } + /** * Set the mischief rate for the adversarial file system. */ @@ -227,7 +238,7 @@ public void setCommandCount( int commandCount ) /** * Set the preparation phase to use. This phase is executed before all the planned commands. It can be used to * prepare some file contents, or reset some external state, such as the - * {@link org.neo4j.test.LinearHistoryPageCacheTracer}. + * {@link LinearTracers}. * * The preparation phase is executed before each iteration. */ diff --git a/community/io/src/test/java/org/neo4j/io/pagecache/tracing/DelegatingPageCursorTracer.java b/community/io/src/test/java/org/neo4j/io/pagecache/tracing/DelegatingPageCursorTracer.java deleted file mode 100644 index cdfa006b08510..0000000000000 --- a/community/io/src/test/java/org/neo4j/io/pagecache/tracing/DelegatingPageCursorTracer.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2002-2017 "Neo Technology," - * Network Engine for Objects in Lund AB [http://neotechnology.com] - * - * This file is part of Neo4j. - * - * Neo4j is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.neo4j.io.pagecache.tracing; - -import org.neo4j.io.pagecache.PageSwapper; -import org.neo4j.io.pagecache.tracing.cursor.PageCursorTracer; - -public class DelegatingPageCursorTracer implements PageCursorTracer -{ - - private final PageCursorTracer delegate; - - public DelegatingPageCursorTracer( PageCursorTracer delegate ) - { - this.delegate = delegate; - } - - @Override - public long faults() - { - return 0; - } - - @Override - public long pins() - { - return 0; - } - - @Override - public long unpins() - { - return 0; - } - - @Override - public long bytesRead() - { - return 0; - } - - @Override - public PinEvent beginPin( boolean writeLock, long filePageId, PageSwapper swapper ) - { - return null; - } - - @Override - public void init( PageCacheTracer tracer ) - { - - } - - @Override - public void reportEvents() - { - - } -} diff --git a/community/io/src/test/java/org/neo4j/test/LinearHistoryPageCacheTracer.java b/community/io/src/test/java/org/neo4j/io/pagecache/tracing/linear/HEvents.java similarity index 53% rename from community/io/src/test/java/org/neo4j/test/LinearHistoryPageCacheTracer.java rename to community/io/src/test/java/org/neo4j/io/pagecache/tracing/linear/HEvents.java index 321ec3ed3196f..76a6a7912e1d0 100644 --- a/community/io/src/test/java/org/neo4j/test/LinearHistoryPageCacheTracer.java +++ b/community/io/src/test/java/org/neo4j/io/pagecache/tracing/linear/HEvents.java @@ -17,22 +17,16 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.test; +package org.neo4j.io.pagecache.tracing.linear; -import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; -import java.io.OutputStream; import java.io.PrintStream; import java.io.StringReader; import java.util.IdentityHashMap; -import java.util.LinkedList; -import java.util.List; import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; import org.neo4j.io.pagecache.PageSwapper; import org.neo4j.io.pagecache.tracing.EvictionEvent; @@ -40,132 +34,36 @@ import org.neo4j.io.pagecache.tracing.FlushEvent; import org.neo4j.io.pagecache.tracing.FlushEventOpportunity; import org.neo4j.io.pagecache.tracing.MajorFlushEvent; -import org.neo4j.io.pagecache.tracing.PageCacheTracer; import org.neo4j.io.pagecache.tracing.PageFaultEvent; import org.neo4j.io.pagecache.tracing.PinEvent; /** - * This PageCacheTracer records a linearized history of the internal page cache events. - * - * This takes up a lot of heap memory, because nothing is ever thrown away. - * - * Only use this for debugging internal data race bugs and the like, in the page cache. + * Container of events for page cache tracers that are used to build linear historical representation of page cache + * events. + * In case if event can generate any other event it will properly add it to corresponding tracer and it will be also + * tracked. + * @see LinearHistoryTracer */ -public final class LinearHistoryPageCacheTracer implements PageCacheTracer +class HEvents { - private final AtomicReference history = new AtomicReference<>(); - - // The output buffering mechanics are pre-allocated in case we have to deal with low-memory situations. - // The output switching is guarded by the monitor lock on the LinearHistoryPageCacheTracer instance. - // The class name cache is similarly guarded the monitor lock. In short, only a single thread can print history - // at a time. - private final SwitchableBufferedOutputStream bufferOut = new SwitchableBufferedOutputStream(); - private final PrintStream out = new PrintStream( bufferOut ); - private final Map, String> classSimpleNameCache = new IdentityHashMap<>(); - - private static class SwitchableBufferedOutputStream extends BufferedOutputStream + private HEvents() { - - public SwitchableBufferedOutputStream() - { - //noinspection ConstantConditions - super( null ); // No output target by default. This is changed in printHistory. - } - - public void setOut( OutputStream out ) - { - super.out = out; - } } - private final HEvent end = new HEvent() - { - @Override - void printBody( PrintStream out, String exceptionLinePrefix ) - { - out.print( " EOF " ); - } - }; - - public abstract class HEvent + static final class EndHEvent extends HEvent { - final long time; - final long threadId; - final String threadName; - volatile HEvent prev; - - private HEvent() - { - time = System.nanoTime(); - Thread thread = Thread.currentThread(); - threadId = thread.getId(); - threadName = thread.getName(); - System.identityHashCode( this ); - } - - public final void print( PrintStream out, String exceptionLinePrefix ) - { - if ( getClass() == EndHEvent.class ) - { - out.print( '-' ); - } - out.print( getClass().getSimpleName() ); - out.print( '#' ); - out.print( System.identityHashCode( this ) ); - out.print( '[' ); - out.print( "time:" ); - out.print( (time - end.time) / 1000 ); - out.print( ", threadId:" ); - out.print( threadId ); - printBody( out, exceptionLinePrefix ); - out.print( ']' ); - } - - abstract void printBody( PrintStream out, String exceptionLinePrefix ); + private static final Map,String> classSimpleNameCache = new IdentityHashMap<>(); + IntervalHEvent event; - protected final void print( PrintStream out, File file ) + EndHEvent( IntervalHEvent event ) { - out.print( ", file:" ); - out.print( file == null ? "" : file.getPath() ); - } - - protected final void print( PrintStream out, Throwable exception, String linePrefix ) - { - if ( exception != null ) - { - out.println( ", exception:" ); - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - PrintStream sbuf = new PrintStream( buf ); - exception.printStackTrace( sbuf ); - sbuf.flush(); - BufferedReader reader = new BufferedReader( new StringReader( buf.toString() ) ); - try - { - String line = reader.readLine(); - while ( line != null ) - { - out.print( linePrefix ); - out.print( '\t' ); - out.println( line ); - line = reader.readLine(); - } - out.print( linePrefix ); - } - catch ( IOException e ) - { - throw new RuntimeException( e ); - } - } + this.event = event; } - } - - public final class EndHEvent extends HEvent - { - IntervalHEven event; - public EndHEvent( IntervalHEven event ) + public void print( PrintStream out, String exceptionLinePrefix ) { - this.event = event; + out.print( '-' ); + super.print( out, exceptionLinePrefix ); } @Override @@ -174,30 +72,22 @@ void printBody( PrintStream out, String exceptionLinePrefix ) out.print( ", elapsedMicros:" ); out.print( (time - event.time) / 1000 ); out.print( ", endOf:" ); - Class eventClass = event.getClass(); - String className = classSimpleNameCache.get( eventClass ); - if ( className == null ) - { - className = eventClass.getSimpleName(); - classSimpleNameCache.put( eventClass, className ); - } + Class eventClass = event.getClass(); + String className = classSimpleNameCache.computeIfAbsent( eventClass, k -> eventClass.getSimpleName() ); out.print( className ); out.print( '#' ); out.print( System.identityHashCode( event ) ); } } - public abstract class IntervalHEven extends HEvent + static class MappedFileHEvent extends HEvent { - public void close() + File file; + + MappedFileHEvent( File file ) { - add( new EndHEvent( this ) ); + this.file = file; } - } - - public class MappedFileHEvent extends HEvent - { - File file; @Override void printBody( PrintStream out, String exceptionLinePrefix ) @@ -206,10 +96,15 @@ void printBody( PrintStream out, String exceptionLinePrefix ) } } - public class UnmappedFileHEvent extends HEvent + static class UnmappedFileHEvent extends HEvent { File file; + UnmappedFileHEvent( File file ) + { + this.file = file; + } + @Override void printBody( PrintStream out, String exceptionLinePrefix ) { @@ -217,19 +112,20 @@ void printBody( PrintStream out, String exceptionLinePrefix ) } } - public class EvictionRunHEvent extends IntervalHEven implements EvictionRunEvent + public static class EvictionRunHEvent extends IntervalHEvent implements EvictionRunEvent { int pagesToEvict; - private EvictionRunHEvent( int pagesToEvict ) + EvictionRunHEvent( LinearHistoryTracer tracer, int pagesToEvict ) { + super( tracer ); this.pagesToEvict = pagesToEvict; } @Override public EvictionEvent beginEviction() { - return add( new EvictionHEvent() ); + return tracer.add( new EvictionHEvent( tracer ) ); } @Override @@ -240,47 +136,47 @@ void printBody( PrintStream out, String exceptionLinePrefix ) } } - public class EvictionHEvent extends IntervalHEven implements EvictionEvent, FlushEventOpportunity + public static class FlushHEvent extends IntervalHEvent implements FlushEvent { private long filePageId; + private int cachePageId; + private int pageCount; private File file; + private int bytesWritten; private IOException exception; - private int cachePageId; - @Override - public void setFilePageId( long filePageId ) + FlushHEvent( LinearHistoryTracer tracer, long filePageId, int cachePageId, PageSwapper swapper ) { + super( tracer ); this.filePageId = filePageId; + this.cachePageId = cachePageId; + this.pageCount = 1; + this.file = swapper.file(); } @Override - public void setSwapper( PageSwapper swapper ) + public void addBytesWritten( long bytes ) { - file = swapper == null? null : swapper.file(); + bytesWritten += bytes; } @Override - public FlushEventOpportunity flushEventOpportunity() + public void done() { - return this; + close(); } @Override - public void threwException( IOException exception ) + public void done( IOException exception ) { this.exception = exception; + done(); } @Override - public void setCachePageId( int cachePageId ) - { - this.cachePageId = cachePageId; - } - - @Override - public FlushEvent beginFlush( long filePageId, int cachePageId, PageSwapper swapper ) + public void addPagesFlushed( int pageCount ) { - return add( new FlushHEvent( filePageId, cachePageId, swapper ) ); + this.pageCount = pageCount; } @Override @@ -290,78 +186,54 @@ void printBody( PrintStream out, String exceptionLinePrefix ) out.print( filePageId ); out.print( ", cachePageId:" ); out.print( cachePageId ); + out.print( ", pageCount:" ); + out.print( pageCount ); print( out, file ); + out.print( ", bytesWritten:" ); + out.print( bytesWritten ); print( out, exception, exceptionLinePrefix ); } } - public class FlushHEvent extends IntervalHEven implements FlushEvent + public static class MajorFlushHEvent extends IntervalHEvent implements MajorFlushEvent, FlushEventOpportunity { - private long filePageId; - private int cachePageId; - private int pageCount; private File file; - private int bytesWritten; - private IOException exception; - - public FlushHEvent( long filePageId, int cachePageId, PageSwapper swapper ) - { - this.filePageId = filePageId; - this.cachePageId = cachePageId; - this.pageCount = 1; - this.file = swapper.file(); - } - - @Override - public void addBytesWritten( long bytes ) - { - bytesWritten += bytes; - } - @Override - public void done() + MajorFlushHEvent( LinearHistoryTracer tracer, File file ) { - close(); + super( tracer ); + this.file = file; } @Override - public void done( IOException exception ) + public FlushEventOpportunity flushEventOpportunity() { - this.exception = exception; - done(); + return this; } @Override - public void addPagesFlushed( int pageCount ) + public FlushEvent beginFlush( long filePageId, int cachePageId, PageSwapper swapper ) { - this.pageCount = pageCount; + return tracer.add( new FlushHEvent( tracer, filePageId, cachePageId, swapper ) ); } @Override void printBody( PrintStream out, String exceptionLinePrefix ) { - out.print( ", filePageId:" ); - out.print( filePageId ); - out.print( ", cachePageId:" ); - out.print( cachePageId ); - out.print( ", pageCount:" ); - out.print( pageCount ); print( out, file ); - out.print( ", bytesWritten:" ); - out.print( bytesWritten ); - print( out, exception, exceptionLinePrefix ); } } - public class PinHEvent extends IntervalHEven implements PinEvent + public static class PinHEvent extends IntervalHEvent implements PinEvent { private boolean exclusiveLock; private long filePageId; private File file; private int cachePageId; - public PinHEvent( boolean exclusiveLock, long filePageId, PageSwapper swapper ) + PinHEvent( LinearHistoryTracer tracer, boolean exclusiveLock, long filePageId, PageSwapper swapper ) { + super( tracer ); this.exclusiveLock = exclusiveLock; this.filePageId = filePageId; this.file = swapper.file(); @@ -376,7 +248,7 @@ public void setCachePageId( int cachePageId ) @Override public PageFaultEvent beginPageFault() { - return add( new PageFaultHEvent() ); + return tracer.add( new PageFaultHEvent( tracer ) ); } @Override @@ -398,13 +270,18 @@ void printBody( PrintStream out, String exceptionLinePrefix ) } } - public class PageFaultHEvent extends IntervalHEven implements PageFaultEvent + public static class PageFaultHEvent extends IntervalHEvent implements PageFaultEvent { private int bytesRead; private int cachePageId; private boolean pageEvictedByFaulter; private Throwable exception; + PageFaultHEvent( LinearHistoryTracer linearHistoryTracer ) + { + super( linearHistoryTracer ); + } + @Override public void addBytesRead( long bytes ) { @@ -434,7 +311,7 @@ public void done( Throwable throwable ) public EvictionEvent beginEviction() { pageEvictedByFaulter = true; - return add( new EvictionHEvent() ); + return tracer.add( new EvictionHEvent( tracer ) ); } @Override @@ -450,279 +327,173 @@ void printBody( PrintStream out, String exceptionLinePrefix ) } } - public class MajorFlushHEvent extends IntervalHEven implements MajorFlushEvent, FlushEventOpportunity + public static class EvictionHEvent extends IntervalHEvent implements EvictionEvent, FlushEventOpportunity { + private long filePageId; private File file; + private IOException exception; + private int cachePageId; - public MajorFlushHEvent( File file ) + EvictionHEvent( LinearHistoryTracer linearHistoryTracer ) { - this.file = file; + super( linearHistoryTracer ); } @Override - public FlushEventOpportunity flushEventOpportunity() + public void setFilePageId( long filePageId ) { - return this; + this.filePageId = filePageId; } @Override - public FlushEvent beginFlush( long filePageId, int cachePageId, PageSwapper swapper ) + public void setSwapper( PageSwapper swapper ) { - return add( new FlushHEvent( filePageId, cachePageId, swapper ) ); + file = swapper == null ? null : swapper.file(); } @Override - void printBody( PrintStream out, String exceptionLinePrefix ) + public FlushEventOpportunity flushEventOpportunity() { - print( out, file ); + return this; } - } - - E add( E event ) - { - HEvent prev = history.getAndSet( event ); - event.prev = prev == null? end : prev; - return event; - } - public synchronized boolean processHistory( Consumer processor ) - { - HEvent events = history.getAndSet( null ); - if ( events == null ) + @Override + public void threwException( IOException exception ) { - return false; + this.exception = exception; } - events = reverse( events ); - while ( events != null ) + + @Override + public void setCachePageId( int cachePageId ) { - processor.accept( events ); - events = events.prev; + this.cachePageId = cachePageId; } - return true; - } - public synchronized void printHistory( PrintStream outputStream ) - { - bufferOut.setOut( outputStream ); - if ( !processHistory( new HistoryPrinter() ) ) + @Override + public FlushEvent beginFlush( long filePageId, int cachePageId, PageSwapper swapper ) { - out.println( "No events recorded." ); + return tracer.add( new FlushHEvent( tracer, filePageId, cachePageId, swapper ) ); } - out.flush(); - } - private HEvent reverse( HEvent events ) - { - HEvent current = end; - while ( events != end ) + @Override + void printBody( PrintStream out, String exceptionLinePrefix ) { - HEvent prev; - do - { - prev = events.prev; - } while ( prev == null ); - events.prev = current; - current = events; - events = prev; + out.print( ", filePageId:" ); + out.print( filePageId ); + out.print( ", cachePageId:" ); + out.print( cachePageId ); + print( out, file ); + print( out, exception, exceptionLinePrefix ); } - return current; - } - - @Override - public void mappedFile( File file ) - { - add( new MappedFileHEvent() ).file = file; - } - - @Override - public void unmappedFile( File file ) - { - add( new UnmappedFileHEvent() ).file = file; - } - - @Override - public EvictionRunEvent beginPageEvictions( int pageCountToEvict ) - { - return add( new EvictionRunHEvent( pageCountToEvict ) ); - } - - @Override - public MajorFlushEvent beginFileFlush( PageSwapper swapper ) - { - return add( new MajorFlushHEvent( swapper.file() ) ); - } - - @Override - public MajorFlushEvent beginCacheFlush() - { - return add( new MajorFlushHEvent( null ) ); - } - - @Override - public long faults() - { - return 0; - } - - @Override - public long evictions() - { - return 0; - } - - @Override - public long pins() - { - return 0; - } - - @Override - public long unpins() - { - return 0; - } - - @Override - public long flushes() - { - return 0; - } - - @Override - public long bytesRead() - { - return 0; - } - - @Override - public long bytesWritten() - { - return 0; - } - - @Override - public long filesMapped() - { - return 0; - } - - @Override - public long filesUnmapped() - { - return 0; - } - - @Override - public long evictionExceptions() - { - return 0; - } - - @Override - public void pins( long pins ) - { - } - - @Override - public void unpins( long unpins ) - { - - } - - @Override - public void faults( long faults ) - { - } - @Override - public void bytesRead( long bytesRead ) + public abstract static class HEvent { + static final HEvent end = new HEvent() + { + @Override + void printBody( PrintStream out, String exceptionLinePrefix ) + { + out.print( " EOF " ); + } + }; - } - - @Override - public void evictions( long evictions ) - { - - } - - @Override - public void bytesWritten( long bytesWritten ) - { + final long time; + final long threadId; + final String threadName; + volatile HEvent prev; - } + HEvent() + { + time = System.nanoTime(); + Thread thread = Thread.currentThread(); + threadId = thread.getId(); + threadName = thread.getName(); + System.identityHashCode( this ); + } - @Override - public void flushes( long flushes ) - { + public static HEvent reverse( HEvent events ) + { + HEvent current = end; + while ( events != end ) + { + HEvent prev; + do + { + prev = events.prev; + } + while ( prev == null ); + events.prev = current; + current = events; + events = prev; + } + return current; + } - } + public void print( PrintStream out, String exceptionLinePrefix ) + { + out.print( getClass().getSimpleName() ); + out.print( '#' ); + out.print( System.identityHashCode( this ) ); + out.print( '[' ); + out.print( "time:" ); + out.print( (time - end.time) / 1000 ); + out.print( ", threadId:" ); + out.print( threadId ); + printBody( out, exceptionLinePrefix ); + out.print( ']' ); + } - private class HistoryPrinter implements Consumer - { - private final List concurrentIntervals; + abstract void printBody( PrintStream out, String exceptionLinePrefix ); - public HistoryPrinter() + protected final void print( PrintStream out, File file ) { - this.concurrentIntervals = new LinkedList<>(); + out.print( ", file:" ); + out.print( file == null ? "" : file.getPath() ); } - @Override - public void accept( HEvent event ) + protected final void print( PrintStream out, Throwable exception, String linePrefix ) { - String exceptionLinePrefix = exceptionLinePrefix( concurrentIntervals.size() ); - if ( event.getClass() == EndHEvent.class ) + if ( exception != null ) { - EndHEvent endHEvent = (EndHEvent) event; - int idx = concurrentIntervals.indexOf( endHEvent.event ); - putcs( out, '|', idx ); - out.print( '-' ); - int left = concurrentIntervals.size() - idx - 1; - putcs( out, '|', left ); - out.print( " " ); - endHEvent.print( out, exceptionLinePrefix ); - concurrentIntervals.remove( idx ); - if ( left > 0 ) + out.println( ", exception:" ); + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + PrintStream sbuf = new PrintStream( buf ); + exception.printStackTrace( sbuf ); + sbuf.flush(); + BufferedReader reader = new BufferedReader( new StringReader( buf.toString() ) ); + try { - out.println(); - putcs( out, '|', idx ); - putcs( out, '/', left ); + String line = reader.readLine(); + while ( line != null ) + { + out.print( linePrefix ); + out.print( '\t' ); + out.println( line ); + line = reader.readLine(); + } + out.print( linePrefix ); + } + catch ( IOException e ) + { + throw new RuntimeException( e ); } } - else if ( event instanceof IntervalHEven ) - { - putcs( out, '|', concurrentIntervals.size() ); - out.print( "+ " ); - event.print( out, exceptionLinePrefix ); - concurrentIntervals.add( event ); - } - else - { - putcs( out, '|', concurrentIntervals.size() ); - out.print( "> " ); - event.print( out, exceptionLinePrefix ); - } - out.println(); } + } + + public abstract static class IntervalHEvent extends HEvent + { + protected LinearHistoryTracer tracer; - private String exceptionLinePrefix( int size ) + IntervalHEvent( LinearHistoryTracer tracer ) { - StringBuilder sb = new StringBuilder(); - for ( int i = 0; i < size; i++ ) - { - sb.append( '|' ); - } - sb.append( ": " ); - return sb.toString(); + this.tracer = tracer; } - private void putcs( PrintStream out, char c, int count ) + public void close() { - for ( int i = 0; i < count; i++ ) - { - out.print( c ); - } + tracer.add( new EndHEvent( this ) ); } } } diff --git a/community/io/src/test/java/org/neo4j/io/pagecache/tracing/linear/LinearHistoryPageCacheTracer.java b/community/io/src/test/java/org/neo4j/io/pagecache/tracing/linear/LinearHistoryPageCacheTracer.java new file mode 100644 index 0000000000000..a217cd45e505d --- /dev/null +++ b/community/io/src/test/java/org/neo4j/io/pagecache/tracing/linear/LinearHistoryPageCacheTracer.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.io.pagecache.tracing.linear; + +import java.io.File; + +import org.neo4j.io.pagecache.PageSwapper; +import org.neo4j.io.pagecache.tracing.EvictionRunEvent; +import org.neo4j.io.pagecache.tracing.MajorFlushEvent; +import org.neo4j.io.pagecache.tracing.PageCacheTracer; + +import static org.neo4j.io.pagecache.tracing.linear.HEvents.EvictionRunHEvent; +import static org.neo4j.io.pagecache.tracing.linear.HEvents.MajorFlushHEvent; +import static org.neo4j.io.pagecache.tracing.linear.HEvents.MappedFileHEvent; +import static org.neo4j.io.pagecache.tracing.linear.HEvents.UnmappedFileHEvent; + +/** + * Tracer for global page cache events that add all of them to event history tracer that can build proper linear + * history across all tracers. + * Only use this for debugging internal data race bugs and the like, in the page cache. + * @see HEvents + * @see LinearHistoryPageCursorTracer + */ +public final class LinearHistoryPageCacheTracer implements PageCacheTracer +{ + + private LinearHistoryTracer tracer; + + LinearHistoryPageCacheTracer( LinearHistoryTracer tracer ) + { + this.tracer = tracer; + } + + @Override + public void mappedFile( File file ) + { + tracer.add( new MappedFileHEvent( file ) ); + } + + @Override + public void unmappedFile( File file ) + { + tracer.add( new UnmappedFileHEvent( file ) ); + } + + @Override + public EvictionRunEvent beginPageEvictions( int pageCountToEvict ) + { + return tracer.add( new EvictionRunHEvent( tracer, pageCountToEvict ) ); + } + + @Override + public MajorFlushEvent beginFileFlush( PageSwapper swapper ) + { + return tracer.add( new MajorFlushHEvent( tracer, swapper.file() ) ); + } + + @Override + public MajorFlushEvent beginCacheFlush() + { + return tracer.add( new MajorFlushHEvent( tracer, null ) ); + } + + @Override + public long faults() + { + return 0; + } + + @Override + public long evictions() + { + return 0; + } + + @Override + public long pins() + { + return 0; + } + + @Override + public long unpins() + { + return 0; + } + + @Override + public long flushes() + { + return 0; + } + + @Override + public long bytesRead() + { + return 0; + } + + @Override + public long bytesWritten() + { + return 0; + } + + @Override + public long filesMapped() + { + return 0; + } + + @Override + public long filesUnmapped() + { + return 0; + } + + @Override + public long evictionExceptions() + { + return 0; + } + + @Override + public void pins( long pins ) + { + } + + @Override + public void unpins( long unpins ) + { + } + + @Override + public void faults( long faults ) + { + } + + @Override + public void bytesRead( long bytesRead ) + { + } + + @Override + public void evictions( long evictions ) + { + } + + @Override + public void bytesWritten( long bytesWritten ) + { + } + + @Override + public void flushes( long flushes ) + { + } +} diff --git a/community/io/src/test/java/org/neo4j/test/LinearHistoryPageCacheTracerTest.java b/community/io/src/test/java/org/neo4j/io/pagecache/tracing/linear/LinearHistoryPageCacheTracerTest.java similarity index 82% rename from community/io/src/test/java/org/neo4j/test/LinearHistoryPageCacheTracerTest.java rename to community/io/src/test/java/org/neo4j/io/pagecache/tracing/linear/LinearHistoryPageCacheTracerTest.java index 0b79a1e38f173..93066edadcbbf 100644 --- a/community/io/src/test/java/org/neo4j/test/LinearHistoryPageCacheTracerTest.java +++ b/community/io/src/test/java/org/neo4j/io/pagecache/tracing/linear/LinearHistoryPageCacheTracerTest.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.test; +package org.neo4j.io.pagecache.tracing.linear; import org.junit.Ignore; import org.junit.Test; @@ -34,18 +34,19 @@ public class LinearHistoryPageCacheTracerTest @Test public void makeSomeTestOutput() throws Exception { - final LinearHistoryPageCacheTracer tracer = new LinearHistoryPageCacheTracer(); + LinearTracers linearTracers = LinearHistoryTracerFactory.pageCacheTracer(); try ( RandomPageCacheTestHarness harness = new RandomPageCacheTestHarness() ) { harness.setUseAdversarialIO( true ); - harness.setTracer( tracer ); + harness.setTracer( linearTracers.getPageCacheTracer() ); + harness.setCursorTracerSupplier( linearTracers.getCursorTracerSupplier() ); harness.setCommandCount( 100 ); harness.setConcurrencyLevel( 2 ); - harness.setPreparation( ( pageCache, fs, files ) -> tracer.processHistory( hEvent -> {} ) ); + harness.setPreparation( ( pageCache, fs, files ) -> linearTracers.processHistory( hEvent -> {} ) ); harness.run( 1, TimeUnit.MINUTES ); - tracer.printHistory( System.out ); + linearTracers.printHistory( System.out ); } } diff --git a/community/io/src/test/java/org/neo4j/test/LinearHistoryPageCursorTracer.java b/community/io/src/test/java/org/neo4j/io/pagecache/tracing/linear/LinearHistoryPageCursorTracer.java similarity index 71% rename from community/io/src/test/java/org/neo4j/test/LinearHistoryPageCursorTracer.java rename to community/io/src/test/java/org/neo4j/io/pagecache/tracing/linear/LinearHistoryPageCursorTracer.java index 204c657c057ce..82c154176d071 100644 --- a/community/io/src/test/java/org/neo4j/test/LinearHistoryPageCursorTracer.java +++ b/community/io/src/test/java/org/neo4j/io/pagecache/tracing/linear/LinearHistoryPageCursorTracer.java @@ -17,15 +17,29 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.test; +package org.neo4j.io.pagecache.tracing.linear; import org.neo4j.io.pagecache.PageSwapper; import org.neo4j.io.pagecache.tracing.PageCacheTracer; import org.neo4j.io.pagecache.tracing.PinEvent; import org.neo4j.io.pagecache.tracing.cursor.PageCursorTracer; +/** + * Tracer for page cache cursor events that add all of them to event history tracer that can build proper linear + * history across all tracers. + * Only use this for debugging internal data race bugs and the like, in the page cache. + * + * @see HEvents + * @see LinearHistoryPageCacheTracer + */ public class LinearHistoryPageCursorTracer implements PageCursorTracer { + private LinearHistoryTracer tracer; + + LinearHistoryPageCursorTracer( LinearHistoryTracer tracer ) + { + this.tracer = tracer; + } @Override public long faults() @@ -51,23 +65,21 @@ public long bytesRead() return 0; } - //TODO: @Override public PinEvent beginPin( boolean writeLock, long filePageId, PageSwapper swapper ) { - return PinEvent.NULL; + return tracer.add( new HEvents.PinHEvent( tracer, writeLock, filePageId, swapper ) ); } @Override public void init( PageCacheTracer tracer ) { - + // nothing to do } @Override public void reportEvents() { - + // nothing to do } - } diff --git a/community/io/src/test/java/org/neo4j/io/pagecache/tracing/linear/LinearHistoryTracer.java b/community/io/src/test/java/org/neo4j/io/pagecache/tracing/linear/LinearHistoryTracer.java new file mode 100644 index 0000000000000..8085aaf6258bc --- /dev/null +++ b/community/io/src/test/java/org/neo4j/io/pagecache/tracing/linear/LinearHistoryTracer.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.io.pagecache.tracing.linear; + +import java.io.BufferedOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +/** + * Records a linearized history of all (global and cursors) internal page cache events. + * Only use this for debugging internal data race bugs and the like, in the page cache. + */ +class LinearHistoryTracer +{ + private final AtomicReference history = new AtomicReference<>(); + + // The output buffering mechanics are pre-allocated in case we have to deal with low-memory situations. + // The output switching is guarded by the monitor lock on the LinearHistoryPageCacheTracer instance. + // The class name cache is similarly guarded the monitor lock. In short, only a single thread can print history + // at a time. + private final SwitchableBufferedOutputStream bufferOut = new SwitchableBufferedOutputStream(); + private final PrintStream out = new PrintStream( bufferOut ); + + synchronized boolean processHistory( Consumer processor ) + { + HEvents.HEvent events = history.getAndSet( null ); + if ( events == null ) + { + return false; + } + events = HEvents.HEvent.reverse( events ); + while ( events != null ) + { + processor.accept( events ); + events = events.prev; + } + return true; + } + + E add( E event ) + { + HEvents.HEvent prev = history.getAndSet( event ); + event.prev = prev == null ? HEvents.HEvent.end : prev; + return event; + } + + synchronized void printHistory( PrintStream outputStream ) + { + bufferOut.setOut( outputStream ); + if ( !processHistory( new HistoryPrinter() ) ) + { + out.println( "No events recorded." ); + } + out.flush(); + } + + private static class SwitchableBufferedOutputStream extends BufferedOutputStream + { + + SwitchableBufferedOutputStream() + { + //noinspection ConstantConditions + super( null ); // No output target by default. This is changed in printHistory. + } + + public void setOut( OutputStream out ) + { + super.out = out; + } + } + + private class HistoryPrinter implements Consumer + { + private final List concurrentIntervals; + + HistoryPrinter() + { + this.concurrentIntervals = new LinkedList<>(); + } + + @Override + public void accept( HEvents.HEvent event ) + { + String exceptionLinePrefix = exceptionLinePrefix( concurrentIntervals.size() ); + if ( event.getClass() == HEvents.EndHEvent.class ) + { + HEvents.EndHEvent endHEvent = (HEvents.EndHEvent) event; + int idx = concurrentIntervals.indexOf( endHEvent.event ); + putcs( out, '|', idx ); + out.print( '-' ); + int left = concurrentIntervals.size() - idx - 1; + putcs( out, '|', left ); + out.print( " " ); + endHEvent.print( out, exceptionLinePrefix ); + concurrentIntervals.remove( idx ); + if ( left > 0 ) + { + out.println(); + putcs( out, '|', idx ); + putcs( out, '/', left ); + } + } + else if ( event instanceof HEvents.IntervalHEvent ) + { + putcs( out, '|', concurrentIntervals.size() ); + out.print( "+ " ); + event.print( out, exceptionLinePrefix ); + concurrentIntervals.add( event ); + } + else + { + putcs( out, '|', concurrentIntervals.size() ); + out.print( "> " ); + event.print( out, exceptionLinePrefix ); + } + out.println(); + } + + private String exceptionLinePrefix( int size ) + { + StringBuilder sb = new StringBuilder(); + for ( int i = 0; i < size; i++ ) + { + sb.append( '|' ); + } + sb.append( ": " ); + return sb.toString(); + } + + private void putcs( PrintStream out, char c, int count ) + { + for ( int i = 0; i < count; i++ ) + { + out.print( c ); + } + } + } +} diff --git a/community/io/src/test/java/org/neo4j/io/pagecache/tracing/linear/LinearHistoryTracerFactory.java b/community/io/src/test/java/org/neo4j/io/pagecache/tracing/linear/LinearHistoryTracerFactory.java new file mode 100644 index 0000000000000..aeaf45d0b9448 --- /dev/null +++ b/community/io/src/test/java/org/neo4j/io/pagecache/tracing/linear/LinearHistoryTracerFactory.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.io.pagecache.tracing.linear; + +public class LinearHistoryTracerFactory +{ + public static LinearTracers pageCacheTracer() + { + LinearHistoryTracer tracer = new LinearHistoryTracer(); + return new LinearTracers( new LinearHistoryPageCacheTracer( tracer ), + () -> new LinearHistoryPageCursorTracer( tracer ), tracer ); + } +} diff --git a/community/io/src/test/java/org/neo4j/io/pagecache/tracing/linear/LinearTracers.java b/community/io/src/test/java/org/neo4j/io/pagecache/tracing/linear/LinearTracers.java new file mode 100644 index 0000000000000..90206101e0f81 --- /dev/null +++ b/community/io/src/test/java/org/neo4j/io/pagecache/tracing/linear/LinearTracers.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.io.pagecache.tracing.linear; + +import java.io.PrintStream; +import java.util.function.Consumer; + +import org.neo4j.io.pagecache.tracing.cursor.PageCursorTracerSupplier; + +public class LinearTracers +{ + private final LinearHistoryPageCacheTracer pageCacheTracer; + private final PageCursorTracerSupplier cursorTracerSupplier; + private final LinearHistoryTracer tracer; + + LinearTracers( LinearHistoryPageCacheTracer pageCacheTracer, PageCursorTracerSupplier cursorTracerSupplier, + LinearHistoryTracer tracer ) + { + this.pageCacheTracer = pageCacheTracer; + this.cursorTracerSupplier = cursorTracerSupplier; + this.tracer = tracer; + } + + public LinearHistoryPageCacheTracer getPageCacheTracer() + { + return pageCacheTracer; + } + + public PageCursorTracerSupplier getCursorTracerSupplier() + { + return cursorTracerSupplier; + } + + public void printHistory( PrintStream err ) + { + tracer.printHistory( err ); + } + + void processHistory( Consumer processor ) + { + tracer.processHistory( processor ); + } +}