This is an automated email from the ASF dual-hosted git repository.
ivandika pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new 4e603aa93c HDDS-11462. Enhancing DataNode I/O Monitoring Capabilities.
(#7206)
4e603aa93c is described below
commit 4e603aa93c8479349776c1597ef54504453cb512
Author: slfan1989 <[email protected]>
AuthorDate: Fri Nov 8 13:54:23 2024 +0800
HDDS-11462. Enhancing DataNode I/O Monitoring Capabilities. (#7206)
---
.../org/apache/hadoop/hdds/HddsConfigKeys.java | 3 +
.../common/src/main/resources/ozone-default.xml | 12 +++
.../java/org/apache/hadoop/ozone/DNMXBean.java | 28 ++++++
.../java/org/apache/hadoop/ozone/DNMXBeanImpl.java | 49 +++++++++-
.../apache/hadoop/ozone/HddsDatanodeService.java | 18 ++--
.../ozone/container/common/impl/ContainerSet.java | 15 ++++
.../ozone/container/common/volume/HddsVolume.java | 20 ++++-
.../container/common/volume/VolumeIOStats.java | 57 +++++++++---
.../container/common/volume/VolumeInfoMetrics.java | 8 ++
.../ozoneimpl/BackgroundContainerDataScanner.java | 1 +
.../container/ozoneimpl/ContainerController.java | 10 +++
.../ozoneimpl/ContainerDataScannerMetrics.java | 11 +++
.../ozone/container/ozoneimpl/OzoneContainer.java | 14 +++
.../webapps/hddsDatanode/dn-overview.html | 28 +++++-
.../resources/webapps/hddsDatanode/dn-scanner.html | 47 ++++++++++
.../src/main/resources/webapps/hddsDatanode/dn.js | 100 +++++++++++++++++++--
.../main/resources/webapps/hddsDatanode/index.html | 9 +-
.../resources/webapps/hddsDatanode/iostatus.html | 76 ++++++++++++++++
.../TestVolumeIOStatsWithPrometheusSink.java | 9 +-
.../src/main/resources/webapps/static/ozone.css | 21 ++++-
.../src/main/resources/webapps/static/ozone.js | 14 ++-
.../resources/webapps/static/templates/jvm.html | 13 ++-
.../resources/webapps/static/templates/menu.html | 2 +
.../webapps/static/templates/overview.html | 2 +-
24 files changed, 526 insertions(+), 41 deletions(-)
diff --git
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java
index 87707f75dc..4d630243e5 100644
---
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java
+++
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java
@@ -401,4 +401,7 @@ public final class HddsConfigKeys {
"hdds.datanode.slow.op.warning.threshold";
public static final String HDDS_DATANODE_SLOW_OP_WARNING_THRESHOLD_DEFAULT =
"500ms";
+
+ public static final String
OZONE_DATANODE_IO_METRICS_PERCENTILES_INTERVALS_SECONDS_KEY =
+ "ozone.volume.io.percentiles.intervals.seconds";
}
diff --git a/hadoop-hdds/common/src/main/resources/ozone-default.xml
b/hadoop-hdds/common/src/main/resources/ozone-default.xml
index bc90a87b11..f3e45c47ee 100644
--- a/hadoop-hdds/common/src/main/resources/ozone-default.xml
+++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml
@@ -4544,4 +4544,16 @@
maximum number of buckets across all volumes.
</description>
</property>
+
+ <property>
+ <name>ozone.volume.io.percentiles.intervals.seconds</name>
+ <value>60</value>
+ <tag>OZONE, DATANODE</tag>
+ <description>
+ This setting specifies the interval (in seconds) for monitoring
percentile performance metrics.
+ It helps in tracking the read and write performance of DataNodes in
real-time,
+ allowing for better identification and analysis of performance issues.
+ </description>
+ </property>
+
</configuration>
diff --git
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/DNMXBean.java
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/DNMXBean.java
index d36fcdb6fc..9c077a8e27 100644
---
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/DNMXBean.java
+++
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/DNMXBean.java
@@ -26,4 +26,32 @@ import org.apache.hadoop.hdds.server.ServiceRuntimeInfo;
*/
@InterfaceAudience.Private
public interface DNMXBean extends ServiceRuntimeInfo {
+
+ /**
+ * Gets the datanode hostname.
+ *
+ * @return the datanode hostname for the datanode.
+ */
+ String getHostname();
+
+ /**
+ * Gets the client rpc port.
+ *
+ * @return the client rpc port
+ */
+ String getClientRpcPort();
+
+ /**
+ * Gets the http port.
+ *
+ * @return the http port
+ */
+ String getHttpPort();
+
+ /**
+ * Gets the https port.
+ *
+ * @return the http port
+ */
+ String getHttpsPort();
}
diff --git
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/DNMXBeanImpl.java
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/DNMXBeanImpl.java
index f7b484c6bb..5a0a455663 100644
---
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/DNMXBeanImpl.java
+++
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/DNMXBeanImpl.java
@@ -25,8 +25,53 @@ import org.apache.hadoop.hdds.utils.VersionInfo;
* This is the JMX management class for DN information.
*/
public class DNMXBeanImpl extends ServiceRuntimeInfoImpl implements DNMXBean {
- public DNMXBeanImpl(
- VersionInfo versionInfo) {
+
+ private String hostName;
+ private String clientRpcPort;
+ private String httpPort;
+ private String httpsPort;
+
+ public DNMXBeanImpl(VersionInfo versionInfo) {
super(versionInfo);
}
+
+ @Override
+ public String getHostname() {
+ return hostName;
+ }
+
+ @Override
+ public String getClientRpcPort() {
+ return clientRpcPort;
+ }
+
+ @Override
+ public String getHttpPort() {
+ return httpPort;
+ }
+
+ @Override
+ public String getHttpsPort() {
+ return httpsPort;
+ }
+
+ public void setHttpPort(String httpPort) {
+ this.httpPort = httpPort;
+ }
+
+ public void setHostName(String hostName) {
+ this.hostName = hostName;
+ }
+
+ public void setClientRpcPort(String rpcPort) {
+ this.clientRpcPort = rpcPort;
+ }
+
+ public String getHostName() {
+ return hostName;
+ }
+
+ public void setHttpsPort(String httpsPort) {
+ this.httpsPort = httpsPort;
+ }
}
diff --git
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/HddsDatanodeService.java
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/HddsDatanodeService.java
index 55aeb466e7..de21e37503 100644
---
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/HddsDatanodeService.java
+++
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/HddsDatanodeService.java
@@ -228,6 +228,7 @@ public class HddsDatanodeService extends GenericCli
implements ServicePlugin {
String ip = InetAddress.getByName(hostname).getHostAddress();
datanodeDetails = initializeDatanodeDetails();
datanodeDetails.setHostName(hostname);
+ serviceRuntimeInfo.setHostName(hostname);
datanodeDetails.setIpAddress(ip);
datanodeDetails.setVersion(
HddsVersionInfo.HDDS_VERSION_INFO.getVersion());
@@ -300,23 +301,30 @@ public class HddsDatanodeService extends GenericCli
implements ServicePlugin {
httpServer = new HddsDatanodeHttpServer(conf);
httpServer.start();
HttpConfig.Policy policy = HttpConfig.getHttpPolicy(conf);
+
if (policy.isHttpEnabled()) {
- datanodeDetails.setPort(DatanodeDetails.newPort(HTTP,
- httpServer.getHttpAddress().getPort()));
+ int httpPort = httpServer.getHttpAddress().getPort();
+ datanodeDetails.setPort(DatanodeDetails.newPort(HTTP, httpPort));
+ serviceRuntimeInfo.setHttpPort(String.valueOf(httpPort));
}
+
if (policy.isHttpsEnabled()) {
- datanodeDetails.setPort(DatanodeDetails.newPort(HTTPS,
- httpServer.getHttpsAddress().getPort()));
+ int httpsPort = httpServer.getHttpAddress().getPort();
+ datanodeDetails.setPort(DatanodeDetails.newPort(HTTPS, httpsPort));
+ serviceRuntimeInfo.setHttpsPort(String.valueOf(httpsPort));
}
+
} catch (Exception ex) {
LOG.error("HttpServer failed to start.", ex);
}
-
clientProtocolServer = new HddsDatanodeClientProtocolServer(
datanodeDetails, conf, HddsVersionInfo.HDDS_VERSION_INFO,
reconfigurationHandler);
+ int clientRpcport = clientProtocolServer.getClientRpcAddress().getPort();
+ serviceRuntimeInfo.setClientRpcPort(String.valueOf(clientRpcport));
+
// Get admin list
String starterUser =
UserGroupInformation.getCurrentUser().getShortUserName();
diff --git
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/ContainerSet.java
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/ContainerSet.java
index 15cc6245dd..5335021da9 100644
---
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/ContainerSet.java
+++
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/ContainerSet.java
@@ -251,6 +251,21 @@ public class ContainerSet implements
Iterable<Container<?>> {
.iterator();
}
+ /**
+ * Get the number of containers based on the given volume.
+ *
+ * @param volume hdds volume.
+ * @return number of containers
+ */
+ public long containerCount(HddsVolume volume) {
+ Preconditions.checkNotNull(volume);
+ Preconditions.checkNotNull(volume.getStorageID());
+ String volumeUuid = volume.getStorageID();
+ return containerMap.values().stream()
+ .filter(x -> volumeUuid.equals(x.getContainerData().getVolume()
+ .getStorageID())).count();
+ }
+
/**
* Return an containerMap iterator over {@link ContainerSet#containerMap}.
* @return containerMap Iterator
diff --git
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/HddsVolume.java
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/HddsVolume.java
index c58aab2e5b..5fced0e39b 100644
---
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/HddsVolume.java
+++
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/HddsVolume.java
@@ -29,6 +29,7 @@ import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.hdds.annotation.InterfaceAudience;
import org.apache.hadoop.hdds.annotation.InterfaceStability;
+import org.apache.hadoop.hdds.conf.ConfigurationSource;
import org.apache.hadoop.hdds.upgrade.HDDSLayoutFeature;
import org.apache.hadoop.hdfs.server.datanode.checker.VolumeCheckResult;
import
org.apache.hadoop.ozone.container.common.statemachine.DatanodeConfiguration;
@@ -36,6 +37,7 @@ import
org.apache.hadoop.ozone.container.common.utils.DatanodeStoreCache;
import org.apache.hadoop.ozone.container.common.utils.HddsVolumeUtil;
import org.apache.hadoop.ozone.container.common.utils.RawDB;
import org.apache.hadoop.ozone.container.common.utils.StorageVolumeUtil;
+import org.apache.hadoop.ozone.container.ozoneimpl.ContainerController;
import org.apache.hadoop.ozone.container.upgrade.VersionedDatanodeFeatures;
import
org.apache.hadoop.ozone.container.upgrade.VersionedDatanodeFeatures.SchemaV3;
import org.apache.hadoop.util.Time;
@@ -44,6 +46,7 @@ import org.slf4j.LoggerFactory;
import jakarta.annotation.Nullable;
+import static
org.apache.hadoop.hdds.HddsConfigKeys.OZONE_DATANODE_IO_METRICS_PERCENTILES_INTERVALS_SECONDS_KEY;
import static org.apache.hadoop.ozone.OzoneConsts.CONTAINER_DB_NAME;
import static
org.apache.hadoop.ozone.container.common.utils.HddsVolumeUtil.initPerDiskDBStore;
@@ -80,6 +83,8 @@ public class HddsVolume extends StorageVolume {
private final VolumeIOStats volumeIOStats;
private final VolumeInfoMetrics volumeInfoMetrics;
+ private ContainerController controller;
+
private final AtomicLong committedBytes = new AtomicLong(); // till Open
containers become full
// Mentions the type of volume
@@ -119,8 +124,10 @@ public class HddsVolume extends StorageVolume {
if (!b.getFailedVolume() && getVolumeInfo().isPresent()) {
this.setState(VolumeState.NOT_INITIALIZED);
+ ConfigurationSource conf = getConf();
+ int[] intervals =
conf.getInts(OZONE_DATANODE_IO_METRICS_PERCENTILES_INTERVALS_SECONDS_KEY);
this.volumeIOStats = new VolumeIOStats(b.getVolumeRootStr(),
- this.getStorageDir().toString());
+ this.getStorageDir().toString(), intervals);
this.volumeInfoMetrics =
new VolumeInfoMetrics(b.getVolumeRootStr(), this);
@@ -382,6 +389,17 @@ public class HddsVolume extends StorageVolume {
getStorageID());
}
+ public void setController(ContainerController controller) {
+ this.controller = controller;
+ }
+
+ public long getContainers() {
+ if (controller != null) {
+ return controller.getContainerCount(this);
+ }
+ return 0;
+ }
+
/**
* Pick a DbVolume for HddsVolume and init db instance.
* Use the HddsVolume directly if no DbVolume found.
diff --git
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/VolumeIOStats.java
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/VolumeIOStats.java
index e22addd354..2ce19c3bf1 100644
---
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/VolumeIOStats.java
+++
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/VolumeIOStats.java
@@ -21,7 +21,10 @@ package org.apache.hadoop.ozone.container.common.volume;
import org.apache.hadoop.metrics2.MetricsSystem;
import org.apache.hadoop.metrics2.annotation.Metric;
import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
+import org.apache.hadoop.metrics2.lib.MetricsRegistry;
import org.apache.hadoop.metrics2.lib.MutableCounterLong;
+import org.apache.hadoop.metrics2.lib.MutableQuantiles;
+import org.apache.hadoop.metrics2.lib.MutableRate;
/**
* This class is used to track Volume IO stats for each HDDS Volume.
@@ -29,12 +32,23 @@ import org.apache.hadoop.metrics2.lib.MutableCounterLong;
public class VolumeIOStats {
private String metricsSourceName = VolumeIOStats.class.getSimpleName();
private String storageDirectory;
- private @Metric MutableCounterLong readBytes;
- private @Metric MutableCounterLong readOpCount;
- private @Metric MutableCounterLong writeBytes;
- private @Metric MutableCounterLong writeOpCount;
- private @Metric MutableCounterLong readTime;
- private @Metric MutableCounterLong writeTime;
+ private final MetricsRegistry registry = new
MetricsRegistry("VolumeIOStats");
+ @Metric
+ private MutableCounterLong readBytes;
+ @Metric
+ private MutableCounterLong readOpCount;
+ @Metric
+ private MutableCounterLong writeBytes;
+ @Metric
+ private MutableCounterLong writeOpCount;
+ @Metric
+ private MutableRate readTime;
+ @Metric
+ private MutableQuantiles[] readLatencyQuantiles;
+ @Metric
+ private MutableRate writeTime;
+ @Metric
+ private MutableQuantiles[] writeLatencyQuantiles;
@Deprecated
public VolumeIOStats() {
@@ -44,9 +58,24 @@ public class VolumeIOStats {
/**
* @param identifier Typically, path to volume root. e.g. /data/hdds
*/
- public VolumeIOStats(String identifier, String storageDirectory) {
+ public VolumeIOStats(String identifier, String storageDirectory, int[]
intervals) {
this.metricsSourceName += '-' + identifier;
this.storageDirectory = storageDirectory;
+
+ // Try initializing `readLatencyQuantiles` and `writeLatencyQuantiles`
+ if (intervals != null && intervals.length > 0) {
+ final int length = intervals.length;
+ readLatencyQuantiles = new MutableQuantiles[intervals.length];
+ writeLatencyQuantiles = new MutableQuantiles[intervals.length];
+ for (int i = 0; i < length; i++) {
+ readLatencyQuantiles[i] = registry.newQuantiles(
+ "readLatency" + intervals[i] + "s",
+ "Read Data File Io Latency in ms", "ops", "latency", intervals[i]);
+ writeLatencyQuantiles[i] = registry.newQuantiles(
+ "writeLatency" + intervals[i] + "s",
+ "Write Data File Io Latency in ms", "ops", "latency",
intervals[i]);
+ }
+ }
init();
}
@@ -99,7 +128,10 @@ public class VolumeIOStats {
* @param time
*/
public void incReadTime(long time) {
- readTime.incr(time);
+ readTime.add(time);
+ for (MutableQuantiles q : readLatencyQuantiles) {
+ q.add(time);
+ }
}
/**
@@ -107,7 +139,10 @@ public class VolumeIOStats {
* @param time
*/
public void incWriteTime(long time) {
- writeTime.incr(time);
+ writeTime.add(time);
+ for (MutableQuantiles q : writeLatencyQuantiles) {
+ q.add(time);
+ }
}
/**
@@ -147,7 +182,7 @@ public class VolumeIOStats {
* @return long
*/
public long getReadTime() {
- return readTime.value();
+ return (long) readTime.lastStat().total();
}
/**
@@ -155,7 +190,7 @@ public class VolumeIOStats {
* @return long
*/
public long getWriteTime() {
- return writeTime.value();
+ return (long) writeTime.lastStat().total();
}
@Metric
diff --git
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/VolumeInfoMetrics.java
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/VolumeInfoMetrics.java
index 68140600db..cd31b8063d 100644
---
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/VolumeInfoMetrics.java
+++
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/VolumeInfoMetrics.java
@@ -37,6 +37,7 @@ public class VolumeInfoMetrics {
private final HddsVolume volume;
@Metric("Returns the RocksDB compact times of the Volume")
private MutableRate dbCompactLatency;
+ private long containers;
/**
* @param identifier Typically, path to volume root. E.g. /data/hdds
@@ -153,4 +154,11 @@ public class VolumeInfoMetrics {
dbCompactLatency.add(time);
}
+ /**
+ * Return the Container Count of the Volume.
+ */
+ @Metric("Returns the Container Count of the Volume")
+ public long getContainers() {
+ return volume.getContainers();
+ }
}
diff --git
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/BackgroundContainerDataScanner.java
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/BackgroundContainerDataScanner.java
index 327f019224..8ff2e30876 100644
---
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/BackgroundContainerDataScanner.java
+++
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/BackgroundContainerDataScanner.java
@@ -63,6 +63,7 @@ public class BackgroundContainerDataScanner extends
throttler = new HddsDataTransferThrottler(conf.getBandwidthPerVolume());
canceler = new Canceler();
this.metrics = ContainerDataScannerMetrics.create(volume.toString());
+ this.metrics.setStorageDirectory(volume.toString());
this.minScanGap = conf.getContainerScanMinGap();
}
diff --git
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/ContainerController.java
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/ContainerController.java
index 0db98a01d8..567741a98d 100644
---
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/ContainerController.java
+++
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/ContainerController.java
@@ -235,6 +235,16 @@ public class ContainerController {
return containerSet.getContainerIterator(volume);
}
+ /**
+ * Get the number of containers based on the given volume.
+ *
+ * @param volume hdds volume.
+ * @return number of containers.
+ */
+ public long getContainerCount(HddsVolume volume) {
+ return containerSet.containerCount(volume);
+ }
+
void updateDataScanTimestamp(long containerId, Instant timestamp)
throws IOException {
Container container = containerSet.getContainer(containerId);
diff --git
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/ContainerDataScannerMetrics.java
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/ContainerDataScannerMetrics.java
index a3f71d34ba..76e71312ae 100644
---
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/ContainerDataScannerMetrics.java
+++
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/ContainerDataScannerMetrics.java
@@ -37,6 +37,8 @@ public final class ContainerDataScannerMetrics
@Metric("disk bandwidth used by the container data scanner per volume")
private MutableRate numBytesScanned;
+ private String storageDirectory;
+
public double getNumBytesScannedMean() {
return numBytesScanned.lastStat().mean();
}
@@ -66,4 +68,13 @@ public final class ContainerDataScannerMetrics
return ms.register(name, null, new ContainerDataScannerMetrics(name, ms));
}
+
+ @Metric("Returns the Directory name for the volume")
+ public String getStorageDirectory() {
+ return storageDirectory;
+ }
+
+ public void setStorageDirectory(final String volumeName) {
+ this.storageDirectory = volumeName;
+ }
}
diff --git
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/OzoneContainer.java
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/OzoneContainer.java
index 62196cdd87..56c4233836 100644
---
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/OzoneContainer.java
+++
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/OzoneContainer.java
@@ -388,6 +388,18 @@ public class OzoneContainer {
}
}
+ /**
+ * We need to inject the containerController into the hddsVolume.
+ * because we need to obtain the container count
+ * for each disk based on the container controller.
+ */
+ private void initHddsVolumeContainer() {
+ for (StorageVolume v : volumeSet.getVolumesList()) {
+ HddsVolume hddsVolume = (HddsVolume) v;
+ hddsVolume.setController(controller);
+ }
+ }
+
private void initMetadataScanner(ContainerScannerConfiguration c) {
if (this.metadataScanner == null) {
this.metadataScanner =
@@ -486,6 +498,8 @@ public class OzoneContainer {
blockDeletingService.start();
recoveringContainerScrubbingService.start();
+ initHddsVolumeContainer();
+
// mark OzoneContainer as INITIALIZED.
initializingStatus.set(InitializingStatus.INITIALIZED);
}
diff --git
a/hadoop-hdds/container-service/src/main/resources/webapps/hddsDatanode/dn-overview.html
b/hadoop-hdds/container-service/src/main/resources/webapps/hddsDatanode/dn-overview.html
index fd3d7407d2..4f51b423e8 100644
---
a/hadoop-hdds/container-service/src/main/resources/webapps/hddsDatanode/dn-overview.html
+++
b/hadoop-hdds/container-service/src/main/resources/webapps/hddsDatanode/dn-overview.html
@@ -22,8 +22,32 @@
</tbody>
</table>
+<h2>HeartBeat Information</h2>
+<table class="table" class="col-md-6">
+ <thead>
+ <tr>
+ <th>Address</th>
+ <th>Last Successful HeartBeat</th>
+ <th>Missed Count</th>
+ <th>State</th>
+ <th>Type</th>
+ <th>Version Number</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="scm in $ctrl.heartbeatmetrics[0].SCMServers">
+ <td>{{scm.addressString}}</td>
+ <td>{{scm.lastSuccessfulHeartbeat}}</td>
+ <td>{{scm.missedCount}}</td>
+ <td>{{scm.state}}</td>
+ <td>{{scm.type}}</td>
+ <td>{{scm.versionNumber}}</td>
+ </tr>
+ </tbody>
+</table>
+
<h2>Volume Information</h2>
-<table class="table table-bordered table-striped" class="col-md-6">
+<table class="table" class="col-md-6">
<thead>
<tr>
<th>Directory</th>
@@ -33,6 +57,7 @@
<th>Available Space</th>
<th>Reserved</th>
<th>Total Capacity</th>
+ <th>Containers</th>
<th>State</th>
</tr>
</thead>
@@ -45,6 +70,7 @@
<td>{{volumeInfo.Available}}</td>
<td>{{volumeInfo.Reserved}}</td>
<td>{{volumeInfo.TotalCapacity}}</td>
+ <td>{{volumeInfo.Containers}}</td>
<td>{{volumeInfo["tag.VolumeState"]}}</td>
</tr>
</tbody>
diff --git
a/hadoop-hdds/container-service/src/main/resources/webapps/hddsDatanode/dn-scanner.html
b/hadoop-hdds/container-service/src/main/resources/webapps/hddsDatanode/dn-scanner.html
new file mode 100644
index 0000000000..5c54a2aa0a
--- /dev/null
+++
b/hadoop-hdds/container-service/src/main/resources/webapps/hddsDatanode/dn-scanner.html
@@ -0,0 +1,47 @@
+<!--
+ 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.
+-->
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>DataNode Scanner Status</title>
+</head>
+<body>
+ <h2>DataNode Scanner Information</h2>
+ <table class="table" class="col-md-6">
+ <thead>
+ <tr>
+ <th>Directory</th>
+ <th>NumBytesScannedNumOps</th>
+ <th>NumBytesScannedAvgTime</th>
+ <th>NumContainersScanned</th>
+ <th>NumScanIterations</th>
+ <th>NumUnHealthyContainers</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="scanner in scannerStatusCtrl.dnscanner">
+ <td>{{scanner["tag.StorageDirectory"]}}</td>
+ <td>{{scanner.NumBytesScannedNumOps}}</td>
+ <td>{{scanner.NumBytesScannedAvgTime | millisecondsToMinutes}}</td>
+ <td>{{scanner.NumContainersScanned}}</td>
+ <td>{{scanner.NumScanIterations}}</td>
+ <td>{{scanner.NumUnHealthyContainers}}</td>
+ </tr>
+ </tbody>
+ </table>
+</body>
+</html>
\ No newline at end of file
diff --git
a/hadoop-hdds/container-service/src/main/resources/webapps/hddsDatanode/dn.js
b/hadoop-hdds/container-service/src/main/resources/webapps/hddsDatanode/dn.js
index adc507acce..547e566ef8 100644
---
a/hadoop-hdds/container-service/src/main/resources/webapps/hddsDatanode/dn.js
+++
b/hadoop-hdds/container-service/src/main/resources/webapps/hddsDatanode/dn.js
@@ -36,20 +36,104 @@
volume.TotalCapacity = transform(volume.TotalCapacity);
})
});
+
+
$http.get("jmx?qry=Hadoop:service=HddsDatanode,name=SCMConnectionManager")
+ .then(function (result) {
+ ctrl.heartbeatmetrics = result.data.beans;
+ ctrl.heartbeatmetrics.forEach(scm => {
+ var scmServers = scm.SCMServers;
+ scmServers.forEach(scmServer => {
+ scmServer.lastSuccessfulHeartbeat =
convertTimestampToDate(scmServer.lastSuccessfulHeartbeat)
+ })
+ })
+ });
}
});
- function transform(v) {
- var UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'ZB'];
- var prev = 0, i = 0;
- while (Math.floor(v) > 0 && i < UNITS.length) {
+
+ // Register ioStatus Controller
+ angular.module('ozone').config(function ($routeProvider) {
+ $routeProvider.when('/iostatus', {
+ templateUrl: 'iostatus.html',
+ controller: 'IOStatusController as ioStatusCtrl',
+ });
+ });
+
+ angular.module('ozone')
+ .controller('IOStatusController', function ($http) {
+ var ctrl = this;
+
$http.get("jmx?qry=Hadoop:service=HddsDatanode,name=VolumeIOStats*")
+ .then(function (result) {
+ ctrl.dniostatus = result.data.beans;
+ });
+ });
+
+ // Register Scanner Controller
+ angular.module('ozone').config(function ($routeProvider) {
+ $routeProvider.when('/dn-scanner', {
+ templateUrl: 'dn-scanner.html',
+ controller: 'DNScannerController as scannerStatusCtrl',
+ });
+ });
+
+ angular.module('ozone')
+ .controller('DNScannerController', function ($http) {
+ var ctrl = this;
+
$http.get("jmx?qry=Hadoop:service=HddsDatanode,name=ContainerDataScannerMetrics*")
+ .then(function (result) {
+ ctrl.dnscanner = result.data.beans;
+ });
+ });
+
+ angular.module('ozone')
+ .filter('millisecondsToMinutes', function() {
+ return function(milliseconds) {
+ if (isNaN(milliseconds)) {
+ return 'Invalid input';
+ }
+ var minutes = Math.floor(milliseconds / 60000); // 1 minute =
60000 milliseconds
+ var seconds = Math.floor((milliseconds % 60000) / 1000);
+ return minutes + ' mins ' + seconds + ' secs';
+ };
+ });
+
+ angular.module('ozone')
+ .filter('twoDecimalPlaces', function() {
+ return function(input) {
+ if (isNaN(input)) {
+ return 'Invalid input';
+ }
+ return parseFloat(input).toFixed(2);
+ };
+ });
+
+ function transform(v) {
+ var UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'ZB'];
+ var prev = 0, i = 0;
+ while (Math.floor(v) > 0 && i < UNITS.length) {
prev = v;
v /= 1024;
i += 1;
- }
- if (i > 0 && i < UNITS.length) {
+ }
+ if (i > 0 && i < UNITS.length) {
v = prev;
i -= 1;
- }
- return Math.round(v * 100) / 100 + ' ' + UNITS[i];
}
+ return Math.round(v * 100) / 100 + ' ' + UNITS[i];
+ }
+
+ function convertTimestampToDate(timestamp) {
+ if (!timestamp) return '';
+ var milliseconds = timestamp * 1000;
+
+ var date = new Date(milliseconds);
+
+ var year = date.getFullYear();
+ var month = date.getMonth() + 1;
+ var day = date.getDate();
+ var hours = date.getHours();
+ var minutes = date.getMinutes();
+ var seconds = date.getSeconds();
+
+ return `${year}-${month.toString().padStart(2,
'0')}-${day.toString().padStart(2, '0')} ${hours.toString().padStart(2,
'0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2,
'0')}`;
+ }
})();
diff --git
a/hadoop-hdds/container-service/src/main/resources/webapps/hddsDatanode/index.html
b/hadoop-hdds/container-service/src/main/resources/webapps/hddsDatanode/index.html
index 1c32fe64e0..0e1cbf21a0 100644
---
a/hadoop-hdds/container-service/src/main/resources/webapps/hddsDatanode/index.html
+++
b/hadoop-hdds/container-service/src/main/resources/webapps/hddsDatanode/index.html
@@ -49,11 +49,10 @@
<a class="navbar-brand" href="#">HDDS Datanode Service</a>
</div>
-
- <navmenu
- metrics="{ 'Rpc metrics' : '#!/metrics/rpc'}"></navmenu>
-
-
+ <navmenu metrics="{ 'Rpc metrics' : '#!/metrics/rpc'}"
+ iostatus="true" io-link-href="#!/iostatus"
+ scanner="true" scanner-link-href="#!/dn-scanner"
+ />
</div>
</header>
diff --git
a/hadoop-hdds/container-service/src/main/resources/webapps/hddsDatanode/iostatus.html
b/hadoop-hdds/container-service/src/main/resources/webapps/hddsDatanode/iostatus.html
new file mode 100644
index 0000000000..94916821bd
--- /dev/null
+++
b/hadoop-hdds/container-service/src/main/resources/webapps/hddsDatanode/iostatus.html
@@ -0,0 +1,76 @@
+<!--
+ 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.
+-->
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>DataNode IO Status</title>
+</head>
+<body>
+
+ <h2>Read Performance</h2>
+ <table class="table" class="col-md-6">
+ <thead>
+ <tr>
+ <th>Directory</th>
+ <th>ReadBytes</th>
+ <th>ReadOpCount</th>
+ <th>ReadAvgTime</th>
+ <th>ReadLatency60s(P90)</th>
+ <th>ReadLatency60s(P95)</th>
+ <th>ReadLatency60s(P99)</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="volumeInfo in ioStatusCtrl.dniostatus">
+ <td>{{volumeInfo["tag.StorageDirectory"]}}</td>
+ <td>{{volumeInfo.ReadBytes}}</td>
+ <td>{{volumeInfo.ReadOpCount}}</td>
+ <td>{{volumeInfo.ReadTimeAvgTime | twoDecimalPlaces}} ms</td>
+ <td>{{volumeInfo.ReadLatency60s90thPercentileLatency |
twoDecimalPlaces}} ms</td>
+ <td>{{volumeInfo.ReadLatency60s95thPercentileLatency |
twoDecimalPlaces}} ms</td>
+ <td>{{volumeInfo.ReadLatency60s99thPercentileLatency |
twoDecimalPlaces}} ms</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <h2>Write Performance</h2>
+ <table class="table" class="col-md-6">
+ <thead>
+ <tr>
+ <th>Directory</th>
+ <th>WriteBytes</th>
+ <th>WriteOpCount</th>
+ <th>WriteAvgTime</th>
+ <th>WriteLatency60s(P90)</th>
+ <th>WriteLatency60s(P95)</th>
+ <th>WriteLatency60s(P99)</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="volumeInfo in ioStatusCtrl.dniostatus">
+ <td>{{volumeInfo["tag.StorageDirectory"]}}</td>
+ <td>{{volumeInfo.WriteBytes}}</td>
+ <td>{{volumeInfo.WriteOpCount}}</td>
+ <td>{{volumeInfo.WriteTimeAvgTime | twoDecimalPlaces}} ms</td>
+ <td>{{volumeInfo.WriteLatency60s90thPercentileLatency |
twoDecimalPlaces}} ms</td>
+ <td>{{volumeInfo.WriteLatency60s95thPercentileLatency |
twoDecimalPlaces}} ms</td>
+ <td>{{volumeInfo.WriteLatency60s99thPercentileLatency |
twoDecimalPlaces}} ms</td>
+ </tr>
+ </tbody>
+ </table>
+</body>
+</html>
\ No newline at end of file
diff --git
a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/volume/TestVolumeIOStatsWithPrometheusSink.java
b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/volume/TestVolumeIOStatsWithPrometheusSink.java
index c8934bab41..1df886098a 100644
---
a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/volume/TestVolumeIOStatsWithPrometheusSink.java
+++
b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/volume/TestVolumeIOStatsWithPrometheusSink.java
@@ -17,6 +17,7 @@
*/
package org.apache.hadoop.ozone.container.common.volume;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.server.http.PrometheusMetricsSink;
import org.apache.hadoop.metrics2.MetricsSystem;
import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
@@ -30,6 +31,7 @@ import java.io.OutputStreamWriter;
import static org.assertj.core.api.Assertions.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static
org.apache.hadoop.hdds.HddsConfigKeys.OZONE_DATANODE_IO_METRICS_PERCENTILES_INTERVALS_SECONDS_KEY;
/**
* Test PrometheusMetricSink regarding VolumeIOStats.
@@ -54,11 +56,14 @@ public class TestVolumeIOStatsWithPrometheusSink {
@Test
public void testMultipleVolumeIOMetricsExist() throws IOException {
+ OzoneConfiguration conf = new OzoneConfiguration();
+ int[] intervals =
conf.getInts(OZONE_DATANODE_IO_METRICS_PERCENTILES_INTERVALS_SECONDS_KEY);
+
//GIVEN
VolumeIOStats volumeIOStats1 = new VolumeIOStats("VolumeIOStat1",
- "vol1/dir");
+ "vol1/dir", intervals);
VolumeIOStats volumeIOStat2 = new VolumeIOStats("VolumeIOStat2",
- "vol2/dir");
+ "vol2/dir", intervals);
//WHEN
String writtenMetrics = publishMetricsAndGetOutput();
diff --git a/hadoop-hdds/framework/src/main/resources/webapps/static/ozone.css
b/hadoop-hdds/framework/src/main/resources/webapps/static/ozone.css
index 389d9d78f2..4988cc8eeb 100644
--- a/hadoop-hdds/framework/src/main/resources/webapps/static/ozone.css
+++ b/hadoop-hdds/framework/src/main/resources/webapps/static/ozone.css
@@ -94,4 +94,23 @@ body {
.scm-roles-background {
background-color: #dcfbcd!important;
-}
\ No newline at end of file
+}
+.toggle-btn {
+ background: transparent; /* No background color */
+ color: #007bff; /* Button text color */
+ border: none; /* No border */
+ font-size: 12px; /* Font size for better readability */
+ cursor: pointer; /* Pointer cursor on hover */
+ padding: 5px 10px; /* Padding around the text */
+ margin-bottom: 5px; /* Space below the button */
+ transition: color 0.3s, transform 0.3s; /* Smooth transition for color and
transform */
+}
+
+.toggle-btn:hover {
+ color: #0056b3; /* Darker color on hover */
+ transform: scale(1.1); /* Slightly scale up the button on hover */
+}
+
+.toggle-btn:focus {
+ outline: none; /* Remove default focus outline */
+}
diff --git a/hadoop-hdds/framework/src/main/resources/webapps/static/ozone.js
b/hadoop-hdds/framework/src/main/resources/webapps/static/ozone.js
index a31078cfd7..7bb9310628 100644
--- a/hadoop-hdds/framework/src/main/resources/webapps/static/ozone.js
+++ b/hadoop-hdds/framework/src/main/resources/webapps/static/ozone.js
@@ -48,8 +48,14 @@
});
angular.module('ozone').component('jvmParameters', {
templateUrl: 'static/templates/jvm.html',
- controller: function($http) {
+ controller: function($http, $scope) {
var ctrl = this;
+
+ $scope.contentVisible = false;
+ $scope.toggleContent = function() {
+ $scope.contentVisible = !$scope.contentVisible;
+ };
+
$http.get("jmx?qry=java.lang:type=Runtime")
.then(function(result) {
ctrl.jmx = result.data.beans[0];
@@ -245,7 +251,11 @@
angular.module('ozone').component('navmenu', {
bindings: {
- metrics: '<'
+ metrics: '<',
+ iostatus: '<',
+ ioLinkHref: '@',
+ scanner: '<',
+ scannerLinkHref: '@',
},
templateUrl: 'static/templates/menu.html',
controller: function($http) {
diff --git
a/hadoop-hdds/framework/src/main/resources/webapps/static/templates/jvm.html
b/hadoop-hdds/framework/src/main/resources/webapps/static/templates/jvm.html
index 9706ebdf6b..c562ae7d9a 100644
--- a/hadoop-hdds/framework/src/main/resources/webapps/static/templates/jvm.html
+++ b/hadoop-hdds/framework/src/main/resources/webapps/static/templates/jvm.html
@@ -20,7 +20,16 @@
<td>{{$ctrl.jmx.SystemProperties.java_vm_name}}
{{$ctrl.jmx.SystemProperties.java_vm_version}}</td>
</tr>
<tr>
- <th>Input arguments:</th>
- <td><pre>{{$ctrl.jmx.InputArguments.join('\n')}}</pre></td>
+ <th>
+ Input arguments:
+ <button class="toggle-btn" ng-click="toggleContent()">
+ {{ contentVisible ? 'Collapse(-)' : 'Expand(+)' }}
+ </button>
+ </th>
+ <td>
+ <div class="content" ng-show="contentVisible">
+ <pre>{{$ctrl.jmx.InputArguments.join('\n')}}</pre>
+ </div>
+ </td>
</tr>
</table>
diff --git
a/hadoop-hdds/framework/src/main/resources/webapps/static/templates/menu.html
b/hadoop-hdds/framework/src/main/resources/webapps/static/templates/menu.html
index 95f1b4842f..9a14f356d7 100644
---
a/hadoop-hdds/framework/src/main/resources/webapps/static/templates/menu.html
+++
b/hadoop-hdds/framework/src/main/resources/webapps/static/templates/menu.html
@@ -56,5 +56,7 @@
aria-hidden="true"></a></li>
</ul>
</li>
+ <li ng-show="$ctrl.iostatus"><a ng-href="{{$ctrl.ioLinkHref}}">IO
Status</a></li>
+ <li ng-show="$ctrl.scanner"><a
ng-href="{{$ctrl.scannerLinkHref}}">Data Scanner</a></li>
</ul>
</div><!--/.nav-collapse -->
diff --git
a/hadoop-hdds/framework/src/main/resources/webapps/static/templates/overview.html
b/hadoop-hdds/framework/src/main/resources/webapps/static/templates/overview.html
index 7ff118b330..2811e8c36a 100644
---
a/hadoop-hdds/framework/src/main/resources/webapps/static/templates/overview.html
+++
b/hadoop-hdds/framework/src/main/resources/webapps/static/templates/overview.html
@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<h1>Overview</h1>
+<h1>Overview <small
ng-if="$ctrl.jmx.Hostname">({{$ctrl.jmx.Hostname}})</small> </h1>
<table class="table table-bordered table-striped">
<tbody>
<tr ng-if="$ctrl.jmx.Namespace && $ctrl.jmx.Namespace !== ''">
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]