org.neo4j.kernel.impl.util.DebugUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of neo4j-kernel Show documentation
Show all versions of neo4j-kernel Show documentation
Neo4j kernel is a lightweight, embedded Java database designed to
store data structured as graphs rather than tables. For more
information, see http://neo4j.org.
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.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.kernel.impl.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import static java.lang.String.format;
import static java.lang.System.currentTimeMillis;
import static java.lang.System.lineSeparator;
import static java.lang.System.nanoTime;
import static org.neo4j.internal.helpers.Exceptions.stringify;
import static org.neo4j.internal.helpers.Format.duration;
public class DebugUtil
{
private DebugUtil()
{
}
public static void logTrace( String fmt, Object... args )
{
logTrace( 2, 5, fmt, args );
}
public static void logTrace( int skip, int limit, String fmt, Object... args )
{
if ( enabledAssertions() )
{
Thread thread = Thread.currentThread();
String threadName = thread.getName();
ThreadGroup group = thread.getThreadGroup();
String groupPart = group != null ? " in group " + group.getName() : "";
String message = "[" + threadName + groupPart + "] " + String.format( fmt, args );
TraceLog traceLog = new TraceLog( message );
printLimitedStackTrace( System.err, traceLog, skip, limit );
}
}
private static class TraceLog extends Exception
{
TraceLog( String message )
{
super( message );
}
}
private static void printLimitedStackTrace( PrintStream out, Throwable cause, int skip, int limit )
{
synchronized ( out )
{
String[] lines = stringify( cause ).split( lineSeparator() );
for ( String line : lines )
{
if ( line.startsWith( "\tat " ) )
{
if ( skip > 0 )
{
skip--;
}
else if ( limit > 0 )
{
limit--;
out.println( line );
}
else
{
break;
}
}
else
{
out.println( line );
}
}
}
}
public static void printShortStackTrace( Throwable cause, int maxNumberOfStackLines )
{
System.out.println( firstLinesOf( stringify( cause ), maxNumberOfStackLines + 1 ) );
}
public static String firstLinesOf( String string, int maxNumberOfLines )
{
// Totally verbose implementation of this functionality :)
StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter( stringWriter );
try
{
try ( BufferedReader reader = new BufferedReader( new StringReader( string ) ) )
{
String line;
for ( int count = 0; (line = reader.readLine()) != null && count < maxNumberOfLines;
count++ )
{
writer.println( line );
}
}
writer.close();
return stringWriter.getBuffer().toString();
}
catch ( IOException e )
{
throw new RuntimeException( "Can't happen", e );
}
}
public static boolean stackTraceContains( Thread thread, Predicate predicate )
{
for ( StackTraceElement element : thread.getStackTrace() )
{
if ( predicate.test( element ) )
{
return true;
}
}
return false;
}
public static boolean currentStackTraceContains( Predicate predicate )
{
return stackTraceContains( Thread.currentThread(), predicate );
}
public static Predicate classNameIs( final String className )
{
return item -> item.getClassName().equals( className );
}
public static Predicate classNameContains( final String classNamePart )
{
return item -> item.getClassName().contains( classNamePart );
}
public static Predicate classIs( final Class> cls )
{
return item -> item.getClassName().equals( cls.getName() );
}
public static Predicate classNameAndMethodAre( final String className,
final String methodName )
{
return item -> item.getClassName().equals( className ) && item.getMethodName().equals( methodName );
}
public static Predicate classAndMethodAre( final Class> cls, final String methodName )
{
return item -> item.getClassName().equals( cls.getName() ) && item.getMethodName().equals( methodName );
}
public static Predicate methodIs( String methodName )
{
return item -> item.getMethodName().equals( methodName );
}
public static class StackTracer
{
private final Map uniqueStackTraces = new HashMap<>();
private boolean considerMessages = true;
/**
* Returns {@link AtomicInteger} for the unique stack trace provided. It gets updated
* as more are added.
*/
public AtomicInteger add( Throwable t )
{
CallStack key = new CallStack( t, considerMessages );
AtomicInteger count = uniqueStackTraces.computeIfAbsent( key, k -> new AtomicInteger() );
count.incrementAndGet();
return count;
}
public void print( PrintStream out, int interestThreshold )
{
System.out.println( "Printing stack trace counts:" );
long total = 0;
for ( Map.Entry entry : uniqueStackTraces.entrySet() )
{
if ( entry.getValue().get() >= interestThreshold )
{
out.println( entry.getValue() + " times:" );
entry.getKey().stackTrace.printStackTrace( out );
}
total += entry.getValue().get();
}
out.println( "------" );
out.println( "Total:" + total );
}
public StackTracer printAtShutdown( final PrintStream out, final int interestThreshold )
{
Runtime.getRuntime().addShutdownHook( new Thread( () -> print( out, interestThreshold ) ) );
return this;
}
public StackTracer ignoreMessages()
{
considerMessages = false;
return this;
}
}
public static class CallStack
{
private final String message;
private final Throwable stackTrace;
private final StackTraceElement[] elements;
private final boolean considerMessage;
public CallStack( Throwable stackTrace, boolean considerMessage )
{
this.message = stackTrace.getMessage();
this.stackTrace = stackTrace;
this.considerMessage = considerMessage;
this.elements = stackTrace.getStackTrace();
}
public CallStack( StackTraceElement[] elements, String message )
{
this.message = message;
this.stackTrace = null;
this.elements = elements;
this.considerMessage = true;
}
@Override
public int hashCode()
{
int hashCode = message == null || !considerMessage ? 31 : message.hashCode();
for ( StackTraceElement element : elements )
{
hashCode = hashCode * 9 + element.hashCode();
}
return hashCode;
}
@Override
public boolean equals( Object obj )
{
if ( !( obj instanceof CallStack) )
{
return false;
}
CallStack o = (CallStack) obj;
if ( considerMessage )
{
if ( message == null )
{
if ( o.message != null )
{
return false;
}
}
else if ( !message.equals( o.message ) )
{
return false;
}
}
if ( elements.length != o.elements.length )
{
return false;
}
for ( int i = 0; i < elements.length; i++ )
{
if ( !elements[i].equals( o.elements[i] ) )
{
return false;
}
}
return true;
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append( stackTrace != null ? stackTrace.getClass().getName() + ": " : "" )
.append( message != null ? message : "" );
for ( StackTraceElement element : elements )
{
builder.append( format( "%n" ) ).append( " at " ).append( element );
}
return builder.toString();
}
}
private static boolean enabledAssertions()
{
boolean enabled = false;
//noinspection AssertWithSideEffects,ConstantConditions
assert enabled = true : "A trick to set this variable to true if assertions are enabled";
//noinspection ConstantConditions
return enabled;
}
/**
* Super simple utility for determining where most time is spent when you don't know where to even start.
* It could be used to home in on right place in a test or in a sequence of operations or similar.
*/
public abstract static class Timer
{
private final TimeUnit unit;
private long startTime;
protected Timer( TimeUnit unit )
{
this.unit = unit;
this.startTime = currentTime();
}
protected abstract long currentTime();
public void reset()
{
startTime = currentTime();
}
public void at( String point )
{
long duration = currentTime() - startTime;
System.out.println( duration( unit.toMillis( duration ) ) + " @ " + point );
startTime = currentTime();
}
public static Timer millis()
{
return new Millis();
}
private static class Millis extends Timer
{
Millis()
{
super( TimeUnit.MILLISECONDS );
}
@Override
protected long currentTime()
{
return currentTimeMillis();
}
}
public static Timer nanos()
{
return new Nanos();
}
private static class Nanos extends Timer
{
Nanos()
{
super( TimeUnit.NANOSECONDS );
}
@Override
protected long currentTime()
{
return nanoTime();
}
}
}
public static long time( long startTime, String message )
{
System.out.println( duration( currentTimeMillis() - startTime ) + ": " + message );
return currentTimeMillis();
}
}