Copilot commented on code in PR #9969:
URL: https://github.com/apache/ozone/pull/9969#discussion_r3007380623


##########
hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java:
##########
@@ -121,20 +176,47 @@ protected String getActionName() {
 
   /**
    * Create a JSON result map for a report.
-   * 
+   *
    * @param report the DiskBalancer report proto
    * @return JSON result map
    */
-  private Map<String, Object> createReportResult(
-      HddsProtos.DatanodeDiskBalancerInfoProto report) {
+  private Map<String, Object> toJson(DatanodeDiskBalancerInfoProto report) {
     Map<String, Object> result = new LinkedHashMap<>();
-    // Format datanode string with hostname and IP address
-    String formattedDatanode = DiskBalancerSubCommandUtil.getDatanodeHostAndIp(
-        report.getNode());
-    result.put("datanode", formattedDatanode);
+    result.put("datanode", 
DiskBalancerSubCommandUtil.getDatanodeHostAndIp(report.getNode()));
     result.put("action", "report");
     result.put("status", "success");
     result.put("volumeDensity", report.getCurrentVolumeDensitySum());
+
+    if (report.hasIdealUsage() && report.hasDiskBalancerConf()
+        && report.getDiskBalancerConf().hasThreshold()) {
+      double idealUsage = report.getIdealUsage();
+      double threshold = report.getDiskBalancerConf().getThreshold();
+      double lt = idealUsage - threshold / 100.0;
+      double ut = idealUsage + threshold / 100.0;
+      result.put("idealUsage", String.format("%.8f", idealUsage));
+      result.put("threshold %", report.getDiskBalancerConf().getThreshold());
+      result.put("thresholdRange", String.format("(%.08f, %.08f)", lt, ut));

Review Comment:
   The JSON keys/types introduced here are inconsistent with existing 
DiskBalancer JSON output (eg status uses numeric `threshold`). Using a key with 
a space/percent sign (`"threshold %"`) and emitting `idealUsage` as a formatted 
string makes the JSON harder to consume programmatically. Prefer stable 
identifier-style keys (eg `thresholdPercent`) and keep numeric values as 
numbers.
   ```suggestion
         result.put("idealUsage", idealUsage);
         result.put("thresholdPercent", threshold);
         Map<String, Double> thresholdRange = new LinkedHashMap<>();
         thresholdRange.put("lower", lt);
         thresholdRange.put("upper", ut);
         result.put("thresholdRange", thresholdRange);
   ```



##########
hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java:
##########
@@ -121,20 +176,47 @@ protected String getActionName() {
 
   /**
    * Create a JSON result map for a report.
-   * 
+   *
    * @param report the DiskBalancer report proto
    * @return JSON result map
    */
-  private Map<String, Object> createReportResult(
-      HddsProtos.DatanodeDiskBalancerInfoProto report) {
+  private Map<String, Object> toJson(DatanodeDiskBalancerInfoProto report) {
     Map<String, Object> result = new LinkedHashMap<>();
-    // Format datanode string with hostname and IP address
-    String formattedDatanode = DiskBalancerSubCommandUtil.getDatanodeHostAndIp(
-        report.getNode());
-    result.put("datanode", formattedDatanode);
+    result.put("datanode", 
DiskBalancerSubCommandUtil.getDatanodeHostAndIp(report.getNode()));
     result.put("action", "report");
     result.put("status", "success");
     result.put("volumeDensity", report.getCurrentVolumeDensitySum());
+
+    if (report.hasIdealUsage() && report.hasDiskBalancerConf()
+        && report.getDiskBalancerConf().hasThreshold()) {
+      double idealUsage = report.getIdealUsage();
+      double threshold = report.getDiskBalancerConf().getThreshold();
+      double lt = idealUsage - threshold / 100.0;
+      double ut = idealUsage + threshold / 100.0;
+      result.put("idealUsage", String.format("%.8f", idealUsage));
+      result.put("threshold %", report.getDiskBalancerConf().getThreshold());
+      result.put("thresholdRange", String.format("(%.08f, %.08f)", lt, ut));

Review Comment:
   As in the text output, the computed thresholdRange bounds can go outside [0, 
1] and produce invalid ranges in JSON. Clamp `lt`/`ut` before formatting so the 
reported range reflects valid utilization ratios.
   ```suggestion
         double ltClamped = Math.max(0.0, Math.min(1.0, lt));
         double utClamped = Math.max(0.0, Math.min(1.0, ut));
         result.put("idealUsage", String.format("%.8f", idealUsage));
         result.put("threshold %", report.getDiskBalancerConf().getThreshold());
         result.put("thresholdRange",
             String.format("(%.08f, %.08f)", ltClamped, utClamped));
   ```



##########
hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java:
##########
@@ -82,36 +83,90 @@ protected void displayResults(List<String> successNodes, 
List<String> failedNode
 
     // Display consolidated report for successful nodes
     if (!successNodes.isEmpty() && !reports.isEmpty()) {
-      List<HddsProtos.DatanodeDiskBalancerInfoProto> reportList = 
-          new ArrayList<>(reports.values());
+      List<DatanodeDiskBalancerInfoProto> reportList = new 
ArrayList<>(reports.values());
       System.out.println(generateReport(reportList));
     }
   }
 
   private String generateReport(List<DatanodeDiskBalancerInfoProto> protos) {
-    // Sort by volume density in descending order (highest imbalance first)
-    List<DatanodeDiskBalancerInfoProto> sortedProtos = new ArrayList<>(protos);
-    sortedProtos.sort((a, b) ->
+    protos.sort((a, b) ->
         Double.compare(b.getCurrentVolumeDensitySum(), 
a.getCurrentVolumeDensitySum()));
 
-    StringBuilder formatBuilder = new StringBuilder("Report result:%n" +
-        "%-60s %s%n");
-
+    StringBuilder formatBuilder = new StringBuilder("Report result:%n");
     List<String> contentList = new ArrayList<>();
-    contentList.add("Datanode");
-    contentList.add("VolumeDensity");
-
-    for (DatanodeDiskBalancerInfoProto proto : sortedProtos) {
-      formatBuilder.append("%-60s %s%n");
-      // Format datanode string with hostname and IP address
-      String formattedDatanode = 
DiskBalancerSubCommandUtil.getDatanodeHostAndIp(
-          proto.getNode());
-      contentList.add(formattedDatanode);
-      contentList.add(String.valueOf(proto.getCurrentVolumeDensitySum()));
+
+    for (int i = 0; i < protos.size(); i++) {
+      DatanodeDiskBalancerInfoProto p = protos.get(i);
+      String dn = DiskBalancerSubCommandUtil.getDatanodeHostAndIp(p.getNode());
+
+      StringBuilder header = new StringBuilder();
+      header.append("Datanode: ").append(dn).append('\n');
+      header.append("Aggregate VolumeDataDensity: ").
+          append(p.getCurrentVolumeDensitySum()).append('\n');
+
+      if (p.hasIdealUsage() && p.hasDiskBalancerConf()
+          && p.getDiskBalancerConf().hasThreshold()) {
+        double idealUsage = p.getIdealUsage();
+        double threshold = p.getDiskBalancerConf().getThreshold();
+        double lt = idealUsage - threshold / 100.0;
+        double ut = idealUsage + threshold / 100.0;
+        header.append("IdealUsage: ").append(String.format("%.8f", 
idealUsage));
+        header.append(" | Threshold: ").append(threshold).append('%');
+        header.append(" | ThresholdRange: (").append(String.format("%.8f", 
lt));
+        header.append(", ").append(String.format("%.8f", 
ut)).append(')').append('\n').append('\n');
+        header.append("Volume Details -:").append('\n');
+      }
+      formatBuilder.append("%s%n");
+      contentList.add(header.toString());
+
+      if (p.getVolumeInfoCount() > 0 && p.hasIdealUsage()) {
+        formatBuilder.append("%-45s %-40s %15s %15s %30s %20s %15s %15s%n");
+        contentList.add("StorageID");
+        contentList.add("StoragePath");
+        contentList.add("TotalCapacity");
+        contentList.add("UsedSpace");
+        contentList.add("Container Pre-AllocatedSpace");
+        contentList.add("EffectiveUsedSpace");
+        contentList.add("Utilization");
+        contentList.add("VolumeDensity");
+
+        double ideal = p.getIdealUsage();
+        for (VolumeReportProto v : p.getVolumeInfoList()) {
+          formatBuilder.append("%-45s %-40s %15s %15s %30s %20s %15s %15s%n");
+          contentList.add(v.getStorageId() != null ? v.getStorageId() : "-");

Review Comment:
   `v.getStorageId()` is never null for protobuf-generated accessors (it 
defaults to an empty string when unset). The current null-check won’t emit "-" 
for missing storageId. Use `v.hasStorageId()` (and/or 
`!v.getStorageId().isEmpty()`) to decide when to print a placeholder.
   ```suggestion
             contentList.add(v.hasStorageId() ? v.getStorageId() : "-");
   ```



##########
hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java:
##########
@@ -82,36 +83,90 @@ protected void displayResults(List<String> successNodes, 
List<String> failedNode
 
     // Display consolidated report for successful nodes
     if (!successNodes.isEmpty() && !reports.isEmpty()) {
-      List<HddsProtos.DatanodeDiskBalancerInfoProto> reportList = 
-          new ArrayList<>(reports.values());
+      List<DatanodeDiskBalancerInfoProto> reportList = new 
ArrayList<>(reports.values());
       System.out.println(generateReport(reportList));
     }
   }
 
   private String generateReport(List<DatanodeDiskBalancerInfoProto> protos) {
-    // Sort by volume density in descending order (highest imbalance first)
-    List<DatanodeDiskBalancerInfoProto> sortedProtos = new ArrayList<>(protos);
-    sortedProtos.sort((a, b) ->
+    protos.sort((a, b) ->
         Double.compare(b.getCurrentVolumeDensitySum(), 
a.getCurrentVolumeDensitySum()));
 
-    StringBuilder formatBuilder = new StringBuilder("Report result:%n" +
-        "%-60s %s%n");
-
+    StringBuilder formatBuilder = new StringBuilder("Report result:%n");
     List<String> contentList = new ArrayList<>();
-    contentList.add("Datanode");
-    contentList.add("VolumeDensity");
-
-    for (DatanodeDiskBalancerInfoProto proto : sortedProtos) {
-      formatBuilder.append("%-60s %s%n");
-      // Format datanode string with hostname and IP address
-      String formattedDatanode = 
DiskBalancerSubCommandUtil.getDatanodeHostAndIp(
-          proto.getNode());
-      contentList.add(formattedDatanode);
-      contentList.add(String.valueOf(proto.getCurrentVolumeDensitySum()));
+
+    for (int i = 0; i < protos.size(); i++) {
+      DatanodeDiskBalancerInfoProto p = protos.get(i);
+      String dn = DiskBalancerSubCommandUtil.getDatanodeHostAndIp(p.getNode());
+
+      StringBuilder header = new StringBuilder();
+      header.append("Datanode: ").append(dn).append('\n');
+      header.append("Aggregate VolumeDataDensity: ").
+          append(p.getCurrentVolumeDensitySum()).append('\n');
+
+      if (p.hasIdealUsage() && p.hasDiskBalancerConf()
+          && p.getDiskBalancerConf().hasThreshold()) {
+        double idealUsage = p.getIdealUsage();
+        double threshold = p.getDiskBalancerConf().getThreshold();
+        double lt = idealUsage - threshold / 100.0;
+        double ut = idealUsage + threshold / 100.0;

Review Comment:
   ThresholdRange is computed as idealUsage +/- threshold/100 without clamping 
to [0, 1]. If idealUsage is near 0 or 1 (or threshold is large), this can print 
negative or >1 ranges, which is misleading to users. Clamp the computed 
lower/upper bounds before formatting.
   ```suggestion
           double margin = threshold / 100.0;
           double lt = Math.max(0.0, idealUsage - margin);
           double ut = Math.min(1.0, idealUsage + margin);
   ```



##########
hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java:
##########
@@ -27,22 +27,23 @@
 import java.util.concurrent.ConcurrentHashMap;
 import org.apache.hadoop.hdds.cli.HddsVersionProvider;
 import org.apache.hadoop.hdds.protocol.DiskBalancerProtocol;
-import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
 import 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.DatanodeDiskBalancerInfoProto;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.VolumeReportProto;
+import org.apache.hadoop.util.StringUtils;
 import picocli.CommandLine.Command;
 
 /**
  * Handler to get disk balancer report.
  */
 @Command(
     name = "report",
-    description = "Get DiskBalancer volume density report",
+    description = "Get DiskBalancer volume density report and per volume info 
from dns.",

Review Comment:
   The command description says "from dns" which is ambiguous (looks like 
Domain Name System). Consider changing to "from DNs" or "from datanodes" for 
clarity, and drop the trailing period to match other subcommand descriptions.
   ```suggestion
       description = "Get DiskBalancer volume density report and per volume 
info from datanodes",
   ```



##########
hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerService.java:
##########
@@ -685,9 +688,42 @@ public DiskBalancerInfo getDiskBalancerInfo() {
       bytesToMove = calculateBytesToMove(volumeUsages);
     }
 
-    return new DiskBalancerInfo(operationalState, threshold, bandwidthInMB,
+    DiskBalancerInfo info = new DiskBalancerInfo(operationalState, threshold, 
bandwidthInMB,
         parallelThread, stopAfterDiskEven, version, metrics.getSuccessCount(),
         metrics.getFailureCount(), bytesToMove, metrics.getSuccessBytes(), 
volumeDataDensity);
+    info.setIdealUsage(getIdealUsage(volumeUsages));
+    info.setVolumeInfo(buildVolumeReportProto(volumeUsages));
+    return info;
+  }
+
+  /**
+   * Build a list of VolumeReportProto from a list of VolumeFixedUsage.
+   * VolumeReportProto consists of information like StorageID,
+   * volume utilization and committed bytes to the client.
+   *
+   * @param volumeSet snapshot of VolumeFixedUsage which contains the usage 
information of each volume
+   * @return a list of VolumeReportProto which will be sent to clients for 
reporting volume status
+   */
+  public static List<VolumeReportProto> 
buildVolumeReportProto(List<VolumeFixedUsage> volumeSet) {
+    if (volumeSet == null || volumeSet.isEmpty()) {
+      return Collections.emptyList();
+    }
+
+    List<VolumeReportProto> result = new ArrayList<>();
+    for (VolumeFixedUsage v : volumeSet) {
+      HddsVolume volume = v.getVolume();
+      String path = volume.getStorageDir() != null ? 
volume.getStorageDir().getPath() : "";
+      result.add(VolumeReportProto.newBuilder()
+          .setStorageId(volume.getStorageID())
+          .setStoragePath(path)
+          .setTotalCapacity(v.getUsage().getCapacity())
+          .setUsedSpace(v.getUsage().getUsedSpace())
+          .setCommittedBytes(volume.getCommittedBytes())
+          .setEffectiveUsedSpace(v.getEffectiveUsed())
+          .setUtilization(v.getUtilization())
+          .build());

Review Comment:
   When `volume.getStorageDir()` is null, `path` is set to an empty string and 
then written into the proto, which makes `hasStoragePath()` true and causes the 
CLI to print an empty value instead of the intended placeholder. Avoid setting 
`storagePath` when it’s unknown (or use a meaningful placeholder consistently).
   ```suggestion
         VolumeReportProto.Builder builder = VolumeReportProto.newBuilder()
             .setStorageId(volume.getStorageID())
             .setTotalCapacity(v.getUsage().getCapacity())
             .setUsedSpace(v.getUsage().getUsedSpace())
             .setCommittedBytes(volume.getCommittedBytes())
             .setEffectiveUsedSpace(v.getEffectiveUsed())
             .setUtilization(v.getUtilization());
         if (volume.getStorageDir() != null) {
           builder.setStoragePath(volume.getStorageDir().getPath());
         }
         result.add(builder.build());
   ```



##########
hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestDiskBalancerSubCommands.java:
##########
@@ -754,9 +763,56 @@ private DatanodeDiskBalancerInfoProto 
generateRandomReportProto(String hostname)
             .build())
         .build();
 
+    DiskBalancerConfigurationProto configProto = createConfigProto(threshold, 
100L, 5, true);
+
+    long committed1 = (random.nextLong() & Long.MAX_VALUE) % (1024L * 1024 * 
1024);
+    long committed2 = (random.nextLong() & Long.MAX_VALUE) % (1024L * 1024 * 
1024);
+    long capacity1 = 500L * 1024 * 1024 * 1024 + random.nextInt(1024 * 1024);
+    long capacity2 = 600L * 1024 * 1024 * 1024 + random.nextInt(1024 * 1024);
+    double util1 = idealUsage + random.nextDouble() * 0.1;
+    double util2 = idealUsage - random.nextDouble() * 0.1;
+    long used1 = (long) (capacity1 * util1);
+    long used2 = (long) (capacity2 * util2);
+    long effective1 = used1 + committed1;
+    long effective2 = used2 + committed2;
+    String path1 = "/data/hdds-" + hostname + "-1";
+    String path2 = "/data/hdds-" + hostname + "-2";
+    VolumeReportProto vol1 = VolumeReportProto.newBuilder()
+        .setStorageId("DISK-" + hostname + "-vol1")
+        .setStoragePath(path1)
+        .setUtilization(idealUsage + random.nextDouble() * 0.1)

Review Comment:
   In this test helper, `used1` is computed using `util1`, but the proto’s 
`vol1` utilization is generated independently (`idealUsage + 
random.nextDouble() * 0.1`). This can make the generated report internally 
inconsistent (utilization doesn’t match usedSpace/capacity). Use `util1` when 
setting `vol1` utilization so the fields stay consistent.
   ```suggestion
           .setUtilization(util1)
   ```



##########
hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java:
##########
@@ -82,36 +83,90 @@ protected void displayResults(List<String> successNodes, 
List<String> failedNode
 
     // Display consolidated report for successful nodes
     if (!successNodes.isEmpty() && !reports.isEmpty()) {
-      List<HddsProtos.DatanodeDiskBalancerInfoProto> reportList = 
-          new ArrayList<>(reports.values());
+      List<DatanodeDiskBalancerInfoProto> reportList = new 
ArrayList<>(reports.values());
       System.out.println(generateReport(reportList));
     }
   }
 
   private String generateReport(List<DatanodeDiskBalancerInfoProto> protos) {
-    // Sort by volume density in descending order (highest imbalance first)
-    List<DatanodeDiskBalancerInfoProto> sortedProtos = new ArrayList<>(protos);
-    sortedProtos.sort((a, b) ->
+    protos.sort((a, b) ->
         Double.compare(b.getCurrentVolumeDensitySum(), 
a.getCurrentVolumeDensitySum()));
 
-    StringBuilder formatBuilder = new StringBuilder("Report result:%n" +
-        "%-60s %s%n");
-
+    StringBuilder formatBuilder = new StringBuilder("Report result:%n");
     List<String> contentList = new ArrayList<>();
-    contentList.add("Datanode");
-    contentList.add("VolumeDensity");
-
-    for (DatanodeDiskBalancerInfoProto proto : sortedProtos) {
-      formatBuilder.append("%-60s %s%n");
-      // Format datanode string with hostname and IP address
-      String formattedDatanode = 
DiskBalancerSubCommandUtil.getDatanodeHostAndIp(
-          proto.getNode());
-      contentList.add(formattedDatanode);
-      contentList.add(String.valueOf(proto.getCurrentVolumeDensitySum()));
+
+    for (int i = 0; i < protos.size(); i++) {
+      DatanodeDiskBalancerInfoProto p = protos.get(i);
+      String dn = DiskBalancerSubCommandUtil.getDatanodeHostAndIp(p.getNode());
+
+      StringBuilder header = new StringBuilder();
+      header.append("Datanode: ").append(dn).append('\n');
+      header.append("Aggregate VolumeDataDensity: ").
+          append(p.getCurrentVolumeDensitySum()).append('\n');
+
+      if (p.hasIdealUsage() && p.hasDiskBalancerConf()
+          && p.getDiskBalancerConf().hasThreshold()) {
+        double idealUsage = p.getIdealUsage();
+        double threshold = p.getDiskBalancerConf().getThreshold();
+        double lt = idealUsage - threshold / 100.0;
+        double ut = idealUsage + threshold / 100.0;
+        header.append("IdealUsage: ").append(String.format("%.8f", 
idealUsage));
+        header.append(" | Threshold: ").append(threshold).append('%');
+        header.append(" | ThresholdRange: (").append(String.format("%.8f", 
lt));
+        header.append(", ").append(String.format("%.8f", 
ut)).append(')').append('\n').append('\n');
+        header.append("Volume Details -:").append('\n');

Review Comment:
   The header label "Volume Details -:" is awkward/typo-like in user-facing 
output. Consider renaming to "Volume Details:" (or similar) for readability.
   ```suggestion
           header.append("Volume Details:").append('\n');
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to