package the8472.mldht; import static the8472.utils.Functional.unchecked; import java.io.IOException; import java.net.InetAddress; import java.nio.file.Path; import java.time.Duration; import java.time.Instant; import java.util.Collection; import java.util.Comparator; import java.util.Formatter; import java.util.List; import java.util.Map; import java.util.NavigableMap; import java.util.Optional; import java.util.TreeMap; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; import lbms.plugins.mldht.kad.DHT; import lbms.plugins.mldht.kad.Database; import lbms.plugins.mldht.kad.Database.PeersSeeds; import lbms.plugins.mldht.kad.GenericStorage; import lbms.plugins.mldht.kad.GenericStorage.StorageItem; import lbms.plugins.mldht.kad.KBucketEntry; import lbms.plugins.mldht.kad.Key; import lbms.plugins.mldht.kad.Node; import lbms.plugins.mldht.kad.Node.RoutingTable; import lbms.plugins.mldht.kad.Prefix; import the8472.bencode.Utils; import the8472.utils.Arrays; import the8472.utils.io.FileIO; import the8472.utils.io.NetMask; public class Diagnostics { Collection<DHT> dhts; Path logDir; public void init(Collection<DHT> dhts, Path logDir) { this.dhts = dhts; this.logDir = logDir; dhts.stream().findAny().ifPresent(d -> d.getScheduler().scheduleWithFixedDelay(this::writeAll, 10, 30, TimeUnit.SECONDS)); } void writeAll() { try { printMain(); printRoutingTable(); printDatabases(); printPUTStorage(); } catch (Exception e) { e.printStackTrace(); } } void printPUTStorage() throws IOException { Path file = logDir.resolve("putDB.log"); FileIO.writeAndAtomicMove(file, writer -> dhts.stream().filter(DHT::isRunning).forEach(d -> { writer.append("Type: " + d.getType().shortName + "\n"); formatStorage(writer, d.getStorage()); })); } public void formatStorage(Appendable writer, GenericStorage storage) { Formatter f = new Formatter(writer); storage.getItems().forEach((k, v) -> { f.format("%s mutable:%b seq:%d %n", k, v.mutable(), v.seq() ); f.format("%s%n%n", Utils.stripToAscii(v.getRawValue())); }); } void printDatabases() throws Exception { Path file = logDir.resolve("getPeersDB.log"); FileIO.writeAndAtomicMove(file, writer -> dhts.stream().filter(DHT::isRunning).forEach(d -> { writer.append("Type: " + d.getType().shortName + "\n"); formatDatabase(writer, d.getDatabase()); })); } /* TODO: fix this java.lang.IllegalArgumentException: Comparison method violates its general contract! at java.util.TimSort.mergeLo(TimSort.java:777) at java.util.TimSort.mergeAt(TimSort.java:514) at java.util.TimSort.mergeCollapse(TimSort.java:441) at java.util.TimSort.sort(TimSort.java:245) at java.util.Arrays.sort(Arrays.java:1512) at java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:348) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418) at the8472.mldht.Diagnostics.formatDatabase(Diagnostics.java:102) at the8472.mldht.Diagnostics.lambda$14(Diagnostics.java:91) at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175) at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418) at the8472.mldht.Diagnostics.lambda$3(Diagnostics.java:89) at the8472.mldht.Diagnostics.writeAndAtomicMove(Diagnostics.java:221) at the8472.mldht.Diagnostics.printDatabases(Diagnostics.java:89) at the8472.mldht.Diagnostics.writeAll(Diagnostics.java:56) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308) at the8472.utils.concurrent.NonblockingScheduledExecutor$SchedF.run(NonblockingScheduledExecutor.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) */ public void formatDatabase(Appendable writer, Database db) { Map<Key, PeersSeeds> items = db.getData(); Formatter f = new Formatter(writer); f.format("Keys: %d Entries: %d%n", items.size(), items.values().stream().collect(Collectors.summingInt(l -> l.size()))); items.entrySet().stream().sorted((a,b) -> b.getValue().size() - a.getValue().size()).forEach(e -> { PeersSeeds v = e.getValue(); f.format("%s s:%5d p:%5d ∑:%5d%n", e.getKey().toString(false), v.seeds().size(), v.peers().size(), v.size()); }); f.format("%n======%n%n"); Instant now = Instant.now(); items.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEachOrdered((e) -> { Key k = e.getKey(); PeersSeeds v = e.getValue(); f.format("%s (s:%d p:%d)%n", k.toString(false), v.seeds().size(), v.peers().size()); Stream.concat(v.seeds().stream(), v.peers().stream()).sorted((a,b) -> Arrays.compareUnsigned(a.getData(), b.getData())).forEachOrdered(i -> { unchecked(() -> writer.append(" ").append(i.toString()).append(" age: ").append(Duration.between(Instant.ofEpochMilli(i.getCreatedAt()), now).toString()).append('\n')); }); f.format("%n"); }); f.format("%n%n"); } private void printRoutingTable() throws Exception { Path file = logDir.resolve("routingTable.log"); FileIO.writeAndAtomicMove(file, writer -> dhts.stream().filter(DHT::isRunning).forEach(d -> this.formatRoutingTable(writer, d.getNode()))); } public void formatRoutingTable(Appendable writer, Node node) { Collection<Key> localIds = node.localIDs(); RoutingTable entries = node.table(); Collection<NetMask> masks = node.getTrustedNetMasks(); NavigableMap<Key, PeersSeeds> peerDB = new TreeMap<>(node.getDHT().getDatabase().getData()); NavigableMap<Key, StorageItem> putDB = new TreeMap<>(node.getDHT().getStorage().getItems()); Formatter f = new Formatter(writer); f.format("Type: %s%n", node.getDHT().getType().shortName); f.format("%nThrottled:%n"); Comparator<Map.Entry<InetAddress, Long>> comp = Map.Entry.<InetAddress, Long>comparingByValue() .reversed().thenComparing(Comparator.comparing(e -> e.getKey().getAddress(), Arrays::compareUnsigned)); node.throttledEntries().filter(e -> e.getValue() > Node.throttleThreshold).sorted(comp).forEach(e -> { f.format("%5d %s%n", e.getValue().intValue(), e.getKey()); }); f.format("%n"); Consumer<Prefix> dbMapper = (Prefix p) -> { NavigableMap<Key, PeersSeeds> subPeers = peerDB.subMap(p.first(), true, p.last(), true); if(subPeers.isEmpty()) f.format("%28s", ""); else f.format("ihash:%5d s:%5d p:%5d ", subPeers.size(), subPeers.values().stream().mapToInt(e -> e.seeds().size()).sum(), subPeers.values().stream().mapToInt(e -> e.peers().size()).sum()); NavigableMap<Key, StorageItem> subStorage = putDB.subMap(p.first(), true, p.last(), true); if(subStorage.isEmpty()) f.format("%14s", ""); else f.format("storage:%5d ", subStorage.size()); return; }; entries.stream().forEach(tableEntry -> { Optional<Key> localId = localIds.stream().filter(i -> tableEntry.prefix.isPrefixOf(i)).findAny(); String isHomeBucket = localId.map(k -> "[Home:"+k.toString(false)+"]").orElse(""); f.format("%s/%-3s main:%d rep:%d ", new Key(tableEntry.prefix).toString(false), tableEntry.prefix.getDepth(), tableEntry.getBucket().getNumEntries(), tableEntry.getBucket().getNumReplacements()); dbMapper.accept(tableEntry.prefix); f.format("%s %s%n", tableEntry.prefix, isHomeBucket); }); f.format("%n======%n%n"); entries.stream().forEach(tableEntry -> { Optional<Key> localId = localIds.stream().filter(i -> tableEntry.prefix.isPrefixOf(i)).findAny(); String isHomeBucket = localId.map(k -> "[Home:"+k.toString(false)+"]").orElse(""); f.format("%40s/%-3d ", new Key(tableEntry.prefix).toString(false), tableEntry.prefix.getDepth()); dbMapper.accept(tableEntry.prefix); f.format("%s%n", isHomeBucket); List<KBucketEntry> bucketEntries = tableEntry.getBucket().getEntries(); if(bucketEntries.size() > 0) { f.format(" Entries (%d)%n", bucketEntries.size()); bucketEntries.forEach(bucketEntry -> f.format(" %s %s%n", bucketEntry,masks.stream().anyMatch(m -> m.contains(bucketEntry.getAddress().getAddress())) ? "[trusted]" : "")); } List<KBucketEntry> replacements = tableEntry.getBucket().getReplacementEntries(); if(replacements.size() > 0) { f.format(" Replacements (%d)%n", replacements.size()); replacements.forEach(bucketEntry -> f.format(" %s%n", bucketEntry)); } if(bucketEntries.size() > 0 || replacements.size() > 0) f.format("%n"); }); f.format("%n%n"); } void printMain() throws Exception { Path diagnostics = logDir.resolve("diagnostics.log"); FileIO.writeAndAtomicMove(diagnostics, w -> dhts.stream().filter(DHT::isRunning).forEach(d -> d.printDiagnostics(w))); } }