001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.metrics.impl; 019 020import java.util.Collection; 021import java.util.HashMap; 022import java.util.Iterator; 023import java.util.Map.Entry; 024import java.util.Optional; 025import java.util.concurrent.TimeUnit; 026import java.util.concurrent.atomic.AtomicBoolean; 027import org.apache.hadoop.hbase.metrics.MetricRegistries; 028import org.apache.hadoop.hbase.metrics.MetricRegistry; 029import org.apache.hadoop.hbase.metrics.MetricRegistryInfo; 030import org.apache.hadoop.metrics2.MetricsCollector; 031import org.apache.hadoop.metrics2.MetricsExecutor; 032import org.apache.hadoop.metrics2.MetricsSource; 033import org.apache.hadoop.metrics2.impl.JmxCacheBuster; 034import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem; 035import org.apache.hadoop.metrics2.lib.DefaultMetricsSystemHelper; 036import org.apache.hadoop.metrics2.lib.MetricsExecutorImpl; 037import org.apache.yetus.audience.InterfaceAudience; 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040 041/** 042 * This class acts as an adapter to export the MetricRegistry's in the global registry. Each 043 * MetricRegistry will be registered or unregistered from the metric2 system. The collection will be 044 * performed via the MetricsSourceAdapter and the MetricRegistry will collected like a BaseSource 045 * instance for a group of metrics (like WAL, RPC, etc) with the MetricRegistryInfo's JMX context. 046 * <p> 047 * Developer note: Unlike the current metrics2 based approach, the new metrics approach 048 * (hbase-metrics-api and hbase-metrics modules) work by having different MetricRegistries that are 049 * initialized and used from the code that lives in their respective modules (hbase-server, etc). 050 * There is no need to define BaseSource classes and do a lot of indirection. The MetricRegistry'es 051 * will be in the global MetricRegistriesImpl, and this class will iterate over 052 * MetricRegistries.global() and register adapters to the metrics2 subsystem. These adapters then 053 * report the actual values by delegating to 054 * {@link HBaseMetrics2HadoopMetricsAdapter#snapshotAllMetrics(MetricRegistry, MetricsCollector)}. 055 * We do not initialize the Hadoop Metrics2 system assuming that other BaseSources already do so 056 * (see BaseSourceImpl). Once the last BaseSource is moved to the new system, the metric2 057 * initialization should be moved here. 058 * </p> 059 */ 060@InterfaceAudience.Private 061public final class GlobalMetricRegistriesAdapter { 062 063 private static final Logger LOG = LoggerFactory.getLogger(GlobalMetricRegistriesAdapter.class); 064 065 private class MetricsSourceAdapter implements MetricsSource { 066 private final MetricRegistryInfo info; 067 068 MetricsSourceAdapter(MetricRegistryInfo info) { 069 this.info = info; 070 } 071 072 @Override 073 public void getMetrics(MetricsCollector collector, boolean all) { 074 Optional<MetricRegistry> registryOpt = MetricRegistries.global().get(info); 075 registryOpt 076 .ifPresent(metricRegistry -> metricsAdapter.snapshotAllMetrics(metricRegistry, collector)); 077 } 078 } 079 080 private final MetricsExecutor executor; 081 private final AtomicBoolean stopped; 082 private final DefaultMetricsSystemHelper helper; 083 private final HBaseMetrics2HadoopMetricsAdapter metricsAdapter; 084 private final HashMap<MetricRegistryInfo, MetricsSourceAdapter> registeredSources; 085 086 private GlobalMetricRegistriesAdapter() { 087 this.executor = new MetricsExecutorImpl(); 088 this.stopped = new AtomicBoolean(false); 089 this.metricsAdapter = new HBaseMetrics2HadoopMetricsAdapter(); 090 this.registeredSources = new HashMap<>(); 091 this.helper = new DefaultMetricsSystemHelper(); 092 executor.getExecutor().scheduleAtFixedRate(() -> this.doRun(), 10, 10, TimeUnit.SECONDS); 093 } 094 095 /** 096 * Make sure that this global MetricSource for hbase-metrics module based metrics are initialized. 097 * This should be called only once. 098 */ 099 public static GlobalMetricRegistriesAdapter init() { 100 return new GlobalMetricRegistriesAdapter(); 101 } 102 103 public void stop() { 104 stopped.set(true); 105 } 106 107 private void doRun() { 108 if (stopped.get()) { 109 executor.stop(); 110 return; 111 } 112 if (LOG.isTraceEnabled()) { 113 LOG.trace("doRun called: " + registeredSources); 114 } 115 116 Collection<MetricRegistry> registries = MetricRegistries.global().getMetricRegistries(); 117 for (MetricRegistry registry : registries) { 118 MetricRegistryInfo info = registry.getMetricRegistryInfo(); 119 120 LOG.trace("MetricRegistryInfo : {}", info.getMetricsName()); 121 if (info.isExistingSource()) { 122 // If there is an already existing BaseSource for this MetricRegistry, skip it here. These 123 // types of registries are there only due to existing BaseSource implementations in the 124 // source code (like MetricsRegionServer, etc). This is to make sure that we can transition 125 // iteratively to the new hbase-metrics system. These type of MetricRegistry metrics will be 126 // exported from the BaseSource.getMetrics() call directly because there is already a 127 // MetricRecordBuilder there (see MetricsRegionServerSourceImpl). 128 continue; 129 } 130 131 if (!registeredSources.containsKey(info)) { 132 if (LOG.isDebugEnabled()) { 133 LOG.debug("Registering adapter for the MetricRegistry: {}", info.getMetricsJmxContext()); 134 } 135 // register this as a MetricSource under different JMX Context'es. 136 MetricsSourceAdapter adapter = new MetricsSourceAdapter(info); 137 if (LOG.isDebugEnabled()) { 138 LOG.debug("Registering {} {}", info.getMetricsJmxContext(), info.getMetricsDescription()); 139 } 140 DefaultMetricsSystem.instance().register(info.getMetricsJmxContext(), 141 info.getMetricsDescription(), adapter); 142 registeredSources.put(info, adapter); 143 // next collection will collect the newly registered MetricSource. Doing this here leads to 144 // ConcurrentModificationException. 145 } 146 } 147 148 boolean removed = false; 149 // Remove registered sources if it is removed from the global registry 150 for (Iterator<Entry<MetricRegistryInfo, MetricsSourceAdapter>> it = 151 registeredSources.entrySet().iterator(); it.hasNext();) { 152 Entry<MetricRegistryInfo, MetricsSourceAdapter> entry = it.next(); 153 MetricRegistryInfo info = entry.getKey(); 154 Optional<MetricRegistry> found = MetricRegistries.global().get(info); 155 if (found.isEmpty()) { 156 if (LOG.isDebugEnabled()) { 157 LOG.debug("Removing adapter for the MetricRegistry: {}", info.getMetricsJmxContext()); 158 } 159 synchronized (DefaultMetricsSystem.instance()) { 160 DefaultMetricsSystem.instance().unregisterSource(info.getMetricsJmxContext()); 161 helper.removeSourceName(info.getMetricsJmxContext()); 162 helper.removeObjectName(info.getMetricsJmxContext()); 163 it.remove(); 164 removed = true; 165 } 166 } 167 } 168 if (removed) { 169 JmxCacheBuster.clearJmxCache(); 170 } 171 } 172}