Skip to content

Commit

Permalink
Added jProcesses for report tool
Browse files Browse the repository at this point in the history
Addressed PR comments
  • Loading branch information
klaren committed Jan 4, 2018
1 parent be854f0 commit 8d87da9
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 73 deletions.
5 changes: 5 additions & 0 deletions community/dbms/pom.xml
Expand Up @@ -164,6 +164,11 @@
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.jprocesses</groupId>
<artifactId>jProcesses</artifactId>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
Expand Down
Expand Up @@ -19,6 +19,9 @@
*/
package org.neo4j.commandline.dbms;

import org.jutils.jprocesses.JProcesses;
import org.jutils.jprocesses.model.ProcessInfo;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
Expand All @@ -30,6 +33,7 @@
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

Expand All @@ -43,9 +47,10 @@
import org.neo4j.commandline.arguments.OptionalNamedArg;
import org.neo4j.commandline.arguments.PositionalArgument;
import org.neo4j.commandline.arguments.common.OptionalCanonicalPath;
import org.neo4j.dbms.report.jmx.JmxDump;
import org.neo4j.dbms.report.jmx.LocalVirtualMachine;
import org.neo4j.dbms.diagnostics.jmx.JmxDump;
import org.neo4j.dbms.diagnostics.jmx.LocalVirtualMachine;
import org.neo4j.diagnostics.DiagnosticsOfflineReportProvider;
import org.neo4j.diagnostics.DiagnosticsReportSource;
import org.neo4j.diagnostics.DiagnosticsReportSources;
import org.neo4j.diagnostics.DiagnosticsReporter;
import org.neo4j.helpers.Args;
Expand All @@ -57,7 +62,7 @@

public class DiagnosticsReportCommand implements AdminCommand
{
private static OptionalNamedArg destinationArgument =
private static final OptionalNamedArg destinationArgument =
new OptionalCanonicalPath( "to", "/tmp/", "reports/", "Destination directory for reports" );
private static final Arguments arguments = new Arguments()
.withArgument( new OptionalListArgument() )
Expand All @@ -67,18 +72,16 @@ public class DiagnosticsReportCommand implements AdminCommand

private final Path homeDir;
private final Path configDir;
private final OutsideWorld outsideWorld;
static final String[] DEFAULT_CLASSIFIERS = new String[]{"logs", "config"};
private boolean verbose;
private PrintStream err;
private PrintStream out;
private FileSystemAbstraction fs;
private final PrintStream err;
private final PrintStream out;
private final FileSystemAbstraction fs;

DiagnosticsReportCommand( Path homeDir, Path configDir, OutsideWorld outsideWorld )
{
this.homeDir = homeDir;
this.configDir = configDir;
this.outsideWorld = outsideWorld;
this.fs = outsideWorld.fileSystem();
this.out = outsideWorld.outStream();
this.err = outsideWorld.errorStream();
Expand All @@ -103,31 +106,7 @@ public void execute( String[] stringArgs ) throws IncorrectUsage, CommandFailed
}
Config config = Config.fromFile( configFile ).withHome( homeDir ).withConnectorsDisabled().build();

File storeDirectory = config.get( database_path );

DiagnosticsReporter reporter = new DiagnosticsReporter( out );

// Find all offline providers and register them
for ( DiagnosticsOfflineReportProvider provider : Service.load( DiagnosticsOfflineReportProvider.class ) )
{
provider.init( fs, config, storeDirectory );
reporter.registerOfflineProvider( provider );
}

// Register sources provided by this tool
reporter.registerSource( "config",
DiagnosticsReportSources.newDiagnosticsFile( "neo4j.conf", fs, configFile ) );

// Online connection
JmxDump jmxDump = connectToNeo4jInstance();
if ( jmxDump != null )
{
reporter.registerSource( "threads", jmxDump.threadDump() );
reporter.registerSource( "heap", jmxDump.heapDump() );
reporter.registerSource( "sysprop", jmxDump.systemProperties() );
//reporter.registerSource( "env", null );
}

DiagnosticsReporter reporter = createAndRegisterSources( config, configFile );
Set<String> availableClassifiers = reporter.getAvailableClassifiers();

// Passing '--list' should print list and end execution
Expand Down Expand Up @@ -188,9 +167,73 @@ public void execute( String[] stringArgs ) throws IncorrectUsage, CommandFailed
}
}

private JmxDump connectToNeo4jInstance()
private DiagnosticsReporter createAndRegisterSources( Config config, File configFile )
{
DiagnosticsReporter reporter = new DiagnosticsReporter( out );
File storeDirectory = config.get( database_path );

// Find all offline providers and register them
for ( DiagnosticsOfflineReportProvider provider : Service.load( DiagnosticsOfflineReportProvider.class ) )
{
provider.init( fs, config, storeDirectory );
reporter.registerOfflineProvider( provider );
}

// Register sources provided by this tool
reporter.registerSource( "config", DiagnosticsReportSources.newDiagnosticsFile( "neo4j.conf", fs, configFile ) );
reporter.registerSource( "ps", runningProcesses() );

// Online connection
Optional<JmxDump> jmxDump = connectToNeo4jInstance();
if ( jmxDump.isPresent() )
{
JmxDump jmx = jmxDump.get();
reporter.registerSource( "threads", jmx.threadDump() );
reporter.registerSource( "heap", jmx.heapDump() );
reporter.registerSource( "sysprop", jmx.systemProperties() );
//reporter.registerSource( "env", null ); // TODO:
}
return reporter;
}

private Optional<JmxDump> connectToNeo4jInstance()
{
out.println( "Trying to find running instance of neo4j" );

Optional<Long> pid = getPid();
if ( pid.isPresent() )
{
try
{
LocalVirtualMachine vm = LocalVirtualMachine.from( pid.get() );
out.println( "Attached to running process with process id " + pid );
try
{
JmxDump jmxDump = JmxDump.connectTo( vm.getJmxAddress() );
jmxDump.attachSystemProperties( vm.getSystemProperties() );
out.println( "Connected to JMX endpoint" );
return Optional.of( jmxDump );
}
catch ( IOException e )
{
printError( "Unable to communicate with JMX endpoint. Reason: " + e.getMessage(), e );
}
}
catch ( IOException e )
{
printError( "Unable to connect to process. Reason: " + e.getMessage(), e );
}
}
else
{
out.println( "No running instance of neo4j was found. Online reports will be omitted." );
}

return Optional.empty();
}

private Optional<Long> getPid()
{
Path pidFile = homeDir.resolve( "run/neo4j.pid" );
if ( fs.fileExists( pidFile.toFile() ) )
{
Expand All @@ -199,29 +242,9 @@ private JmxDump connectToNeo4jInstance()
String pidFileContent = reader.readLine();
try
{
long pid = Long.parseLong( pidFileContent );

try
{
LocalVirtualMachine vm = LocalVirtualMachine.from( pid );
out.println( "Attached to running process with process id " + pid );
try
{
JmxDump jmxDump = JmxDump.connectTo( vm.getJmxAddress() );
jmxDump.attachSystemProperties( vm.getSystemProperties() );
out.println( "Connected to JMX endpoint" );
return jmxDump;
}
catch ( IOException e )
{
printError( "Unable to communicate with JMX endpoint. Reason: " + e.getMessage(), e );
}
}
catch ( IOException e )
{
printError( "Unable to connect to process. Reason: " + e.getMessage(), e );
}
return Optional.of( Long.parseLong( pidFileContent ) );
}

catch ( NumberFormatException e )
{
printError( pidFile.toString() + " does not contain a valid id. Found: " + pidFileContent );
Expand All @@ -232,12 +255,7 @@ private JmxDump connectToNeo4jInstance()
printError( "Error reading the .pid file. Reason: " + e.getMessage(), e );
}
}
else
{
out.println( "No running instance of neo4j was found. Online reports will be omitted." );
}

return null;
return Optional.empty();
}

private void printError( String message )
Expand Down Expand Up @@ -282,11 +300,37 @@ static String describeClassifier( String classifier )
return "include the raft log";
case "queries":
return "include the output of dbms.listQueries()";
case "ps":
return "include a list of running processes";
default:
}
throw new IllegalArgumentException( "Unknown classifier: " + classifier );
}

private static DiagnosticsReportSource runningProcesses()
{
return DiagnosticsReportSources.newDiagnosticsString( "ps.txt", () ->
{
List<ProcessInfo> processesList = JProcesses.getProcessList();

StringBuilder sb = new StringBuilder();
for (final ProcessInfo processInfo : processesList) {
sb.append( "Process PID: " ).append( processInfo.getPid() ).append( '\n' )
.append( "Process Name: " ).append( processInfo.getName() ).append( '\n' )
.append( "Process Time: " ).append( processInfo.getTime() ).append( '\n' )
.append( "User: " ).append( processInfo.getUser() ).append( '\n' )
.append( "Virtual Memory: " ).append( processInfo.getVirtualMemory() ).append( '\n' )
.append( "Physical Memory: " ).append( processInfo.getPhysicalMemory() ).append( '\n' )
.append( "CPU usage: " ).append( processInfo.getCpuUsage() ).append( '\n' )
.append( "Start Time: " ).append( processInfo.getStartTime() ).append( '\n' )
.append( "Priority: " ).append( processInfo.getPriority() ).append( '\n' )
.append( "Full command: " ).append( processInfo.getCommand() ).append( '\n' )
.append("------------------").append( '\n' );
}
return sb.toString();
});
}

/**
* Helper class to format output of {@link Usage}. Parsing is done manually in this command module.
*/
Expand Down
Expand Up @@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.dbms.report.jmx;
package org.neo4j.dbms.diagnostics.jmx;

import com.sun.management.HotSpotDiagnosticMXBean;

Expand Down Expand Up @@ -193,8 +193,10 @@ public void addToArchive( Path archiveDestination, DiagnosticsReporterProgressCa
monitor.info( "archiving..." );
long size = Files.size( tempFile );
InputStream in = Files.newInputStream( tempFile );
ProgressAwareInputStream inStream = new ProgressAwareInputStream( in, size, monitor::percentChanged );
Files.copy( inStream, archiveDestination );
try ( ProgressAwareInputStream inStream = new ProgressAwareInputStream( in, size, monitor::percentChanged ) )
{
Files.copy( inStream, archiveDestination );
}

Files.delete( tempFile );
}
Expand Down
Expand Up @@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.dbms.report.jmx;
package org.neo4j.dbms.diagnostics.jmx;

import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
Expand Down
Expand Up @@ -20,7 +20,6 @@
package org.neo4j.commandline.dbms;

import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
Expand All @@ -45,6 +44,7 @@

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;

Expand Down Expand Up @@ -74,7 +74,7 @@ public void tearDown()
public void shouldBeAbleToAttachToPidAndRunThreadDump() throws IOException, CommandFailed, IncorrectUsage
{
long pid = getPID();
Assume.assumeTrue( pid != 0 );
assertThat( pid, is( not( 0 ) ) );

// Write config file
Files.createFile( testDirectory.file( "neo4j.conf" ).toPath() );
Expand Down
Expand Up @@ -19,6 +19,7 @@
*/
package org.neo4j.commandline.dbms;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
Expand Down Expand Up @@ -69,6 +70,7 @@ public class DiagnosticsReportCommandTest
private Path homeDir;
private Path configDir;
private Path configFile;
private String originalUserDir;

public static class MyDiagnosticsOfflineReportProvider extends DiagnosticsOfflineReportProvider
{
Expand Down Expand Up @@ -101,7 +103,14 @@ public void setUp() throws Exception
Files.createFile( configFile );

// To make sure files are resolved from the working directory
System.setProperty( "user.dir", testDirectory.absolutePath().getAbsolutePath() );
originalUserDir = System.setProperty( "user.dir", testDirectory.absolutePath().getAbsolutePath() );
}

@After
public void tearDown()
{
// Restore directory
System.setProperty( "user.dir", originalUserDir );
}

@Test
Expand Down
Expand Up @@ -22,7 +22,7 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
Expand Down Expand Up @@ -126,8 +126,10 @@ public void addToArchive( Path archiveDestination, DiagnosticsReporterProgressCa
InputStream in = fs.openAsInputStream( source );

// Track progress of the file reading, source might be a very large file
ProgressAwareInputStream inStream = new ProgressAwareInputStream( in, size, monitor::percentChanged );
Files.copy( inStream, archiveDestination );
try ( ProgressAwareInputStream inStream = new ProgressAwareInputStream( in, size, monitor::percentChanged ) )
{
Files.copy( inStream, archiveDestination );
}
}
}

Expand All @@ -153,7 +155,7 @@ public void addToArchive( Path archiveDestination, DiagnosticsReporterProgressCa
throws IOException
{
String message = messageSupplier.get();
Files.write( archiveDestination, message.getBytes( Charset.forName( "UTF8" ) ), StandardOpenOption.CREATE,
Files.write( archiveDestination, message.getBytes( StandardCharsets.UTF_8 ), StandardOpenOption.CREATE,
StandardOpenOption.APPEND );
}
}
Expand Down
Expand Up @@ -110,6 +110,7 @@ public Set<String> getAvailableClassifiers()
return availableClassifiers;
}

// TODO:
private static class Mon implements DiagnosticsReporterProgressCallback
{
private final String prefix;
Expand Down

0 comments on commit 8d87da9

Please sign in to comment.