From c6be09e581d506fb4178bf65c8a1e0ab868cccbe Mon Sep 17 00:00:00 2001 From: Anton Klaren Date: Mon, 26 Jun 2017 18:43:17 +0200 Subject: [PATCH] Extracted common size calculations to FileUtils. Added jmx bean to causal cluster and added basic information: * Role in core cluster * Size of raft log * Size of replicated states --- .../main/java/org/neo4j/io/fs/FileUtils.java | 33 ++++- .../org/neo4j/jmx/impl/StoreFileBean.java | 39 ++--- .../org/neo4j/jmx/impl/StoreFileBeanTest.java | 47 +++--- enterprise/causal-clustering/pom.xml | 11 ++ .../core/EnterpriseCoreEditionModule.java | 1 + .../management/CausalClusteringBean.java | 135 ++++++++++++++++++ .../org.neo4j.jmx.impl.ManagementBeanProvider | 1 + .../management/CausalClusteringBeanTest.java | 129 +++++++++++++++++ .../ha/management/BranchedStoreBean.java | 2 +- .../neo4j/management/CausalClustering.java | 39 +++++ 10 files changed, 374 insertions(+), 63 deletions(-) create mode 100644 enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/management/CausalClusteringBean.java create mode 100644 enterprise/causal-clustering/src/main/resources/META-INF/services/org.neo4j.jmx.impl.ManagementBeanProvider create mode 100644 enterprise/causal-clustering/src/test/java/org/neo4j/causalclustering/management/CausalClusteringBeanTest.java create mode 100644 enterprise/management/src/main/java/org/neo4j/management/CausalClustering.java diff --git a/community/io/src/main/java/org/neo4j/io/fs/FileUtils.java b/community/io/src/main/java/org/neo4j/io/fs/FileUtils.java index 360b7248f1631..598175271351b 100644 --- a/community/io/src/main/java/org/neo4j/io/fs/FileUtils.java +++ b/community/io/src/main/java/org/neo4j/io/fs/FileUtils.java @@ -68,6 +68,7 @@ public class FileUtils private FileUtils() { + throw new AssertionError(); } public static void deleteRecursively( File directory ) throws IOException @@ -515,7 +516,7 @@ public interface LineListener public static LineListener echo( final PrintStream target ) { - return line -> target.println( line ); + return target::println; } public static void readTextFile( File file, LineListener listener ) throws IOException @@ -762,4 +763,34 @@ public FileVisitResult postVisitDirectory( Path dir, IOException exc ) return size.getValue(); } + + /** + * Calculates the size of a given directory or file given the provided abstract filesystem. + * + * @param fs the filesystem abstraction to use + * @param path to the file or directory. + * @return the size, in bytes, of the file or the total size of the content in the directory, including + * subdirectories. + */ + public static long size( FileSystemAbstraction fs, File path ) + { + if ( fs.isDirectory( path ) ) + { + long size = 0L; + File[] files = fs.listFiles( path ); + if ( files == null ) + { + return 0L; + } + for ( File child : files ) + { + size += size( fs, child ); + } + return size; + } + else + { + return fs.getFileSize( path ); + } + } } diff --git a/community/jmx/src/main/java/org/neo4j/jmx/impl/StoreFileBean.java b/community/jmx/src/main/java/org/neo4j/jmx/impl/StoreFileBean.java index 0a499186861ed..c685f0e445df2 100644 --- a/community/jmx/src/main/java/org/neo4j/jmx/impl/StoreFileBean.java +++ b/community/jmx/src/main/java/org/neo4j/jmx/impl/StoreFileBean.java @@ -25,6 +25,7 @@ import org.neo4j.helpers.Service; import org.neo4j.io.fs.FileSystemAbstraction; +import org.neo4j.io.fs.FileUtils; import org.neo4j.jmx.StoreFile; import org.neo4j.kernel.NeoStoreDataSource; import org.neo4j.kernel.api.index.SchemaIndexProvider; @@ -123,40 +124,18 @@ private File resolvePath( NeoStoreDataSource ds ) @Override public long getTotalStoreSize() { - return storePath == null ? 0 : sizeOf( fs, storePath ); + return storePath == null ? 0 : FileUtils.size( fs, storePath ); } @Override public long getLogicalLogSize() { - return logFile == null ? 0 : sizeOf( fs, logFile.currentLogFile() ); - } - - private static long sizeOf( FileSystemAbstraction fs, File file ) - { - if ( fs.isDirectory( file ) ) - { - long size = 0; - File[] files = fs.listFiles( file ); - if ( files == null ) - { - return 0; - } - for ( File child : files ) - { - size += sizeOf( fs, child ); - } - return size; - } - else - { - return fs.getFileSize( file ); - } + return logFile == null ? 0 : FileUtils.size( fs, logFile.currentLogFile() ); } private long sizeOf( String name ) { - return storePath == null ? 0 : sizeOf( fs, new File( storePath, name ) ); + return storePath == null ? 0 : FileUtils.size( fs, new File( storePath, name ) ); } @Override @@ -178,19 +157,19 @@ public long getAllLogicalLogsSize() @Override public long getIndexStoreSize() { - long size = 0; + long size = 0L; // Add legacy indices for ( IndexImplementation index : legacyIndexProviderLookup.all() ) { - size += sizeOf( fs, index.getIndexImplementationDirectory( storePath ) ); + size += FileUtils.size( fs, index.getIndexImplementationDirectory( storePath ) ); } // Add schema index - size += sizeOf( fs, schemaIndexProvider.getSchemaIndexStoreDirectory( storePath ) ); + size += FileUtils.size( fs, schemaIndexProvider.getSchemaIndexStoreDirectory( storePath ) ); // Add label index - size += sizeOf( fs, labelScanStore.getLabelScanStoreFile() ); + size += FileUtils.size( fs, labelScanStore.getLabelScanStoreFile() ); return size; } @@ -237,7 +216,7 @@ long getTotalSize() @Override public void visit( File file, long logVersion ) { - totalSize += sizeOf( fs, file ); + totalSize += FileUtils.size( fs, file ); } } } diff --git a/community/jmx/src/test/java/org/neo4j/jmx/impl/StoreFileBeanTest.java b/community/jmx/src/test/java/org/neo4j/jmx/impl/StoreFileBeanTest.java index e4493a54c350a..eadd7c87c2c9f 100644 --- a/community/jmx/src/test/java/org/neo4j/jmx/impl/StoreFileBeanTest.java +++ b/community/jmx/src/test/java/org/neo4j/jmx/impl/StoreFileBeanTest.java @@ -24,6 +24,7 @@ import org.mockito.Mockito; import java.io.File; +import java.io.IOException; import java.nio.ByteBuffer; import org.neo4j.graphdb.mockfs.EphemeralFileSystemAbstraction; @@ -94,19 +95,8 @@ public void setUp() throws Throwable @Test public void shouldCountAllLogFiles() throws Throwable { - try ( StoreChannel storeChannel = fs.create( physicalLogFiles.getLogFileForVersion( 0 ) ) ) - { - byte[] bytes = new byte[10]; - ByteBuffer buffer = ByteBuffer.wrap( bytes ); - storeChannel.writeAll( buffer ); - } - - try ( StoreChannel storeChannel = fs.create( physicalLogFiles.getLogFileForVersion( 1 ) ) ) - { - byte[] bytes = new byte[20]; - ByteBuffer buffer = ByteBuffer.wrap( bytes ); - storeChannel.writeAll( buffer ); - } + createFileOfSize( physicalLogFiles.getLogFileForVersion( 0 ), 10 ); + createFileOfSize( physicalLogFiles.getLogFileForVersion( 1 ), 20 ); assertEquals( 30, storeFileBean.getAllLogicalLogsSize() ); } @@ -116,12 +106,7 @@ public void shouldCountAllIndexFiles() throws Exception { // Legacy index file File legacyIndex = new File( storeDir, "legacyIndex" ); - try ( StoreChannel storeChannel = fs.create( legacyIndex ) ) - { - byte[] bytes = new byte[10]; - ByteBuffer buffer = ByteBuffer.wrap( bytes ); - storeChannel.writeAll( buffer ); - } + createFileOfSize( legacyIndex, 10 ); IndexImplementation indexImplementation = mock( IndexImplementation.class ); when( indexImplementation.getIndexImplementationDirectory( Mockito.any() ) ).thenReturn( legacyIndex ); @@ -129,25 +114,25 @@ public void shouldCountAllIndexFiles() throws Exception // Legacy index file File schemaIndex = new File( storeDir, "schemaIndex" ); - try ( StoreChannel storeChannel = fs.create( schemaIndex ) ) - { - byte[] bytes = new byte[5]; - ByteBuffer buffer = ByteBuffer.wrap( bytes ); - storeChannel.writeAll( buffer ); - } + createFileOfSize( schemaIndex, 5 ); when( schemaIndexProvider.getSchemaIndexStoreDirectory( Mockito.any() ) ).thenReturn( schemaIndex ); // Label scan store File labelScan = new File( storeDir, "labelScanStore" ); - try ( StoreChannel storeChannel = fs.create( labelScan ) ) - { - byte[] bytes = new byte[20]; - ByteBuffer buffer = ByteBuffer.wrap( bytes ); - storeChannel.writeAll( buffer ); - } + createFileOfSize( labelScan, 20 ); when( labelScanStore.getLabelScanStoreFile() ).thenReturn( labelScan ); // Count all files assertEquals( 35, storeFileBean.getIndexStoreSize() ); } + + private void createFileOfSize( File file, int size ) throws IOException + { + try ( StoreChannel storeChannel = fs.create( file ) ) + { + byte[] bytes = new byte[size]; + ByteBuffer buffer = ByteBuffer.wrap( bytes ); + storeChannel.writeAll( buffer ); + } + } } diff --git a/enterprise/causal-clustering/pom.xml b/enterprise/causal-clustering/pom.xml index 4c6b132a49f79..b23d5945e84c4 100644 --- a/enterprise/causal-clustering/pom.xml +++ b/enterprise/causal-clustering/pom.xml @@ -73,6 +73,17 @@ ${project.version} + + org.neo4j + neo4j-jmx + ${project.version} + + + org.neo4j + neo4j-management + ${project.version} + + org.neo4j neo4j-consistency-check diff --git a/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/core/EnterpriseCoreEditionModule.java b/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/core/EnterpriseCoreEditionModule.java index 368febd6b530b..2aaf163d83f71 100644 --- a/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/core/EnterpriseCoreEditionModule.java +++ b/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/core/EnterpriseCoreEditionModule.java @@ -171,6 +171,7 @@ public void registerEditionSpecificProcedures( Procedures procedures ) throws Ke { throw new RuntimeException( e ); } + dependencies.satisfyDependency( clusterStateDirectory ); eligibleForIdReuse = IdReuseEligibility.ALWAYS; diff --git a/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/management/CausalClusteringBean.java b/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/management/CausalClusteringBean.java new file mode 100644 index 0000000000000..09006cb262248 --- /dev/null +++ b/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/management/CausalClusteringBean.java @@ -0,0 +1,135 @@ +/* + * 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 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 . + */ +package org.neo4j.causalclustering.management; + + +import java.io.File; +import javax.management.NotCompliantMBeanException; + +import org.neo4j.causalclustering.core.CoreGraphDatabase; +import org.neo4j.causalclustering.core.consensus.RaftMachine; +import org.neo4j.causalclustering.core.state.ClusterStateDirectory; +import org.neo4j.helpers.Service; +import org.neo4j.io.fs.FileSystemAbstraction; +import org.neo4j.io.fs.FileUtils; +import org.neo4j.jmx.impl.ManagementBeanProvider; +import org.neo4j.jmx.impl.ManagementData; +import org.neo4j.jmx.impl.Neo4jMBean; +import org.neo4j.management.CausalClustering; + +import static org.neo4j.causalclustering.core.consensus.log.RaftLog.RAFT_LOG_DIRECTORY_NAME; + +@Service.Implementation( ManagementBeanProvider.class ) +public class CausalClusteringBean extends ManagementBeanProvider +{ + CausalClusteringBean() + { + super( CausalClustering.class ); + } + + @Override + protected Neo4jMBean createMBean( ManagementData management ) throws NotCompliantMBeanException + { + if ( isCausalClustering( management ) ) + { + return new CausalClusteringBeanImpl( management ); + } + return null; + } + + @Override + protected Neo4jMBean createMXBean( ManagementData management ) throws NotCompliantMBeanException + { + if ( isCausalClustering( management ) ) + { + return new CausalClusteringBeanImpl( management, true ); + } + return null; + } + + private static boolean isCausalClustering( ManagementData management ) + { + return management.getKernelData().graphDatabase() instanceof CoreGraphDatabase; + } + + private static class CausalClusteringBeanImpl extends Neo4jMBean implements CausalClustering + { + private final ClusterStateDirectory clusterStateDirectory; + private final RaftMachine raftMachine; + private final FileSystemAbstraction fs; + + CausalClusteringBeanImpl( ManagementData management ) throws NotCompliantMBeanException + { + super( management ); + clusterStateDirectory = management.resolveDependency( ClusterStateDirectory.class ); + raftMachine = management.resolveDependency( RaftMachine.class ); + + fs = management.getKernelData().getFilesystemAbstraction(); + } + + CausalClusteringBeanImpl( ManagementData management, boolean isMXBean ) + { + super( management, isMXBean ); + clusterStateDirectory = management.resolveDependency( ClusterStateDirectory.class ); + raftMachine = management.resolveDependency( RaftMachine.class ); + + fs = management.getKernelData().getFilesystemAbstraction(); + } + + @Override + public String getRole() + { + return raftMachine.currentRole().toString(); + } + + @Override + public long getRaftLogSize() + { + File raftLogDirectory = new File( clusterStateDirectory.get(), RAFT_LOG_DIRECTORY_NAME ); + return FileUtils.size( fs, raftLogDirectory ); + } + + @Override + public long getReplicatedStateSize() + { + File replicatedStateDirectory = clusterStateDirectory.get(); + + File[] files = fs.listFiles( replicatedStateDirectory ); + if ( files == null ) + { + return 0L; + } + + long size = 0L; + for ( File file : files ) + { + // Exclude raft log that resides in same directory + if ( fs.isDirectory( file ) && file.getName().equals( RAFT_LOG_DIRECTORY_NAME ) ) + { + continue; + } + + size += FileUtils.size( fs, file ); + } + + return size; + } + } +} diff --git a/enterprise/causal-clustering/src/main/resources/META-INF/services/org.neo4j.jmx.impl.ManagementBeanProvider b/enterprise/causal-clustering/src/main/resources/META-INF/services/org.neo4j.jmx.impl.ManagementBeanProvider new file mode 100644 index 0000000000000..6ae9c7b673bcf --- /dev/null +++ b/enterprise/causal-clustering/src/main/resources/META-INF/services/org.neo4j.jmx.impl.ManagementBeanProvider @@ -0,0 +1 @@ +org.neo4j.causalclustering.management.CausalClusteringBean diff --git a/enterprise/causal-clustering/src/test/java/org/neo4j/causalclustering/management/CausalClusteringBeanTest.java b/enterprise/causal-clustering/src/test/java/org/neo4j/causalclustering/management/CausalClusteringBeanTest.java new file mode 100644 index 0000000000000..58a4fdac3a556 --- /dev/null +++ b/enterprise/causal-clustering/src/test/java/org/neo4j/causalclustering/management/CausalClusteringBeanTest.java @@ -0,0 +1,129 @@ +/* + * 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 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 . + */ +package org.neo4j.causalclustering.management; + +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.neo4j.causalclustering.core.CoreGraphDatabase; +import org.neo4j.causalclustering.core.consensus.RaftMachine; +import org.neo4j.causalclustering.core.consensus.roles.Role; +import org.neo4j.causalclustering.core.state.ClusterStateDirectory; +import org.neo4j.graphdb.mockfs.EphemeralFileSystemAbstraction; +import org.neo4j.io.fs.FileSystemAbstraction; +import org.neo4j.io.fs.StoreChannel; +import org.neo4j.io.pagecache.PageCache; +import org.neo4j.jmx.impl.ManagementData; +import org.neo4j.jmx.impl.ManagementSupport; +import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.impl.util.Dependencies; +import org.neo4j.kernel.internal.DefaultKernelData; +import org.neo4j.kernel.internal.GraphDatabaseAPI; +import org.neo4j.kernel.internal.KernelData; +import org.neo4j.management.CausalClustering; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.neo4j.causalclustering.core.consensus.log.RaftLog.RAFT_LOG_DIRECTORY_NAME; +import static org.neo4j.causalclustering.core.state.machines.CoreStateMachinesModule.ID_ALLOCATION_NAME; +import static org.neo4j.causalclustering.core.state.machines.CoreStateMachinesModule.LOCK_TOKEN_NAME; + +public class CausalClusteringBeanTest +{ + private final FileSystemAbstraction fs = new EphemeralFileSystemAbstraction(); + private final GraphDatabaseAPI db = mock( CoreGraphDatabase.class ); + private final File dataDir = new File( "dataDir" ); + private final ClusterStateDirectory clusterStateDirectory = ClusterStateDirectory.withoutInitializing( dataDir ); + private final RaftMachine raftMachine = mock( RaftMachine.class ); + private CausalClustering ccBean; + + @Before + public void setUp() throws Exception + { + KernelData kernelData = + new DefaultKernelData( fs, mock( PageCache.class ), new File( "storeDir" ), Config.empty(), db ); + + Dependencies dependencies = new Dependencies(); + dependencies.satisfyDependency( clusterStateDirectory ); + dependencies.satisfyDependency( raftMachine ); + + when( db.getDependencyResolver() ).thenReturn( dependencies ); + ManagementData data = new ManagementData( new CausalClusteringBean(), kernelData, ManagementSupport.load() ); + + ccBean = (CausalClustering) new CausalClusteringBean().createMBean( data ); + } + + @Test + public void getCurrentRoleFromRaftMachine() throws Exception + { + when( raftMachine.currentRole() ).thenReturn( Role.LEADER, Role.FOLLOWER, Role.CANDIDATE ); + assertEquals( "LEADER", ccBean.getRole() ); + assertEquals( "FOLLOWER", ccBean.getRole() ); + assertEquals( "CANDIDATE", ccBean.getRole() ); + } + + @Test + public void returnSumOfRaftLogDirectory() throws Exception + { + File raftLogDirectory = new File( clusterStateDirectory.get(), RAFT_LOG_DIRECTORY_NAME ); + fs.mkdirs( raftLogDirectory ); + + createFileOfSize( new File( raftLogDirectory, "raftLog1" ), 5 ); + createFileOfSize( new File( raftLogDirectory, "raftLog2" ), 10 ); + + assertEquals( 15L, ccBean.getRaftLogSize() ); + } + + @Test + public void excludeRaftLogFromReplicatedStateSize() throws Exception + { + File stateDir = clusterStateDirectory.get(); + + // Raft log + File raftLogDirectory = new File( stateDir, RAFT_LOG_DIRECTORY_NAME ); + fs.mkdirs( raftLogDirectory ); + createFileOfSize( new File( raftLogDirectory, "raftLog1" ), 5 ); + + // Other state + File idAllocationDir = new File( stateDir, ID_ALLOCATION_NAME ); + fs.mkdirs( idAllocationDir ); + createFileOfSize( new File( idAllocationDir, "state" ), 10 ); + File lockTokenDir = new File( stateDir, LOCK_TOKEN_NAME ); + fs.mkdirs( lockTokenDir ); + createFileOfSize( new File( lockTokenDir, "state" ), 20 ); + + assertEquals( 30L, ccBean.getReplicatedStateSize() ); + } + + private void createFileOfSize( File file, int size ) throws IOException + { + try ( StoreChannel storeChannel = fs.create( file ) ) + { + byte[] bytes = new byte[size]; + ByteBuffer buffer = ByteBuffer.wrap( bytes ); + storeChannel.writeAll( buffer ); + } + } +} diff --git a/enterprise/ha/src/main/java/org/neo4j/kernel/ha/management/BranchedStoreBean.java b/enterprise/ha/src/main/java/org/neo4j/kernel/ha/management/BranchedStoreBean.java index 483ea682181a7..68000abece4d5 100644 --- a/enterprise/ha/src/main/java/org/neo4j/kernel/ha/management/BranchedStoreBean.java +++ b/enterprise/ha/src/main/java/org/neo4j/kernel/ha/management/BranchedStoreBean.java @@ -124,7 +124,7 @@ private BranchedStoreInfo parseBranchedStore( File branchDirectory ) final File neoStoreFile = new File( branchDirectory, MetaDataStore.DEFAULT_NAME ); final long txId = MetaDataStore.getRecord( pageCache, neoStoreFile, Position.LAST_TRANSACTION_ID ); final long timestamp = Long.parseLong( branchDirectory.getName() ); - final long branchedStoreSize = FileUtils.size( branchDirectory ); + final long branchedStoreSize = FileUtils.size( fileSystem, branchDirectory ); return new BranchedStoreInfo( branchDirectory.getName(), txId, timestamp, branchedStoreSize ); } diff --git a/enterprise/management/src/main/java/org/neo4j/management/CausalClustering.java b/enterprise/management/src/main/java/org/neo4j/management/CausalClustering.java new file mode 100644 index 0000000000000..7f584418a27ec --- /dev/null +++ b/enterprise/management/src/main/java/org/neo4j/management/CausalClustering.java @@ -0,0 +1,39 @@ +/* + * 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 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 . + */ +package org.neo4j.management; + +import org.neo4j.jmx.Description; +import org.neo4j.jmx.ManagementInterface; + +@ManagementInterface( name = CausalClustering.NAME ) +@Description( "Information about an instance participating in a causal cluster" ) +public interface CausalClustering +{ + String NAME = "Causal Clustering"; + + @Description( "The current role this member has in the cluster" ) + String getRole(); + + @Description( "The total amount of disk space used by the raft log, in bytes" ) + long getRaftLogSize(); + + @Description( "The total amount of disk space used by the replicated states, in bytes" ) + long getReplicatedStateSize(); +}