Repository: apache/ozone
Pull request #8016: HDDS-12053. Make print-log-dag command run locally and offline
Author: login: Tejaskriya
Labels: name: tools
description: Tools that helps with debugging
Created: 2025-03-05T22:21:02Z | Merged: 2025-05-05T20:09:52Z

## What changes were proposed in this pull request?

ozone debug print-log-dag has a few implementation issues:

The command goes over the network, but writes the resulting graph locally to the server
The command requires the OM to be online, which may not be the case if it has crashed or we are examining disk state we received from a cluster we cannot access

Like other debug commands, This PR makes make this run only on the disk state present on OM. The OM process may be running while this is happening, but this is not required.

Most of the changes are of code refactoring to reduce code duplication between the command and RocksDBCheckpointDiffer.

## What is the link to the Apache JIRA

https://issues.apache.org/jira/browse/HDDS-12053

## How was this patch tested?

Existing tests for the refactored code.
For the command, tested manually using the tools jar created. 
```
Usage: ozone debug om generate-compaction-dag [-hV] [--verbose] --db=<dbPath>
       -o=<imageLocation>
Create an image of the current compaction log DAG. This command is an offline
command. i.e., it can run on any instance of om.db and does not require OM to
be up.
      --db=<dbPath>   Path to OM RocksDB
  -h, --help          Show this help message and exit.
  -o, --output-file=<imageLocation>
                      Path to location at which image will be downloaded.
                        Should include the image file name with ".png"
                        extension.
  -V, --version       Print version information and exit.
      --verbose       More verbose output. Show the stack trace of the errors.

```

The dag is obtained:
```
$ ozone debug om gcd --db=/home/tmp/metadata/om.db -o=/home/Desktop/dag-try-4.png
Graph was generated at '/home/Desktop/dag-try-4.png'.
```
![dag1](https://github.com/user-attachments/assets/f12c6738-665b-4fad-b179-87738a3eae40)





Diff:
diff --git a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/CompactionDag.java b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/CompactionDag.java
new file mode 100644
index 000000000000..a7d78d16a869
--- /dev/null
+++ b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/CompactionDag.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ozone.rocksdiff;
+
+import com.google.common.graph.GraphBuilder;
+import com.google.common.graph.MutableGraph;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import org.apache.ozone.compaction.log.CompactionFileInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Wrapper class storing DAGs of SST files for tracking compactions.
+ */
+public class CompactionDag {
+  private static final Logger LOG = LoggerFactory.getLogger(CompactionDag.class);
+
+  private final ConcurrentMap<String, CompactionNode> compactionNodeMap = new ConcurrentHashMap<>();
+  private final MutableGraph<CompactionNode> forwardCompactionDAG = GraphBuilder.directed().build();
+  private final MutableGraph<CompactionNode> backwardCompactionDAG = GraphBuilder.directed().build();
+
+  private CompactionNode addNodeToDAG(String file, long seqNum, String startKey, String endKey, String columnFamily) {
+    CompactionNode fileNode = new CompactionNode(file, seqNum, startKey, endKey, columnFamily);
+    backwardCompactionDAG.addNode(fileNode);
+    forwardCompactionDAG.addNode(fileNode);
+    return fileNode;
+  }
+
+  /**
+   * Populate the compaction DAG with input and output SST files lists.
+   *
+   * @param inputFiles  List of compaction input files.
+   * @param outputFiles List of compaction output files.
+   * @param seqNum      DB transaction sequence number.
+   */
+  public void populateCompactionDAG(List<CompactionFileInfo> inputFiles,
+      List<CompactionFileInfo> outputFiles,
+      long seqNum) {
+
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Input files: {} -> Output files: {}", inputFiles, outputFiles);
+    }
+
+    for (CompactionFileInfo outfile : outputFiles) {
+      final CompactionNode outfileNode = compactionNodeMap.computeIfAbsent(outfile.getFileName(),
+          file -> addNodeToDAG(file, seqNum, outfile.getStartKey(), outfile.getEndKey(), outfile.getColumnFamily()));
+
+      for (CompactionFileInfo infile : inputFiles) {
+        final CompactionNode infileNode = compactionNodeMap.computeIfAbsent(infile.getFileName(),
+            file -> addNodeToDAG(file, seqNum, infile.getStartKey(), infile.getEndKey(), infile.getColumnFamily()));
+
+        // Draw the edges
+        if (!Objects.equals(outfileNode.getFileName(), infileNode.getFileName())) {
+          forwardCompactionDAG.putEdge(outfileNode, infileNode);
+          backwardCompactionDAG.putEdge(infileNode, outfileNode);
+        }
+      }
+    }
+  }
+
+  public Set<String> pruneNodesFromDag(Set<CompactionNode> nodesToRemove) {
+    pruneBackwardDag(backwardCompactionDAG, nodesToRemove);
+    Set<String> sstFilesPruned = pruneForwardDag(forwardCompactionDAG, nodesToRemove);
+    // Remove SST file nodes from compactionNodeMap too,
+    // since those nodes won't be needed after clean up.
+    nodesToRemove.forEach(compactionNodeMap::remove);
+    return sstFilesPruned;
+  }
+
+  /**
+   * Prunes backward DAG's upstream from the level, that needs to be removed.
+   */
+  Set<String> pruneBackwardDag(MutableGraph<CompactionNode> backwardDag, Set<CompactionNode> startNodes) {
+    Set<String> removedFiles = new HashSet<>();
+    Set<CompactionNode> currentLevel = startNodes;
+
+    while (!currentLevel.isEmpty()) {
+      Set<CompactionNode> nextLevel = new HashSet<>();
+      for (CompactionNode current : currentLevel) {
+        if (!backwardDag.nodes().contains(current)) {
+          continue;
+        }
+
+        nextLevel.addAll(backwardDag.predecessors(current));
+        backwardDag.removeNode(current);
+        removedFiles.add(current.getFileName());
+      }
+      currentLevel = nextLevel;
+    }
+
+    return removedFiles;
+  }
+
+  /**
+   * Prunes forward DAG's downstream from the level that needs to be removed.
+   */
+  Set<String> pruneForwardDag(MutableGraph<CompactionNode> forwardDag, Set<CompactionNode> startNodes) {
+    Set<String> removedFiles = new HashSet<>();
+    Set<CompactionNode> currentLevel = new HashSet<>(startNodes);
+
+    while (!currentLevel.isEmpty()) {
+      Set<CompactionNode> nextLevel = new HashSet<>();
+      for (CompactionNode current : currentLevel) {
+        if (!forwardDag.nodes().contains(current)) {
+          continue;
+        }
+
+        nextLevel.addAll(forwardDag.successors(current));
+        forwardDag.removeNode(current);
+        removedFiles.add(current.getFileName());
+      }
+
+      currentLevel = nextLevel;
+    }
+
+    return removedFiles;
+  }
+
+  public MutableGraph<CompactionNode> getForwardCompactionDAG() {
+    return forwardCompactionDAG;
+  }
+
+  public MutableGraph<CompactionNode> getBackwardCompactionDAG() {
+    return backwardCompactionDAG;
+  }
+
+  public ConcurrentMap<String, CompactionNode> getCompactionMap() {
+    return compactionNodeMap;
+  }
+
+  public CompactionNode getCompactionNode(String fileName) {
+    return compactionNodeMap.get(fileName);
+  }
+}
diff --git a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/CompactionNode.java b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/CompactionNode.java
index 4e7c38c62c9a..91c7272d239a 100644
--- a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/CompactionNode.java
+++ b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/CompactionNode.java
@@ -32,17 +32,9 @@ public class CompactionNode {
   private final String endKey;
   private final String columnFamily;
 
-  /**
-   * CompactionNode constructor.
-   * @param file SST file (filename without extension)
-   * @param numKeys Number of keys in the SST
-   * @param seqNum Snapshot generation (sequence number)
-   */
-
-  public CompactionNode(String file, long numKeys, long seqNum,
-                        String startKey, String endKey, String columnFamily) {
+  public CompactionNode(String file, long seqNum, String startKey, String endKey, String columnFamily) {
     fileName = file;
-    totalNumberOfKeys = numKeys;
+    totalNumberOfKeys = 0L;
     snapshotGeneration = seqNum;
     cumulativeKeysReverseTraversal = 0L;
     this.startKey = startKey;
@@ -51,7 +43,7 @@ public CompactionNode(String file, long numKeys, long seqNum,
   }
 
   public CompactionNode(CompactionFileInfo compactionFileInfo) {
-    this(compactionFileInfo.getFileName(), -1, -1, compactionFileInfo.getStartKey(),
+    this(compactionFileInfo.getFileName(), -1, compactionFileInfo.getStartKey(),
         compactionFileInfo.getEndKey(), compactionFileInfo.getColumnFamily());
   }
 
diff --git a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDBCheckpointDiffer.java b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDBCheckpointDiffer.java
index c9f8c726d284..3dae2bc6981e 100644
--- a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDBCheckpointDiffer.java
+++ b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDBCheckpointDiffer.java
@@ -26,12 +26,10 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.graph.GraphBuilder;
 import com.google.common.graph.MutableGraph;
 import com.google.protobuf.InvalidProtocolBufferException;
 import java.io.BufferedWriter;
 import java.io.File;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.Serializable;
 import java.nio.file.FileAlreadyExistsException;
@@ -47,7 +45,6 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -65,15 +62,11 @@
 import org.apache.hadoop.hdds.utils.IOUtils;
 import org.apache.hadoop.hdds.utils.Scheduler;
 import org.apache.hadoop.hdds.utils.db.managed.ManagedDBOptions;
-import org.apache.hadoop.hdds.utils.db.managed.ManagedOptions;
 import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
 import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator;
-import org.apache.hadoop.hdds.utils.db.managed.ManagedSstFileReader;
 import org.apache.hadoop.ozone.lock.BootstrapStateHandler;
 import org.apache.ozone.compaction.log.CompactionFileInfo;
 import org.apache.ozone.compaction.log.CompactionLogEntry;
-import org.apache.ozone.graph.PrintableGraph;
-import org.apache.ozone.graph.PrintableGraph.GraphType;
 import org.apache.ozone.rocksdb.util.RdbUtil;
 import org.rocksdb.AbstractEventListener;
 import org.rocksdb.ColumnFamilyHandle;
@@ -81,7 +74,6 @@
 import org.rocksdb.LiveFileMetaData;
 import org.rocksdb.RocksDB;
 import org.rocksdb.RocksDBException;
-import org.rocksdb.TableProperties;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -103,8 +95,6 @@ public class RocksDBCheckpointDiffer implements AutoCloseable,
 
   private final String metadataDir;
   private final String sstBackupDir;
-  private final String activeDBLocationStr;
-
   private final String compactionLogDir;
 
   public static final String COMPACTION_LOG_FILE_NAME_SUFFIX = ".log";
@@ -170,7 +160,7 @@ public class RocksDBCheckpointDiffer implements AutoCloseable,
 
   private ColumnFamilyHandle compactionLogTableCFHandle;
   private ManagedRocksDB activeRocksDB;
-  private ConcurrentMap<String, CompactionFileInfo> inflightCompactions;
+  private final ConcurrentMap<String, CompactionFileInfo> inflightCompactions;
 
   /**
    * For snapshot diff calculation we only need to track following column
@@ -179,30 +169,7 @@ public class RocksDBCheckpointDiffer implements AutoCloseable,
   public static final Set<String> COLUMN_FAMILIES_TO_TRACK_IN_DAG =
       ImmutableSet.of("keyTable", "directoryTable", "fileTable");
 
-  // Hash table to track CompactionNode for a given SST File.
-  private final ConcurrentHashMap<String, CompactionNode> compactionNodeMap =
-      new ConcurrentHashMap<>();
-
-  // We are maintaining a two way DAG. This allows easy traversal from
-  // source snapshot to destination snapshot as well as the other direction.
-
-  private final MutableGraph<CompactionNode> forwardCompactionDAG =
-      GraphBuilder.directed().build();
-
-  private final MutableGraph<CompactionNode> backwardCompactionDAG =
-      GraphBuilder.directed().build();
-
-  public static final Integer DEBUG_DAG_BUILD_UP = 2;
-  public static final Integer DEBUG_DAG_TRAVERSAL = 3;
-  public static final Integer DEBUG_DAG_LIVE_NODES = 4;
-  public static final Integer DEBUG_READ_ALL_DB_KEYS = 5;
-  private static final HashSet<Integer> DEBUG_LEVEL = new HashSet<>();
-
-  static {
-    addDebugLevel(DEBUG_DAG_BUILD_UP);
-    addDebugLevel(DEBUG_DAG_TRAVERSAL);
-    addDebugLevel(DEBUG_DAG_LIVE_NODES);
-  }
+  private final CompactionDag compactionDag;
 
   static {
     RocksDB.loadLibrary();
@@ -238,8 +205,6 @@ public class RocksDBCheckpointDiffer implements AutoCloseable,
     this.sstBackupDir = Paths.get(metadataDirName, sstBackupDirName) + "/";
     createSstBackUpDir();
 
-    // Active DB location is used in getSSTFileSummary
-    this.activeDBLocationStr = activeDBLocationName + "/";
     this.maxAllowedTimeInDag = configuration.getTimeDuration(
         OZONE_OM_SNAPSHOT_COMPACTION_DAG_MAX_TIME_ALLOWED,
         OZONE_OM_SNAPSHOT_COMPACTION_DAG_MAX_TIME_ALLOWED_DEFAULT,
@@ -272,10 +237,11 @@ public class RocksDBCheckpointDiffer implements AutoCloseable,
       this.scheduler = null;
     }
     this.inflightCompactions = new ConcurrentHashMap<>();
+    this.compactionDag = new CompactionDag();
   }
 
   private String createCompactionLogDir(String metadataDirName,
-                                        String compactionLogDirName) {
+      String compactionLogDirName) {
 
     final File parentDir = new File(metadataDirName);
     if (!parentDir.exists()) {
@@ -338,10 +304,6 @@ public void close() {
     }
   }
 
-  public static void addDebugLevel(Integer level) {
-    DEBUG_LEVEL.add(level);
-  }
-
   public void setRocksDBForCompactionTracking(ManagedDBOptions rocksOptions) {
     List<AbstractEventListener> events = new ArrayList<>();
     events.add(newCompactionBeginListener());
@@ -519,7 +481,7 @@ public void onCompactionCompleted(RocksDB db,
           addToCompactionLogTable(compactionLogEntry);
 
           // Populate the DAG
-          populateCompactionDAG(compactionLogEntry.getInputFileInfoList(),
+          compactionDag.populateCompactionDAG(compactionLogEntry.getInputFileInfoList(),
               compactionLogEntry.getOutputFileInfoList(),
               compactionLogEntry.getDbSequenceNumber());
           for (String inputFile : inputFileCompactions.keySet()) {
@@ -576,47 +538,6 @@ private void createLink(Path link, Path source) {
     }
   }
 
-  /**
-   * Get number of keys in an SST file.
-   * @param filename SST filename
-   * @return number of keys
-   */
-  private long getSSTFileSummary(String filename)
-      throws RocksDBException, FileNotFoundException {
-
-    if (!filename.endsWith(SST_FILE_EXTENSION)) {
-      filename += SST_FILE_EXTENSION;
-    }
-
-    try (ManagedOptions option = new ManagedOptions();
-         ManagedSstFileReader reader = new ManagedSstFileReader(option)) {
-
-      reader.open(getAbsoluteSstFilePath(filename));
-
-      TableProperties properties = reader.getTableProperties();
-      if (LOG.isDebugEnabled()) {
-        LOG.debug("{} has {} keys", filename, properties.getNumEntries());
-      }
-      return properties.getNumEntries();
-    }
-  }
-
-  private String getAbsoluteSstFilePath(String filename)
-      throws FileNotFoundException {
-    if (!filename.endsWith(SST_FILE_EXTENSION)) {
-      filename += SST_FILE_EXTENSION;
-    }
-    File sstFile = new File(sstBackupDir + filename);
-    File sstFileInActiveDB = new File(activeDBLocationStr + filename);
-    if (sstFile.exists()) {
-      return sstBackupDir + filename;
-    } else if (sstFileInActiveDB.exists()) {
-      return activeDBLocationStr + filename;
-    } else {
-      throw new FileNotFoundException("Can't find SST file: " + filename);
-    }
-  }
-
   /**
    * Helper method to trim the filename retrieved from LiveFileMetaData.
    */
@@ -753,27 +674,33 @@ public void addEntriesFromLogFilesToDagAndCompactionLogTable() {
   /**
    * Load existing compaction log from table to the in-memory DAG.
    * This only needs to be done once during OM startup.
+   * It is only for backward compatibility.
    */
   public void loadAllCompactionLogs() {
     synchronized (this) {
       preconditionChecksForLoadAllCompactionLogs();
       addEntriesFromLogFilesToDagAndCompactionLogTable();
-      try (ManagedRocksIterator managedRocksIterator = new ManagedRocksIterator(
-          activeRocksDB.get().newIterator(compactionLogTableCFHandle))) {
-        managedRocksIterator.get().seekToFirst();
-        while (managedRocksIterator.get().isValid()) {
-          byte[] value = managedRocksIterator.get().value();
-          CompactionLogEntry compactionLogEntry =
-              CompactionLogEntry.getFromProtobuf(
-                  CompactionLogEntryProto.parseFrom(value));
-          populateCompactionDAG(compactionLogEntry.getInputFileInfoList(),
-              compactionLogEntry.getOutputFileInfoList(),
-              compactionLogEntry.getDbSequenceNumber());
-          managedRocksIterator.get().next();
-        }
-      } catch (InvalidProtocolBufferException e) {
-        throw new RuntimeException(e);
+      loadCompactionDagFromDB();
+    }
+  }
+
+  /**
+   * Read a compactionLofTable and create entries in the dags.
+   */
+  private void loadCompactionDagFromDB() {
+    try (ManagedRocksIterator managedRocksIterator = new ManagedRocksIterator(
+        activeRocksDB.get().newIterator(compactionLogTableCFHandle))) {
+      managedRocksIterator.get().seekToFirst();
+      while (managedRocksIterator.get().isValid()) {
+        byte[] value = managedRocksIterator.get().value();
+        CompactionLogEntry compactionLogEntry =
+            CompactionLogEntry.getFromProtobuf(CompactionLogEntryProto.parseFrom(value));
+        compactionDag.populateCompactionDAG(compactionLogEntry.getInputFileInfoList(),
+            compactionLogEntry.getOutputFileInfoList(), compactionLogEntry.getDbSequenceNumber());
+        managedRocksIterator.get().next();
       }
+    } catch (InvalidProtocolBufferException e) {
+      throw new RuntimeException(e);
     }
   }
 
@@ -833,8 +760,8 @@ private String getSSTFullPath(String sstFilenameWithoutExtension,
    *               "/path/to/sstBackupDir/000060.sst"]
    */
   public synchronized Optional<List<String>> getSSTDiffListWithFullPath(DifferSnapshotInfo src,
-                                                                        DifferSnapshotInfo dest,
-                                                                        String sstFilesDirForSnapDiffJob) {
+      DifferSnapshotInfo dest,
+      String sstFilesDirForSnapDiffJob) {
 
     Optional<List<String>> sstDiffList = getSSTDiffList(src, dest);
 
@@ -863,7 +790,7 @@ public synchronized Optional<List<String>> getSSTDiffListWithFullPath(DifferSnap
    * @return A list of SST files without extension. e.g. ["000050", "000060"]
    */
   public synchronized Optional<List<String>> getSSTDiffList(DifferSnapshotInfo src,
-                                                            DifferSnapshotInfo dest) {
+      DifferSnapshotInfo dest) {
 
     // TODO: Reject or swap if dest is taken after src, once snapshot chain
     //  integration is done.
@@ -905,8 +832,8 @@ public synchronized Optional<List<String>> getSSTDiffList(DifferSnapshotInfo src
     }
 
     if (src.getTablePrefixes() != null && !src.getTablePrefixes().isEmpty()) {
-      RocksDiffUtils.filterRelevantSstFiles(fwdDAGDifferentFiles, src.getTablePrefixes(), compactionNodeMap,
-          src.getRocksDB(), dest.getRocksDB());
+      RocksDiffUtils.filterRelevantSstFiles(fwdDAGDifferentFiles, src.getTablePrefixes(),
+          compactionDag.getCompactionMap(), src.getRocksDB(), dest.getRocksDB());
     }
     return Optional.of(new ArrayList<>(fwdDAGDifferentFiles));
   }
@@ -939,7 +866,7 @@ synchronized void internalGetSSTDiffList(
         continue;
       }
 
-      CompactionNode infileNode = compactionNodeMap.get(fileName);
+      CompactionNode infileNode = compactionDag.getCompactionNode(fileName);
       if (infileNode == null) {
         LOG.debug("Source '{}' SST file '{}' is never compacted",
             src.getDbPath(), fileName);
@@ -983,8 +910,7 @@ synchronized void internalGetSSTDiffList(
             continue;
           }
 
-          Set<CompactionNode> successors =
-              forwardCompactionDAG.successors(current);
+          Set<CompactionNode> successors = compactionDag.getForwardCompactionDAG().successors(current);
           if (successors.isEmpty()) {
             LOG.debug("No further compaction happened to the current file. " +
                 "Src '{}' and dest '{}' have different file: {}",
@@ -1038,7 +964,7 @@ public Comparator<CompactionNode> reversed() {
 
   @VisibleForTesting
   void dumpCompactionNodeTable() {
-    List<CompactionNode> nodeList = compactionNodeMap.values().stream()
+    List<CompactionNode> nodeList = compactionDag.getCompactionMap().values().stream()
         .sorted(new NodeComparator()).collect(Collectors.toList());
     for (CompactionNode n : nodeList) {
       LOG.debug("File '{}' total keys: {}",
@@ -1050,74 +976,12 @@ void dumpCompactionNodeTable() {
 
   @VisibleForTesting
   public MutableGraph<CompactionNode> getForwardCompactionDAG() {
-    return forwardCompactionDAG;
+    return compactionDag.getForwardCompactionDAG();
   }
 
   @VisibleForTesting
   public MutableGraph<CompactionNode> getBackwardCompactionDAG() {
-    return backwardCompactionDAG;
-  }
-
-  /**
-   * Helper method to add a new file node to the DAG.
-   * @return CompactionNode
-   */
-  private CompactionNode addNodeToDAG(String file, long seqNum, String startKey,
-                                      String endKey, String columnFamily) {
-    long numKeys = 0L;
-    try {
-      numKeys = getSSTFileSummary(file);
-    } catch (RocksDBException e) {
-      LOG.warn("Can't get num of keys in SST '{}': {}", file, e.getMessage());
-    } catch (FileNotFoundException e) {
-      LOG.info("Can't find SST '{}'", file);
-    }
-
-    CompactionNode fileNode = new CompactionNode(file, numKeys,
-        seqNum, startKey, endKey, columnFamily);
-
-    forwardCompactionDAG.addNode(fileNode);
-    backwardCompactionDAG.addNode(fileNode);
-
-    return fileNode;
-  }
-
-  /**
-   * Populate the compaction DAG with input and output SST files lists.
-   * @param inputFiles List of compaction input files.
-   * @param outputFiles List of compaction output files.
-   * @param seqNum DB transaction sequence number.
-   */
-  private void populateCompactionDAG(List<CompactionFileInfo> inputFiles,
-                                     List<CompactionFileInfo> outputFiles,
-                                     long seqNum) {
-
-    if (LOG.isDebugEnabled()) {
-      LOG.debug("Input files: {} -> Output files: {}", inputFiles, outputFiles);
-    }
-
-    for (CompactionFileInfo outfile : outputFiles) {
-      final CompactionNode outfileNode = compactionNodeMap.computeIfAbsent(
-          outfile.getFileName(),
-
-          file -> addNodeToDAG(file, seqNum, outfile.getStartKey(),
-              outfile.getEndKey(), outfile.getColumnFamily()));
-
-
-      for (CompactionFileInfo infile : inputFiles) {
-        final CompactionNode infileNode = compactionNodeMap.computeIfAbsent(
-            infile.getFileName(),
-
-            file -> addNodeToDAG(file, seqNum, infile.getStartKey(),
-                infile.getEndKey(), infile.getColumnFamily()));
-
-        // Draw the edges
-        if (!outfileNode.getFileName().equals(infileNode.getFileName())) {
-          forwardCompactionDAG.putEdge(outfileNode, infileNode);
-          backwardCompactionDAG.putEdge(infileNode, outfileNode);
-        }
-      }
-    }
+    return compactionDag.getBackwardCompactionDAG();
   }
 
   private void addFileInfoToCompactionLogTable(
@@ -1245,7 +1109,7 @@ private void removeSstFiles(Set<String> sstFileNodes) {
   public Set<String> pruneSstFileNodesFromDag(Set<String> sstFileNodes) {
     Set<CompactionNode> startNodes = new HashSet<>();
     for (String sstFileNode : sstFileNodes) {
-      CompactionNode infileNode = compactionNodeMap.get(sstFileNode);
+      CompactionNode infileNode = compactionDag.getCompactionNode(sstFileNode);
       if (infileNode == null) {
         LOG.warn("Compaction node doesn't exist for sstFile: {}.", sstFileNode);
         continue;
@@ -1255,14 +1119,7 @@ public Set<String> pruneSstFileNodesFromDag(Set<String> sstFileNodes) {
     }
 
     synchronized (this) {
-      pruneBackwardDag(backwardCompactionDAG, startNodes);
-      Set<String> sstFilesPruned = pruneForwardDag(forwardCompactionDAG,
-          startNodes);
-
-      // Remove SST file nodes from compactionNodeMap too,
-      // since those nodes won't be needed after clean up.
-      sstFilesPruned.forEach(compactionNodeMap::remove);
-      return sstFilesPruned;
+      return compactionDag.pruneNodesFromDag(startNodes);
     }
   }
 
@@ -1272,26 +1129,7 @@ public Set<String> pruneSstFileNodesFromDag(Set<String> sstFileNodes) {
   @VisibleForTesting
   Set<String> pruneBackwardDag(MutableGraph<CompactionNode> backwardDag,
                                Set<CompactionNode> startNodes) {
-    Set<String> removedFiles = new HashSet<>();
-    Set<CompactionNode> currentLevel = startNodes;
-
-    synchronized (this) {
-      while (!currentLevel.isEmpty()) {
-        Set<CompactionNode> nextLevel = new HashSet<>();
-        for (CompactionNode current : currentLevel) {
-          if (!backwardDag.nodes().contains(current)) {
-            continue;
-          }
-
-          nextLevel.addAll(backwardDag.predecessors(current));
-          backwardDag.removeNode(current);
-          removedFiles.add(current.getFileName());
-        }
-        currentLevel = nextLevel;
-      }
-    }
-
-    return removedFiles;
+    return compactionDag.pruneBackwardDag(backwardDag, startNodes);
   }
 
   /**
@@ -1300,27 +1138,7 @@ Set<String> pruneBackwardDag(MutableGraph<CompactionNode> backwardDag,
   @VisibleForTesting
   Set<String> pruneForwardDag(MutableGraph<CompactionNode> forwardDag,
                               Set<CompactionNode> startNodes) {
-    Set<String> removedFiles = new HashSet<>();
-    Set<CompactionNode> currentLevel = new HashSet<>(startNodes);
-
-    synchronized (this) {
-      while (!currentLevel.isEmpty()) {
-        Set<CompactionNode> nextLevel = new HashSet<>();
-        for (CompactionNode current : currentLevel) {
-          if (!forwardDag.nodes().contains(current)) {
-            continue;
-          }
-
-          nextLevel.addAll(forwardDag.successors(current));
-          forwardDag.removeNode(current);
-          removedFiles.add(current.getFileName());
-        }
-
-        currentLevel = nextLevel;
-      }
-    }
-
-    return removedFiles;
+    return compactionDag.pruneForwardDag(forwardDag, startNodes);
   }
 
   private long getSnapshotCreationTimeFromLogLine(String logLine) {
@@ -1364,8 +1182,8 @@ public void pruneSstFiles() {
     // when nodes are added to the graph, but arcs are still in progress.
     // Hence, the lock is taken.
     synchronized (this) {
-      nonLeafSstFiles = forwardCompactionDAG.nodes().stream()
-          .filter(node -> !forwardCompactionDAG.successors(node).isEmpty())
+      nonLeafSstFiles = compactionDag.getForwardCompactionDAG().nodes().stream()
+          .filter(node -> !compactionDag.getForwardCompactionDAG().successors(node).isEmpty())
           .map(node -> node.getFileName())
           .collect(Collectors.toSet());
     }
@@ -1386,14 +1204,9 @@ public boolean shouldRun() {
     return !suspended.get();
   }
 
-  @VisibleForTesting
-  public boolean debugEnabled(Integer level) {
-    return DEBUG_LEVEL.contains(level);
-  }
-
   @VisibleForTesting
   public ConcurrentHashMap<String, CompactionNode> getCompactionNodeMap() {
-    return compactionNodeMap;
+    return (ConcurrentHashMap<String, CompactionNode>) compactionDag.getCompactionMap();
   }
 
   @VisibleForTesting
@@ -1446,19 +1259,6 @@ public BootstrapStateHandler.Lock getBootstrapStateLock() {
     return lock;
   }
 
-  public void pngPrintMutableGraph(String filePath, GraphType graphType)
-      throws IOException {
-    Objects.requireNonNull(filePath, "Image file path is required.");
-    Objects.requireNonNull(graphType, "Graph type is required.");
-
-    PrintableGraph graph;
-    synchronized (this) {
-      graph = new PrintableGraph(backwardCompactionDAG, graphType);
-    }
-
-    graph.generateImage(filePath);
-  }
-
   private Map<String, CompactionFileInfo> toFileInfoList(List<String> sstFiles, RocksDB db) {
     if (CollectionUtils.isEmpty(sstFiles)) {
       return Collections.emptyMap();
diff --git a/hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/rocksdiff/TestRocksDBCheckpointDiffer.java b/hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/rocksdiff/TestRocksDBCheckpointDiffer.java
index 50be6bfb705f..6c6ba6f1ac19 100644
--- a/hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/rocksdiff/TestRocksDBCheckpointDiffer.java
+++ b/hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/rocksdiff/TestRocksDBCheckpointDiffer.java
@@ -28,8 +28,6 @@
 import static org.apache.hadoop.util.Time.now;
 import static org.apache.ozone.rocksdiff.RocksDBCheckpointDiffer.COLUMN_FAMILIES_TO_TRACK_IN_DAG;
 import static org.apache.ozone.rocksdiff.RocksDBCheckpointDiffer.COMPACTION_LOG_FILE_NAME_SUFFIX;
-import static org.apache.ozone.rocksdiff.RocksDBCheckpointDiffer.DEBUG_DAG_LIVE_NODES;
-import static org.apache.ozone.rocksdiff.RocksDBCheckpointDiffer.DEBUG_READ_ALL_DB_KEYS;
 import static org.apache.ozone.rocksdiff.RocksDBCheckpointDiffer.SST_FILE_EXTENSION;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -65,6 +63,7 @@
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -138,7 +137,6 @@ public class TestRocksDBCheckpointDiffer {
                   .map(
                       sstFile -> new CompactionNode(sstFile,
                           1000L,
-                          Long.parseLong(sstFile.substring(0, 6)),
                           null, null, null
                       ))
                   .collect(Collectors.toList()))
@@ -310,6 +308,18 @@ public class TestRocksDBCheckpointDiffer {
   private ColumnFamilyHandle fileTableCFHandle;
   private ColumnFamilyHandle compactionLogTableCFHandle;
 
+  public static final Integer DEBUG_DAG_BUILD_UP = 2;
+  public static final Integer DEBUG_DAG_TRAVERSAL = 3;
+  public static final Integer DEBUG_DAG_LIVE_NODES = 4;
+  public static final Integer DEBUG_READ_ALL_DB_KEYS = 5;
+  private static final HashSet<Integer> DEBUG_LEVEL = new HashSet<>();
+
+  static {
+    DEBUG_LEVEL.add(DEBUG_DAG_BUILD_UP);
+    DEBUG_LEVEL.add(DEBUG_DAG_TRAVERSAL);
+    DEBUG_LEVEL.add(DEBUG_DAG_LIVE_NODES);
+  }
+
   @BeforeEach
   public void init() throws RocksDBException {
     // Checkpoint differ log level. Set to DEBUG for verbose output
@@ -1035,7 +1045,7 @@ private void readRocksDBInstance(String dbPathArg,
         LOG.debug("\tLevel: {}", m.level());
         LOG.debug("\tTable: {}", bytes2String(m.columnFamilyName()));
         LOG.debug("\tKey Range: {}", bytes2String(m.smallestKey()) + " <-> " + bytes2String(m.largestKey()));
-        if (differ.debugEnabled(DEBUG_DAG_LIVE_NODES)) {
+        if (debugEnabled(DEBUG_DAG_LIVE_NODES)) {
           printMutableGraphFromAGivenNode(
               differ.getCompactionNodeMap(),
               m.fileName(), m.level(),
@@ -1043,7 +1053,7 @@ private void readRocksDBInstance(String dbPathArg,
         }
       }
 
-      if (differ.debugEnabled(DEBUG_READ_ALL_DB_KEYS)) {
+      if (debugEnabled(DEBUG_READ_ALL_DB_KEYS)) {
         try (ManagedRocksIterator iter = new ManagedRocksIterator(rocksDB.get().newIterator())) {
           for (iter.get().seekToFirst(); iter.get().isValid(); iter.get().next()) {
             LOG.debug(
@@ -1065,6 +1075,10 @@ private void readRocksDBInstance(String dbPathArg,
     }
   }
 
+  public boolean debugEnabled(Integer level) {
+    return DEBUG_LEVEL.contains(level);
+  }
+
   /**
    * Helper that traverses the graphs for testing.
    * @param compactionNodeMap
@@ -1072,7 +1086,7 @@ private void readRocksDBInstance(String dbPathArg,
    * @param fwdMutableGraph
    */
   private void traverseGraph(
-      ConcurrentHashMap<String, CompactionNode> compactionNodeMap,
+      ConcurrentMap<String, CompactionNode> compactionNodeMap,
       MutableGraph<CompactionNode> reverseMutableGraph,
       MutableGraph<CompactionNode> fwdMutableGraph) {
 
@@ -1968,14 +1982,10 @@ public void testShouldSkipNode(Map<String, String> columnFamilyToPrefixMap,
   }
 
   private static Stream<Arguments> shouldSkipNodeEdgeCases() {
-    CompactionNode node = new CompactionNode("fileName",
-        100, 100, "startKey", "endKey", "columnFamily");
-    CompactionNode nullColumnFamilyNode = new CompactionNode("fileName",
-        100, 100, "startKey", "endKey", null);
-    CompactionNode nullStartKeyNode = new CompactionNode("fileName",
-        100, 100, null, "endKey", "columnFamily");
-    CompactionNode nullEndKeyNode = new CompactionNode("fileName",
-        100, 100, "startKey", null, "columnFamily");
+    CompactionNode node = new CompactionNode("fileName", 100, "startKey", "endKey", "columnFamily");
+    CompactionNode nullColumnFamilyNode = new CompactionNode("fileName", 100, "startKey", "endKey", null);
+    CompactionNode nullStartKeyNode = new CompactionNode("fileName", 100, null, "endKey", "columnFamily");
+    CompactionNode nullEndKeyNode = new CompactionNode("fileName", 100, "startKey", null, "columnFamily");
 
     return Stream.of(
         Arguments.of(node, Collections.emptyMap(), false),
diff --git a/hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/rocksdiff/TestRocksDiffUtils.java b/hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/rocksdiff/TestRocksDiffUtils.java
index d68004a775eb..324c29015e12 100644
--- a/hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/rocksdiff/TestRocksDiffUtils.java
+++ b/hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/rocksdiff/TestRocksDiffUtils.java
@@ -105,9 +105,9 @@ public void testFilterRelevantSstFilesWithPreExistingCompactionInfo(String valid
           validSSTFileStartRange.charAt(0)) / 2));
       Set<String> sstFile = Sets.newTreeSet(validSstFile, invalidSstFile, untrackedSstFile);
       RocksDiffUtils.filterRelevantSstFiles(sstFile, ImmutableMap.of(validSSTColumnFamilyName, expectedPrefix),
-          ImmutableMap.of("validSSTFile", new CompactionNode(validSstFile, 0, 0, validSSTFileStartRange,
+          ImmutableMap.of("validSSTFile", new CompactionNode(validSstFile, 0, validSSTFileStartRange,
                   validSSTFileEndRange, validSSTColumnFamilyName), "invalidSSTFile",
-              new CompactionNode(invalidSstFile, 0, 0, invalidSSTFileStartRange,
+              new CompactionNode(invalidSstFile, 0, invalidSSTFileStartRange,
                   invalidSSTFileEndRange, invalidColumnFamilyName)));
       Assertions.assertEquals(Sets.newTreeSet(validSstFile, untrackedSstFile), sstFile);
     }
diff --git a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
index edbe74205bdb..90da0d676990 100644
--- a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
+++ b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
@@ -1207,6 +1207,7 @@ OzoneSnapshot getSnapshotInfo(String volumeName,
    * @return message which tells the image name, parent dir and OM leader
    * node information.
    */
+  @Deprecated
   String printCompactionLogDag(String fileNamePrefix, String graphType)
       throws IOException;
 
diff --git a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
index f1e26ed87e58..7b1959dbdb05 100644
--- a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
+++ b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
@@ -1027,6 +1027,7 @@ public OzoneSnapshot getSnapshotInfo(String volumeName,
    * @return message which tells the image name, parent dir and OM leader
    * node information.
    */
+  @Deprecated
   @Override
   public String printCompactionLogDag(String fileNamePrefix,
                                       String graphType) throws IOException {
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
index 9ca59b9c38c6..81588381769c 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
@@ -274,6 +274,8 @@ public static boolean isReadOnly(
     case TransferLeadership:
     case SetSafeMode:
     case PrintCompactionLogDag:
+      // printCompactionLogDag is deprecated by HDDS-12053,
+      // keeping it here for compatibility
     case GetSnapshotInfo:
     case GetObjectTagging:
     case GetQuotaRepairStatus:
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
index d164587ffa55..3bcf190662af 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
@@ -760,10 +760,10 @@ default SnapshotInfo getSnapshotInfo(String volumeName,
    * @return message which tells the image name, parent dir and OM leader
    * node information.
    */
+  @Deprecated
   default String printCompactionLogDag(String fileNamePrefix, String graphType)
       throws IOException {
-    throw new UnsupportedOperationException("OzoneManager does not require " +
-        "this to be implemented");
+    throw new UnsupportedOperationException("This API has been deprecated.");
   }
 
   /**
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
index 0e23fc1937b7..671a93a486ec 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
@@ -187,7 +187,6 @@
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.PrepareResponse;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.PrepareStatusRequest;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.PrepareStatusResponse;
-import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.PrintCompactionLogDagRequest;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.PutObjectTaggingRequest;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.RangerBGSyncRequest;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.RangerBGSyncResponse;
@@ -1327,30 +1326,6 @@ public SnapshotInfo getSnapshotInfo(String volumeName, String bucketName,
         .getSnapshotInfo());
   }
 
-  /**
-   * {@inheritDoc}
-   */
-  @Override
-  public String printCompactionLogDag(String fileNamePrefix, String graphType)
-      throws IOException {
-    final PrintCompactionLogDagRequest.Builder request =
-        PrintCompactionLogDagRequest.newBuilder();
-
-    if (fileNamePrefix != null) {
-      request.setFileNamePrefix(fileNamePrefix);
-    }
-    if (graphType != null) {
-      request.setGraphType(graphType);
-    }
-
-    final OMRequest omRequest = createOMRequest(Type.PrintCompactionLogDag)
-        .setPrintCompactionLogDagRequest(request.build())
-        .build();
-    final OMResponse omResponse = submitRequest(omRequest);
-    handleError(omResponse);
-    return omResponse.getPrintCompactionLogDagResponse().getMessage();
-  }
-
   /**
    * {@inheritDoc}
    */
diff --git a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
index 4108af198bff..9fc2803fe0fe 100644
--- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
+++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
@@ -141,7 +141,7 @@ enum Type {
   ListSnapshotDiffJobs = 122;
   CancelSnapshotDiff = 123;
   SetSafeMode = 124;
-  PrintCompactionLogDag = 125;
+  PrintCompactionLogDag = 125; // [deprecated = true] by HDDS-12053
   ListKeysLight = 126;
   AbortExpiredMultiPartUploads = 127;
   SetSnapshotProperty = 128;
@@ -287,7 +287,7 @@ message OMRequest {
   optional ListSnapshotDiffJobRequest       ListSnapshotDiffJobRequest     = 122;
   optional CancelSnapshotDiffRequest        CancelSnapshotDiffRequest      = 123;
   optional SetSafeModeRequest               SetSafeModeRequest             = 124;
-  optional PrintCompactionLogDagRequest     PrintCompactionLogDagRequest   = 125;
+  optional PrintCompactionLogDagRequest     PrintCompactionLogDagRequest   = 125 [deprecated = true];
 
   optional MultipartUploadsExpiredAbortRequest multipartUploadsExpiredAbortRequest = 126;
   optional SetSnapshotPropertyRequest       SetSnapshotPropertyRequest     = 127;
@@ -420,7 +420,7 @@ message OMResponse {
   optional ListSnapshotDiffJobResponse       ListSnapshotDiffJobResponse   = 122;
   optional CancelSnapshotDiffResponse        cancelSnapshotDiffResponse    = 123;
   optional SetSafeModeResponse               SetSafeModeResponse           = 124;
-  optional PrintCompactionLogDagResponse     PrintCompactionLogDagResponse = 125;
+  optional PrintCompactionLogDagResponse     PrintCompactionLogDagResponse = 125 [deprecated = true];
   optional ListKeysLightResponse             listKeysLightResponse         = 126;
   optional MultipartUploadsExpiredAbortResponse multipartUploadsExpiredAbortResponse = 127;
   optional SetSnapshotPropertyResponse       SetSnapshotPropertyResponse   = 128;
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
index d265e6dfd8eb..fb9445c8287b 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
@@ -102,7 +102,6 @@
 import static org.apache.hadoop.security.UserGroupInformation.getCurrentUser;
 import static org.apache.hadoop.util.ExitUtil.terminate;
 import static org.apache.hadoop.util.Time.monotonicNow;
-import static org.apache.ozone.graph.PrintableGraph.GraphType.FILE_NAME;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.ObjectReader;
@@ -330,7 +329,6 @@
 import org.apache.hadoop.security.token.Token;
 import org.apache.hadoop.util.KMSUtil;
 import org.apache.hadoop.util.Time;
-import org.apache.ozone.graph.PrintableGraph;
 import org.apache.ratis.grpc.GrpcTlsConfig;
 import org.apache.ratis.proto.RaftProtos.RaftPeerRole;
 import org.apache.ratis.protocol.RaftGroupId;
@@ -5030,36 +5028,6 @@ public ListSnapshotDiffJobResponse listSnapshotDiffJobs(
         jobStatus, listAllStatus, prevSnapshotDiffJob, maxListResult);
   }
 
-  @Override
-  public String printCompactionLogDag(String fileNamePrefix,
-                                      String graphType)
-      throws IOException {
-    checkAdminUserPrivilege("print compaction DAG.");
-
-    if (StringUtils.isBlank(fileNamePrefix)) {
-      fileNamePrefix = "dag-";
-    } else {
-      fileNamePrefix = fileNamePrefix + "-";
-    }
-    File tempFile = File.createTempFile(fileNamePrefix, ".png");
-
-    PrintableGraph.GraphType type;
-
-    try {
-      type = PrintableGraph.GraphType.valueOf(graphType);
-    } catch (IllegalArgumentException e) {
-      type = FILE_NAME;
-    }
-
-    getMetadataManager()
-        .getStore()
-        .getRocksDBCheckpointDiffer()
-        .pngPrintMutableGraph(tempFile.getAbsolutePath(), type);
-
-    return String.format("Graph was generated at '\\tmp\\%s' on OM " +
-        "node '%s'.", tempFile.getName(), getOMNodeId());
-  }
-
   private String reconfOzoneAdmins(String newVal) {
     getConfiguration().set(OZONE_ADMINISTRATORS, newVal);
     Collection<String> admins =
diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
index b11322a351c0..17f62e77ceee 100644
--- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
+++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
@@ -724,6 +724,7 @@ public OzoneSnapshot getSnapshotInfo(String volumeName, String bucketName,
   }
 
   @Override
+  @Deprecated
   public String printCompactionLogDag(String fileNamePrefix,
                                       String graphType) throws IOException {
     return null;
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/CompactionLogDagPrinter.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/CompactionLogDagPrinter.java
deleted file mode 100644
index 504ec23fdaf8..000000000000
--- a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/CompactionLogDagPrinter.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.hadoop.ozone.debug;
-
-import java.io.IOException;
-import org.apache.hadoop.hdds.cli.DebugSubcommand;
-import org.apache.hadoop.ozone.client.OzoneClient;
-import org.apache.hadoop.ozone.shell.Handler;
-import org.apache.hadoop.ozone.shell.OzoneAddress;
-import org.kohsuke.MetaInfServices;
-import picocli.CommandLine;
-
-/**
- * Handler to generate image for current compaction DAG in the OM leader node.
- * ozone sh snapshot print-log-dag.
- */
-@CommandLine.Command(
-    name = "print-log-dag",
-    aliases = "pld",
-    description = "Create an image of the current compaction log DAG in OM.")
-@MetaInfServices(DebugSubcommand.class)
-public class CompactionLogDagPrinter extends Handler
-    implements DebugSubcommand {
-
-  @CommandLine.Option(names = {"-f", "--file-name-prefix"},
-      description = "Prefix to be use in image file name. (optional)")
-  private String fileNamePrefix;
-
-  // TODO: Change graphType to enum.
-  @CommandLine.Option(names = {"-t", "--graph-type"},
-      description = "Type of node name to use in the graph image. " +
-          "(optional)\n Accepted values are: \n" +
-          "  file_name (default) : to use file name as node name in DAG,\n" +
-          "  key_size: to show the no. of keys in the file along with file " +
-          "name in the DAG node name,\n" +
-          "  cumulative_size: to show the cumulative size along with file " +
-          "name in the DAG node name.",
-      defaultValue = "file_name")
-  private String graphType;
-
-  @Override
-  protected void execute(OzoneClient client, OzoneAddress address)
-      throws IOException {
-    String message = client.getObjectStore()
-        .printCompactionLogDag(fileNamePrefix, graphType);
-    System.out.println(message);
-  }
-}
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/CompactionLogDagPrinter.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/CompactionLogDagPrinter.java
new file mode 100644
index 000000000000..1e460b3a5332
--- /dev/null
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/CompactionLogDagPrinter.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.debug.om;
+
+import static org.apache.hadoop.ozone.OzoneConsts.COMPACTION_LOG_TABLE;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import org.apache.hadoop.hdds.cli.AbstractSubcommand;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator;
+import org.apache.hadoop.ozone.debug.RocksDBUtils;
+import org.apache.ozone.compaction.log.CompactionLogEntry;
+import org.apache.ozone.graph.PrintableGraph;
+import org.apache.ozone.rocksdiff.CompactionDag;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import picocli.CommandLine;
+
+/**
+ * Handler to generate image for current compaction DAG.
+ * ozone debug om generate-compaction-dag.
+ */
+@CommandLine.Command(
+    name = "generate-compaction-dag",
+    aliases = "gcd",
+    description = "Create an image of the current compaction log DAG. " +
+        "This command is an offline command. i.e., it can run on any instance of om.db " +
+        "and does not require OM to be up.")
+public class CompactionLogDagPrinter extends AbstractSubcommand implements Callable<Void> {
+
+  @CommandLine.ParentCommand
+  private OMDebug parent;
+
+  @CommandLine.Option(names = {"-o", "--output-file"},
+      required = true,
+      description = "Path to location at which image will be downloaded. " +
+          "Should include the image file name with \".png\" extension.")
+  private String imageLocation;
+
+  @Override
+  public Void call() throws Exception {
+    try {
+      final List<ColumnFamilyHandle> cfHandleList = new ArrayList<>();
+      List<ColumnFamilyDescriptor> cfDescList = RocksDBUtils.getColumnFamilyDescriptors(parent.getDbPath());
+      ManagedRocksDB activeRocksDB = ManagedRocksDB.openReadOnly(parent.getDbPath(), cfDescList, cfHandleList);
+      ColumnFamilyHandle compactionLogTableCFHandle =
+          RocksDBUtils.getColumnFamilyHandle(COMPACTION_LOG_TABLE, cfHandleList);
+
+      CompactionDag compactionDag = new CompactionDag();
+      loadCompactionDagFromDB(activeRocksDB, compactionLogTableCFHandle, compactionDag);
+
+      pngPrintMutableGraph(compactionDag, imageLocation);
+      out().println("Graph was generated at '" + imageLocation + "'.");
+    } catch (RocksDBException ex) {
+      err().println("Failed to open RocksDB: " + ex);
+      throw ex;
+    }
+    return null;
+  }
+
+  /**
+   * Read a compactionLofTable and create entries in the dags.
+   */
+  private void loadCompactionDagFromDB(ManagedRocksDB activeRocksDB,
+      ColumnFamilyHandle compactionLogTableCFHandle, CompactionDag compactionDag) {
+    try (ManagedRocksIterator managedRocksIterator = new ManagedRocksIterator(
+        activeRocksDB.get().newIterator(compactionLogTableCFHandle))) {
+      managedRocksIterator.get().seekToFirst();
+      while (managedRocksIterator.get().isValid()) {
+        byte[] value = managedRocksIterator.get().value();
+        CompactionLogEntry compactionLogEntry =
+            CompactionLogEntry.getFromProtobuf(HddsProtos.CompactionLogEntryProto.parseFrom(value));
+        compactionDag.populateCompactionDAG(compactionLogEntry.getInputFileInfoList(),
+            compactionLogEntry.getOutputFileInfoList(), compactionLogEntry.getDbSequenceNumber());
+        managedRocksIterator.get().next();
+      }
+    } catch (InvalidProtocolBufferException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public void pngPrintMutableGraph(CompactionDag helper, String filePath)
+      throws IOException {
+    Objects.requireNonNull(filePath, "Image file path is required.");
+    PrintableGraph graph;
+    graph = new PrintableGraph(helper.getBackwardCompactionDAG(), PrintableGraph.GraphType.FILE_NAME);
+    graph.generateImage(filePath);
+  }
+}
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/OMDebug.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/OMDebug.java
index 06b30b335e7d..dc8c5cb59e24 100644
--- a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/OMDebug.java
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/OMDebug.java
@@ -28,9 +28,20 @@
     name = "om",
     description = "Debug commands related to OM.",
     subcommands = {
+        CompactionLogDagPrinter.class,
         PrefixParser.class
     }
 )
 @MetaInfServices(DebugSubcommand.class)
 public class OMDebug implements DebugSubcommand {
+
+  @CommandLine.Option(names = {"--db"},
+      required = true,
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to OM RocksDB")
+  private String dbPath;
+
+  public String getDbPath() {
+    return dbPath;
+  }
 }
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/PrefixParser.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/PrefixParser.java
index 31b14754eeb0..35e21f5811f0 100644
--- a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/PrefixParser.java
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/PrefixParser.java
@@ -48,12 +48,10 @@
     description = "Parse prefix contents")
 public class PrefixParser implements Callable<Void> {
 
-  private final int[] parserStats = new int[Types.values().length];
+  @CommandLine.ParentCommand
+  private OMDebug parent;
 
-  @CommandLine.Option(names = {"--db"},
-      required = true,
-      description = "Database File Path")
-  private String dbPath;
+  private final int[] parserStats = new int[Types.values().length];
 
   @CommandLine.Option(names = {"--path"},
       required = true,
@@ -71,16 +69,12 @@ public class PrefixParser implements Callable<Void> {
   private String volume;
 
   public String getDbPath() {
-    return dbPath;
-  }
-
-  public void setDbPath(String dbPath) {
-    this.dbPath = dbPath;
+    return parent.getDbPath();
   }
 
   @Override
   public Void call() throws Exception {
-    parse(volume, bucket, dbPath, filePath);
+    parse(volume, bucket, getDbPath(), filePath);
     return null;
   }
 
diff --git a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/graph/Edge.java b/hadoop-ozone/tools/src/main/java/org/apache/ozone/graph/Edge.java
similarity index 100%
rename from hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/graph/Edge.java
rename to hadoop-ozone/tools/src/main/java/org/apache/ozone/graph/Edge.java
diff --git a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/graph/PrintableGraph.java b/hadoop-ozone/tools/src/main/java/org/apache/ozone/graph/PrintableGraph.java
similarity index 100%
rename from hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/graph/PrintableGraph.java
rename to hadoop-ozone/tools/src/main/java/org/apache/ozone/graph/PrintableGraph.java
diff --git a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/graph/package-info.java b/hadoop-ozone/tools/src/main/java/org/apache/ozone/graph/package-info.java
similarity index 100%
rename from hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/graph/package-info.java
rename to hadoop-ozone/tools/src/main/java/org/apache/ozone/graph/package-info.java
diff --git a/hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/graph/TestPrintableGraph.java b/hadoop-ozone/tools/src/test/java/org/apache/ozone/graph/TestPrintableGraph.java
similarity index 87%
rename from hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/graph/TestPrintableGraph.java
rename to hadoop-ozone/tools/src/test/java/org/apache/ozone/graph/TestPrintableGraph.java
index c742a83eb905..fb7dbe31999a 100644
--- a/hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/graph/TestPrintableGraph.java
+++ b/hadoop-ozone/tools/src/test/java/org/apache/ozone/graph/TestPrintableGraph.java
@@ -63,14 +63,10 @@ void testPrintNoGraphMessage(PrintableGraph.GraphType graphType) {
   @EnumSource(PrintableGraph.GraphType.class)
   void testPrintActualGraph(PrintableGraph.GraphType graphType) throws IOException {
     Set<CompactionNode> nodes = Stream.of(
-        new CompactionNode("fileName1",
-            100, 100, "startKey1", "endKey1", "columnFamily1"),
-        new CompactionNode("fileName2",
-        200, 200, "startKey2", "endKey2", null),
-        new CompactionNode("fileName3",
-        300, 300, null, "endKey3", "columnFamily3"),
-        new CompactionNode("fileName4",
-        400, 400, "startKey4", null, "columnFamily4")
+        new CompactionNode("fileName1", 100, "startKey1", "endKey1", "columnFamily1"),
+        new CompactionNode("fileName2", 200, "startKey2", "endKey2", null),
+        new CompactionNode("fileName3", 300, null, "endKey3", "columnFamily3"),
+        new CompactionNode("fileName4", 400, "startKey4", null, "columnFamily4")
     ).collect(Collectors.toSet());
     when(mutableGraph.nodes()).thenReturn(nodes);
 


Reviews:
user: login: adoroszlai
body: Thanks @Tejaskriya for the patch.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T12:48:23Z
user: login: adoroszlai
body: Thanks @Tejaskriya for updating the patch.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T09:31:24Z
user: login: errose28
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T16:49:10Z
user: login: hemantk-12
body: Thanks, @Tejaskriya for working on this improvement.

Rather than providing a util class, I would prefer to wrap MutableGraph and provide functions to add/remove input/output file nodes.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T18:01:02Z
user: login: Tejaskriya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T04:52:01Z
user: login: hemantk-12
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T17:44:41Z
user: login: Tejaskriya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T08:56:10Z
user: login: Tejaskriya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T08:56:18Z
user: login: Tejaskriya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T09:08:42Z
user: login: Tejaskriya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T09:10:40Z
user: login: Tejaskriya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T09:13:04Z
user: login: hemantk-12
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T23:42:52Z
user: login: Tejaskriya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T08:32:13Z
user: login: Tejaskriya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T08:33:51Z
user: login: hemantk-12
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T22:27:09Z
user: login: hemantk-12
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T22:39:42Z
user: login: hemantk-12
body: Thanks @Tejaskriya for addressing the review comments.
I left a few more comments. Please tale a look.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T19:37:11Z
user: login: Tejaskriya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T07:15:13Z
user: login: Tejaskriya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T07:15:14Z
user: login: adoroszlai
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T10:48:26Z
user: login: Tejaskriya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T06:45:56Z
user: login: Tejaskriya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T06:47:46Z
user: login: adoroszlai
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T07:02:01Z
user: login: Tejaskriya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T07:02:54Z
user: login: Tejaskriya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T07:08:54Z
user: login: Tejaskriya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T07:33:59Z
user: login: adoroszlai
body: Thanks @Tejaskriya for updating the patch.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T12:13:57Z
user: login: Tejaskriya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T04:20:54Z
user: login: adoroszlai
body: Thanks @Tejaskriya for updating the patch, LGTM.  Let me know if you would like someone familiar with snapshots take another look or if you think we can merge this.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T06:28:47Z
user: login: jojochuang
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T19:48:35Z
user: login: jojochuang
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T20:06:57Z

Review comments:
diff_hunk: @@ -1411,6 +1198,11 @@ public void suspend() {
     suspended.set(true);
   }
 
+  @VisibleForTesting
+  public PopulateCompactionTable getPopulateCompactionTable() {
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDBCheckpointDiffer.java
user: login: adoroszlai
body: ```suggestion
  PopulateCompactionTable getPopulateCompactionTable() {
```
created_at: 2025-03-06T05:30:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ozone.compaction.log;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.Preconditions;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedOptions;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedSstFileReader;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import org.rocksdb.TableProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility methods to populate compactionLogTable from a compaction-log file.
+ */
+public final class PopulateCompactionTable {
+  private static final Logger LOG =
+      LoggerFactory.getLogger(PopulateCompactionTable.class);
+
+  /**
+   * Used during DAG construction.
+   */
+  private long reconstructionSnapshotCreationTime;
+  private String reconstructionCompactionReason;
+  private final String compactionLogDir;
+  private ManagedRocksDB activeRocksDB;
+  private ColumnFamilyHandle compactionLogTableCFHandle;
+
+  public PopulateCompactionTable(String compactLogDir, ManagedRocksDB db, ColumnFamilyHandle cf) {
+    compactionLogDir = compactLogDir;
+    activeRocksDB = db;
+    compactionLogTableCFHandle = cf;
+    reconstructionSnapshotCreationTime = 0L;
+    reconstructionCompactionReason = null;
+  }
+
+  public void setActiveRocksDB(ManagedRocksDB activeRocksDB) {
+    this.activeRocksDB = activeRocksDB;
+  }
+
+  public void setCompactionLogTableCFHandle(ColumnFamilyHandle compactionLogTableCFHandle) {
+    this.compactionLogTableCFHandle = compactionLogTableCFHandle;
+  }
+
+  public void addEntriesFromLogFilesToDagAndCompactionLogTable() {
+    preconditionChecksForLoadAllCompactionLogs();
+    try {
+      try (Stream<Path> pathStream = Files.list(Paths.get(compactionLogDir))
+          .filter(e -> e.toString().toLowerCase()
+              .endsWith(RocksDBConsts.COMPACTION_LOG_FILE_NAME_SUFFIX))
+          .sorted()) {
+        for (Path logPath : pathStream.collect(Collectors.toList())) {
+          readCompactionLogFile(logPath.toString());
+          // Delete the file once entries are added to compaction table
+          // so that on next restart, only compaction log table is used.
+          Files.delete(logPath);
+        }
+      }
+    } catch (IOException e) {
+      throw new RuntimeException("Error listing compaction log dir " +
+          "compaction-log", e);
+    }
+  }
+
+  /**
+   * Helper to read compaction log file to the internal DAG and compaction log
+   * table.
+   */
+  public void readCompactionLogFile(String currCompactionLogPath) {
+    LOG.debug("Loading compaction log: {}", currCompactionLogPath);
+    try (Stream<String> logLineStream =
+             Files.lines(Paths.get(currCompactionLogPath), UTF_8)) {
+      logLineStream.forEach(this::processCompactionLogLine);
+    } catch (IOException ioEx) {
+      throw new RuntimeException(ioEx);
+    }
+  }
+
+  /**
+   * Process log line of compaction log text file input and populate the DAG.
+   * It also adds the compaction log entry to compaction log table.
+   */
+  public void processCompactionLogLine(String line) {
+    LOG.debug("Processing line: {}", line);
+    if (line.startsWith(RocksDBConsts.COMPACTION_LOG_COMMENT_LINE_PREFIX)) {
+      reconstructionCompactionReason =
+          line.substring(RocksDBConsts.COMPACTION_LOG_COMMENT_LINE_PREFIX.length());
+    } else if (line.startsWith(RocksDBConsts.COMPACTION_LOG_SEQ_NUM_LINE_PREFIX)) {
+      reconstructionSnapshotCreationTime = getSnapshotCreationTimeFromLogLine(line);
+    } else if (line.startsWith(RocksDBConsts.COMPACTION_LOG_ENTRY_LINE_PREFIX)) {
+      // Compaction log entry is like following:
+      // C sequence_number input_files:output_files
+      // where input_files and output_files are joined by ','.
+      String[] lineSpilt = line.split(RocksDBConsts.SPACE_DELIMITER);
+      if (lineSpilt.length != 3) {
+        LOG.error("Invalid line in compaction log: {}", line);
+        return;
+      }
+
+      String dbSequenceNumber = lineSpilt[1];
+      String[] io = lineSpilt[2]
+          .split(RocksDBConsts.COMPACTION_LOG_ENTRY_INPUT_OUTPUT_FILES_DELIMITER);
+      if (io.length != 2) {
+        if (line.endsWith(":")) {
+          LOG.debug("Ignoring compaction log line for SST deletion");
+        } else {
+          LOG.error("Invalid line in compaction log: {}", line);
+        }
+        return;
+      }
+
+      String[] inputFiles = io[0].split(RocksDBConsts.COMPACTION_LOG_ENTRY_FILE_DELIMITER);
+      String[] outputFiles = io[1].split(RocksDBConsts.COMPACTION_LOG_ENTRY_FILE_DELIMITER);
+      CompactionLogEntry logEntry = createCompactionLogEntry(Long.parseLong(dbSequenceNumber),
+          reconstructionSnapshotCreationTime, inputFiles, outputFiles, reconstructionCompactionReason);
+      addToCompactionLogTable(logEntry, activeRocksDB, compactionLogTableCFHandle);
+    } else {
+      LOG.error("Invalid line in compaction log: {}", line);
+    }
+  }
+
+  public CompactionLogEntry createCompactionLogEntry(
+      long dbSequenceNumber, long creationTime,
+      String[] inputFiles, String[] outputFiles,
+      String compactionReason) {
+    List<CompactionFileInfo> inputFileInfoList = Arrays.stream(inputFiles)
+        .map(inputFile -> new CompactionFileInfo.Builder(inputFile).build())
+        .collect(Collectors.toList());
+    List<CompactionFileInfo> outputFileInfoList = Arrays.stream(outputFiles)
+        .map(outputFile -> new CompactionFileInfo.Builder(outputFile).build())
+        .collect(Collectors.toList());
+
+    CompactionLogEntry.Builder builder =
+        new CompactionLogEntry.Builder(dbSequenceNumber, creationTime,
+            inputFileInfoList, outputFileInfoList);
+    if (compactionReason != null) {
+      builder.setCompactionReason(compactionReason);
+    }
+    return builder.build();
+  }
+
+  public void addToCompactionLogTable(CompactionLogEntry compactionLogEntry, ManagedRocksDB rocksDB,
+                                      ColumnFamilyHandle cfHandle) {
+    String dbSequenceIdStr =
+        String.valueOf(compactionLogEntry.getDbSequenceNumber());
+
+    if (dbSequenceIdStr.length() < RocksDBConsts.LONG_MAX_STR_LEN) {
+      // Pad zeroes to the left to make sure it is lexicographic ordering.
+      dbSequenceIdStr = org.apache.commons.lang3.StringUtils.leftPad(
+          dbSequenceIdStr, RocksDBConsts.LONG_MAX_STR_LEN, "0");
+    }
+
+    // Key in the transactionId-currentTime
+    // Just trxId can't be used because multiple compaction might be
+    // running, and it is possible no new entry was added to DB.
+    // Adding current time to transactionId eliminates key collision.
+    String keyString = dbSequenceIdStr + "-" +
+        compactionLogEntry.getCompactionTime();
+
+    byte[] key = keyString.getBytes(UTF_8);
+    byte[] value = compactionLogEntry.getProtobuf().toByteArray();
+    try {
+      rocksDB.get().put(cfHandle, key, value);
+    } catch (RocksDBException exception) {
+      // TODO: Revisit exception handling before merging the PR.
+      throw new RuntimeException(exception);
+    }
+  }
+
+  public long getSnapshotCreationTimeFromLogLine(String logLine) {
+    // Remove `S ` from the line.
+    String line = logLine.substring(RocksDBConsts.COMPACTION_LOG_SEQ_NUM_LINE_PREFIX.length());
+
+    String[] splits = line.split(RocksDBConsts.SPACE_DELIMITER);
+    Preconditions.checkArgument(splits.length == 3,
+        "Snapshot info log statement has more than expected parameters.");
+
+    return Long.parseLong(splits[2]);
+  }
+
+  public void preconditionChecksForLoadAllCompactionLogs() {
+    Preconditions.checkNotNull(compactionLogDir,
+        "Compaction log directory must be set.");
+    Preconditions.checkNotNull(compactionLogTableCFHandle,
+        "compactionLogTableCFHandle must be set before calling " +
+            "loadAllCompactionLogs.");
+    Preconditions.checkNotNull(activeRocksDB,
+        "activeRocksDB must be set before calling loadAllCompactionLogs.");
+  }
+
+  public long getSSTFileSummary(String filename, String dbPath)
+      throws RocksDBException, FileNotFoundException {
+    LOG.info("tej get summary");
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/compaction/log/PopulateCompactionTable.java
user: login: adoroszlai
body: nit: leftover debug

Also several in `CompactionLogDagPrinter`.
created_at: 2025-03-06T12:37:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.debug.om;
+
+import static org.apache.hadoop.ozone.OzoneConsts.COMPACTION_LOG_TABLE;
+
+import com.google.common.graph.GraphBuilder;
+import com.google.common.graph.MutableGraph;
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator;
+import org.apache.hadoop.ozone.debug.RocksDBUtils;
+import org.apache.ozone.compaction.log.CompactionFileInfo;
+import org.apache.ozone.compaction.log.CompactionLogEntry;
+import org.apache.ozone.compaction.log.PopulateCompactionTable;
+import org.apache.ozone.graph.PrintableGraph;
+import org.apache.ozone.rocksdiff.CompactionNode;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import picocli.CommandLine;
+
+/**
+ * Handler to generate image for current compaction DAG in the OM leader node.
+ * ozone debug om print-log-dag.
+ */
+@CommandLine.Command(
+    name = "print-log-dag",
+    aliases = "pld",
+    description = "Create an image of the current compaction log DAG.")
+public class CompactionLogDagPrinter implements Callable<Void> {
+
+  @CommandLine.Option(names = {"-f", "--file-location"},
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/CompactionLogDagPrinter.java
user: login: adoroszlai
body: Would `-o, --output-file` be better?
created_at: 2025-03-06T12:42:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.debug.om;
+
+import static org.apache.hadoop.ozone.OzoneConsts.COMPACTION_LOG_TABLE;
+
+import com.google.common.graph.GraphBuilder;
+import com.google.common.graph.MutableGraph;
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator;
+import org.apache.hadoop.ozone.debug.RocksDBUtils;
+import org.apache.ozone.compaction.log.CompactionFileInfo;
+import org.apache.ozone.compaction.log.CompactionLogEntry;
+import org.apache.ozone.compaction.log.PopulateCompactionTable;
+import org.apache.ozone.graph.PrintableGraph;
+import org.apache.ozone.rocksdiff.CompactionNode;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import picocli.CommandLine;
+
+/**
+ * Handler to generate image for current compaction DAG in the OM leader node.
+ * ozone debug om print-log-dag.
+ */
+@CommandLine.Command(
+    name = "print-log-dag",
+    aliases = "pld",
+    description = "Create an image of the current compaction log DAG.")
+public class CompactionLogDagPrinter implements Callable<Void> {
+
+  @CommandLine.Option(names = {"-f", "--file-location"},
+      required = true,
+      description = "Path to location at which image will be downloaded. " +
+          "Should include the image file name with \".png\" extension.")
+  private String imageLocation;
+
+  @CommandLine.Option(names = {"--db"},
+      required = true,
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to OM RocksDB")
+  private String dbPath;
+
+  @CommandLine.Option(names = {"--compaction-log"},
+      required = true,
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to compaction-log directory.")
+  private String compactionLogDir;
+
+  // TODO: Change graphType to enum.
+  @CommandLine.Option(names = {"-t", "--graph-type"},
+      description = "Type of node name to use in the graph image. (optional)\n Accepted values are: \n" +
+          "  FILE_NAME (default) : to use file name as node name in DAG,\n" +
+          "  KEY_SIZE: to show the no. of keys in the file along with file name in the DAG node name,\n" +
+          "  CUMULATIVE_SIZE: to show the cumulative size along with file name in the DAG node name.",
+      defaultValue = "FILE_NAME")
+  private String graphType;
+
+  @Override
+  public Void call() throws Exception {
+    try {
+      System.out.println("tej enter try ");
+      CreateCompactionDag createCompactionDag = new CreateCompactionDag(dbPath, compactionLogDir);
+      System.out.println("tej create compactDag obj");
+      createCompactionDag.pngPrintMutableGraph(imageLocation, PrintableGraph.GraphType.valueOf(graphType));
+      System.out.println("Graph was generated at '" + imageLocation + "'.");
+    } catch (RocksDBException ex) {
+      System.err.println("Failed to open RocksDB: " + ex);
+      throw new IOException(ex);
+    }
+    return null;
+  }
+
+  class CreateCompactionDag {
+    // Hash table to track CompactionNode for a given SST File.
+    private final ConcurrentHashMap<String, CompactionNode> compactionNodeMap =
+        new ConcurrentHashMap<>();
+    private final MutableGraph<CompactionNode> backwardCompactionDAG =
+        GraphBuilder.directed().build();
+
+    private ColumnFamilyHandle compactionLogTableCFHandle;
+    private ManagedRocksDB activeRocksDB;
+    private PopulateCompactionTable populateCompactionTable;
+
+    CreateCompactionDag(String dbPath, String compactDir) throws RocksDBException {
+      final List<ColumnFamilyHandle> cfHandleList = new ArrayList<>();
+      List<ColumnFamilyDescriptor> cfDescList = RocksDBUtils.getColumnFamilyDescriptors(dbPath);
+      System.out.println("tej get desc");
+      activeRocksDB = ManagedRocksDB.openReadOnly(dbPath, cfDescList, cfHandleList);
+      System.out.println("tej open db");
+      compactionLogTableCFHandle = RocksDBUtils.getColumnFamilyHandle(COMPACTION_LOG_TABLE, cfHandleList);
+      System.out.println("tej get handle");
+      populateCompactionTable = new PopulateCompactionTable(compactDir, activeRocksDB, compactionLogTableCFHandle);
+      System.out.println("tej populate obj");
+    }
+
+    public void pngPrintMutableGraph(String filePath, PrintableGraph.GraphType gType)
+        throws IOException, RocksDBException {
+      System.out.println("tej start pnt");
+      Objects.requireNonNull(filePath, "Image file path is required.");
+      Objects.requireNonNull(gType, "Graph type is required.");
+
+      System.out.println("tej start loading");
+      loadAllCompactionLogs();
+
+      PrintableGraph graph;
+      synchronized (this) {
+        graph = new PrintableGraph(backwardCompactionDAG, gType);
+      }
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/CompactionLogDagPrinter.java
user: login: adoroszlai
body: `synchronized` does not seem to be necessary in the CLI class.
created_at: 2025-03-06T12:43:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.debug.om;
+
+import static org.apache.hadoop.ozone.OzoneConsts.COMPACTION_LOG_TABLE;
+
+import com.google.common.graph.GraphBuilder;
+import com.google.common.graph.MutableGraph;
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator;
+import org.apache.hadoop.ozone.debug.RocksDBUtils;
+import org.apache.ozone.compaction.log.CompactionFileInfo;
+import org.apache.ozone.compaction.log.CompactionLogEntry;
+import org.apache.ozone.compaction.log.PopulateCompactionTable;
+import org.apache.ozone.graph.PrintableGraph;
+import org.apache.ozone.rocksdiff.CompactionNode;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import picocli.CommandLine;
+
+/**
+ * Handler to generate image for current compaction DAG in the OM leader node.
+ * ozone debug om print-log-dag.
+ */
+@CommandLine.Command(
+    name = "print-log-dag",
+    aliases = "pld",
+    description = "Create an image of the current compaction log DAG.")
+public class CompactionLogDagPrinter implements Callable<Void> {
+
+  @CommandLine.Option(names = {"-f", "--file-location"},
+      required = true,
+      description = "Path to location at which image will be downloaded. " +
+          "Should include the image file name with \".png\" extension.")
+  private String imageLocation;
+
+  @CommandLine.Option(names = {"--db"},
+      required = true,
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to OM RocksDB")
+  private String dbPath;
+
+  @CommandLine.Option(names = {"--compaction-log"},
+      required = true,
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to compaction-log directory.")
+  private String compactionLogDir;
+
+  // TODO: Change graphType to enum.
+  @CommandLine.Option(names = {"-t", "--graph-type"},
+      description = "Type of node name to use in the graph image. (optional)\n Accepted values are: \n" +
+          "  FILE_NAME (default) : to use file name as node name in DAG,\n" +
+          "  KEY_SIZE: to show the no. of keys in the file along with file name in the DAG node name,\n" +
+          "  CUMULATIVE_SIZE: to show the cumulative size along with file name in the DAG node name.",
+      defaultValue = "FILE_NAME")
+  private String graphType;
+
+  @Override
+  public Void call() throws Exception {
+    try {
+      System.out.println("tej enter try ");
+      CreateCompactionDag createCompactionDag = new CreateCompactionDag(dbPath, compactionLogDir);
+      System.out.println("tej create compactDag obj");
+      createCompactionDag.pngPrintMutableGraph(imageLocation, PrintableGraph.GraphType.valueOf(graphType));
+      System.out.println("Graph was generated at '" + imageLocation + "'.");
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/CompactionLogDagPrinter.java
user: login: adoroszlai
body: Please add parent class `AbstractSubcommand` and use `out()` and `err()` instead of `System.out` and `System.err`.
created_at: 2025-03-06T12:45:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.debug.om;
+
+import static org.apache.hadoop.ozone.OzoneConsts.COMPACTION_LOG_TABLE;
+
+import com.google.common.graph.GraphBuilder;
+import com.google.common.graph.MutableGraph;
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator;
+import org.apache.hadoop.ozone.debug.RocksDBUtils;
+import org.apache.ozone.compaction.log.CompactionFileInfo;
+import org.apache.ozone.compaction.log.CompactionLogEntry;
+import org.apache.ozone.compaction.log.PopulateCompactionTable;
+import org.apache.ozone.graph.PrintableGraph;
+import org.apache.ozone.rocksdiff.CompactionNode;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import picocli.CommandLine;
+
+/**
+ * Handler to generate image for current compaction DAG in the OM leader node.
+ * ozone debug om print-log-dag.
+ */
+@CommandLine.Command(
+    name = "print-log-dag",
+    aliases = "pld",
+    description = "Create an image of the current compaction log DAG.")
+public class CompactionLogDagPrinter implements Callable<Void> {
+
+  @CommandLine.Option(names = {"-f", "--file-location"},
+      required = true,
+      description = "Path to location at which image will be downloaded. " +
+          "Should include the image file name with \".png\" extension.")
+  private String imageLocation;
+
+  @CommandLine.Option(names = {"--db"},
+      required = true,
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to OM RocksDB")
+  private String dbPath;
+
+  @CommandLine.Option(names = {"--compaction-log"},
+      required = true,
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to compaction-log directory.")
+  private String compactionLogDir;
+
+  // TODO: Change graphType to enum.
+  @CommandLine.Option(names = {"-t", "--graph-type"},
+      description = "Type of node name to use in the graph image. (optional)\n Accepted values are: \n" +
+          "  FILE_NAME (default) : to use file name as node name in DAG,\n" +
+          "  KEY_SIZE: to show the no. of keys in the file along with file name in the DAG node name,\n" +
+          "  CUMULATIVE_SIZE: to show the cumulative size along with file name in the DAG node name.",
+      defaultValue = "FILE_NAME")
+  private String graphType;
+
+  @Override
+  public Void call() throws Exception {
+    try {
+      System.out.println("tej enter try ");
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/CompactionLogDagPrinter.java
user: login: hemantk-12
body: I believe these print statements were for local testing only. Please remove them.
created_at: 2025-03-06T23:30:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -322,16 +268,12 @@ public void close() {
   private final MutableGraph<CompactionNode> backwardCompactionDAG =
       GraphBuilder.directed().build();
 
-  public static final Integer DEBUG_DAG_BUILD_UP = 2;
-  public static final Integer DEBUG_DAG_TRAVERSAL = 3;
-  public static final Integer DEBUG_DAG_LIVE_NODES = 4;
-  public static final Integer DEBUG_READ_ALL_DB_KEYS = 5;
   private static final HashSet<Integer> DEBUG_LEVEL = new HashSet<>();
 
   static {
-    addDebugLevel(DEBUG_DAG_BUILD_UP);
-    addDebugLevel(DEBUG_DAG_TRAVERSAL);
-    addDebugLevel(DEBUG_DAG_LIVE_NODES);
+    addDebugLevel(RocksDBConsts.DEBUG_DAG_BUILD_UP);
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDBCheckpointDiffer.java
user: login: hemantk-12
body: nit: Use static import constants to reduce the number of lines that have changed. 
created_at: 2025-03-06T23:32:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ozone.compaction.log;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.Preconditions;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedOptions;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedSstFileReader;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import org.rocksdb.TableProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility methods to populate compactionLogTable from a compaction-log file.
+ */
+public final class PopulateCompactionTable {
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/compaction/log/PopulateCompactionTable.java
user: login: hemantk-12
body: nit: maybe `CompactionDagHelper`.
created_at: 2025-03-10T15:50:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.debug.om;
+
+import static org.apache.hadoop.ozone.OzoneConsts.COMPACTION_LOG_TABLE;
+
+import com.google.common.graph.GraphBuilder;
+import com.google.common.graph.MutableGraph;
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.hadoop.hdds.cli.AbstractSubcommand;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator;
+import org.apache.hadoop.ozone.debug.RocksDBUtils;
+import org.apache.ozone.compaction.log.CompactionFileInfo;
+import org.apache.ozone.compaction.log.CompactionLogEntry;
+import org.apache.ozone.compaction.log.PopulateCompactionTable;
+import org.apache.ozone.graph.PrintableGraph;
+import org.apache.ozone.rocksdiff.CompactionNode;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import picocli.CommandLine;
+
+/**
+ * Handler to generate image for current compaction DAG in the OM leader node.
+ * ozone debug om print-log-dag.
+ */
+@CommandLine.Command(
+    name = "print-log-dag",
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/CompactionLogDagPrinter.java
user: login: errose28
body: Let's rename this command. Most user's first thoughts on "log" are going to be the application logs. I would suggest `print-db-compaction-dag` or just `print-compaction-dag`.
created_at: 2025-03-10T16:49:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ozone.compaction.log;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.Preconditions;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedOptions;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedSstFileReader;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import org.rocksdb.TableProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility methods to populate compactionLogTable from a compaction-log file.
+ */
+public final class PopulateCompactionTable {
+  private static final Logger LOG =
+      LoggerFactory.getLogger(PopulateCompactionTable.class);
+
+  /**
+   * Used during DAG construction.
+   */
+  private long reconstructionSnapshotCreationTime;
+  private String reconstructionCompactionReason;
+  private final String compactionLogDir;
+  private ManagedRocksDB activeRocksDB;
+  private ColumnFamilyHandle compactionLogTableCFHandle;
+
+  public PopulateCompactionTable(String compactLogDir, ManagedRocksDB db, ColumnFamilyHandle cf) {
+    compactionLogDir = compactLogDir;
+    activeRocksDB = db;
+    compactionLogTableCFHandle = cf;
+    reconstructionSnapshotCreationTime = 0L;
+    reconstructionCompactionReason = null;
+  }
+
+  public void setActiveRocksDB(ManagedRocksDB activeRocksDB) {
+    this.activeRocksDB = activeRocksDB;
+  }
+
+  public void setCompactionLogTableCFHandle(ColumnFamilyHandle compactionLogTableCFHandle) {
+    this.compactionLogTableCFHandle = compactionLogTableCFHandle;
+  }
+
+  public void addEntriesFromLogFilesToDagAndCompactionLogTable() {
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/compaction/log/PopulateCompactionTable.java
user: login: hemantk-12
body: This was for backward compatibility to read compaction logs from the comapction-backup-dir.
created_at: 2025-03-10T17:04:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,63 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.hadoop.ozone.debug;
-
-import java.io.IOException;
-import org.apache.hadoop.hdds.cli.DebugSubcommand;
-import org.apache.hadoop.ozone.client.OzoneClient;
-import org.apache.hadoop.ozone.shell.Handler;
-import org.apache.hadoop.ozone.shell.OzoneAddress;
-import org.kohsuke.MetaInfServices;
-import picocli.CommandLine;
-
-/**
- * Handler to generate image for current compaction DAG in the OM leader node.
- * ozone sh snapshot print-log-dag.
- */
-@CommandLine.Command(
-    name = "print-log-dag",
-    aliases = "pld",
-    description = "Create an image of the current compaction log DAG in OM.")
-@MetaInfServices(DebugSubcommand.class)
-public class CompactionLogDagPrinter extends Handler
-    implements DebugSubcommand {
-
-  @CommandLine.Option(names = {"-f", "--file-name-prefix"},
-      description = "Prefix to be use in image file name. (optional)")
-  private String fileNamePrefix;
-
-  // TODO: Change graphType to enum.
-  @CommandLine.Option(names = {"-t", "--graph-type"},
-      description = "Type of node name to use in the graph image. " +
-          "(optional)\n Accepted values are: \n" +
-          "  file_name (default) : to use file name as node name in DAG,\n" +
-          "  key_size: to show the no. of keys in the file along with file " +
-          "name in the DAG node name,\n" +
-          "  cumulative_size: to show the cumulative size along with file " +
-          "name in the DAG node name.",
-      defaultValue = "file_name")
-  private String graphType;
-
-  @Override
-  protected void execute(OzoneClient client, OzoneAddress address)
-      throws IOException {
-    String message = client.getObjectStore()
-        .printCompactionLogDag(fileNamePrefix, graphType);
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/CompactionLogDagPrinter.java
user: login: hemantk-12
body: Please deprecate this API.
created_at: 2025-03-10T17:07:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ozone.compaction.log;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.Preconditions;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedOptions;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedSstFileReader;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import org.rocksdb.TableProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility methods to populate compactionLogTable from a compaction-log file.
+ */
+public final class PopulateCompactionTable {
+  private static final Logger LOG =
+      LoggerFactory.getLogger(PopulateCompactionTable.class);
+
+  /**
+   * Used during DAG construction.
+   */
+  private long reconstructionSnapshotCreationTime;
+  private String reconstructionCompactionReason;
+  private final String compactionLogDir;
+  private ManagedRocksDB activeRocksDB;
+  private ColumnFamilyHandle compactionLogTableCFHandle;
+
+  public PopulateCompactionTable(String compactLogDir, ManagedRocksDB db, ColumnFamilyHandle cf) {
+    compactionLogDir = compactLogDir;
+    activeRocksDB = db;
+    compactionLogTableCFHandle = cf;
+    reconstructionSnapshotCreationTime = 0L;
+    reconstructionCompactionReason = null;
+  }
+
+  public void setActiveRocksDB(ManagedRocksDB activeRocksDB) {
+    this.activeRocksDB = activeRocksDB;
+  }
+
+  public void setCompactionLogTableCFHandle(ColumnFamilyHandle compactionLogTableCFHandle) {
+    this.compactionLogTableCFHandle = compactionLogTableCFHandle;
+  }
+
+  public void addEntriesFromLogFilesToDagAndCompactionLogTable() {
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/compaction/log/PopulateCompactionTable.java
user: login: Tejaskriya
body: If we want to keep the command compatible with older metadata, we can use this with the command too. Might give some more flexibility.
created_at: 2025-03-12T04:52:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,254 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ozone.compaction.log;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_COMMENT_LINE_PREFIX;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_ENTRY_FILE_DELIMITER;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_ENTRY_INPUT_OUTPUT_FILES_DELIMITER;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_ENTRY_LINE_PREFIX;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_FILE_NAME_SUFFIX;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_SEQ_NUM_LINE_PREFIX;
+import static org.apache.ozone.compaction.log.RocksDBConsts.LONG_MAX_STR_LEN;
+import static org.apache.ozone.compaction.log.RocksDBConsts.SPACE_DELIMITER;
+import static org.apache.ozone.compaction.log.RocksDBConsts.SST_FILE_EXTENSION;
+
+import com.google.common.base.Preconditions;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedOptions;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedSstFileReader;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import org.rocksdb.TableProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility methods to populate compactionLogTable from a compaction-log file.
+ */
+public final class CompactionDagHelper {
+  private static final Logger LOG =
+      LoggerFactory.getLogger(CompactionDagHelper.class);
+
+  /**
+   * Used during DAG construction.
+   */
+  private long reconstructionSnapshotCreationTime;
+  private String reconstructionCompactionReason;
+  private final String compactionLogDir;
+  private ManagedRocksDB activeRocksDB;
+  private ColumnFamilyHandle compactionLogTableCFHandle;
+
+  public CompactionDagHelper(String compactLogDir, ManagedRocksDB db, ColumnFamilyHandle cf) {
+    compactionLogDir = compactLogDir;
+    activeRocksDB = db;
+    compactionLogTableCFHandle = cf;
+    reconstructionSnapshotCreationTime = 0L;
+    reconstructionCompactionReason = null;
+  }
+
+  public void setActiveRocksDB(ManagedRocksDB activeRocksDB) {
+    this.activeRocksDB = activeRocksDB;
+  }
+
+  public void setCompactionLogTableCFHandle(ColumnFamilyHandle compactionLogTableCFHandle) {
+    this.compactionLogTableCFHandle = compactionLogTableCFHandle;
+  }
+
+  public void addEntriesFromLogFilesToCompactionLogTable() {
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/compaction/log/CompactionDagHelper.java
user: login: hemantk-12
body: Let's not move this or anything related to compaction-log file. During the start-up, RockDBCheckpointDiffer populates the compaction-log table from the compaction-log file and clean-up these files. So it is no-op to do it for `CompactionLogDagPrinter`.
created_at: 2025-03-28T15:54:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.debug.om;
+
+import static org.apache.hadoop.ozone.OzoneConsts.COMPACTION_LOG_TABLE;
+
+import com.google.common.graph.GraphBuilder;
+import com.google.common.graph.MutableGraph;
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.hadoop.hdds.cli.AbstractSubcommand;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator;
+import org.apache.hadoop.ozone.debug.RocksDBUtils;
+import org.apache.ozone.compaction.log.CompactionDagHelper;
+import org.apache.ozone.compaction.log.CompactionFileInfo;
+import org.apache.ozone.compaction.log.CompactionLogEntry;
+import org.apache.ozone.graph.PrintableGraph;
+import org.apache.ozone.rocksdiff.CompactionNode;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import picocli.CommandLine;
+
+/**
+ * Handler to generate image for current compaction DAG in the OM leader node.
+ * ozone debug om print-compaction-dag.
+ */
+@CommandLine.Command(
+    name = "print-compaction-dag",
+    aliases = "pcd",
+    description = "Create an image of the current compaction log DAG. " +
+        "This command is an offline command. i.e., it can run on any instance of om.db " +
+        "and does not require OM to be up.")
+public class CompactionLogDagPrinter extends AbstractSubcommand implements Callable<Void> {
+
+  @CommandLine.Option(names = {"-o", "--output-file"},
+      required = true,
+      description = "Path to location at which image will be downloaded. " +
+          "Should include the image file name with \".png\" extension.")
+  private String imageLocation;
+
+  @CommandLine.Option(names = {"--db"},
+      required = true,
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to OM RocksDB")
+  private String dbPath;
+
+  @CommandLine.Option(names = {"--compaction-log"},
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to compaction-log directory.")
+  private String compactionLogDir;
+
+  // TODO: Change graphType to enum.
+  @CommandLine.Option(names = {"-t", "--graph-type"},
+      description = "Type of node name to use in the graph image. (optional)\n Accepted values are: \n" +
+          "  FILE_NAME (default) : to use file name as node name in DAG,\n" +
+          "  KEY_SIZE: to show the no. of keys in the file along with file name in the DAG node name,\n" +
+          "  CUMULATIVE_SIZE: to show the cumulative size along with file name in the DAG node name.",
+      defaultValue = "FILE_NAME")
+  private String graphType;
+
+  @Override
+  public Void call() throws Exception {
+    try {
+      CreateCompactionDag createCompactionDag = new CreateCompactionDag(dbPath, compactionLogDir);
+      createCompactionDag.pngPrintMutableGraph(imageLocation, PrintableGraph.GraphType.valueOf(graphType));
+      out().println("Graph was generated at '" + imageLocation + "'.");
+    } catch (RocksDBException ex) {
+      err().println("Failed to open RocksDB: " + ex);
+      throw new IOException(ex);
+    }
+    return null;
+  }
+
+  class CreateCompactionDag {
+    // Hash table to track CompactionNode for a given SST File.
+    private final ConcurrentHashMap<String, CompactionNode> compactionNodeMap =
+        new ConcurrentHashMap<>();
+    private final MutableGraph<CompactionNode> backwardCompactionDAG =
+        GraphBuilder.directed().build();
+
+    private ColumnFamilyHandle compactionLogTableCFHandle;
+    private ManagedRocksDB activeRocksDB;
+    private CompactionDagHelper compactionDagHelper;
+
+    CreateCompactionDag(String dbPath, String compactDir) throws RocksDBException {
+      final List<ColumnFamilyHandle> cfHandleList = new ArrayList<>();
+      List<ColumnFamilyDescriptor> cfDescList = RocksDBUtils.getColumnFamilyDescriptors(dbPath);
+      activeRocksDB = ManagedRocksDB.openReadOnly(dbPath, cfDescList, cfHandleList);
+      compactionLogTableCFHandle = RocksDBUtils.getColumnFamilyHandle(COMPACTION_LOG_TABLE, cfHandleList);
+      compactionDagHelper = new CompactionDagHelper(compactDir, activeRocksDB, compactionLogTableCFHandle);
+    }
+
+    public void pngPrintMutableGraph(String filePath, PrintableGraph.GraphType gType)
+        throws IOException, RocksDBException {
+      Objects.requireNonNull(filePath, "Image file path is required.");
+      Objects.requireNonNull(gType, "Graph type is required.");
+
+      loadAllCompactionLogs();
+
+      PrintableGraph graph;
+      graph = new PrintableGraph(backwardCompactionDAG, gType);
+      graph.generateImage(filePath);
+    }
+
+    public void loadAllCompactionLogs() {
+      compactionDagHelper.preconditionChecksForLoadAllCompactionLogs(false);
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/CompactionLogDagPrinter.java
user: login: hemantk-12
body: Why are we doing `preconditionChecksForLoadAllCompactionLogs` twice, one here with `false` value and another one inside `addEntriesFromLogFilesToCompactionLogTable` with `true` value?
created_at: 2025-03-28T15:57:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ozone.compaction.log;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+
+/**
+ * Constants related to rocksDB compaction.
+ */
+public final class RocksDBConsts {
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/compaction/log/RocksDBConsts.java
user: login: hemantk-12
body: 1. This separate file is not needed if we don't move compaction-log file code to CompactionDagHelper.
2. If this class is needed, rename it to `RocksDBDifferConsts`.
created_at: 2025-03-28T16:00:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -322,10 +271,6 @@ public void close() {
   private final MutableGraph<CompactionNode> backwardCompactionDAG =
       GraphBuilder.directed().build();
 
-  public static final Integer DEBUG_DAG_BUILD_UP = 2;
-  public static final Integer DEBUG_DAG_TRAVERSAL = 3;
-  public static final Integer DEBUG_DAG_LIVE_NODES = 4;
-  public static final Integer DEBUG_READ_ALL_DB_KEYS = 5;
   private static final HashSet<Integer> DEBUG_LEVEL = new HashSet<>();
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDBCheckpointDiffer.java
user: login: hemantk-12
body: nit: can you please move this test util code to `TestRocksDBCheckpointDiffer`? I don't know why we have it here.
created_at: 2025-03-28T16:11:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,254 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ozone.compaction.log;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_COMMENT_LINE_PREFIX;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_ENTRY_FILE_DELIMITER;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_ENTRY_INPUT_OUTPUT_FILES_DELIMITER;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_ENTRY_LINE_PREFIX;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_FILE_NAME_SUFFIX;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_SEQ_NUM_LINE_PREFIX;
+import static org.apache.ozone.compaction.log.RocksDBConsts.LONG_MAX_STR_LEN;
+import static org.apache.ozone.compaction.log.RocksDBConsts.SPACE_DELIMITER;
+import static org.apache.ozone.compaction.log.RocksDBConsts.SST_FILE_EXTENSION;
+
+import com.google.common.base.Preconditions;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedOptions;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedSstFileReader;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import org.rocksdb.TableProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility methods to populate compactionLogTable from a compaction-log file.
+ */
+public final class CompactionDagHelper {
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/compaction/log/CompactionDagHelper.java
user: login: hemantk-12
body: I initially thought this was just helper code in the draft version of the patch, but now it seems to be more than that.  
Can we consider it merely a wrapper over the `compaction-log` table? If so, I will rename it to `CompactionLogTable` and keep only minimal functions, such as adding and removing an entry from the table.
created_at: 2025-03-28T17:22:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.debug.om;
+
+import static org.apache.hadoop.ozone.OzoneConsts.COMPACTION_LOG_TABLE;
+
+import com.google.common.graph.GraphBuilder;
+import com.google.common.graph.MutableGraph;
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.hadoop.hdds.cli.AbstractSubcommand;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator;
+import org.apache.hadoop.ozone.debug.RocksDBUtils;
+import org.apache.ozone.compaction.log.CompactionDagHelper;
+import org.apache.ozone.compaction.log.CompactionFileInfo;
+import org.apache.ozone.compaction.log.CompactionLogEntry;
+import org.apache.ozone.graph.PrintableGraph;
+import org.apache.ozone.rocksdiff.CompactionNode;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import picocli.CommandLine;
+
+/**
+ * Handler to generate image for current compaction DAG in the OM leader node.
+ * ozone debug om print-compaction-dag.
+ */
+@CommandLine.Command(
+    name = "print-compaction-dag",
+    aliases = "pcd",
+    description = "Create an image of the current compaction log DAG. " +
+        "This command is an offline command. i.e., it can run on any instance of om.db " +
+        "and does not require OM to be up.")
+public class CompactionLogDagPrinter extends AbstractSubcommand implements Callable<Void> {
+
+  @CommandLine.Option(names = {"-o", "--output-file"},
+      required = true,
+      description = "Path to location at which image will be downloaded. " +
+          "Should include the image file name with \".png\" extension.")
+  private String imageLocation;
+
+  @CommandLine.Option(names = {"--db"},
+      required = true,
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to OM RocksDB")
+  private String dbPath;
+
+  @CommandLine.Option(names = {"--compaction-log"},
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to compaction-log directory.")
+  private String compactionLogDir;
+
+  // TODO: Change graphType to enum.
+  @CommandLine.Option(names = {"-t", "--graph-type"},
+      description = "Type of node name to use in the graph image. (optional)\n Accepted values are: \n" +
+          "  FILE_NAME (default) : to use file name as node name in DAG,\n" +
+          "  KEY_SIZE: to show the no. of keys in the file along with file name in the DAG node name,\n" +
+          "  CUMULATIVE_SIZE: to show the cumulative size along with file name in the DAG node name.",
+      defaultValue = "FILE_NAME")
+  private String graphType;
+
+  @Override
+  public Void call() throws Exception {
+    try {
+      CreateCompactionDag createCompactionDag = new CreateCompactionDag(dbPath, compactionLogDir);
+      createCompactionDag.pngPrintMutableGraph(imageLocation, PrintableGraph.GraphType.valueOf(graphType));
+      out().println("Graph was generated at '" + imageLocation + "'.");
+    } catch (RocksDBException ex) {
+      err().println("Failed to open RocksDB: " + ex);
+      throw new IOException(ex);
+    }
+    return null;
+  }
+
+  class CreateCompactionDag {
+    // Hash table to track CompactionNode for a given SST File.
+    private final ConcurrentHashMap<String, CompactionNode> compactionNodeMap =
+        new ConcurrentHashMap<>();
+    private final MutableGraph<CompactionNode> backwardCompactionDAG =
+        GraphBuilder.directed().build();
+
+    private ColumnFamilyHandle compactionLogTableCFHandle;
+    private ManagedRocksDB activeRocksDB;
+    private CompactionDagHelper compactionDagHelper;
+
+    CreateCompactionDag(String dbPath, String compactDir) throws RocksDBException {
+      final List<ColumnFamilyHandle> cfHandleList = new ArrayList<>();
+      List<ColumnFamilyDescriptor> cfDescList = RocksDBUtils.getColumnFamilyDescriptors(dbPath);
+      activeRocksDB = ManagedRocksDB.openReadOnly(dbPath, cfDescList, cfHandleList);
+      compactionLogTableCFHandle = RocksDBUtils.getColumnFamilyHandle(COMPACTION_LOG_TABLE, cfHandleList);
+      compactionDagHelper = new CompactionDagHelper(compactDir, activeRocksDB, compactionLogTableCFHandle);
+    }
+
+    public void pngPrintMutableGraph(String filePath, PrintableGraph.GraphType gType)
+        throws IOException, RocksDBException {
+      Objects.requireNonNull(filePath, "Image file path is required.");
+      Objects.requireNonNull(gType, "Graph type is required.");
+
+      loadAllCompactionLogs();
+
+      PrintableGraph graph;
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/CompactionLogDagPrinter.java
user: login: hemantk-12
body: Can you move [graph](https://github.com/apache/ozone/tree/9d2c7a8ac5de788da83c7b691d3f82a2af02a6b9/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/graph) to tools package and deprecate the `PrintCompactionLogDag` API and related code?
created_at: 2025-03-28T17:44:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.debug.om;
+
+import static org.apache.hadoop.ozone.OzoneConsts.COMPACTION_LOG_TABLE;
+
+import com.google.common.graph.GraphBuilder;
+import com.google.common.graph.MutableGraph;
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.hadoop.hdds.cli.AbstractSubcommand;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator;
+import org.apache.hadoop.ozone.debug.RocksDBUtils;
+import org.apache.ozone.compaction.log.CompactionDagHelper;
+import org.apache.ozone.compaction.log.CompactionFileInfo;
+import org.apache.ozone.compaction.log.CompactionLogEntry;
+import org.apache.ozone.graph.PrintableGraph;
+import org.apache.ozone.rocksdiff.CompactionNode;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import picocli.CommandLine;
+
+/**
+ * Handler to generate image for current compaction DAG in the OM leader node.
+ * ozone debug om print-compaction-dag.
+ */
+@CommandLine.Command(
+    name = "print-compaction-dag",
+    aliases = "pcd",
+    description = "Create an image of the current compaction log DAG. " +
+        "This command is an offline command. i.e., it can run on any instance of om.db " +
+        "and does not require OM to be up.")
+public class CompactionLogDagPrinter extends AbstractSubcommand implements Callable<Void> {
+
+  @CommandLine.Option(names = {"-o", "--output-file"},
+      required = true,
+      description = "Path to location at which image will be downloaded. " +
+          "Should include the image file name with \".png\" extension.")
+  private String imageLocation;
+
+  @CommandLine.Option(names = {"--db"},
+      required = true,
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to OM RocksDB")
+  private String dbPath;
+
+  @CommandLine.Option(names = {"--compaction-log"},
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to compaction-log directory.")
+  private String compactionLogDir;
+
+  // TODO: Change graphType to enum.
+  @CommandLine.Option(names = {"-t", "--graph-type"},
+      description = "Type of node name to use in the graph image. (optional)\n Accepted values are: \n" +
+          "  FILE_NAME (default) : to use file name as node name in DAG,\n" +
+          "  KEY_SIZE: to show the no. of keys in the file along with file name in the DAG node name,\n" +
+          "  CUMULATIVE_SIZE: to show the cumulative size along with file name in the DAG node name.",
+      defaultValue = "FILE_NAME")
+  private String graphType;
+
+  @Override
+  public Void call() throws Exception {
+    try {
+      CreateCompactionDag createCompactionDag = new CreateCompactionDag(dbPath, compactionLogDir);
+      createCompactionDag.pngPrintMutableGraph(imageLocation, PrintableGraph.GraphType.valueOf(graphType));
+      out().println("Graph was generated at '" + imageLocation + "'.");
+    } catch (RocksDBException ex) {
+      err().println("Failed to open RocksDB: " + ex);
+      throw new IOException(ex);
+    }
+    return null;
+  }
+
+  class CreateCompactionDag {
+    // Hash table to track CompactionNode for a given SST File.
+    private final ConcurrentHashMap<String, CompactionNode> compactionNodeMap =
+        new ConcurrentHashMap<>();
+    private final MutableGraph<CompactionNode> backwardCompactionDAG =
+        GraphBuilder.directed().build();
+
+    private ColumnFamilyHandle compactionLogTableCFHandle;
+    private ManagedRocksDB activeRocksDB;
+    private CompactionDagHelper compactionDagHelper;
+
+    CreateCompactionDag(String dbPath, String compactDir) throws RocksDBException {
+      final List<ColumnFamilyHandle> cfHandleList = new ArrayList<>();
+      List<ColumnFamilyDescriptor> cfDescList = RocksDBUtils.getColumnFamilyDescriptors(dbPath);
+      activeRocksDB = ManagedRocksDB.openReadOnly(dbPath, cfDescList, cfHandleList);
+      compactionLogTableCFHandle = RocksDBUtils.getColumnFamilyHandle(COMPACTION_LOG_TABLE, cfHandleList);
+      compactionDagHelper = new CompactionDagHelper(compactDir, activeRocksDB, compactionLogTableCFHandle);
+    }
+
+    public void pngPrintMutableGraph(String filePath, PrintableGraph.GraphType gType)
+        throws IOException, RocksDBException {
+      Objects.requireNonNull(filePath, "Image file path is required.");
+      Objects.requireNonNull(gType, "Graph type is required.");
+
+      loadAllCompactionLogs();
+
+      PrintableGraph graph;
+      graph = new PrintableGraph(backwardCompactionDAG, gType);
+      graph.generateImage(filePath);
+    }
+
+    public void loadAllCompactionLogs() {
+      compactionDagHelper.preconditionChecksForLoadAllCompactionLogs(false);
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/CompactionLogDagPrinter.java
user: login: Tejaskriya
body: I have moved this logic out of the command now
created_at: 2025-04-02T08:56:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ozone.compaction.log;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+
+/**
+ * Constants related to rocksDB compaction.
+ */
+public final class RocksDBConsts {
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/compaction/log/RocksDBConsts.java
user: login: Tejaskriya
body: Removed the file
created_at: 2025-04-02T08:56:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,254 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ozone.compaction.log;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_COMMENT_LINE_PREFIX;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_ENTRY_FILE_DELIMITER;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_ENTRY_INPUT_OUTPUT_FILES_DELIMITER;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_ENTRY_LINE_PREFIX;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_FILE_NAME_SUFFIX;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_SEQ_NUM_LINE_PREFIX;
+import static org.apache.ozone.compaction.log.RocksDBConsts.LONG_MAX_STR_LEN;
+import static org.apache.ozone.compaction.log.RocksDBConsts.SPACE_DELIMITER;
+import static org.apache.ozone.compaction.log.RocksDBConsts.SST_FILE_EXTENSION;
+
+import com.google.common.base.Preconditions;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedOptions;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedSstFileReader;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import org.rocksdb.TableProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility methods to populate compactionLogTable from a compaction-log file.
+ */
+public final class CompactionDagHelper {
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/compaction/log/CompactionDagHelper.java
user: login: Tejaskriya
body: I had added this for compactionFile related utility methods. As now we are not including that, I have removed this
created_at: 2025-04-02T09:08:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.debug.om;
+
+import static org.apache.hadoop.ozone.OzoneConsts.COMPACTION_LOG_TABLE;
+
+import com.google.common.graph.GraphBuilder;
+import com.google.common.graph.MutableGraph;
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.hadoop.hdds.cli.AbstractSubcommand;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator;
+import org.apache.hadoop.ozone.debug.RocksDBUtils;
+import org.apache.ozone.compaction.log.CompactionDagHelper;
+import org.apache.ozone.compaction.log.CompactionFileInfo;
+import org.apache.ozone.compaction.log.CompactionLogEntry;
+import org.apache.ozone.graph.PrintableGraph;
+import org.apache.ozone.rocksdiff.CompactionNode;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import picocli.CommandLine;
+
+/**
+ * Handler to generate image for current compaction DAG in the OM leader node.
+ * ozone debug om print-compaction-dag.
+ */
+@CommandLine.Command(
+    name = "print-compaction-dag",
+    aliases = "pcd",
+    description = "Create an image of the current compaction log DAG. " +
+        "This command is an offline command. i.e., it can run on any instance of om.db " +
+        "and does not require OM to be up.")
+public class CompactionLogDagPrinter extends AbstractSubcommand implements Callable<Void> {
+
+  @CommandLine.Option(names = {"-o", "--output-file"},
+      required = true,
+      description = "Path to location at which image will be downloaded. " +
+          "Should include the image file name with \".png\" extension.")
+  private String imageLocation;
+
+  @CommandLine.Option(names = {"--db"},
+      required = true,
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to OM RocksDB")
+  private String dbPath;
+
+  @CommandLine.Option(names = {"--compaction-log"},
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to compaction-log directory.")
+  private String compactionLogDir;
+
+  // TODO: Change graphType to enum.
+  @CommandLine.Option(names = {"-t", "--graph-type"},
+      description = "Type of node name to use in the graph image. (optional)\n Accepted values are: \n" +
+          "  FILE_NAME (default) : to use file name as node name in DAG,\n" +
+          "  KEY_SIZE: to show the no. of keys in the file along with file name in the DAG node name,\n" +
+          "  CUMULATIVE_SIZE: to show the cumulative size along with file name in the DAG node name.",
+      defaultValue = "FILE_NAME")
+  private String graphType;
+
+  @Override
+  public Void call() throws Exception {
+    try {
+      CreateCompactionDag createCompactionDag = new CreateCompactionDag(dbPath, compactionLogDir);
+      createCompactionDag.pngPrintMutableGraph(imageLocation, PrintableGraph.GraphType.valueOf(graphType));
+      out().println("Graph was generated at '" + imageLocation + "'.");
+    } catch (RocksDBException ex) {
+      err().println("Failed to open RocksDB: " + ex);
+      throw new IOException(ex);
+    }
+    return null;
+  }
+
+  class CreateCompactionDag {
+    // Hash table to track CompactionNode for a given SST File.
+    private final ConcurrentHashMap<String, CompactionNode> compactionNodeMap =
+        new ConcurrentHashMap<>();
+    private final MutableGraph<CompactionNode> backwardCompactionDAG =
+        GraphBuilder.directed().build();
+
+    private ColumnFamilyHandle compactionLogTableCFHandle;
+    private ManagedRocksDB activeRocksDB;
+    private CompactionDagHelper compactionDagHelper;
+
+    CreateCompactionDag(String dbPath, String compactDir) throws RocksDBException {
+      final List<ColumnFamilyHandle> cfHandleList = new ArrayList<>();
+      List<ColumnFamilyDescriptor> cfDescList = RocksDBUtils.getColumnFamilyDescriptors(dbPath);
+      activeRocksDB = ManagedRocksDB.openReadOnly(dbPath, cfDescList, cfHandleList);
+      compactionLogTableCFHandle = RocksDBUtils.getColumnFamilyHandle(COMPACTION_LOG_TABLE, cfHandleList);
+      compactionDagHelper = new CompactionDagHelper(compactDir, activeRocksDB, compactionLogTableCFHandle);
+    }
+
+    public void pngPrintMutableGraph(String filePath, PrintableGraph.GraphType gType)
+        throws IOException, RocksDBException {
+      Objects.requireNonNull(filePath, "Image file path is required.");
+      Objects.requireNonNull(gType, "Graph type is required.");
+
+      loadAllCompactionLogs();
+
+      PrintableGraph graph;
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/CompactionLogDagPrinter.java
user: login: Tejaskriya
body: In order to deprecate the APIs, I have added the @Deprecated annotation. Didn't make any changes on OM server side to keep compatibility. Because of that I couldn't move the graph dir to tools as it is being used in RocksDBCheckpointDiffer. 
Is there any other way to correctly deprecate this?
created_at: 2025-04-02T09:10:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -322,10 +271,6 @@ public void close() {
   private final MutableGraph<CompactionNode> backwardCompactionDAG =
       GraphBuilder.directed().build();
 
-  public static final Integer DEBUG_DAG_BUILD_UP = 2;
-  public static final Integer DEBUG_DAG_TRAVERSAL = 3;
-  public static final Integer DEBUG_DAG_LIVE_NODES = 4;
-  public static final Integer DEBUG_READ_ALL_DB_KEYS = 5;
   private static final HashSet<Integer> DEBUG_LEVEL = new HashSet<>();
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDBCheckpointDiffer.java
user: login: Tejaskriya
body: Moved it to the test code
created_at: 2025-04-02T09:13:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -273,6 +273,7 @@ public static boolean isReadOnly(
     case TransferLeadership:
     case SetSafeMode:
     case PrintCompactionLogDag:
+      // deprecated by HDDS-12053, keeping it here for compatibility
path: hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
user: login: hemantk-12
body: Can you please move it above the `PrintCompactionLogDag`? Or on the side of `PrintCompactionLogDag`. Currently, it feels like `GetSnapshotInfo` API is deprecated. 
created_at: 2025-04-02T20:36:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.debug.om;
+
+import static org.apache.hadoop.ozone.OzoneConsts.COMPACTION_LOG_TABLE;
+
+import com.google.common.graph.GraphBuilder;
+import com.google.common.graph.MutableGraph;
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.hadoop.hdds.cli.AbstractSubcommand;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator;
+import org.apache.hadoop.ozone.debug.RocksDBUtils;
+import org.apache.ozone.compaction.log.CompactionDagHelper;
+import org.apache.ozone.compaction.log.CompactionFileInfo;
+import org.apache.ozone.compaction.log.CompactionLogEntry;
+import org.apache.ozone.graph.PrintableGraph;
+import org.apache.ozone.rocksdiff.CompactionNode;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import picocli.CommandLine;
+
+/**
+ * Handler to generate image for current compaction DAG in the OM leader node.
+ * ozone debug om print-compaction-dag.
+ */
+@CommandLine.Command(
+    name = "print-compaction-dag",
+    aliases = "pcd",
+    description = "Create an image of the current compaction log DAG. " +
+        "This command is an offline command. i.e., it can run on any instance of om.db " +
+        "and does not require OM to be up.")
+public class CompactionLogDagPrinter extends AbstractSubcommand implements Callable<Void> {
+
+  @CommandLine.Option(names = {"-o", "--output-file"},
+      required = true,
+      description = "Path to location at which image will be downloaded. " +
+          "Should include the image file name with \".png\" extension.")
+  private String imageLocation;
+
+  @CommandLine.Option(names = {"--db"},
+      required = true,
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to OM RocksDB")
+  private String dbPath;
+
+  @CommandLine.Option(names = {"--compaction-log"},
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to compaction-log directory.")
+  private String compactionLogDir;
+
+  // TODO: Change graphType to enum.
+  @CommandLine.Option(names = {"-t", "--graph-type"},
+      description = "Type of node name to use in the graph image. (optional)\n Accepted values are: \n" +
+          "  FILE_NAME (default) : to use file name as node name in DAG,\n" +
+          "  KEY_SIZE: to show the no. of keys in the file along with file name in the DAG node name,\n" +
+          "  CUMULATIVE_SIZE: to show the cumulative size along with file name in the DAG node name.",
+      defaultValue = "FILE_NAME")
+  private String graphType;
+
+  @Override
+  public Void call() throws Exception {
+    try {
+      CreateCompactionDag createCompactionDag = new CreateCompactionDag(dbPath, compactionLogDir);
+      createCompactionDag.pngPrintMutableGraph(imageLocation, PrintableGraph.GraphType.valueOf(graphType));
+      out().println("Graph was generated at '" + imageLocation + "'.");
+    } catch (RocksDBException ex) {
+      err().println("Failed to open RocksDB: " + ex);
+      throw new IOException(ex);
+    }
+    return null;
+  }
+
+  class CreateCompactionDag {
+    // Hash table to track CompactionNode for a given SST File.
+    private final ConcurrentHashMap<String, CompactionNode> compactionNodeMap =
+        new ConcurrentHashMap<>();
+    private final MutableGraph<CompactionNode> backwardCompactionDAG =
+        GraphBuilder.directed().build();
+
+    private ColumnFamilyHandle compactionLogTableCFHandle;
+    private ManagedRocksDB activeRocksDB;
+    private CompactionDagHelper compactionDagHelper;
+
+    CreateCompactionDag(String dbPath, String compactDir) throws RocksDBException {
+      final List<ColumnFamilyHandle> cfHandleList = new ArrayList<>();
+      List<ColumnFamilyDescriptor> cfDescList = RocksDBUtils.getColumnFamilyDescriptors(dbPath);
+      activeRocksDB = ManagedRocksDB.openReadOnly(dbPath, cfDescList, cfHandleList);
+      compactionLogTableCFHandle = RocksDBUtils.getColumnFamilyHandle(COMPACTION_LOG_TABLE, cfHandleList);
+      compactionDagHelper = new CompactionDagHelper(compactDir, activeRocksDB, compactionLogTableCFHandle);
+    }
+
+    public void pngPrintMutableGraph(String filePath, PrintableGraph.GraphType gType)
+        throws IOException, RocksDBException {
+      Objects.requireNonNull(filePath, "Image file path is required.");
+      Objects.requireNonNull(gType, "Graph type is required.");
+
+      loadAllCompactionLogs();
+
+      PrintableGraph graph;
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/CompactionLogDagPrinter.java
user: login: hemantk-12
body: Since it is an ops tool, IOM, it is OK if you just throw an exception and remove the older implementation in [OzoneManager](https://github.com/apache/ozone/blob/42133076373dd3c1b72f0f8028771166a0594b50/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java#L5050) and [OzoneManagerProtocolClientSideTranslatorPB](https://github.com/apache/ozone/blob/2b48e8c6ec1739d541d5c02183ad1a91d9f7a308/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java#L1333). Change the exception message in [OzoneManagerProtocol](https://github.com/apache/ozone/blob/2b48e8c6ec1739d541d5c02183ad1a91d9f7a308/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java#L782).
created_at: 2025-04-02T20:48:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.debug.om;
+
+import static org.apache.hadoop.ozone.OzoneConsts.COMPACTION_LOG_TABLE;
+
+import com.google.common.graph.GraphBuilder;
+import com.google.common.graph.MutableGraph;
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.hadoop.hdds.cli.AbstractSubcommand;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator;
+import org.apache.hadoop.ozone.debug.RocksDBUtils;
+import org.apache.ozone.compaction.log.CompactionFileInfo;
+import org.apache.ozone.compaction.log.CompactionLogEntry;
+import org.apache.ozone.graph.PrintableGraph;
+import org.apache.ozone.rocksdiff.CompactionNode;
+import org.apache.ozone.rocksdiff.RocksDiffUtils;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import picocli.CommandLine;
+
+/**
+ * Handler to generate image for current compaction DAG.
+ * ozone debug om print-compaction-dag.
+ */
+@CommandLine.Command(
+    name = "print-compaction-dag",
+    aliases = "pcd",
+    description = "Create an image of the current compaction log DAG. " +
+        "This command is an offline command. i.e., it can run on any instance of om.db " +
+        "and does not require OM to be up.")
+public class CompactionLogDagPrinter extends AbstractSubcommand implements Callable<Void> {
+
+  @CommandLine.Option(names = {"-o", "--output-file"},
+      required = true,
+      description = "Path to location at which image will be downloaded. " +
+          "Should include the image file name with \".png\" extension.")
+  private String imageLocation;
+
+  @CommandLine.Option(names = {"--db"},
+      required = true,
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to OM RocksDB")
+  private String dbPath;
+
+  // TODO: Change graphType to enum.
+  @CommandLine.Option(names = {"-t", "--graph-type"},
+      description = "Type of node name to use in the graph image. (optional)\n Accepted values are: \n" +
+          "  FILE_NAME (default) : to use file name as node name in DAG,\n" +
+          "  KEY_SIZE: to show the no. of keys in the file along with file name in the DAG node name,\n" +
+          "  CUMULATIVE_SIZE: to show the cumulative size along with file name in the DAG node name.",
+      defaultValue = "FILE_NAME")
+  private String graphType;
+
+  @Override
+  public Void call() throws Exception {
+    try {
+      CreateCompactionDag createCompactionDag = new CreateCompactionDag(dbPath);
+      createCompactionDag.pngPrintMutableGraph(imageLocation, PrintableGraph.GraphType.valueOf(graphType));
+      out().println("Graph was generated at '" + imageLocation + "'.");
+    } catch (RocksDBException ex) {
+      err().println("Failed to open RocksDB: " + ex);
+      throw new IOException(ex);
+    }
+    return null;
+  }
+
+  class CreateCompactionDag {
+    // Hash table to track CompactionNode for a given SST File.
+    private final ConcurrentHashMap<String, CompactionNode> compactionNodeMap =
+        new ConcurrentHashMap<>();
+    private final MutableGraph<CompactionNode> backwardCompactionDAG =
+        GraphBuilder.directed().build();
+
+    private ColumnFamilyHandle compactionLogTableCFHandle;
+    private ManagedRocksDB activeRocksDB;
+
+    CreateCompactionDag(String dbPath) throws RocksDBException {
+      final List<ColumnFamilyHandle> cfHandleList = new ArrayList<>();
+      List<ColumnFamilyDescriptor> cfDescList = RocksDBUtils.getColumnFamilyDescriptors(dbPath);
+      activeRocksDB = ManagedRocksDB.openReadOnly(dbPath, cfDescList, cfHandleList);
+      compactionLogTableCFHandle = RocksDBUtils.getColumnFamilyHandle(COMPACTION_LOG_TABLE, cfHandleList);
+    }
+
+    public void pngPrintMutableGraph(String filePath, PrintableGraph.GraphType gType)
+        throws IOException, RocksDBException {
+      Objects.requireNonNull(filePath, "Image file path is required.");
+      Objects.requireNonNull(gType, "Graph type is required.");
+
+      loadAllCompactionLogs();
+
+      PrintableGraph graph;
+      graph = new PrintableGraph(backwardCompactionDAG, gType);
+      graph.generateImage(filePath);
+    }
+
+    public void loadAllCompactionLogs() {
+      try (ManagedRocksIterator managedRocksIterator = new ManagedRocksIterator(
+          activeRocksDB.get().newIterator(compactionLogTableCFHandle))) {
+        managedRocksIterator.get().seekToFirst();
+        while (managedRocksIterator.get().isValid()) {
+          byte[] value = managedRocksIterator.get().value();
+          CompactionLogEntry compactionLogEntry =
+              CompactionLogEntry.getFromProtobuf(
+                  HddsProtos.CompactionLogEntryProto.parseFrom(value));
+          populateCompactionDAG(compactionLogEntry.getInputFileInfoList(),
+              compactionLogEntry.getOutputFileInfoList(),
+              compactionLogEntry.getDbSequenceNumber());
+          managedRocksIterator.get().next();
+        }
+      } catch (InvalidProtocolBufferException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    /**
+     * Populate the compaction DAG with input and output SST files lists.
+     * @param inputFiles List of compaction input files.
+     * @param outputFiles List of compaction output files.
+     * @param seqNum DB transaction sequence number.
+     */
+    private void populateCompactionDAG(List<CompactionFileInfo> inputFiles,
+                                       List<CompactionFileInfo> outputFiles,
+                                       long seqNum) {
+
+      for (CompactionFileInfo outfile : outputFiles) {
+
+        final CompactionNode outfileNode = compactionNodeMap.computeIfAbsent(
+            outfile.getFileName(),
+            file -> addNodeToDAG(file, seqNum, outfile.getStartKey(),
+                outfile.getEndKey(), outfile.getColumnFamily()));
+
+
+        for (CompactionFileInfo infile : inputFiles) {
+          final CompactionNode infileNode = compactionNodeMap.computeIfAbsent(
+              infile.getFileName(),
+              file -> addNodeToDAG(file, seqNum, infile.getStartKey(),
+                  infile.getEndKey(), infile.getColumnFamily()));
+
+          // Draw the edges
+          if (!outfileNode.getFileName().equals(infileNode.getFileName())) {
+            backwardCompactionDAG.putEdge(infileNode, outfileNode);
+          }
+        }
+      }
+    }
+
+    private CompactionNode addNodeToDAG(String file, long seqNum, String startKey,
+                                        String endKey, String columnFamily) {
+      long numKeys = 0L;
+      try {
+        numKeys = RocksDiffUtils.getSSTFileSummary(dbPath + "/" + file);
+      } catch (RocksDBException e) {
+        err().println("Warning: Can't get num of keys in SST '" + file + "'. Reason: " + e.getMessage());
+      } catch (FileNotFoundException e) {
+        out().println("Warning: Can't find SST : " + file);
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/CompactionLogDagPrinter.java
user: login: hemantk-12
body: This message will come most of the time because the file may not exist in dbPath because it is either in a snapshotted DB or a compaction-back-up dir. You have to check it in all the snapshot dirs and back-up dir. I'll suggest not having `numKeys` for this tool.
created_at: 2025-04-02T21:05:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,254 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ozone.compaction.log;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_COMMENT_LINE_PREFIX;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_ENTRY_FILE_DELIMITER;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_ENTRY_INPUT_OUTPUT_FILES_DELIMITER;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_ENTRY_LINE_PREFIX;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_FILE_NAME_SUFFIX;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_SEQ_NUM_LINE_PREFIX;
+import static org.apache.ozone.compaction.log.RocksDBConsts.LONG_MAX_STR_LEN;
+import static org.apache.ozone.compaction.log.RocksDBConsts.SPACE_DELIMITER;
+import static org.apache.ozone.compaction.log.RocksDBConsts.SST_FILE_EXTENSION;
+
+import com.google.common.base.Preconditions;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedOptions;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedSstFileReader;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import org.rocksdb.TableProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility methods to populate compactionLogTable from a compaction-log file.
+ */
+public final class CompactionDagHelper {
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/compaction/log/CompactionDagHelper.java
user: login: hemantk-12
body: But now, we have duplicated code.
created_at: 2025-04-02T23:42:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -273,6 +273,7 @@ public static boolean isReadOnly(
     case TransferLeadership:
     case SetSafeMode:
     case PrintCompactionLogDag:
+      // deprecated by HDDS-12053, keeping it here for compatibility
path: hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
user: login: Tejaskriya
body: In multiple other instances in this file (ListTrash, RecoverTrash), we have mentioned the deprecated status and other relevant info below the case itself. Keeping it consistent would be better IMO. I have changed the description a little to make it clearer
created_at: 2025-04-04T08:32:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.debug.om;
+
+import static org.apache.hadoop.ozone.OzoneConsts.COMPACTION_LOG_TABLE;
+
+import com.google.common.graph.GraphBuilder;
+import com.google.common.graph.MutableGraph;
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.hadoop.hdds.cli.AbstractSubcommand;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator;
+import org.apache.hadoop.ozone.debug.RocksDBUtils;
+import org.apache.ozone.compaction.log.CompactionDagHelper;
+import org.apache.ozone.compaction.log.CompactionFileInfo;
+import org.apache.ozone.compaction.log.CompactionLogEntry;
+import org.apache.ozone.graph.PrintableGraph;
+import org.apache.ozone.rocksdiff.CompactionNode;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import picocli.CommandLine;
+
+/**
+ * Handler to generate image for current compaction DAG in the OM leader node.
+ * ozone debug om print-compaction-dag.
+ */
+@CommandLine.Command(
+    name = "print-compaction-dag",
+    aliases = "pcd",
+    description = "Create an image of the current compaction log DAG. " +
+        "This command is an offline command. i.e., it can run on any instance of om.db " +
+        "and does not require OM to be up.")
+public class CompactionLogDagPrinter extends AbstractSubcommand implements Callable<Void> {
+
+  @CommandLine.Option(names = {"-o", "--output-file"},
+      required = true,
+      description = "Path to location at which image will be downloaded. " +
+          "Should include the image file name with \".png\" extension.")
+  private String imageLocation;
+
+  @CommandLine.Option(names = {"--db"},
+      required = true,
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to OM RocksDB")
+  private String dbPath;
+
+  @CommandLine.Option(names = {"--compaction-log"},
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to compaction-log directory.")
+  private String compactionLogDir;
+
+  // TODO: Change graphType to enum.
+  @CommandLine.Option(names = {"-t", "--graph-type"},
+      description = "Type of node name to use in the graph image. (optional)\n Accepted values are: \n" +
+          "  FILE_NAME (default) : to use file name as node name in DAG,\n" +
+          "  KEY_SIZE: to show the no. of keys in the file along with file name in the DAG node name,\n" +
+          "  CUMULATIVE_SIZE: to show the cumulative size along with file name in the DAG node name.",
+      defaultValue = "FILE_NAME")
+  private String graphType;
+
+  @Override
+  public Void call() throws Exception {
+    try {
+      CreateCompactionDag createCompactionDag = new CreateCompactionDag(dbPath, compactionLogDir);
+      createCompactionDag.pngPrintMutableGraph(imageLocation, PrintableGraph.GraphType.valueOf(graphType));
+      out().println("Graph was generated at '" + imageLocation + "'.");
+    } catch (RocksDBException ex) {
+      err().println("Failed to open RocksDB: " + ex);
+      throw new IOException(ex);
+    }
+    return null;
+  }
+
+  class CreateCompactionDag {
+    // Hash table to track CompactionNode for a given SST File.
+    private final ConcurrentHashMap<String, CompactionNode> compactionNodeMap =
+        new ConcurrentHashMap<>();
+    private final MutableGraph<CompactionNode> backwardCompactionDAG =
+        GraphBuilder.directed().build();
+
+    private ColumnFamilyHandle compactionLogTableCFHandle;
+    private ManagedRocksDB activeRocksDB;
+    private CompactionDagHelper compactionDagHelper;
+
+    CreateCompactionDag(String dbPath, String compactDir) throws RocksDBException {
+      final List<ColumnFamilyHandle> cfHandleList = new ArrayList<>();
+      List<ColumnFamilyDescriptor> cfDescList = RocksDBUtils.getColumnFamilyDescriptors(dbPath);
+      activeRocksDB = ManagedRocksDB.openReadOnly(dbPath, cfDescList, cfHandleList);
+      compactionLogTableCFHandle = RocksDBUtils.getColumnFamilyHandle(COMPACTION_LOG_TABLE, cfHandleList);
+      compactionDagHelper = new CompactionDagHelper(compactDir, activeRocksDB, compactionLogTableCFHandle);
+    }
+
+    public void pngPrintMutableGraph(String filePath, PrintableGraph.GraphType gType)
+        throws IOException, RocksDBException {
+      Objects.requireNonNull(filePath, "Image file path is required.");
+      Objects.requireNonNull(gType, "Graph type is required.");
+
+      loadAllCompactionLogs();
+
+      PrintableGraph graph;
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/CompactionLogDagPrinter.java
user: login: Tejaskriya
body: Okay, I have changed it as you have suggested. Could you please take a look again?
created_at: 2025-04-04T08:33:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,254 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ozone.compaction.log;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_COMMENT_LINE_PREFIX;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_ENTRY_FILE_DELIMITER;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_ENTRY_INPUT_OUTPUT_FILES_DELIMITER;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_ENTRY_LINE_PREFIX;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_FILE_NAME_SUFFIX;
+import static org.apache.ozone.compaction.log.RocksDBConsts.COMPACTION_LOG_SEQ_NUM_LINE_PREFIX;
+import static org.apache.ozone.compaction.log.RocksDBConsts.LONG_MAX_STR_LEN;
+import static org.apache.ozone.compaction.log.RocksDBConsts.SPACE_DELIMITER;
+import static org.apache.ozone.compaction.log.RocksDBConsts.SST_FILE_EXTENSION;
+
+import com.google.common.base.Preconditions;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedOptions;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedSstFileReader;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import org.rocksdb.TableProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility methods to populate compactionLogTable from a compaction-log file.
+ */
+public final class CompactionDagHelper {
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/compaction/log/CompactionDagHelper.java
user: login: hemantk-12
body: I think we should do something like this: https://github.com/hemantk-12/ozone/commit/ecf3d0f6aac6e0325c57de1fb6c2a7c1fc45ed1b#diff-332b998c05d99c645df628b01e2b3fc94ce540b5d3c73ed348c3c9e605e57bcbR6-R36
created_at: 2025-04-07T22:27:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.debug.om;
+
+import static org.apache.hadoop.ozone.OzoneConsts.COMPACTION_LOG_TABLE;
+
+import com.google.common.graph.GraphBuilder;
+import com.google.common.graph.MutableGraph;
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.hadoop.hdds.cli.AbstractSubcommand;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator;
+import org.apache.hadoop.ozone.debug.RocksDBUtils;
+import org.apache.ozone.compaction.log.CompactionDagHelper;
+import org.apache.ozone.compaction.log.CompactionFileInfo;
+import org.apache.ozone.compaction.log.CompactionLogEntry;
+import org.apache.ozone.graph.PrintableGraph;
+import org.apache.ozone.rocksdiff.CompactionNode;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import picocli.CommandLine;
+
+/**
+ * Handler to generate image for current compaction DAG in the OM leader node.
+ * ozone debug om print-compaction-dag.
+ */
+@CommandLine.Command(
+    name = "print-compaction-dag",
+    aliases = "pcd",
+    description = "Create an image of the current compaction log DAG. " +
+        "This command is an offline command. i.e., it can run on any instance of om.db " +
+        "and does not require OM to be up.")
+public class CompactionLogDagPrinter extends AbstractSubcommand implements Callable<Void> {
+
+  @CommandLine.Option(names = {"-o", "--output-file"},
+      required = true,
+      description = "Path to location at which image will be downloaded. " +
+          "Should include the image file name with \".png\" extension.")
+  private String imageLocation;
+
+  @CommandLine.Option(names = {"--db"},
+      required = true,
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to OM RocksDB")
+  private String dbPath;
+
+  @CommandLine.Option(names = {"--compaction-log"},
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to compaction-log directory.")
+  private String compactionLogDir;
+
+  // TODO: Change graphType to enum.
+  @CommandLine.Option(names = {"-t", "--graph-type"},
+      description = "Type of node name to use in the graph image. (optional)\n Accepted values are: \n" +
+          "  FILE_NAME (default) : to use file name as node name in DAG,\n" +
+          "  KEY_SIZE: to show the no. of keys in the file along with file name in the DAG node name,\n" +
+          "  CUMULATIVE_SIZE: to show the cumulative size along with file name in the DAG node name.",
+      defaultValue = "FILE_NAME")
+  private String graphType;
+
+  @Override
+  public Void call() throws Exception {
+    try {
+      CreateCompactionDag createCompactionDag = new CreateCompactionDag(dbPath, compactionLogDir);
+      createCompactionDag.pngPrintMutableGraph(imageLocation, PrintableGraph.GraphType.valueOf(graphType));
+      out().println("Graph was generated at '" + imageLocation + "'.");
+    } catch (RocksDBException ex) {
+      err().println("Failed to open RocksDB: " + ex);
+      throw new IOException(ex);
+    }
+    return null;
+  }
+
+  class CreateCompactionDag {
+    // Hash table to track CompactionNode for a given SST File.
+    private final ConcurrentHashMap<String, CompactionNode> compactionNodeMap =
+        new ConcurrentHashMap<>();
+    private final MutableGraph<CompactionNode> backwardCompactionDAG =
+        GraphBuilder.directed().build();
+
+    private ColumnFamilyHandle compactionLogTableCFHandle;
+    private ManagedRocksDB activeRocksDB;
+    private CompactionDagHelper compactionDagHelper;
+
+    CreateCompactionDag(String dbPath, String compactDir) throws RocksDBException {
+      final List<ColumnFamilyHandle> cfHandleList = new ArrayList<>();
+      List<ColumnFamilyDescriptor> cfDescList = RocksDBUtils.getColumnFamilyDescriptors(dbPath);
+      activeRocksDB = ManagedRocksDB.openReadOnly(dbPath, cfDescList, cfHandleList);
+      compactionLogTableCFHandle = RocksDBUtils.getColumnFamilyHandle(COMPACTION_LOG_TABLE, cfHandleList);
+      compactionDagHelper = new CompactionDagHelper(compactDir, activeRocksDB, compactionLogTableCFHandle);
+    }
+
+    public void pngPrintMutableGraph(String filePath, PrintableGraph.GraphType gType)
+        throws IOException, RocksDBException {
+      Objects.requireNonNull(filePath, "Image file path is required.");
+      Objects.requireNonNull(gType, "Graph type is required.");
+
+      loadAllCompactionLogs();
+
+      PrintableGraph graph;
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/CompactionLogDagPrinter.java
user: login: hemantk-12
body: Can you please do the following as well?

1. Mark [printCompactionLogDag](https://github.com/apache/ozone/blob/2b48e8c6ec1739d541d5c02183ad1a91d9f7a308/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java#L1455) deprecated.
2. Remove it form [RpcClient](https://github.com/apache/ozone/blob/2b48e8c6ec1739d541d5c02183ad1a91d9f7a308/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java#L1031), [ClientProtocol](https://github.com/apache/ozone/blob/2b48e8c6ec1739d541d5c02183ad1a91d9f7a308/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java) and [ObjectStore](https://github.com/apache/ozone/blob/2b48e8c6ec1739d541d5c02183ad1a91d9f7a308/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/ObjectStore.java).

created_at: 2025-04-07T22:39:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ozone.rocksdiff;
+
+import com.google.common.graph.GraphBuilder;
+import com.google.common.graph.MutableGraph;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import org.apache.ozone.compaction.log.CompactionFileInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Helper methods for creating compaction DAGs.
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/CompactionDag.java
user: login: hemantk-12
body: nit: please update this comment. It is not a helper anymore.
created_at: 2025-04-22T06:35:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ozone.rocksdiff;
+
+import com.google.common.graph.GraphBuilder;
+import com.google.common.graph.MutableGraph;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import org.apache.ozone.compaction.log.CompactionFileInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Helper methods for creating compaction DAGs.
+ */
+public class CompactionDag {
+  private static final Logger LOG = LoggerFactory.getLogger(CompactionDag.class);
+
+  private final ConcurrentMap<String, CompactionNode> compactionNodeMap = new ConcurrentHashMap<>();
+  private final MutableGraph<CompactionNode> forwardCompactionDAG = GraphBuilder.directed().build();
+  private final MutableGraph<CompactionNode> backwardCompactionDAG = GraphBuilder.directed().build();
+
+  private CompactionNode addNodeToDAG(String file, long seqNum, String startKey, String endKey, String columnFamily) {
+    CompactionNode fileNode = new CompactionNode(file, seqNum, startKey, endKey, columnFamily);
+    backwardCompactionDAG.addNode(fileNode);
+    forwardCompactionDAG.addNode(fileNode);
+    return fileNode;
+  }
+
+  /**
+   * Populate the compaction DAG with input and output SST files lists.
+   *
+   * @param inputFiles  List of compaction input files.
+   * @param outputFiles List of compaction output files.
+   * @param seqNum      DB transaction sequence number.
+   */
+  public void populateCompactionDAG(List<CompactionFileInfo> inputFiles,
+                                    List<CompactionFileInfo> outputFiles,
+                                    long seqNum) {
+
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Input files: {} -> Output files: {}", inputFiles, outputFiles);
+    }
+
+    for (CompactionFileInfo outfile : outputFiles) {
+      final CompactionNode outfileNode = compactionNodeMap.computeIfAbsent(outfile.getFileName(),
+          file -> addNodeToDAG(file, seqNum, outfile.getStartKey(), outfile.getEndKey(), outfile.getColumnFamily()));
+
+      for (CompactionFileInfo infile : inputFiles) {
+        final CompactionNode infileNode = compactionNodeMap.computeIfAbsent(infile.getFileName(),
+            file -> addNodeToDAG(file, seqNum, infile.getStartKey(), infile.getEndKey(), infile.getColumnFamily()));
+
+        // Draw the edges
+        if (!Objects.equals(outfileNode.getFileName(), infileNode.getFileName())) {
+          forwardCompactionDAG.putEdge(outfileNode, infileNode);
+          backwardCompactionDAG.putEdge(infileNode, outfileNode);
+        }
+      }
+    }
+  }
+
+  public Set<String> pruneNodesFromDag(Set<CompactionNode> nodesToRemove) {
+    pruneBackwardDag(backwardCompactionDAG, nodesToRemove);
+    Set<String> sstFilesPruned = pruneForwardDag(forwardCompactionDAG, nodesToRemove);
+    // Remove SST file nodes from compactionNodeMap too,
+    // since those nodes won't be needed after clean up.
+    nodesToRemove.forEach(compactionNodeMap::remove);
+    return sstFilesPruned;
+  }
+
+  /**
+   * Prunes backward DAG's upstream from the level, that needs to be removed.
+   */
+  Set<String> pruneBackwardDag(MutableGraph<CompactionNode> backwardDag,
+                               Set<CompactionNode> startNodes) {
+    Set<String> removedFiles = new HashSet<>();
+    Set<CompactionNode> currentLevel = startNodes;
+
+    synchronized (this) {
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/CompactionDag.java
user: login: hemantk-12
body: `CompactionDag` is not completely thread safe. I think `synchronized` should be on `RocksDBCheckpointDiffer` and should be responsible to update `CompactionDag` in a thread-safe manner.
created_at: 2025-04-22T06:39:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -758,22 +681,27 @@ public void loadAllCompactionLogs() {
     synchronized (this) {
       preconditionChecksForLoadAllCompactionLogs();
       addEntriesFromLogFilesToDagAndCompactionLogTable();
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDBCheckpointDiffer.java
user: login: hemantk-12
body: Can you please comment here that it is only for backward compatibility?
created_at: 2025-04-22T19:27:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -938,7 +866,7 @@ synchronized void internalGetSSTDiffList(
         continue;
       }
 
-      CompactionNode infileNode = compactionNodeMap.get(fileName);
+      CompactionNode infileNode = compactionDag.getCompactionMap().get(fileName);
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDBCheckpointDiffer.java
user: login: hemantk-12
body: nit: maybe add a function `getCompactionNode` in `CompactionDag`.
created_at: 2025-04-22T19:29:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1037,7 +964,7 @@ public Comparator<CompactionNode> reversed() {
 
   @VisibleForTesting
   void dumpCompactionNodeTable() {
-    List<CompactionNode> nodeList = compactionNodeMap.values().stream()
+    List<CompactionNode> nodeList = compactionDag.getCompactionMap().values().stream()
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDBCheckpointDiffer.java
user: login: hemantk-12
body: Similar to the previous one.
created_at: 2025-04-22T19:29:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1386,14 +1205,9 @@ public boolean shouldRun() {
     return !suspended.get();
   }
 
-  @VisibleForTesting
-  public boolean debugEnabled(Integer level) {
-    return DEBUG_LEVEL.contains(level);
-  }
-
   @VisibleForTesting
   public ConcurrentHashMap<String, CompactionNode> getCompactionNodeMap() {
-    return compactionNodeMap;
+    return (ConcurrentHashMap<String, CompactionNode>) compactionDag.getCompactionMap();
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDBCheckpointDiffer.java
user: login: hemantk-12
body: It is Ok to return like this in this patch, but we should do a clean-up and use  `CompactionDag` as encapsulation.
created_at: 2025-04-22T19:36:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -758,22 +681,27 @@ public void loadAllCompactionLogs() {
     synchronized (this) {
       preconditionChecksForLoadAllCompactionLogs();
       addEntriesFromLogFilesToDagAndCompactionLogTable();
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDBCheckpointDiffer.java
user: login: Tejaskriya
body: Added to the java doc for `loadAllCompactionLogs()`
created_at: 2025-04-24T07:15:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ozone.rocksdiff;
+
+import com.google.common.graph.GraphBuilder;
+import com.google.common.graph.MutableGraph;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import org.apache.ozone.compaction.log.CompactionFileInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Helper methods for creating compaction DAGs.
+ */
+public class CompactionDag {
+  private static final Logger LOG = LoggerFactory.getLogger(CompactionDag.class);
+
+  private final ConcurrentMap<String, CompactionNode> compactionNodeMap = new ConcurrentHashMap<>();
+  private final MutableGraph<CompactionNode> forwardCompactionDAG = GraphBuilder.directed().build();
+  private final MutableGraph<CompactionNode> backwardCompactionDAG = GraphBuilder.directed().build();
+
+  private CompactionNode addNodeToDAG(String file, long seqNum, String startKey, String endKey, String columnFamily) {
+    CompactionNode fileNode = new CompactionNode(file, seqNum, startKey, endKey, columnFamily);
+    backwardCompactionDAG.addNode(fileNode);
+    forwardCompactionDAG.addNode(fileNode);
+    return fileNode;
+  }
+
+  /**
+   * Populate the compaction DAG with input and output SST files lists.
+   *
+   * @param inputFiles  List of compaction input files.
+   * @param outputFiles List of compaction output files.
+   * @param seqNum      DB transaction sequence number.
+   */
+  public void populateCompactionDAG(List<CompactionFileInfo> inputFiles,
+                                    List<CompactionFileInfo> outputFiles,
+                                    long seqNum) {
+
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Input files: {} -> Output files: {}", inputFiles, outputFiles);
+    }
+
+    for (CompactionFileInfo outfile : outputFiles) {
+      final CompactionNode outfileNode = compactionNodeMap.computeIfAbsent(outfile.getFileName(),
+          file -> addNodeToDAG(file, seqNum, outfile.getStartKey(), outfile.getEndKey(), outfile.getColumnFamily()));
+
+      for (CompactionFileInfo infile : inputFiles) {
+        final CompactionNode infileNode = compactionNodeMap.computeIfAbsent(infile.getFileName(),
+            file -> addNodeToDAG(file, seqNum, infile.getStartKey(), infile.getEndKey(), infile.getColumnFamily()));
+
+        // Draw the edges
+        if (!Objects.equals(outfileNode.getFileName(), infileNode.getFileName())) {
+          forwardCompactionDAG.putEdge(outfileNode, infileNode);
+          backwardCompactionDAG.putEdge(infileNode, outfileNode);
+        }
+      }
+    }
+  }
+
+  public Set<String> pruneNodesFromDag(Set<CompactionNode> nodesToRemove) {
+    pruneBackwardDag(backwardCompactionDAG, nodesToRemove);
+    Set<String> sstFilesPruned = pruneForwardDag(forwardCompactionDAG, nodesToRemove);
+    // Remove SST file nodes from compactionNodeMap too,
+    // since those nodes won't be needed after clean up.
+    nodesToRemove.forEach(compactionNodeMap::remove);
+    return sstFilesPruned;
+  }
+
+  /**
+   * Prunes backward DAG's upstream from the level, that needs to be removed.
+   */
+  Set<String> pruneBackwardDag(MutableGraph<CompactionNode> backwardDag,
+                               Set<CompactionNode> startNodes) {
+    Set<String> removedFiles = new HashSet<>();
+    Set<CompactionNode> currentLevel = startNodes;
+
+    synchronized (this) {
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/CompactionDag.java
user: login: Tejaskriya
body: We have the following synchronized block in RocksDBCheckpointDiffer#pruneSstFileNodesFromDag
```
synchronized (this) {
      return compactionDag.pruneNodesFromDag(startNodes);
    }
```
I have removed the synchronized blocks present in CompactionDag now
created_at: 2025-04-24T07:15:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.debug.om;
+
+import static org.apache.hadoop.ozone.OzoneConsts.COMPACTION_LOG_TABLE;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import org.apache.hadoop.hdds.cli.AbstractSubcommand;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator;
+import org.apache.hadoop.ozone.debug.RocksDBUtils;
+import org.apache.hadoop.ozone.graph.PrintableGraph;
+import org.apache.ozone.compaction.log.CompactionLogEntry;
+import org.apache.ozone.rocksdiff.CompactionDag;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import picocli.CommandLine;
+
+/**
+ * Handler to generate image for current compaction DAG.
+ * ozone debug om print-compaction-dag.
+ */
+@CommandLine.Command(
+    name = "print-compaction-dag",
+    aliases = "pcd",
+    description = "Create an image of the current compaction log DAG. " +
+        "This command is an offline command. i.e., it can run on any instance of om.db " +
+        "and does not require OM to be up.")
+public class CompactionLogDagPrinter extends AbstractSubcommand implements Callable<Void> {
+
+  @CommandLine.Option(names = {"-o", "--output-file"},
+      required = true,
+      description = "Path to location at which image will be downloaded. " +
+          "Should include the image file name with \".png\" extension.")
+  private String imageLocation;
+
+  @CommandLine.Option(names = {"--db"},
+      required = true,
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to OM RocksDB")
+  private String dbPath;
+
+  @Override
+  public Void call() throws Exception {
+    try {
+      final List<ColumnFamilyHandle> cfHandleList = new ArrayList<>();
+      List<ColumnFamilyDescriptor> cfDescList = RocksDBUtils.getColumnFamilyDescriptors(dbPath);
+      ManagedRocksDB activeRocksDB = ManagedRocksDB.openReadOnly(dbPath, cfDescList, cfHandleList);
+      ColumnFamilyHandle compactionLogTableCFHandle =
+          RocksDBUtils.getColumnFamilyHandle(COMPACTION_LOG_TABLE, cfHandleList);
+
+      CompactionDag compactionDag = new CompactionDag();
+      loadCompactionDagFromDB(activeRocksDB, compactionLogTableCFHandle, compactionDag);
+
+      pngPrintMutableGraph(compactionDag, imageLocation);
+      out().println("Graph was generated at '" + imageLocation + "'.");
+    } catch (RocksDBException ex) {
+      err().println("Failed to open RocksDB: " + ex);
+      throw new IOException(ex);
+    }
+    return null;
+  }
+
+  /**
+   * Read a compactionLofTable and create entries in the dags.
+   */
+  private void loadCompactionDagFromDB(ManagedRocksDB activeRocksDB,
+                                      ColumnFamilyHandle compactionLogTableCFHandle,
+                                      CompactionDag compactionDag) {
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/CompactionLogDagPrinter.java
user: login: adoroszlai
body: nit: Please do not format method signature like this. Whenever visibility / return type / method name / other modifiers are changed, we would have to reindent all parameters.
created_at: 2025-04-29T10:38:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ozone.graph;
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/graph/Edge.java
user: login: adoroszlai
body: Why do we need this change?  `org.apache.hadoop.ozone` is "legacy" and does not need to be used for new packages.
created_at: 2025-04-29T10:38:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.debug.om;
+
+import static org.apache.hadoop.ozone.OzoneConsts.COMPACTION_LOG_TABLE;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import org.apache.hadoop.hdds.cli.AbstractSubcommand;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator;
+import org.apache.hadoop.ozone.debug.RocksDBUtils;
+import org.apache.hadoop.ozone.graph.PrintableGraph;
+import org.apache.ozone.compaction.log.CompactionLogEntry;
+import org.apache.ozone.rocksdiff.CompactionDag;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import picocli.CommandLine;
+
+/**
+ * Handler to generate image for current compaction DAG.
+ * ozone debug om print-compaction-dag.
+ */
+@CommandLine.Command(
+    name = "print-compaction-dag",
+    aliases = "pcd",
+    description = "Create an image of the current compaction log DAG. " +
+        "This command is an offline command. i.e., it can run on any instance of om.db " +
+        "and does not require OM to be up.")
+public class CompactionLogDagPrinter extends AbstractSubcommand implements Callable<Void> {
+
+  @CommandLine.Option(names = {"-o", "--output-file"},
+      required = true,
+      description = "Path to location at which image will be downloaded. " +
+          "Should include the image file name with \".png\" extension.")
+  private String imageLocation;
+
+  @CommandLine.Option(names = {"--db"},
+      required = true,
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to OM RocksDB")
+  private String dbPath;
+
+  @Override
+  public Void call() throws Exception {
+    try {
+      final List<ColumnFamilyHandle> cfHandleList = new ArrayList<>();
+      List<ColumnFamilyDescriptor> cfDescList = RocksDBUtils.getColumnFamilyDescriptors(dbPath);
+      ManagedRocksDB activeRocksDB = ManagedRocksDB.openReadOnly(dbPath, cfDescList, cfHandleList);
+      ColumnFamilyHandle compactionLogTableCFHandle =
+          RocksDBUtils.getColumnFamilyHandle(COMPACTION_LOG_TABLE, cfHandleList);
+
+      CompactionDag compactionDag = new CompactionDag();
+      loadCompactionDagFromDB(activeRocksDB, compactionLogTableCFHandle, compactionDag);
+
+      pngPrintMutableGraph(compactionDag, imageLocation);
+      out().println("Graph was generated at '" + imageLocation + "'.");
+    } catch (RocksDBException ex) {
+      err().println("Failed to open RocksDB: " + ex);
+      throw new IOException(ex);
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/CompactionLogDagPrinter.java
user: login: adoroszlai
body: Do we need to wrap in `IOException`?
created_at: 2025-04-29T10:39:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.debug.om;
+
+import static org.apache.hadoop.ozone.OzoneConsts.COMPACTION_LOG_TABLE;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import org.apache.hadoop.hdds.cli.AbstractSubcommand;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator;
+import org.apache.hadoop.ozone.debug.RocksDBUtils;
+import org.apache.hadoop.ozone.graph.PrintableGraph;
+import org.apache.ozone.compaction.log.CompactionLogEntry;
+import org.apache.ozone.rocksdiff.CompactionDag;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import picocli.CommandLine;
+
+/**
+ * Handler to generate image for current compaction DAG.
+ * ozone debug om print-compaction-dag.
+ */
+@CommandLine.Command(
+    name = "print-compaction-dag",
+    aliases = "pcd",
+    description = "Create an image of the current compaction log DAG. " +
+        "This command is an offline command. i.e., it can run on any instance of om.db " +
+        "and does not require OM to be up.")
+public class CompactionLogDagPrinter extends AbstractSubcommand implements Callable<Void> {
+
+  @CommandLine.Option(names = {"-o", "--output-file"},
+      required = true,
+      description = "Path to location at which image will be downloaded. " +
+          "Should include the image file name with \".png\" extension.")
+  private String imageLocation;
+
+  @CommandLine.Option(names = {"--db"},
+      required = true,
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to OM RocksDB")
+  private String dbPath;
+
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/CompactionLogDagPrinter.java
user: login: adoroszlai
body: Move this to `OMDebug` and remove similar option from `PrefixParser`?
created_at: 2025-04-29T10:40:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.debug.om;
+
+import static org.apache.hadoop.ozone.OzoneConsts.COMPACTION_LOG_TABLE;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import org.apache.hadoop.hdds.cli.AbstractSubcommand;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator;
+import org.apache.hadoop.ozone.debug.RocksDBUtils;
+import org.apache.hadoop.ozone.graph.PrintableGraph;
+import org.apache.ozone.compaction.log.CompactionLogEntry;
+import org.apache.ozone.rocksdiff.CompactionDag;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import picocli.CommandLine;
+
+/**
+ * Handler to generate image for current compaction DAG.
+ * ozone debug om print-compaction-dag.
+ */
+@CommandLine.Command(
+    name = "print-compaction-dag",
+    aliases = "pcd",
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/CompactionLogDagPrinter.java
user: login: adoroszlai
body: Do we need `print-`, i.e. is there any other operation we can do with compaction DAG?

Image is saved to a file, not printed to stdout.  If we need any prefix, maybe `generate-` would be better.
created_at: 2025-04-29T10:43:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,7 +28,8 @@
     name = "om",
     description = "Debug commands related to OM.",
     subcommands = {
-        PrefixParser.class
+        PrefixParser.class,
+        CompactionLogDagPrinter.class
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/OMDebug.java
user: login: adoroszlai
body: nit: Let's add in alphabetical order.
created_at: 2025-04-29T10:44:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -63,14 +63,10 @@ void testPrintNoGraphMessage(PrintableGraph.GraphType graphType) {
   @EnumSource(PrintableGraph.GraphType.class)
   void testPrintActualGraph(PrintableGraph.GraphType graphType) throws IOException {
     Set<CompactionNode> nodes = Stream.of(
-        new CompactionNode("fileName1",
-            100, 100, "startKey1", "endKey1", "columnFamily1"),
-        new CompactionNode("fileName2",
-        200, 200, "startKey2", "endKey2", null),
-        new CompactionNode("fileName3",
-        300, 300, null, "endKey3", "columnFamily3"),
-        new CompactionNode("fileName4",
-        400, 400, "startKey4", null, "columnFamily4")
+        new CompactionNode("fileName1", 100, "startKey1", "endKey1", "columnFamily1"),
+        new CompactionNode("fileName2", 200, "startKey2", "endKey2", null),
+        new CompactionNode("fileName3", 300, null, "endKey3", "columnFamily3"),
+        new CompactionNode("fileName4", 400, "startKey4", null, "columnFamily4")
path: hadoop-ozone/tools/src/test/java/org/apache/hadoop/ozone/graph/TestPrintableGraph.java
user: login: adoroszlai
body: nit: avoid formatting-only changes
created_at: 2025-04-29T10:45:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -724,6 +724,7 @@ public OzoneSnapshot getSnapshotInfo(String volumeName, String bucketName,
   }
 
   @Override
+  @Deprecated
path: hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
user: login: adoroszlai
body: Is it enough to add `@Deprecated` in the interface, and not all implementations?
created_at: 2025-04-29T10:47:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ozone.graph;
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/graph/Edge.java
user: login: Tejaskriya
body: While moving the class this change happened I think inherently. The hierarchy of the packaged seems to be `hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/graph/Edge.java`.
Should I change it manually?
created_at: 2025-04-30T06:45:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -63,14 +63,10 @@ void testPrintNoGraphMessage(PrintableGraph.GraphType graphType) {
   @EnumSource(PrintableGraph.GraphType.class)
   void testPrintActualGraph(PrintableGraph.GraphType graphType) throws IOException {
     Set<CompactionNode> nodes = Stream.of(
-        new CompactionNode("fileName1",
-            100, 100, "startKey1", "endKey1", "columnFamily1"),
-        new CompactionNode("fileName2",
-        200, 200, "startKey2", "endKey2", null),
-        new CompactionNode("fileName3",
-        300, 300, null, "endKey3", "columnFamily3"),
-        new CompactionNode("fileName4",
-        400, 400, "startKey4", null, "columnFamily4")
+        new CompactionNode("fileName1", 100, "startKey1", "endKey1", "columnFamily1"),
+        new CompactionNode("fileName2", 200, "startKey2", "endKey2", null),
+        new CompactionNode("fileName3", 300, null, "endKey3", "columnFamily3"),
+        new CompactionNode("fileName4", 400, "startKey4", null, "columnFamily4")
path: hadoop-ozone/tools/src/test/java/org/apache/hadoop/ozone/graph/TestPrintableGraph.java
user: login: Tejaskriya
body: Apart form the formatting changes we are also removing one parameter (the 3rd one)
created_at: 2025-04-30T06:47:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ozone.graph;
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/graph/Edge.java
user: login: adoroszlai
body: We can create dir `hadoop-ozone/tools/src/main/java/org/apache/ozone` and move these files to `hadoop-ozone/tools/src/main/java/org/apache/ozone/graph`.  (Same for `test`.)
created_at: 2025-04-30T07:02:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -724,6 +724,7 @@ public OzoneSnapshot getSnapshotInfo(String volumeName, String bucketName,
   }
 
   @Override
+  @Deprecated
path: hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
user: login: Tejaskriya
body: I have added it to all places where I saw the annotation was used. Some server side code for it was entirely removed as for this command we don't it to be compatible.
Cross checked with a few other PRs which deprecated some APIs. 
created_at: 2025-04-30T07:02:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.debug.om;
+
+import static org.apache.hadoop.ozone.OzoneConsts.COMPACTION_LOG_TABLE;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import org.apache.hadoop.hdds.cli.AbstractSubcommand;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator;
+import org.apache.hadoop.ozone.debug.RocksDBUtils;
+import org.apache.hadoop.ozone.graph.PrintableGraph;
+import org.apache.ozone.compaction.log.CompactionLogEntry;
+import org.apache.ozone.rocksdiff.CompactionDag;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import picocli.CommandLine;
+
+/**
+ * Handler to generate image for current compaction DAG.
+ * ozone debug om print-compaction-dag.
+ */
+@CommandLine.Command(
+    name = "print-compaction-dag",
+    aliases = "pcd",
+    description = "Create an image of the current compaction log DAG. " +
+        "This command is an offline command. i.e., it can run on any instance of om.db " +
+        "and does not require OM to be up.")
+public class CompactionLogDagPrinter extends AbstractSubcommand implements Callable<Void> {
+
+  @CommandLine.Option(names = {"-o", "--output-file"},
+      required = true,
+      description = "Path to location at which image will be downloaded. " +
+          "Should include the image file name with \".png\" extension.")
+  private String imageLocation;
+
+  @CommandLine.Option(names = {"--db"},
+      required = true,
+      scope = CommandLine.ScopeType.INHERIT,
+      description = "Path to OM RocksDB")
+  private String dbPath;
+
+  @Override
+  public Void call() throws Exception {
+    try {
+      final List<ColumnFamilyHandle> cfHandleList = new ArrayList<>();
+      List<ColumnFamilyDescriptor> cfDescList = RocksDBUtils.getColumnFamilyDescriptors(dbPath);
+      ManagedRocksDB activeRocksDB = ManagedRocksDB.openReadOnly(dbPath, cfDescList, cfHandleList);
+      ColumnFamilyHandle compactionLogTableCFHandle =
+          RocksDBUtils.getColumnFamilyHandle(COMPACTION_LOG_TABLE, cfHandleList);
+
+      CompactionDag compactionDag = new CompactionDag();
+      loadCompactionDagFromDB(activeRocksDB, compactionLogTableCFHandle, compactionDag);
+
+      pngPrintMutableGraph(compactionDag, imageLocation);
+      out().println("Graph was generated at '" + imageLocation + "'.");
+    } catch (RocksDBException ex) {
+      err().println("Failed to open RocksDB: " + ex);
+      throw new IOException(ex);
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/CompactionLogDagPrinter.java
user: login: Tejaskriya
body: Right, we don't need to. Changing it to simply throw ex directly.
created_at: 2025-04-30T07:08:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -724,6 +724,7 @@ public OzoneSnapshot getSnapshotInfo(String volumeName, String bucketName,
   }
 
   @Override
+  @Deprecated
path: hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
user: login: Tejaskriya
body: Followed @hemantk-12 's suggestion from [here](https://github.com/apache/ozone/pull/8016#discussion_r2032103738)
created_at: 2025-04-30T07:33:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -71,16 +69,16 @@ public class PrefixParser implements Callable<Void> {
   private String volume;
 
   public String getDbPath() {
-    return dbPath;
+    return parent.getDbPath();
   }
 
-  public void setDbPath(String dbPath) {
-    this.dbPath = dbPath;
+  public void setDbPath(String path) {
path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/debug/om/PrefixParser.java
user: login: adoroszlai
body: `setDbPath` is unused, can be removed.  Then we don't need to add it in `OMDebug` either.
created_at: 2025-04-30T11:47:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ozone.rocksdiff;
+
+import com.google.common.graph.GraphBuilder;
+import com.google.common.graph.MutableGraph;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import org.apache.ozone.compaction.log.CompactionFileInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Wrapper class storing DAGs of SST files for tracking compactions.
+ */
+public class CompactionDag {
+  private static final Logger LOG = LoggerFactory.getLogger(CompactionDag.class);
+
+  private final ConcurrentMap<String, CompactionNode> compactionNodeMap = new ConcurrentHashMap<>();
+  private final MutableGraph<CompactionNode> forwardCompactionDAG = GraphBuilder.directed().build();
+  private final MutableGraph<CompactionNode> backwardCompactionDAG = GraphBuilder.directed().build();
+
+  private CompactionNode addNodeToDAG(String file, long seqNum, String startKey, String endKey, String columnFamily) {
+    CompactionNode fileNode = new CompactionNode(file, seqNum, startKey, endKey, columnFamily);
+    backwardCompactionDAG.addNode(fileNode);
+    forwardCompactionDAG.addNode(fileNode);
+    return fileNode;
+  }
+
+  /**
+   * Populate the compaction DAG with input and output SST files lists.
+   *
+   * @param inputFiles  List of compaction input files.
+   * @param outputFiles List of compaction output files.
+   * @param seqNum      DB transaction sequence number.
+   */
+  public void populateCompactionDAG(List<CompactionFileInfo> inputFiles,
+                                    List<CompactionFileInfo> outputFiles,
+                                    long seqNum) {
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/CompactionDag.java
user: login: adoroszlai
body: Sorry if it was unclear, but the comment about formatting method signatures was meant to apply to all similar cases.
created_at: 2025-04-30T11:52:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ozone.rocksdiff;
+
+import com.google.common.graph.GraphBuilder;
+import com.google.common.graph.MutableGraph;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import org.apache.ozone.compaction.log.CompactionFileInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Wrapper class storing DAGs of SST files for tracking compactions.
+ */
+public class CompactionDag {
+  private static final Logger LOG = LoggerFactory.getLogger(CompactionDag.class);
+
+  private final ConcurrentMap<String, CompactionNode> compactionNodeMap = new ConcurrentHashMap<>();
+  private final MutableGraph<CompactionNode> forwardCompactionDAG = GraphBuilder.directed().build();
+  private final MutableGraph<CompactionNode> backwardCompactionDAG = GraphBuilder.directed().build();
+
+  private CompactionNode addNodeToDAG(String file, long seqNum, String startKey, String endKey, String columnFamily) {
+    CompactionNode fileNode = new CompactionNode(file, seqNum, startKey, endKey, columnFamily);
+    backwardCompactionDAG.addNode(fileNode);
+    forwardCompactionDAG.addNode(fileNode);
+    return fileNode;
+  }
+
+  /**
+   * Populate the compaction DAG with input and output SST files lists.
+   *
+   * @param inputFiles  List of compaction input files.
+   * @param outputFiles List of compaction output files.
+   * @param seqNum      DB transaction sequence number.
+   */
+  public void populateCompactionDAG(List<CompactionFileInfo> inputFiles,
+                                    List<CompactionFileInfo> outputFiles,
+                                    long seqNum) {
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/CompactionDag.java
user: login: Tejaskriya
body: Got it, I have changed it in all the files that are being touched in the PR. 
created_at: 2025-05-02T04:20:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -32,17 +32,9 @@ public class CompactionNode {
   private final String endKey;
   private final String columnFamily;
 
-  /**
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/CompactionNode.java
user: login: jojochuang
body: perhaps we should keep the javadoc for the constructor?
created_at: 2025-05-05T19:33:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1386,14 +1205,9 @@ public boolean shouldRun() {
     return !suspended.get();
   }
 
-  @VisibleForTesting
-  public boolean debugEnabled(Integer level) {
-    return DEBUG_LEVEL.contains(level);
-  }
-
   @VisibleForTesting
   public ConcurrentHashMap<String, CompactionNode> getCompactionNodeMap() {
-    return compactionNodeMap;
+    return (ConcurrentHashMap<String, CompactionNode>) compactionDag.getCompactionMap();
path: hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDBCheckpointDiffer.java
user: login: jojochuang
body: IMO this method should be rewritten as ConcurrentMap.
In general, returning more abstract type is preferred, as it avoid a lot of unnecessary code change later, just like this.
created_at: 2025-05-05T20:06:57Z
author_association: CONTRIBUTOR

PR comments:
user: login: Tejaskriya
created_at: 2025-03-07T08:16:41Z
body: @adoroszlai thank you for the review! I have addressed all of the comments
author_association: CONTRIBUTOR
user: login: Tejaskriya
created_at: 2025-03-12T08:52:15Z
body: @hemantk-12 The util class I had added `PopulateCompactionTable` was for the backward compatibility point you have mentioned above, all the methods in that class are for parsing the log file and adding it to the rocksDB compactionLogTable. It doesn't deal with the graph creation aspect of the command.

For the methods to add/remove input/output file nodes, I have created the methods in `CompactionLogDagPrinter` itself. It can be moved to `PrintableGraph` but the patch size would increase even more as it will need refactoring in `RocksDBCheckpointDiffer, PrintableGraph, CompactionLogDagPrinter`. I suggest to have a follow-up jira which refactors the graph related method. Would that be okay?
author_association: CONTRIBUTOR
user: login: Tejaskriya
created_at: 2025-04-22T04:04:45Z
body: @hemantk-12 , I have done the additional refactoring as you have suggested. Could you please take a look again?
author_association: CONTRIBUTOR
user: login: Tejaskriya
created_at: 2025-04-24T07:18:22Z
body: Thanks for the review @hemantk-12 , I have addressed all the comments. If the new changes look good could you please approve the PR?
author_association: CONTRIBUTOR
user: login: Tejaskriya
created_at: 2025-04-30T09:23:19Z
body: Thanks for the review @adoroszlai , I have made the suggestions as you suggested. Could you please take a look at it again?
author_association: CONTRIBUTOR
user: login: Tejaskriya
created_at: 2025-05-05T06:33:33Z
body: @adoroszlai thanks for the review
@hemantk-12 reviewed this multiple rounds, and the last round had some minor comments only. I think we are good to merge this from snapshot point of view.

But @jojochuang self-request a review a few days ago, Would you like to take a look?
author_association: CONTRIBUTOR
user: login: jojochuang
created_at: 2025-05-05T20:10:31Z
body: Thanks for the PR @Tejaskriya and review comments from @adoroszlai @hemantk-12 @errose28 !
author_association: CONTRIBUTOR
user: login: Tejaskriya
created_at: 2025-05-06T04:31:24Z
body: Thanks for the review and merge @jojochuang . I have addressed the couple of comments in this [PR](https://github.com/apache/ozone/pull/8395)
author_association: CONTRIBUTOR