Skip to content

Commit

Permalink
DumpLogicalLog can dump transactions relevant to cc report
Browse files Browse the repository at this point in the history
after a consisistency checker has run a report is made, potentially including
incosistencies. DumpLogicalLog can be given a --ccfilter which should
point to such a report file. DumpLogicalLog will then only include transactions
which touches any of the records in the cc report and mark the records it
matched with a `<----` arrow to easier navigate.
  • Loading branch information
tinwelint committed Nov 11, 2016
1 parent 5078171 commit e0f409a
Show file tree
Hide file tree
Showing 3 changed files with 401 additions and 7 deletions.
153 changes: 146 additions & 7 deletions tools/src/main/java/org/neo4j/tools/dump/DumpLogicalLog.java
Expand Up @@ -25,14 +25,21 @@
import java.io.PrintStream;
import java.nio.ByteBuffer;
import java.util.TimeZone;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Pattern;

import org.neo4j.collection.primitive.Primitive;
import org.neo4j.collection.primitive.PrimitiveLongSet;
import org.neo4j.cursor.IOCursor;
import org.neo4j.helpers.Args;
import org.neo4j.io.fs.DefaultFileSystemAbstraction;
import org.neo4j.io.fs.FileSystemAbstraction;
import org.neo4j.io.fs.StoreChannel;
import org.neo4j.kernel.impl.transaction.command.Command.NodeCommand;
import org.neo4j.kernel.impl.transaction.command.Command.PropertyCommand;
import org.neo4j.kernel.impl.transaction.command.Command.RelationshipCommand;
import org.neo4j.kernel.impl.transaction.command.Command.RelationshipGroupCommand;
import org.neo4j.kernel.impl.transaction.log.FilteringIOCursor;
import org.neo4j.kernel.impl.transaction.log.LogEntryCursor;
import org.neo4j.kernel.impl.transaction.log.LogVersionBridge;
Expand All @@ -44,9 +51,12 @@
import org.neo4j.kernel.impl.transaction.log.ReaderLogVersionBridge;
import org.neo4j.kernel.impl.transaction.log.TransactionLogEntryCursor;
import org.neo4j.kernel.impl.transaction.log.entry.LogEntry;
import org.neo4j.kernel.impl.transaction.log.entry.LogEntryCommand;
import org.neo4j.kernel.impl.transaction.log.entry.LogEntryReader;
import org.neo4j.kernel.impl.transaction.log.entry.LogHeader;
import org.neo4j.kernel.impl.transaction.log.entry.VersionAwareLogEntryReader;
import org.neo4j.storageengine.api.StorageCommand;
import org.neo4j.tools.dump.InconsistencyReportReader.Inconsistencies;

import static java.util.TimeZone.getTimeZone;

Expand All @@ -63,6 +73,7 @@ public class DumpLogicalLog
{
private static final String TO_FILE = "tofile";
private static final String TX_FILTER = "txfilter";
private static final String CC_FILTER = "ccfilter";

private final FileSystemAbstraction fileSystem;

Expand All @@ -72,7 +83,7 @@ public DumpLogicalLog( FileSystemAbstraction fileSystem )
}

public void dump( String filenameOrDirectory, PrintStream out,
TimeZone timeZone, String regex ) throws IOException
Predicate<LogEntry[]> filter, Function<LogEntry,String> serializer ) throws IOException
{
File file = new File( filenameOrDirectory );
printFile( file, out );
Expand Down Expand Up @@ -129,14 +140,14 @@ public LogVersionedStoreChannel next( LogVersionedStoreChannel channel ) throws

IOCursor<LogEntry> entryCursor = new LogEntryCursor( entryReader, logChannel );
TransactionLogEntryCursor transactionCursor = new TransactionLogEntryCursor( entryCursor );
try ( IOCursor<LogEntry[]> cursor = regex == null ? transactionCursor :
new FilteringIOCursor<>( transactionCursor, new TransactionRegexCriteria( regex, timeZone ) ) )
try ( IOCursor<LogEntry[]> cursor = filter == null ? transactionCursor :
new FilteringIOCursor<>( transactionCursor, filter ) )
{
while ( cursor.next() )
{
for ( LogEntry entry : cursor.get() )
{
out.println( entry.toString( timeZone ) );
out.println( serializer.apply( entry ) );
}
}
}
Expand Down Expand Up @@ -172,32 +183,160 @@ public boolean test( LogEntry[] transaction )
}
}

public static class ConsistencyCheckOutputCriteria implements Predicate<LogEntry[]>, Function<LogEntry,String>
{
private final TimeZone timeZone;
private final PrimitiveLongSet relationshipIds = Primitive.longSet();
private final PrimitiveLongSet nodeIds = Primitive.longSet();
private final PrimitiveLongSet propertyIds = Primitive.longSet();
private final PrimitiveLongSet relationshipGroupIds = Primitive.longSet();

public ConsistencyCheckOutputCriteria( String ccFile, TimeZone timeZone ) throws IOException
{
this.timeZone = timeZone;
new InconsistencyReportReader( new Inconsistencies()
{
@Override
public void relationshipGroup( long id )
{
relationshipGroupIds.add( id );
}

@Override
public void relationship( long id )
{
relationshipIds.add( id );
}

@Override
public void property( long id )
{
propertyIds.add( id );
}

@Override
public void node( long id )
{
nodeIds.add( id );
}
} ).read( new File( ccFile ) );
}

@Override
public boolean test( LogEntry[] transaction )
{
for ( LogEntry logEntry : transaction )
{
if ( matches( logEntry ) )
{
return true;
}
}
return false;
}

private boolean matches( LogEntry logEntry )
{
if ( logEntry instanceof LogEntryCommand )
{
if ( matches( ((LogEntryCommand)logEntry).getXaCommand() ) )
{
return true;
}
}
return false;
}

private boolean matches( StorageCommand command )
{
if ( command instanceof NodeCommand )
{
return nodeIds.contains( ((NodeCommand) command).getKey() );
}
if ( command instanceof RelationshipCommand )
{
return relationshipIds.contains( ((RelationshipCommand) command).getKey() );
}
if ( command instanceof PropertyCommand )
{
return propertyIds.contains( ((PropertyCommand) command).getKey() );
}
if ( command instanceof RelationshipGroupCommand )
{
return relationshipGroupIds.contains( ((RelationshipGroupCommand) command).getKey() );
}
return false;
}

@Override
public String apply( LogEntry logEntry )
{
String result = logEntry.toString( timeZone );
if ( matches( logEntry ) )
{
result += " <----";
}
return result;
}
}

/**
* Usage: [--txfilter "regex"] [--tofile] storeDirOrFile1 storeDirOrFile2 ...
* Usage: [--txfilter "regex"] [--ccfilter cc-report-file] [--tofile] storeDirOrFile1 storeDirOrFile2 ...
*
* --txfilter
* Will match regex against each {@link LogEntry} and if there is a match,
* include transaction containing the LogEntry in the dump.
* regex matching is done with {@link Pattern}
*
* --ccfilter
* Will look at an inconsistency report file from consistency checker and
* include transactions that are relevant to them
*
* --tofile
* Redirects output to dump-logical-log.txt in the store directory
*/
public static void main( String args[] ) throws IOException
{
Args arguments = Args.withFlags( TO_FILE ).parse( args );
TimeZone timeZone = parseTimeZoneConfig( arguments );
String regex = arguments.get( TX_FILTER );
Predicate<LogEntry[]> filter = parseFilter( arguments, timeZone );
Function<LogEntry,String> serializer = parseSerializer( arguments, filter, timeZone );
try ( Printer printer = getPrinter( arguments ) )
{
for ( String fileAsString : arguments.orphans() )
{
new DumpLogicalLog( new DefaultFileSystemAbstraction() )
.dump( fileAsString, printer.getFor( fileAsString ), timeZone, regex );
.dump( fileAsString, printer.getFor( fileAsString ), filter, serializer );
}
}
}

@SuppressWarnings( "unchecked" )
private static Function<LogEntry,String> parseSerializer( Args arguments, Predicate<LogEntry[]> filter,
TimeZone timeZone )
{
if ( filter instanceof Function )
{
return (Function<LogEntry,String>) filter;
}
return logEntry -> logEntry.toString( timeZone );
}

private static Predicate<LogEntry[]> parseFilter( Args arguments, TimeZone timeZone ) throws IOException
{
String regex = arguments.get( TX_FILTER );
if ( regex != null )
{
return new TransactionRegexCriteria( regex, timeZone );
}
String cc = arguments.get( CC_FILTER );
if ( cc != null )
{
return new ConsistencyCheckOutputCriteria( cc, timeZone );
}
return null;
}

public static Printer getPrinter( Args args )
{
boolean toFile = args.getBoolean( TO_FILE, false, true );
Expand Down
@@ -0,0 +1,149 @@
/*
* Copyright (c) 2002-2016 "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 Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.tools.dump;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

/**
* Reads CC inconsistency reports. Example of entry:
*
* <pre>
* ERROR: The referenced relationship record is not in use.
* Node[3496089,used=true,rel=14833798,prop=13305361,labels=Inline(0x1000000006:[6]),light,secondaryUnitId=-1]
* Inconsistent with: Relationship[14833798,used=false,source=0,target=0,type=0,sPrev=0,sNext=0,tPrev=0,tNext=0,prop=0,secondaryUnitId=-1,!sFirst,!tFirst]
* </pre>
*/
public class InconsistencyReportReader
{
private final Inconsistencies inconsistencies;

public interface Inconsistencies
{
void node( long id );

void relationship( long id );

void property( long id );

void relationshipGroup( long id );

// TODO: incomplete list/arguments, driven by actual need a.t.m.
}

public InconsistencyReportReader( Inconsistencies inconsistencies )
{
this.inconsistencies = inconsistencies;
}

public void read( File file ) throws IOException
{
try ( Reader reader = new FileReader( file ) )
{
read( reader );
}
}

public void read( Reader reader ) throws IOException
{
int state = 0; // 0:inconsistency description, 1:entity, 2:inconsistent with
BufferedReader bufferedReader = new BufferedReader( reader );
String line;
while ( (line = bufferedReader.readLine()) != null )
{
line = line.trim();
if ( state == 0 )
{
if ( !line.contains( "ERROR" ) && !line.contains( "WARNING" ) )
{
break;
}
}
else if ( state == 1 )
{
String entityType = entityType( line );
long id = id( line );
if ( entityType != null && id != -1 )
{
propagate( entityType, id );
}
}
else if ( state == 1 || state == 2 )
{
if ( line.startsWith( "Inconsistent with: " ) )
{
line = line.substring( "Inconsistent with: ".length() );
}

String entityType = entityType( line );
long id = id( line );
if ( entityType != null && id != -1 )
{
propagate( entityType, id );
}
}
state = (state + 1) % 3;
}
}

private void propagate( String entityType, long id )
{
switch ( entityType )
{
case "Relationship":
inconsistencies.relationship( id );
break;
case "Node":
inconsistencies.node( id );
break;
case "Property":
inconsistencies.property( id );
break;
case "RelationshipGroup":
inconsistencies.relationshipGroup( id );
break;
default:
// it's OK, we just haven't implemented support for this yet
}
}

private long id( String line )
{
int bracket = line.indexOf( '[' );
if ( bracket > -1 )
{
int comma = line.indexOf( ',', bracket );
if ( comma > -1 )
{
return Long.parseLong( line.substring( bracket + 1, comma ) );
}
}
return -1;
}

private String entityType( String line )
{
int bracket = line.indexOf( '[' );
return bracket == -1 ? null : line.substring( 0, bracket );
}
}

0 comments on commit e0f409a

Please sign in to comment.