All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.opentable.metrics.jvm.NmtMetrics Maven / Gradle / Ivy

Go to download

Manages metrics and healthchecks and sends them to Graphite and exposes them via endpoints

There is a newer version: 6.0.1
Show newest version
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.opentable.metrics.jvm;

import java.time.Duration;
import java.time.Instant;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.function.Supplier;

import com.codahale.metrics.Gauge;
import com.codahale.metrics.MetricRegistry;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

import com.opentable.jvm.Memory;
import com.opentable.jvm.Nmt;

/**
 * If JVM argument {@code -XX:NativeMemoryTracking=summary} is present, will register NMT-related metrics.
 * If not, won't do anything.
 *
 * As the lifetime of a process goes on, NMT returns increasingly more categories of information when you query it.
 * This means that at the outset, we do not know the complete set of gauges to register.  Given how DropWizard works,
 * the lightest-weight opportunity we have to identify new metrics is when the gauge functions are called.
 * Furthermore, this is also the opportunity to ensure that we are reading the latest NMT values.  However, fetching
 * NMT information is non-trivial, so we want to rate-limit it.  Therefore, the design employed here is that on gauge
 * measurement, we check to update our NMT observations according to a refresh period.  Having refreshed the NMT
 * information, we can also check for further metrics to register.
 *
 * Cf. https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr022.html
 *
 * In terms of thread-safety, we assume the worst, and handle {@link #register()} and the gauge functions being called
 * by arbitrary threads.
 *
 * FindBugs is bad at understanding the synchronized access and modification of {@link #nmt}, particularly when
 * referred to in lambdas, so we suppress {@code IS2_INCONSISTENT_SYNC} warnings.
 */
@SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC",
        justification = "FindBugs gets confused by the lambda references to nmt.")
public class NmtMetrics {
    private static final Logger LOG = LoggerFactory.getLogger(NmtMetrics.class);
    private static final Duration REFRESH_PERIOD = Duration.ofSeconds(10);

    private final String prefix;
    private final MetricRegistry metrics;
    private final Set registeredMetrics = new HashSet<>();

    private Nmt nmt;
    private Instant lastRefresh;

    public NmtMetrics(final String metricNamePrefix, final MetricRegistry metrics) {
        prefix = metricNamePrefix;
        this.metrics = metrics;
    }

    public synchronized void register() {
        nmt = Memory.getNmt();
        if (nmt == null) {
            LOG.info("got null NMT info; not registering any metrics");
            return;
        }
        lastRefresh = Instant.now();
        refreshCategories();
        if (registeredMetrics.isEmpty()) {
            LOG.warn("Didn't find any NMT metrics to register!");
        }
        registerGauges("total", () -> nmt.total);
    }

    private void refreshCategories() {
        final Set atStart = ImmutableSet.copyOf(registeredMetrics);
        nmt.categories.keySet().forEach(categoryName -> {
            final String metricName = categoryName.toLowerCase(Locale.ROOT).replaceAll(" ", "-");
            if (registeredMetrics.contains(metricName)) {
                return;
            }
            registerCategoryGauges(metricName, categoryName);
        });
        final SetView added = Sets.difference(registeredMetrics, atStart);
        if (!added.isEmpty()) {
            LOG.debug("Registered metrics: {}", added);
        }
    }

    private void refreshNmt() {
        final Instant now = Instant.now();
        if (now.isBefore(lastRefresh.plus(REFRESH_PERIOD))) {
            return;
        }
        nmt = Memory.getNmt();
        Preconditions.checkNotNull(nmt);
        lastRefresh = now;
        refreshCategories();
    }

    private void registerGauges(final String metricName, final Supplier usageSupplier) {
        if (!registeredMetrics.add(metricName)) {
            throw new IllegalStateException(String.format("%s already registered", metricName));
        }

        final String fullMetricName = prefix + "." + metricName;

        metrics.register(fullMetricName + ".reserved", (Gauge)() -> {
            synchronized (this) {
                refreshNmt();
                return usageSupplier.get().reserved;
            }
        });

        metrics.register(fullMetricName + ".committed", (Gauge)() -> {
            synchronized (this) {
                refreshNmt();
                return usageSupplier.get().committed;
            }
        });
    }

    private void registerCategoryGauges(final String metricName, final String categoryName) {
        registerGauges(metricName, () -> nmt.categories.get(categoryName));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy