check-data-integrity v2
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target/
|
||||||
40
pom.xml
Normal file
40
pom.xml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>de.extio</groupId>
|
||||||
|
<artifactId>check-data-integrity2</artifactId>
|
||||||
|
<version>2.0.0</version>
|
||||||
|
<name>check-data-integrity</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>21</java.version>
|
||||||
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<shadedArtifactAttached>true</shadedArtifactAttached>
|
||||||
|
<transformers>
|
||||||
|
<transformer
|
||||||
|
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
|
<mainClass>checkDataIntegrity.Main</mainClass>
|
||||||
|
</transformer>
|
||||||
|
</transformers>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
5
src/main/java/de/extio/checkdataintegrity/Args.java
Normal file
5
src/main/java/de/extio/checkdataintegrity/Args.java
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package de.extio.checkdataintegrity;
|
||||||
|
|
||||||
|
record Args(String path0, String path1, int threads, int skip, int timeLimit) {
|
||||||
|
|
||||||
|
}
|
||||||
29
src/main/java/de/extio/checkdataintegrity/BufferPool.java
Normal file
29
src/main/java/de/extio/checkdataintegrity/BufferPool.java
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package de.extio.checkdataintegrity;
|
||||||
|
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
|
class BufferPool {
|
||||||
|
|
||||||
|
private static final LinkedBlockingQueue<Buffer> queue = new LinkedBlockingQueue<>();
|
||||||
|
|
||||||
|
static Buffer get() {
|
||||||
|
Buffer buf = queue.poll();
|
||||||
|
if (buf == null) {
|
||||||
|
buf = new Buffer();
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void put(final Buffer buf) {
|
||||||
|
queue.offer(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Buffer {
|
||||||
|
|
||||||
|
byte[] buf = new byte[16384];
|
||||||
|
|
||||||
|
volatile int len;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
98
src/main/java/de/extio/checkdataintegrity/Main.java
Normal file
98
src/main/java/de/extio/checkdataintegrity/Main.java
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package de.extio.checkdataintegrity;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.FileVisitResult;
|
||||||
|
import java.nio.file.FileVisitor;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
public class Main {
|
||||||
|
|
||||||
|
public static void main(final String[] args) throws Exception {
|
||||||
|
final Args parsedArgs = parseArgs(args);
|
||||||
|
if (parsedArgs == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ProgramState state = new ProgramState(System.currentTimeMillis(), new AtomicLong(), new AtomicLong(), new AtomicLong());
|
||||||
|
final WorkerPool workerPool = new WorkerPool(parsedArgs, state, parsedArgs.threads());
|
||||||
|
|
||||||
|
walk(parsedArgs, state, workerPool);
|
||||||
|
|
||||||
|
workerPool.awaitEmpty();
|
||||||
|
workerPool.interrupt();
|
||||||
|
workerPool.join();
|
||||||
|
|
||||||
|
printStats(state);
|
||||||
|
|
||||||
|
System.exit(state.countErr().get() > 0 ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Args parseArgs(final String[] args) {
|
||||||
|
if (args.length < 5) {
|
||||||
|
System.err.println("Usage: checkDataIntegrity.java <Path0> <Path1> <threads> <skip> <timeLimit ms>");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Args result = new Args(
|
||||||
|
args[0],
|
||||||
|
args[1],
|
||||||
|
Integer.parseInt(args[2]),
|
||||||
|
Integer.parseInt(args[3]),
|
||||||
|
Integer.parseInt(args[4]));
|
||||||
|
System.out.println(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void printStats(final ProgramState state) {
|
||||||
|
final Duration duration = Duration.ofMillis(System.currentTimeMillis() - state.start());
|
||||||
|
System.out.println("Took: " + duration.toString());
|
||||||
|
System.out.println("Files checked: " + state.count().get());
|
||||||
|
if (duration.toSeconds() > 0) {
|
||||||
|
System.out.println("Throughput: " + (state.bytes().get() / duration.toSeconds() / 1024 / 1024) + " Mib/s");
|
||||||
|
}
|
||||||
|
System.out.println("Errors: " + state.countErr().get());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void walk(final Args parsedArgs, final ProgramState state, final WorkerPool workerPool) throws IOException {
|
||||||
|
final AtomicLong walkTreeCounter = new AtomicLong();
|
||||||
|
Files.walkFileTree(Path.of(parsedArgs.path0()), new FileVisitor<>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException {
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
|
||||||
|
if (attrs.isRegularFile() && walkTreeCounter.incrementAndGet() > parsedArgs.skip()) {
|
||||||
|
try {
|
||||||
|
workerPool.put(file);
|
||||||
|
}
|
||||||
|
catch (final InterruptedException e) {
|
||||||
|
return FileVisitResult.TERMINATE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean timeLimitReached = System.currentTimeMillis() - state.start() >= parsedArgs.timeLimit();
|
||||||
|
if (timeLimitReached) {
|
||||||
|
System.out.println("Time limit reached");
|
||||||
|
}
|
||||||
|
return timeLimitReached ? FileVisitResult.TERMINATE : FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileVisitResult visitFileFailed(final Path file, final IOException exc) throws IOException {
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException {
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package de.extio.checkdataintegrity;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
record ProgramState(long start, AtomicLong count, AtomicLong countErr, AtomicLong bytes) {
|
||||||
|
|
||||||
|
}
|
||||||
118
src/main/java/de/extio/checkdataintegrity/Task.java
Normal file
118
src/main/java/de/extio/checkdataintegrity/Task.java
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package de.extio.checkdataintegrity;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.nio.file.InvalidPathException;
|
||||||
|
import java.nio.file.NoSuchFileException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import de.extio.checkdataintegrity.BufferPool.Buffer;
|
||||||
|
|
||||||
|
class Task implements Consumer<Path> {
|
||||||
|
|
||||||
|
private final Args args;
|
||||||
|
|
||||||
|
private final ProgramState state;
|
||||||
|
|
||||||
|
private final ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
|
||||||
|
|
||||||
|
Task(final Args args, final ProgramState state) {
|
||||||
|
this.args = args;
|
||||||
|
this.state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(final Path path) {
|
||||||
|
try {
|
||||||
|
final long cnt = this.state.count().incrementAndGet();
|
||||||
|
if (cnt % 1000 == 0) {
|
||||||
|
System.out.println(cnt);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String rel = path.toString().substring(this.args.path0().length());
|
||||||
|
final Path p1 = Path.of(this.args.path1() + rel);
|
||||||
|
|
||||||
|
try (RandomAccessFile raf0 = new RandomAccessFile(path.toFile(), "r"); RandomAccessFile raf1 = new RandomAccessFile(p1.toFile(), "r")) {
|
||||||
|
final BlockingQueue<Buffer> queue0 = new LinkedBlockingQueue<>(1);
|
||||||
|
final BlockingQueue<Buffer> queue1 = new LinkedBlockingQueue<>(1);
|
||||||
|
final Future<?> future0 = this.executorService.submit(() -> this.loader(raf0, queue0));
|
||||||
|
final Future<?> future1 = this.executorService.submit(() -> this.loader(raf1, queue1));
|
||||||
|
try {
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
final Buffer buf0 = queue0.take();
|
||||||
|
final Buffer buf1 = queue1.take();
|
||||||
|
try {
|
||||||
|
if (buf0.len != buf1.len) {
|
||||||
|
System.out.println(rel + ": Length mismatch");
|
||||||
|
this.state.countErr().incrementAndGet();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (buf0.len == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.state.bytes().addAndGet(buf0.len);
|
||||||
|
if (!Arrays.equals(buf0.buf, 0, buf0.len, buf1.buf, 0, buf1.len)) {
|
||||||
|
System.out.println(rel + ": Data mismatch");
|
||||||
|
this.state.countErr().incrementAndGet();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
BufferPool.put(buf0);
|
||||||
|
BufferPool.put(buf1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
future0.cancel(true);
|
||||||
|
future1.cancel(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NoSuchFileException | FileNotFoundException | InvalidPathException ex) {
|
||||||
|
}
|
||||||
|
catch (final InterruptedException ex) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
catch (final Exception ex) {
|
||||||
|
System.out.println(path + ": Exception: " + ex.getClass().getName() + " " + ex.getMessage());
|
||||||
|
this.state.countErr().incrementAndGet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loader(final RandomAccessFile raf, final BlockingQueue<Buffer> queue) {
|
||||||
|
try {
|
||||||
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
|
final Buffer buf = BufferPool.get();
|
||||||
|
buf.len = raf.read(buf.buf);
|
||||||
|
if (buf.len == -1) {
|
||||||
|
queue.put(buf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
queue.put(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (final IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
final Buffer buf = BufferPool.get();
|
||||||
|
buf.len = -1;
|
||||||
|
try {
|
||||||
|
queue.put(buf);
|
||||||
|
}
|
||||||
|
catch (final InterruptedException e1) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (final InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
30
src/main/java/de/extio/checkdataintegrity/Worker.java
Normal file
30
src/main/java/de/extio/checkdataintegrity/Worker.java
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package de.extio.checkdataintegrity;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
|
||||||
|
class Worker extends Thread {
|
||||||
|
|
||||||
|
private final Task task;
|
||||||
|
|
||||||
|
private final BlockingQueue<Path> queue;
|
||||||
|
|
||||||
|
public Worker(final Args args, final ProgramState state, final BlockingQueue<Path> queue) {
|
||||||
|
super();
|
||||||
|
this.queue = queue;
|
||||||
|
this.task = new Task(args, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
while (!Thread.interrupted()) {
|
||||||
|
final Path p = this.queue.take();
|
||||||
|
this.task.accept(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (final InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
62
src/main/java/de/extio/checkdataintegrity/WorkerPool.java
Normal file
62
src/main/java/de/extio/checkdataintegrity/WorkerPool.java
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package de.extio.checkdataintegrity;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
|
class WorkerPool {
|
||||||
|
|
||||||
|
private final List<Worker> workers;
|
||||||
|
|
||||||
|
private final BlockingQueue<Path> queue;
|
||||||
|
|
||||||
|
WorkerPool(final Args args, final ProgramState state, final int threads) {
|
||||||
|
System.out.println("Threads: " + threads);
|
||||||
|
|
||||||
|
this.queue = new LinkedBlockingQueue<>(threads * 2);
|
||||||
|
|
||||||
|
this.workers = new ArrayList<>(threads);
|
||||||
|
for (int i = 0; i < threads; i++) {
|
||||||
|
final Worker worker = new Worker(args, state, this.queue);
|
||||||
|
worker.setDaemon(true);
|
||||||
|
worker.setName("Worker #" + i);
|
||||||
|
worker.start();
|
||||||
|
|
||||||
|
this.workers.add(worker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void put(final Path path) throws InterruptedException {
|
||||||
|
this.queue.put(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void awaitEmpty() {
|
||||||
|
while (!this.queue.isEmpty()) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(10);
|
||||||
|
}
|
||||||
|
catch (final InterruptedException e) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void interrupt() {
|
||||||
|
for (final Worker worker : this.workers) {
|
||||||
|
worker.interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void join() {
|
||||||
|
for (final Worker worker : this.workers) {
|
||||||
|
try {
|
||||||
|
worker.join();
|
||||||
|
}
|
||||||
|
catch (final InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user