From 2ccb5ec947727d718d01160a274456258e36bb09 Mon Sep 17 00:00:00 2001 From: Gargi Jaiswal Date: Mon, 23 Mar 2026 12:41:10 +0530 Subject: [PATCH 1/4] added detailed volume info in diskbalancer report --- .../diskbalancer/DiskBalancerInfo.java | 26 ++++- .../DiskBalancerProtocolServer.java | 2 + .../diskbalancer/DiskBalancerService.java | 34 +++++- .../TestDiskBalancerProtocolServer.java | 39 ++++++- .../src/main/proto/hdds.proto | 8 ++ .../DiskBalancerReportSubcommand.java | 107 +++++++++++++----- .../datanode/TestDiskBalancerSubCommands.java | 48 ++++++-- 7 files changed, 221 insertions(+), 43 deletions(-) diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerInfo.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerInfo.java index 15cfadbabdd8..4492047844dd 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerInfo.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerInfo.java @@ -17,11 +17,15 @@ package org.apache.hadoop.ozone.container.diskbalancer; +import java.util.Collections; +import java.util.List; import java.util.Objects; import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DiskBalancerRunningStatus; +import org.apache.hadoop.hdds.protocol.proto.HddsProtos.VolumeReportProto; /** - * DiskBalancer's information to persist. + * DiskBalancer's information to persist and for report. + * Report-only fields (idealUsage, volumeInfo) are NOT persisted to YAML. */ public class DiskBalancerInfo { private DiskBalancerRunningStatus operationalState; @@ -35,6 +39,10 @@ public class DiskBalancerInfo { private long bytesToMove; private long balancedBytes; private double volumeDataDensity; + // Report-only: ideal usage from volume snapshot. NOT persisted. + private double idealUsage; + // Report-only: per-volume info. NOT persisted. + private List volumeInfo; public DiskBalancerInfo(DiskBalancerRunningStatus operationalState, double threshold, long bandwidthInMB, int parallelThread, boolean stopAfterDiskEven) { @@ -208,6 +216,22 @@ public void setVolumeDataDensity(double volumeDataDensity) { this.volumeDataDensity = volumeDataDensity; } + public double getIdealUsage() { + return idealUsage; + } + + public void setIdealUsage(double idealUsage) { + this.idealUsage = idealUsage; + } + + public List getVolumeInfo() { + return volumeInfo != null ? volumeInfo : Collections.emptyList(); + } + + public void setVolumeInfo(List volumeInfo) { + this.volumeInfo = volumeInfo; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerProtocolServer.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerProtocolServer.java index b92391dc8b9d..4bde1cedc497 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerProtocolServer.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerProtocolServer.java @@ -82,6 +82,8 @@ public DatanodeDiskBalancerInfoProto getDiskBalancerInfo(GetDiskBalancerInfoRequ .setBytesToMove(info.getBytesToMove()) .setBytesMoved(info.getBalancedBytes()) .setRunningStatus(info.getOperationalState()) + .setIdealUsage(info.getIdealUsage()) + .addAllVolumeInfo(info.getVolumeInfo()) .build(); } diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerService.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerService.java index 596cf87fde3d..3264977efd28 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerService.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerService.java @@ -31,6 +31,8 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; @@ -47,6 +49,7 @@ import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ContainerDataProto.State; import org.apache.hadoop.hdds.protocol.proto.HddsProtos; import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DiskBalancerRunningStatus; +import org.apache.hadoop.hdds.protocol.proto.HddsProtos.VolumeReportProto; import org.apache.hadoop.hdds.scm.container.ContainerID; import org.apache.hadoop.hdds.scm.container.common.helpers.StorageContainerException; import org.apache.hadoop.hdds.server.ServerUtils; @@ -685,9 +688,38 @@ 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 VolumeInfoProto from a list of VolumeFixedUsage. + * VolumeInfoProto 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 + * @throws IllegalArgumentException if volumeSet is null or empty + */ + public static List buildVolumeReportProto(List volumeSet) { + if (volumeSet == null || volumeSet.size() < 1) { + return Collections.emptyList(); + } + + List result = new ArrayList<>(); + for (VolumeFixedUsage v : volumeSet) { + HddsVolume volume = v.getVolume(); + result.add(VolumeReportProto.newBuilder() + .setStorageId(volume.getStorageID()) + .setUtilization(v.getUtilization()) + .setCommittedBytes(volume.getCommittedBytes()) + .build()); + } + return result; } public long calculateBytesToMove(List inputVolumeSet) { diff --git a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/diskbalancer/TestDiskBalancerProtocolServer.java b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/diskbalancer/TestDiskBalancerProtocolServer.java index 361f5cce051b..9186c8c67932 100644 --- a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/diskbalancer/TestDiskBalancerProtocolServer.java +++ b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/diskbalancer/TestDiskBalancerProtocolServer.java @@ -28,12 +28,14 @@ import static org.mockito.Mockito.when; import java.io.IOException; +import java.util.Arrays; import java.util.UUID; import org.apache.hadoop.hdds.protocol.DatanodeDetails; import org.apache.hadoop.hdds.protocol.proto.DiskBalancerProtocolProtos.GetDiskBalancerInfoRequestProto; import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DatanodeDiskBalancerInfoProto; import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DiskBalancerConfigurationProto; import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DiskBalancerRunningStatus; +import org.apache.hadoop.hdds.protocol.proto.HddsProtos.VolumeReportProto; import org.apache.hadoop.ozone.container.common.statemachine.DatanodeStateMachine; import org.apache.hadoop.ozone.container.diskbalancer.DiskBalancerProtocolServer.PrivilegedOperation; import org.apache.hadoop.ozone.container.ozoneimpl.OzoneContainer; @@ -51,8 +53,17 @@ class TestDiskBalancerProtocolServer { private static final int TEST_THREADS = 5; private static final boolean TEST_STOP_AFTER_DISK_EVEN = true; private static final double TEST_VOLUME_DENSITY = 15.5; + private static final double TEST_IDEAL_USAGE = 0.6; + private static final String TEST_STORAGE_ID_1 = "vol1"; + private static final String TEST_STORAGE_ID_2 = "vol2"; + private static final double TEST_UTILIZATION_1 = 0.57; + private static final double TEST_UTILIZATION_2 = 0.63; + private static final long TEST_COMMITTED_BYTES_1 = 10L * 1024 * 1024; + private static final long TEST_COMMITTED_BYTES_2 = 25L * 1024 * 1024; + private static final int TEST_VOLUME_INFO_COUNT = 2; private DatanodeStateMachine datanodeStateMachine; + private OzoneContainer ozoneContainer; private DiskBalancerService diskBalancerService; private DiskBalancerInfo diskBalancerInfo; private PrivilegedOperation denyAdminChecker; @@ -61,7 +72,7 @@ class TestDiskBalancerProtocolServer { @BeforeEach void setup() throws IOException { datanodeStateMachine = mock(DatanodeStateMachine.class); - OzoneContainer ozoneContainer = mock(OzoneContainer.class); + ozoneContainer = mock(OzoneContainer.class); when(datanodeStateMachine.getContainer()).thenReturn(ozoneContainer); diskBalancerService = mock(DiskBalancerService.class); when(ozoneContainer.getDiskBalancerService()).thenReturn(diskBalancerService); @@ -80,6 +91,19 @@ void setup() throws IOException { 0L, // balancedBytes TEST_VOLUME_DENSITY ); + diskBalancerInfo.setIdealUsage(TEST_IDEAL_USAGE); + diskBalancerInfo.setVolumeInfo(Arrays.asList( + VolumeReportProto.newBuilder() + .setStorageId(TEST_STORAGE_ID_1) + .setUtilization(TEST_UTILIZATION_1) + .setCommittedBytes(TEST_COMMITTED_BYTES_1) + .build(), + VolumeReportProto.newBuilder() + .setStorageId(TEST_STORAGE_ID_2) + .setUtilization(TEST_UTILIZATION_2) + .setCommittedBytes(TEST_COMMITTED_BYTES_2) + .build())); + when(ozoneContainer.getDiskBalancerInfo()).thenReturn(diskBalancerInfo); // Create datanode details @@ -102,12 +126,21 @@ void setup() throws IOException { @Test void testGetDiskBalancerInfoReport() throws IOException { - // Test REPORT type - should only return volume density DatanodeDiskBalancerInfoProto report = server.getDiskBalancerInfo(REQUEST_WITH_OLD_CLIENT_VERSION); - + VolumeReportProto volReport0 = report.getVolumeInfo(0); + VolumeReportProto volReport1 = report.getVolumeInfo(1); + assertNotNull(report); assertNotNull(report.getNode()); assertEquals(TEST_VOLUME_DENSITY, report.getCurrentVolumeDensitySum()); + assertEquals(TEST_IDEAL_USAGE, report.getIdealUsage()); + assertEquals(TEST_VOLUME_INFO_COUNT, report.getVolumeInfoCount()); + assertEquals(TEST_STORAGE_ID_1, volReport0.getStorageId()); + assertEquals(TEST_UTILIZATION_1, volReport0.getUtilization()); + assertEquals(TEST_COMMITTED_BYTES_1, volReport0.getCommittedBytes()); + assertEquals(TEST_STORAGE_ID_2, volReport1.getStorageId()); + assertEquals(TEST_UTILIZATION_2, volReport1.getUtilization()); + assertEquals(TEST_COMMITTED_BYTES_2, volReport1.getCommittedBytes()); } @Test diff --git a/hadoop-hdds/interface-client/src/main/proto/hdds.proto b/hadoop-hdds/interface-client/src/main/proto/hdds.proto index 2f20fde3e093..9214629064f2 100644 --- a/hadoop-hdds/interface-client/src/main/proto/hdds.proto +++ b/hadoop-hdds/interface-client/src/main/proto/hdds.proto @@ -579,6 +579,12 @@ enum DiskBalancerRunningStatus { PAUSED = 3; } +message VolumeReportProto { + optional string storageId = 1; + optional double utilization = 2; + optional uint64 committedBytes = 3; +} + message DatanodeDiskBalancerInfoProto { required DatanodeDetailsProto node = 1; required double currentVolumeDensitySum = 2; @@ -588,4 +594,6 @@ message DatanodeDiskBalancerInfoProto { optional uint64 failureMoveCount = 6; optional uint64 bytesToMove = 7; optional uint64 bytesMoved = 8; + optional double idealUsage = 9; + repeated VolumeReportProto volumeInfo = 10; } diff --git a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java index 200c9b2cd329..5f8657230fb4 100644 --- a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java +++ b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java @@ -27,8 +27,8 @@ 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 picocli.CommandLine.Command; /** @@ -36,13 +36,13 @@ */ @Command( name = "report", - description = "Get DiskBalancer volume density report", + description = "Get DiskBalancer volume density report and per volume info from dns.", mixinStandardHelpOptions = true, versionProvider = HddsVersionProvider.class) public class DiskBalancerReportSubcommand extends AbstractDiskBalancerSubCommand { // Store reports temporarily for non-JSON mode consolidation - private final Map reports = + private final Map reports = new ConcurrentHashMap<>(); @Override @@ -54,7 +54,7 @@ protected Object executeCommand(String hostName) throws IOException { // Only create JSON result object if JSON mode is enabled if (getOptions().isJson()) { - return createReportResult(report); + return toJson(report); } // For non-JSON mode, store the proto for later consolidation @@ -82,36 +82,66 @@ protected void displayResults(List successNodes, List failedNode // Display consolidated report for successful nodes if (!successNodes.isEmpty() && !reports.isEmpty()) { - List reportList = - new ArrayList<>(reports.values()); + List reportList = new ArrayList<>(reports.values()); System.out.println(generateReport(reportList)); } } private String generateReport(List protos) { - // Sort by volume density in descending order (highest imbalance first) - List 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 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(idealUsage); + header.append(" | Threshold: ").append(threshold).append("%"); + header.append(" | ThresholdRange: (").append(lt); + header.append(", ").append(ut).append(")").append("\n\n"); + header.append("Volume Details -:").append("\n"); + } + formatBuilder.append("%s%n"); + contentList.add(header.toString()); + + if (p.getVolumeInfoCount() > 0 && p.hasIdealUsage()) { + formatBuilder.append("%-60s %-25s %-25s %-25s%n"); + contentList.add("StorageID"); + contentList.add("Utilization"); + contentList.add("VolumeDensity"); + contentList.add("Pre-Allocated Container Bytes"); + + double ideal = p.getIdealUsage(); + for (VolumeReportProto v : p.getVolumeInfoList()) { + formatBuilder.append("%-60s %-25s %-25s %-25s%n"); + contentList.add(v.getStorageId() != null ? v.getStorageId() : "-"); + contentList.add(String.format("%.20f", v.getUtilization())); + contentList.add(String.format("%.20f", Math.abs(v.getUtilization() - ideal))); + contentList.add(String.valueOf(v.getCommittedBytes())); + } + formatBuilder.append("%n"); + } + + if (i < protos.size() - 1) { + formatBuilder.append("-------%n%n"); + } } - return String.format(formatBuilder.toString(), - contentList.toArray(new String[0])); + return String.format(formatBuilder.toString(), contentList.toArray(new String[0])); } @Override @@ -121,20 +151,35 @@ protected String getActionName() { /** * Create a JSON result map for a report. - * + * * @param report the DiskBalancer report proto * @return JSON result map */ - private Map createReportResult( - HddsProtos.DatanodeDiskBalancerInfoProto report) { + private Map toJson(DatanodeDiskBalancerInfoProto report) { Map 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()) { + result.put("idealUsage", report.getIdealUsage()); + } + + if (report.getVolumeInfoCount() > 0) { + double ideal = report.hasIdealUsage() ? report.getIdealUsage() : 0.0; + List> vols = new ArrayList<>(); + for (VolumeReportProto v : report.getVolumeInfoList()) { + Map vm = new LinkedHashMap<>(); + vm.put("storageId", v.getStorageId()); + vm.put("utilization", v.getUtilization()); + vm.put("volumeDensity", Math.abs(v.getUtilization() - ideal)); + vm.put("pre-Allocated container bytes", v.getCommittedBytes()); + vols.add(vm); + } + + result.put("volumes", vols); + } return result; } } diff --git a/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestDiskBalancerSubCommands.java b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestDiskBalancerSubCommands.java index 760408679204..6dfebb10245c 100644 --- a/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestDiskBalancerSubCommands.java +++ b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestDiskBalancerSubCommands.java @@ -51,6 +51,7 @@ import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DatanodeDiskBalancerInfoProto; import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DiskBalancerConfigurationProto; import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DiskBalancerRunningStatus; +import org.apache.hadoop.hdds.protocol.proto.HddsProtos.VolumeReportProto; import org.apache.hadoop.hdds.scm.cli.ContainerOperationClient; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -615,6 +616,12 @@ public void testReportDiskBalancerWithJson() throws Exception { String output = outContent.toString(DEFAULT_ENCODING); assertTrue(output.contains("\"datanode\"")); assertTrue(output.contains("\"volumeDensity\"")); + assertTrue(output.contains("\"idealUsage\"")); + assertTrue(output.contains("\"volumes\"")); + assertTrue(output.contains("\"storageId\"")); + assertTrue(output.contains("\"utilization\"")); + assertTrue(output.contains("\"volumeDensity\"")); + assertTrue(output.contains("\"pre-Allocated container bytes\"")); } } @@ -699,12 +706,8 @@ private DatanodeDiskBalancerInfoProto createStatusProto(String hostname, .build()) .build(); - DiskBalancerConfigurationProto configProto = DiskBalancerConfigurationProto.newBuilder() - .setThreshold(threshold) - .setDiskBandwidthInMB(bandwidthInMB) - .setParallelThread(parallelThread) - .setStopAfterDiskEven(true) - .build(); + DiskBalancerConfigurationProto configProto = createConfigProto(threshold, bandwidthInMB, parallelThread, true); + return DatanodeDiskBalancerInfoProto.newBuilder() .setNode(nodeProto) @@ -741,10 +744,12 @@ private DatanodeDiskBalancerInfoProto generateRandomStatusProto(String hostname) /** * Generates a random report proto for a given hostname. * @param hostname the hostname - * @return DatanodeDiskBalancerInfoProto with random volume density + * @return DatanodeDiskBalancerInfoProto with random volume density and volume info */ private DatanodeDiskBalancerInfoProto generateRandomReportProto(String hostname) { double volumeDensity = random.nextDouble() * 0.1; + double idealUsage = 0.1 + random.nextDouble() * 0.3; + double threshold = 5.0 + random.nextDouble() * 10.0; DatanodeDetailsProto nodeProto = DatanodeDetailsProto.newBuilder() .setHostName(hostname) .setIpAddress("127.0.0.1") @@ -754,9 +759,38 @@ private DatanodeDiskBalancerInfoProto generateRandomReportProto(String hostname) .build()) .build(); + DiskBalancerConfigurationProto configProto = createConfigProto(threshold, 100L, 5, true); + + long committed1 = Math.abs(random.nextLong()) % (1024L * 1024 * 1024); + long committed2 = Math.abs(random.nextLong()) % (1024L * 1024 * 1024); + VolumeReportProto vol1 = VolumeReportProto.newBuilder() + .setStorageId("DISK-" + hostname + "-vol1") + .setUtilization(idealUsage + random.nextDouble() * 0.1) + .setCommittedBytes(committed1) + .build(); + VolumeReportProto vol2 = VolumeReportProto.newBuilder() + .setStorageId("DISK-" + hostname + "-vol2") + .setUtilization(idealUsage - random.nextDouble() * 0.1) + .setCommittedBytes(committed2) + .build(); + return DatanodeDiskBalancerInfoProto.newBuilder() .setNode(nodeProto) .setCurrentVolumeDensitySum(volumeDensity) + .setIdealUsage(idealUsage) + .setDiskBalancerConf(configProto) + .addVolumeInfo(vol1) + .addVolumeInfo(vol2) + .build(); + } + + private DiskBalancerConfigurationProto createConfigProto(double threshold, long bandwidthInMB, int parallelThread, + boolean stopAfterDiskEven) { + return DiskBalancerConfigurationProto.newBuilder() + .setThreshold(threshold) + .setDiskBandwidthInMB(bandwidthInMB) + .setParallelThread(parallelThread) + .setStopAfterDiskEven(stopAfterDiskEven) .build(); } } From 9bb1dfe06bf400fd2786237d45fc804e1befa0b6 Mon Sep 17 00:00:00 2001 From: Gargi Jaiswal Date: Mon, 23 Mar 2026 17:21:19 +0530 Subject: [PATCH 2/4] fix bugs and pmd issue --- .../container/diskbalancer/DiskBalancerService.java | 2 +- .../diskbalancer/TestDiskBalancerProtocolServer.java | 3 +-- .../scm/cli/datanode/DiskBalancerReportSubcommand.java | 10 +++++----- .../scm/cli/datanode/TestDiskBalancerSubCommands.java | 4 ++-- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerService.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerService.java index 3264977efd28..01fed5eccecb 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerService.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerService.java @@ -706,7 +706,7 @@ public DiskBalancerInfo getDiskBalancerInfo() { * @throws IllegalArgumentException if volumeSet is null or empty */ public static List buildVolumeReportProto(List volumeSet) { - if (volumeSet == null || volumeSet.size() < 1) { + if (volumeSet == null || volumeSet.isEmpty()) { return Collections.emptyList(); } diff --git a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/diskbalancer/TestDiskBalancerProtocolServer.java b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/diskbalancer/TestDiskBalancerProtocolServer.java index 9186c8c67932..94ee298f5ce2 100644 --- a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/diskbalancer/TestDiskBalancerProtocolServer.java +++ b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/diskbalancer/TestDiskBalancerProtocolServer.java @@ -63,7 +63,6 @@ class TestDiskBalancerProtocolServer { private static final int TEST_VOLUME_INFO_COUNT = 2; private DatanodeStateMachine datanodeStateMachine; - private OzoneContainer ozoneContainer; private DiskBalancerService diskBalancerService; private DiskBalancerInfo diskBalancerInfo; private PrivilegedOperation denyAdminChecker; @@ -72,7 +71,7 @@ class TestDiskBalancerProtocolServer { @BeforeEach void setup() throws IOException { datanodeStateMachine = mock(DatanodeStateMachine.class); - ozoneContainer = mock(OzoneContainer.class); + OzoneContainer ozoneContainer = mock(OzoneContainer.class); when(datanodeStateMachine.getContainer()).thenReturn(ozoneContainer); diskBalancerService = mock(DiskBalancerService.class); when(ozoneContainer.getDiskBalancerService()).thenReturn(diskBalancerService); diff --git a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java index 5f8657230fb4..e4346d5558b4 100644 --- a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java +++ b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java @@ -99,9 +99,9 @@ private String generateReport(List protos) { String dn = DiskBalancerSubCommandUtil.getDatanodeHostAndIp(p.getNode()); StringBuilder header = new StringBuilder(); - header.append("Datanode: ").append(dn).append("\n"); + header.append("Datanode: ").append(dn).append('\n'); header.append("Aggregate VolumeDataDensity: "). - append(p.getCurrentVolumeDensitySum()).append("\n"); + append(p.getCurrentVolumeDensitySum()).append('\n'); if (p.hasIdealUsage() && p.hasDiskBalancerConf() && p.getDiskBalancerConf().hasThreshold()) { @@ -110,10 +110,10 @@ private String generateReport(List protos) { double lt = idealUsage - threshold / 100.0; double ut = idealUsage + threshold / 100.0; header.append("IdealUsage: ").append(idealUsage); - header.append(" | Threshold: ").append(threshold).append("%"); + header.append(" | Threshold: ").append(threshold).append('%'); header.append(" | ThresholdRange: (").append(lt); - header.append(", ").append(ut).append(")").append("\n\n"); - header.append("Volume Details -:").append("\n"); + header.append(", ").append(ut).append(')').append('\n').append('\n'); + header.append("Volume Details -:").append('\n'); } formatBuilder.append("%s%n"); contentList.add(header.toString()); diff --git a/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestDiskBalancerSubCommands.java b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestDiskBalancerSubCommands.java index 6dfebb10245c..02c5404723d4 100644 --- a/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestDiskBalancerSubCommands.java +++ b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestDiskBalancerSubCommands.java @@ -761,8 +761,8 @@ private DatanodeDiskBalancerInfoProto generateRandomReportProto(String hostname) DiskBalancerConfigurationProto configProto = createConfigProto(threshold, 100L, 5, true); - long committed1 = Math.abs(random.nextLong()) % (1024L * 1024 * 1024); - long committed2 = Math.abs(random.nextLong()) % (1024L * 1024 * 1024); + long committed1 = (random.nextLong() & Long.MAX_VALUE) % (1024L * 1024 * 1024); + long committed2 = (random.nextLong() & Long.MAX_VALUE) % (1024L * 1024 * 1024); VolumeReportProto vol1 = VolumeReportProto.newBuilder() .setStorageId("DISK-" + hostname + "-vol1") .setUtilization(idealUsage + random.nextDouble() * 0.1) From bdab8ffb61316c9393bfda6c51b3b0b23c18fb79 Mon Sep 17 00:00:00 2001 From: Gargi Jaiswal Date: Tue, 24 Mar 2026 10:51:33 +0530 Subject: [PATCH 3/4] added storage path to report --- .../diskbalancer/DiskBalancerService.java | 2 ++ .../TestDiskBalancerProtocolServer.java | 6 +++++ .../src/main/proto/hdds.proto | 5 ++-- .../DiskBalancerReportSubcommand.java | 24 +++++++++++++++---- .../datanode/TestDiskBalancerSubCommands.java | 4 ++++ 5 files changed, 34 insertions(+), 7 deletions(-) diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerService.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerService.java index 01fed5eccecb..4e8db9fa5d15 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerService.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerService.java @@ -713,10 +713,12 @@ public static List buildVolumeReportProto(List 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()) .setUtilization(v.getUtilization()) .setCommittedBytes(volume.getCommittedBytes()) + .setStoragePath(path) .build()); } return result; diff --git a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/diskbalancer/TestDiskBalancerProtocolServer.java b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/diskbalancer/TestDiskBalancerProtocolServer.java index 94ee298f5ce2..d1dcbd387aed 100644 --- a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/diskbalancer/TestDiskBalancerProtocolServer.java +++ b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/diskbalancer/TestDiskBalancerProtocolServer.java @@ -55,7 +55,9 @@ class TestDiskBalancerProtocolServer { private static final double TEST_VOLUME_DENSITY = 15.5; private static final double TEST_IDEAL_USAGE = 0.6; private static final String TEST_STORAGE_ID_1 = "vol1"; + private static final String TEST_STORAGE_PATH_1 = "/data/hdds1"; private static final String TEST_STORAGE_ID_2 = "vol2"; + private static final String TEST_STORAGE_PATH_2 = "/data/hdds2"; private static final double TEST_UTILIZATION_1 = 0.57; private static final double TEST_UTILIZATION_2 = 0.63; private static final long TEST_COMMITTED_BYTES_1 = 10L * 1024 * 1024; @@ -94,11 +96,13 @@ void setup() throws IOException { diskBalancerInfo.setVolumeInfo(Arrays.asList( VolumeReportProto.newBuilder() .setStorageId(TEST_STORAGE_ID_1) + .setStoragePath(TEST_STORAGE_PATH_1) .setUtilization(TEST_UTILIZATION_1) .setCommittedBytes(TEST_COMMITTED_BYTES_1) .build(), VolumeReportProto.newBuilder() .setStorageId(TEST_STORAGE_ID_2) + .setStoragePath(TEST_STORAGE_PATH_2) .setUtilization(TEST_UTILIZATION_2) .setCommittedBytes(TEST_COMMITTED_BYTES_2) .build())); @@ -135,9 +139,11 @@ void testGetDiskBalancerInfoReport() throws IOException { assertEquals(TEST_IDEAL_USAGE, report.getIdealUsage()); assertEquals(TEST_VOLUME_INFO_COUNT, report.getVolumeInfoCount()); assertEquals(TEST_STORAGE_ID_1, volReport0.getStorageId()); + assertEquals(TEST_STORAGE_PATH_1, volReport0.getStoragePath()); assertEquals(TEST_UTILIZATION_1, volReport0.getUtilization()); assertEquals(TEST_COMMITTED_BYTES_1, volReport0.getCommittedBytes()); assertEquals(TEST_STORAGE_ID_2, volReport1.getStorageId()); + assertEquals(TEST_STORAGE_PATH_2, volReport1.getStoragePath()); assertEquals(TEST_UTILIZATION_2, volReport1.getUtilization()); assertEquals(TEST_COMMITTED_BYTES_2, volReport1.getCommittedBytes()); } diff --git a/hadoop-hdds/interface-client/src/main/proto/hdds.proto b/hadoop-hdds/interface-client/src/main/proto/hdds.proto index 9214629064f2..6c93bd9cd83c 100644 --- a/hadoop-hdds/interface-client/src/main/proto/hdds.proto +++ b/hadoop-hdds/interface-client/src/main/proto/hdds.proto @@ -581,8 +581,9 @@ enum DiskBalancerRunningStatus { message VolumeReportProto { optional string storageId = 1; - optional double utilization = 2; - optional uint64 committedBytes = 3; + optional string storagePath = 2; + optional double utilization = 3; + optional uint64 committedBytes = 4; } message DatanodeDiskBalancerInfoProto { diff --git a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java index e4346d5558b4..e6ccc0d93090 100644 --- a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java +++ b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java @@ -119,18 +119,20 @@ private String generateReport(List protos) { contentList.add(header.toString()); if (p.getVolumeInfoCount() > 0 && p.hasIdealUsage()) { - formatBuilder.append("%-60s %-25s %-25s %-25s%n"); + formatBuilder.append("%-45s %-50s %-25s %-25s %-25s%n"); contentList.add("StorageID"); - contentList.add("Utilization"); + contentList.add("StoragePath"); contentList.add("VolumeDensity"); + contentList.add("Utilization"); contentList.add("Pre-Allocated Container Bytes"); double ideal = p.getIdealUsage(); for (VolumeReportProto v : p.getVolumeInfoList()) { - formatBuilder.append("%-60s %-25s %-25s %-25s%n"); + formatBuilder.append("%-45s %-50s %-25s %-25s %-25s%n"); contentList.add(v.getStorageId() != null ? v.getStorageId() : "-"); - contentList.add(String.format("%.20f", v.getUtilization())); + contentList.add(v.hasStoragePath() ? v.getStoragePath() : "-"); contentList.add(String.format("%.20f", Math.abs(v.getUtilization() - ideal))); + contentList.add(String.format("%.20f", v.getUtilization())); contentList.add(String.valueOf(v.getCommittedBytes())); } formatBuilder.append("%n"); @@ -141,6 +143,17 @@ private String generateReport(List protos) { } } + formatBuilder.append("%nNote:%n"); + formatBuilder.append(" - Aggregate VolumeDataDensity: Sum of per-volume density" + + " (deviation from ideal); higher means more imbalance.%n"); + formatBuilder.append(" - IdealUsage: Target utilization ratio (0-1) when volumes" + + " are evenly balanced.%n"); + formatBuilder.append(" - ThresholdRange: Acceptable deviation (percent); volumes within" + + " IdealUsage +/- Threshold are considered balanced.%n"); + formatBuilder.append(" - VolumeDensity: Deviation of a particular volume's utilization from IdealUsage.%n"); + formatBuilder.append(" - Utilization: Ratio of actual used space to capacity (0-1) for a particular volume.%n"); + formatBuilder.append(" - Pre-Allocated Container Bytes: Space reserved for containers not yet written to disk.%n"); + return String.format(formatBuilder.toString(), contentList.toArray(new String[0])); } @@ -172,8 +185,9 @@ private Map toJson(DatanodeDiskBalancerInfoProto report) { for (VolumeReportProto v : report.getVolumeInfoList()) { Map vm = new LinkedHashMap<>(); vm.put("storageId", v.getStorageId()); - vm.put("utilization", v.getUtilization()); + vm.put("storagePath", v.hasStoragePath() ? v.getStoragePath() : "-"); vm.put("volumeDensity", Math.abs(v.getUtilization() - ideal)); + vm.put("utilization", v.getUtilization()); vm.put("pre-Allocated container bytes", v.getCommittedBytes()); vols.add(vm); } diff --git a/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestDiskBalancerSubCommands.java b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestDiskBalancerSubCommands.java index 02c5404723d4..ffc1c35fa4eb 100644 --- a/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestDiskBalancerSubCommands.java +++ b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestDiskBalancerSubCommands.java @@ -763,13 +763,17 @@ private DatanodeDiskBalancerInfoProto generateRandomReportProto(String hostname) long committed1 = (random.nextLong() & Long.MAX_VALUE) % (1024L * 1024 * 1024); long committed2 = (random.nextLong() & Long.MAX_VALUE) % (1024L * 1024 * 1024); + 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) .setCommittedBytes(committed1) .build(); VolumeReportProto vol2 = VolumeReportProto.newBuilder() .setStorageId("DISK-" + hostname + "-vol2") + .setStoragePath(path2) .setUtilization(idealUsage - random.nextDouble() * 0.1) .setCommittedBytes(committed2) .build(); From c61491dc17ab05a91128b54cf14973a860307751 Mon Sep 17 00:00:00 2001 From: Gargi Jaiswal Date: Tue, 24 Mar 2026 17:07:38 +0530 Subject: [PATCH 4/4] added missing fields for report in json format --- .../container/diskbalancer/DiskBalancerService.java | 5 ++--- .../scm/cli/datanode/DiskBalancerReportSubcommand.java | 9 ++++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerService.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerService.java index 4e8db9fa5d15..5a392d1fcf05 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerService.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/diskbalancer/DiskBalancerService.java @@ -697,13 +697,12 @@ public DiskBalancerInfo getDiskBalancerInfo() { } /** - * Build a list of VolumeInfoProto from a list of VolumeFixedUsage. - * VolumeInfoProto consists of information like StorageID, + * 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 - * @throws IllegalArgumentException if volumeSet is null or empty */ public static List buildVolumeReportProto(List volumeSet) { if (volumeSet == null || volumeSet.isEmpty()) { diff --git a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java index e6ccc0d93090..e358e97e5e88 100644 --- a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java +++ b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DiskBalancerReportSubcommand.java @@ -175,8 +175,15 @@ private Map toJson(DatanodeDiskBalancerInfoProto report) { result.put("status", "success"); result.put("volumeDensity", report.getCurrentVolumeDensitySum()); - if (report.hasIdealUsage()) { + 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", report.getIdealUsage()); + result.put("threshold %", report.getDiskBalancerConf().getThreshold()); + result.put("thresholdRange", String.format("(%.20f, %.20f)", lt, ut)); } if (report.getVolumeInfoCount() > 0) {