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.coprocessor.example;
019
020import java.io.IOException;
021import java.util.Optional;
022import org.apache.hadoop.hbase.CoprocessorEnvironment;
023import org.apache.hadoop.hbase.TableName;
024import org.apache.hadoop.hbase.client.RegionInfo;
025import org.apache.hadoop.hbase.client.TableDescriptor;
026import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
027import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
028import org.apache.hadoop.hbase.coprocessor.MasterObserver;
029import org.apache.hadoop.hbase.coprocessor.ObserverContext;
030import org.apache.hadoop.hbase.metrics.Counter;
031import org.apache.hadoop.hbase.metrics.Gauge;
032import org.apache.hadoop.hbase.metrics.MetricRegistry;
033import org.apache.hadoop.hbase.metrics.Timer;
034import org.apache.yetus.audience.InterfaceAudience;
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037
038/**
039 * An example coprocessor that collects some metrics to demonstrate the usage of exporting custom
040 * metrics from the coprocessor.
041 *
042 * <p>
043 * These metrics will be available through the regular Hadoop metrics2 sinks (ganglia, opentsdb,
044 * etc) as well as JMX output. You can view a snapshot of the metrics by going to the http web UI
045 * of the master page, something like http://mymasterhost:16010/jmx
046 * </p>
047 * @see ExampleRegionObserverWithMetrics
048 */
049@InterfaceAudience.Private
050public class ExampleMasterObserverWithMetrics implements MasterCoprocessor, MasterObserver {
051  @Override
052  public Optional<MasterObserver> getMasterObserver() {
053    return Optional.of(this);
054  }
055
056  private static final Logger LOG = LoggerFactory.getLogger(ExampleMasterObserverWithMetrics.class);
057
058  /** This is the Timer metric object to keep track of the current count across invocations */
059  private Timer createTableTimer;
060  private long createTableStartTime = Long.MIN_VALUE;
061
062  /** This is a Counter object to keep track of disableTable operations */
063  private Counter disableTableCounter;
064
065  /** Returns the total memory of the process. We will use this to define a gauge metric */
066  private long getTotalMemory() {
067    return Runtime.getRuntime().totalMemory();
068  }
069
070  /** Returns the max memory of the process. We will use this to define a gauge metric */
071  private long getMaxMemory() {
072    return Runtime.getRuntime().maxMemory();
073  }
074
075  @Override
076  public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
077                             TableDescriptor desc, RegionInfo[] regions) throws IOException {
078    // we rely on the fact that there is only 1 instance of our MasterObserver. We keep track of
079    // when the operation starts before the operation is executing.
080    this.createTableStartTime = System.currentTimeMillis();
081  }
082
083  @Override
084  public void postCreateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
085                              TableDescriptor desc, RegionInfo[] regions) throws IOException {
086    if (this.createTableStartTime > 0) {
087      long time = System.currentTimeMillis() - this.createTableStartTime;
088      LOG.info("Create table took: " + time);
089
090      // Update the timer metric for the create table operation duration.
091      createTableTimer.updateMillis(time);
092    }
093  }
094
095  @Override
096  public void preDisableTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
097      TableName tableName) throws IOException {
098    // Increment the Counter for disable table operations
099    this.disableTableCounter.increment();
100  }
101
102  @Override
103  public void start(CoprocessorEnvironment env) throws IOException {
104    // start for the MasterObserver will be called only once in the lifetime of the
105    // server. We will construct and register all metrics that we will track across method
106    // invocations.
107
108    if (env instanceof MasterCoprocessorEnvironment) {
109      // Obtain the MetricRegistry for the Master. Metrics from this registry will be reported
110      // at the master level per-server.
111      MetricRegistry registry =
112          ((MasterCoprocessorEnvironment) env).getMetricRegistryForMaster();
113
114      if (createTableTimer == null) {
115        // Create a new Counter, or get the already registered counter.
116        // It is much better to only call this once and save the Counter as a class field instead
117        // of creating the counter every time a coprocessor method is invoked. This will negate
118        // any performance bottleneck coming from map lookups tracking metrics in the registry.
119        createTableTimer = registry.timer("CreateTable");
120
121        // on stop(), we can remove these registered metrics via calling registry.remove(). But
122        // it is not needed for coprocessors at the master level. If coprocessor is stopped,
123        // the server is stopping anyway, so there will not be any resource leaks.
124      }
125
126      if (disableTableCounter == null) {
127        disableTableCounter = registry.counter("DisableTable");
128      }
129
130      // Register a custom gauge. The Gauge object will be registered in the metrics registry and
131      // periodically the getValue() is invoked to obtain the snapshot.
132      registry.register("totalMemory", new Gauge<Long>() {
133        @Override
134        public Long getValue() {
135          return getTotalMemory();
136        }
137      });
138
139      // Register a custom gauge using Java-8 lambdas (Supplier converted into Gauge)
140      registry.register("maxMemory", this::getMaxMemory);
141    }
142  }
143}