Skip to content

HDDS-15960. Hadoop metrics leak detection - #10941

Open
ivandika3 wants to merge 32 commits into
apache:masterfrom
ivandika3:HDDS-15960
Open

ivandika3 wants to merge 32 commits into
apache:masterfrom
ivandika3:HDDS-15960

Conversation

@ivandika3

@ivandika3 ivandika3 commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

Add guardrails to detect Hadoop metrics leak similar to ManagedRocksObjectMetrics.INSTANCE.assertNoLeaks.

One idea is enable all the metrics related configuration in all the integration tests (e.g. percentiles configuration) and after the MiniOzoneCluster are shut down, check the metric registry to ensure that there are no more metrics that are still registered. We will fail the test so that we can fail loudly.

The main class is MetricsLeakAssertion, we have two list of leaky Metric sources:

  • EXPECTED_LEFTOVER_SOURCES: These are expected sources or the ones from Hadoop project (not handled in Ozone), we leave it be.
  • TODO_LEFTOVER_SOURCES: These should be reviewed and fixed in the subsequent patches. This can be moved to EXPECTED_LEFTOVER_SOURCES if necessary with valid justification.

(Generated-by: Kimi K3)

What is the link to the Apache JIRA

https://issues.apache.org/jira/browse/HDDS-15960

How was this patch tested?

CI. (Clean CI: https://github.com/ivandika3/ozone/actions/runs/38711698118)

Add MetricsLeakAssertion which inspects the DefaultMetricsSystem's
private allSources map via reflection and fails the test if any metrics
source is still registered after MiniOzoneCluster shutdown. The check
verifies the field exists and logs a WARN (skipping) if the underlying
Hadoop metrics implementation changes, rather than failing spuriously.

Also enable the metrics percentile-interval configs by default in the
mini cluster builder so quantile code paths are exercised by every
integration test.

Generated-by: Codex (GPT-5)
Throw AssertionError instead of logging a WARN and skipping when the
metrics system's allSources field cannot be found, is not a Map, or
cannot be read, so a broken leak check fails the test loudly rather than
being silently ignored.

Generated-by: Codex (GPT-5)
Checking allSources before DefaultMetricsSystem.shutdown() makes the
assertion independent of whether Hadoop clears allSources on shutdown
(it currently does not), and asserts the invariant while the metrics
system is still fully populated. Well-behaved sources unregister
themselves during cluster stop(), so anything still registered at this
point is a genuine leak.

Generated-by: Codex (GPT-5)
Add an EXPECTED_LEFTOVER_SOURCES allowlist for metrics that are
registered once per JVM or per service and intentionally never
unregistered (JvmMetrics/JvmMetricsCpu from HddsServerUtil, UgiMetrics,
ManagedRocksObjectMetrics, ContainerCacheMetrics).  Entries are matched
by prefix to cover the numeric suffixes the metrics system appends for
repeated registrations.  This keeps the assertion focused on genuine
per-instance service metrics leaks; verified to reduce the leftover
count from 50 to 41 on TestMiniOzoneCluster.

Generated-by: Codex (GPT-5)
ContainerCacheMetrics has a real per-cluster lifecycle: it is registered
in ContainerCache.getInstance() and ContainerCache.shutdownCache() is
its natural teardown.  Add an unregister() and call it from
shutdownCache(), and drop ContainerCacheMetrics from the leak-assertion
allowlist since it no longer leaks.

The remaining allowlisted Ozone sources (JvmMetrics, JvmMetricsCpu,
ManagedRocksObjectMetrics) are JVM-scoped singletons with no per-service
owner, so they are correctly left on the allowlist alongside Hadoop's
UgiMetrics.

Generated-by: Codex (GPT-5)
…y subsystem

Running the assertion across the full integration suite surfaced a broad
set of pre-existing metrics leaks (JVM singletons, per-service sources,
and Ratis/Hadoop infrastructure metrics).  Allowlisting all of them so
the suite stays green while the assertion guards against new leaks; each
group is commented and is meant to be burned down in follow-up issues.

Matching now supports a leading '*' (suffix match) in addition to a
trailing '*' (prefix match), to cover names that embed a table-specific
prefix (keyTableCache-1), a random id (CSMMetricsgroup-...), an absolute
path (VolumeIOStats-/...), or a port (RpcActivityForPort15000).

Verified against all 4423 distinct leftover source names from the failed
CI run: zero remain unmatched.  TestMiniOzoneCluster (5 tests) and
TestOzoneIntegrationNonHA (318 tests) pass.

Generated-by: Codex (GPT-5)
Split the leftover-sources list into EXPECTED_LEFTOVER_SOURCES (JVM-level
singletons that are intentionally never unregistered) and
TODO_LEFTOVER_SOURCES (genuine per-service leaks to be fixed and
removed).  This makes the intent of each entry explicit and keeps the
burn-down list self-documenting.

Also correct the ContainerCacheMetrics handling: it is registered once
per JVM by the ContainerCache singleton (whose reference is never
reset), so it cannot be unregistered per-cluster.  Running the full
TestMiniOzoneCluster showed that a single leftover ContainerCacheMetrics
appears when an earlier test in the shared JVM creates the singleton.
Move it back to EXPECTED_LEFTOVER_SOURCES and drop the unregister()
call, keeping only the SOURCE_NAME constant cleanup.

Verified: all 4424 distinct leftover names from CI are covered;
TestMiniOzoneCluster (5 tests) passes.

Generated-by: Codex (GPT-5)
ManagedRocksObjectMetrics.assertNoLeaks() reports RocksDB objects that
were GC'd without being closed, which is often the root cause of a
failing shutdown.  Run it before MetricsLeakAssertion.assertNoLeaks() so
the more actionable failure surfaces first.  Both assertions still run
before DefaultMetricsSystem.shutdown().

Generated-by: Codex (GPT-5)
HddsVolume.failVolume() unregistered VolumeIOStats but not
VolumeInfoMetrics, while shutdown() unregisters both.  Make failVolume()
symmetric so a failed volume does not leak its VolumeInfoMetrics source.
Add TestHddsVolume.testFailVolumeUnregistersMetrics which verifies both
sources are removed; the test fails without the fix.

Generated-by: Codex (GPT-5)
…umes

VolumeInfoMetrics is deliberately kept registered on a failed volume
(HDDS-7086) so its FAILED state is visible via JMX / the DataNode UI, so
a leftover VolumeInfoMetrics is only a leak for healthy volumes that
were not shut down.  Document this so follow-up work does not try to
unregister it from failVolume().

Generated-by: Codex (GPT-5)
…trics

MutableVolumeSet.shutdown() only shut down healthy volumes in volumeMap
and never touched failedVolumeMap.  A failed volume registers a
VolumeInfoMetrics source when it is created (HDDS-7086 keeps it
registered so the FAILED state is visible while the datanode runs), but
it was never unregistered because shutdown() skipped failed volumes,
leaking the source for the life of the JVM.

Shut down and clear failedVolumeMap in shutdown() as well.  HddsVolume
and StorageVolume shutdown() are null-safe for failed volumes
(volumeIOStats / volumeUsage are null), so this only unregisters the
info metrics and is safe.  Extend TestVolumeSet.testFailVolumes to assert
the failed volume's VolumeInfoMetrics is registered before shutdown and
removed after; it fails without the fix.

Generated-by: Codex (GPT-5)
The metrics leak assertion reflects over the JVM-wide DefaultMetricsSystem
registry, so it cannot attribute a source to a specific cluster.  Tests
that run clusters concurrently via MiniOzoneClusterProvider (which builds
a reserve cluster in the background while another is still running) would
otherwise see a concurrent cluster's sources flagged as leaks.

Add a setMetricsLeakAssertEnabled(boolean) flag to MiniOzoneCluster.Builder
(default true); when false, MiniOzoneClusterImpl.shutdown() skips the
assertion.  Disable it in the three tests that use MiniOzoneClusterProvider:
TestSafeMode, TestHDDSUpgrade, and TestDecommissionAndMaintenance.

Verified: TestSafeMode, TestDecommissionAndMaintenance (9 tests), and
TestMiniOzoneCluster (5 tests) pass; 0 checkstyle violations.

Generated-by: Codex (GPT-5)
OmSnapshotInternalMetrics is created in the OzoneManager constructor and
has an unregister() method, but OzoneManager.stop() never called it, so
the source stayed registered after a single-OM mini cluster shut down.
Add the unregister call alongside the existing DeletingServiceMetrics and
OMPerformanceMetrics unregisters.  Verified: a single-OM cluster no
longer leaks OmSnapshotInternalMetrics (passes with the source removed
from the leak-assertion allowlist).

Note: in OM HA (multiple OMs in one JVM) the metrics system registers
OmSnapshotInternalMetrics-1, -2, ... for the additional OMs, and those
suffixed registrations still leak because unregister() only removes the
base name.  That shared per-OM registration problem affects many OM
metrics uniformly and is tracked separately.

Generated-by: Codex (GPT-5)
… mini-cluster mode

Apply the pattern from HDDS-16119 (DatanodeStorageMetrics): in
mini-cluster mode many OMs share one metrics system, which uniquifies a
constant source name on registration (OmSnapshotInternalMetrics-1, -2,
...).  Unregistering by the constant base name then leaks every source
past the first.  Register and unregister under a per-OM name
(OmSnapshotInternalMetrics-<omNodeId>) in mini-cluster mode so the two
stay symmetric; production keeps the plain name for stable JMX and
Prometheus metric names.

create() now takes the OM node id and stores the resolved sourceName in
the instance; unregister() becomes an instance method that unregisters
that name.  OzoneManager passes getOMNodeId() and calls the instance
unregister().  This fixes the HA leak too, so remove
OmSnapshotInternalMetrics from the leak-assertion allowlist.

Verified: TestOmSnapshotDisabled (3-OM HA) passes with the source removed
from the allowlist; single-OM and snapshot tests pass; 0 checkstyle
violations.

Generated-by: Codex (GPT-5)
…etrics in mini-cluster mode

Apply the HDDS-16119 pattern to SCMMetrics and SCMContainerMetrics: in
mini-cluster mode many SCMs share one metrics system, which uniquifies a
constant source name on registration (-1, -2, ...).  Unregistering by the
constant base name then leaks every source past the first.  Register and
unregister under a per-SCM name (suffixed with the scmId) in mini-cluster
mode so the two stay symmetric; production keeps the plain name for
stable JMX and Prometheus metric names.  create() takes the scmId and
stores the resolved sourceName in the instance; unRegister() unregisters
that name.  This fixes the HA leak, so remove both from the leak-assertion
allowlist.

Applying the per-SCM name also surfaced a pre-existing leak in the SCM
constructor failure path: initMetrics() registered SCMMetrics before the
storage-state check that throws SCM_NOT_INITIALIZED, so a failed SCM
construction (e.g. testSCMInitializationFailure) leaked the source with no
instance left to unregister it.  Move the storage-state check before
initMetrics()/initPerfMetrics() so a failed construction registers nothing.

Verified: TestStorageContainerManagerHA (3 SCMs) and TestStorageContainerManager
(9 tests) pass with both sources removed from the allowlist; TestSCMStateMachine
passes; 0 checkstyle violations.

Generated-by: Codex (GPT-5)
…r change

Making the SCMContainerMetrics constructor private and two-arg broke
TestSCMContainerMetrics, which instantiates the source directly to test
getMetrics() against a mocked SCMMXBean.  Make the constructor
package-private so the same-package test can use it, and update the test
to the two-arg form.

Generated-by: Codex (GPT-5)
PMD FieldDeclarationsShouldBeAtStartOfClass flagged the sourceName field
declared after a method.  Move it up with the other field declarations.

Generated-by: Codex (GPT-5)
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

This PR has been marked as stale due to 21 days of inactivity. Please comment or remove the stale label to keep it open. Otherwise, it will be automatically closed in 7 days.

@github-actions

Copy link
Copy Markdown

This PR has been marked as stale due to 21 days of inactivity. Please comment or remove the stale label to keep it open. Otherwise, it will be automatically closed in 7 days.

@github-actions github-actions Bot added the stale label Oct 15, 2026
@yandrey321

Copy link
Copy Markdown
Contributor

@ivandika3 can we resurrect this PR?

@ivandika3

Copy link
Copy Markdown
Contributor Author

@yandrey321 Thanks for checking this. Yes, my current plan is first to setup the Hadoop metric leak detection framework (which should already be done in this patch) and then integrate some of the metrics to this framework.

There are some metrics that are not included yet in TODO_LEFTOVER_SOURCES, this can be addressed in follow up tasks so that the size of the patch remains small.

What do you think?

@ivandika3 ivandika3 removed the stale label Oct 15, 2026
@yandrey321

Copy link
Copy Markdown
Contributor

@yandrey321 Thanks for checking this. Yes, my current plan is first to setup the Hadoop metric leak detection framework (which should already be done in this patch) and then integrate some of the metrics to this framework.

There are some metrics that are not included yet in TODO_LEFTOVER_SOURCES, this can be addressed in follow up tasks so that the size of the patch remains small.

What do you think?

Lets start with foundation done in this PR and file follow up JIRA(-s) to core remaining metric sources.

# Conflicts:
#	hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMStateMachine.java
#	hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/upgrade/TestHDDSUpgrade.java
#	hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
@ivandika3
ivandika3 marked this pull request as ready for review October 16, 2026 02:14
@ivandika3

Copy link
Copy Markdown
Contributor Author

@yandrey321 Ok this is ready to be reviewed. PTAL.

@ivandika3
ivandika3 requested a review from jojochuang October 16, 2026 02:15
@ivandika3
ivandika3 requested a review from smengcl October 21, 2026 03:52

@smengcl smengcl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ivandika3 for the patch.

@smengcl smengcl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ivandika3 for the updates. +1

# Conflicts:
#	hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestMiniOzoneCluster.java
#	hadoop-ozone/mini-cluster/src/main/java/org/apache/hadoop/ozone/MiniOzoneHAClusterImpl.java
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants