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 */
018
019package org.apache.hadoop.hbase.coprocessor.example;
020
021import java.io.IOException;
022import java.util.Optional;
023import org.apache.hadoop.hbase.CoprocessorEnvironment;
024import org.apache.hadoop.hbase.TableName;
025import org.apache.hadoop.hbase.client.RegionInfo;
026import org.apache.hadoop.hbase.client.TableDescriptor;
027import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
028import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
029import org.apache.hadoop.hbase.coprocessor.MasterObserver;
030import org.apache.hadoop.hbase.coprocessor.ObserverContext;
031import org.apache.hadoop.hbase.metrics.Counter;
032import org.apache.hadoop.hbase.metrics.Gauge;
033import org.apache.hadoop.hbase.metrics.MetricRegistry;
034import org.apache.hadoop.hbase.metrics.Timer;
035import org.apache.yetus.audience.InterfaceAudience;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039/**
040 * An example coprocessor that collects some metrics to demonstrate the usage of exporting custom
041 * metrics from the coprocessor.
042 *
043 * <p>
044 * These metrics will be available through the regular Hadoop metrics2 sinks (ganglia, opentsdb,
045 * etc) as well as JMX output. You can view a snapshot of the metrics by going to the http web UI
046 * of the master page, something like http://mymasterhost:16010/jmx
047 * </p>
048 * @see ExampleRegionObserverWithMetrics
049 */
050@InterfaceAudience.Private
051public class ExampleMasterObserverWithMetrics implements MasterCoprocessor, MasterObserver {
052  @Override
053  public Optional<MasterObserver> getMasterObserver() {
054    return Optional.of(this);
055  }
056
057  private static final Logger LOG = LoggerFactory.getLogger(ExampleMasterObserverWithMetrics.class);
058
059  /** This is the Timer metric object to keep track of the current count across invocations */
060  private Timer createTableTimer;
061  private long createTableStartTime = Long.MIN_VALUE;
062
063  /** This is a Counter object to keep track of disableTable operations */
064  private Counter disableTableCounter;
065
066  /** Returns the total memory of the process. We will use this to define a gauge metric */
067  private long getTotalMemory() {
068    return Runtime.getRuntime().totalMemory();
069  }
070
071  /** Returns the max memory of the process. We will use this to define a gauge metric */
072  private long getMaxMemory() {
073    return Runtime.getRuntime().maxMemory();
074  }
075
076  @Override
077  public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
078                             TableDescriptor desc, RegionInfo[] regions) throws IOException {
079    // we rely on the fact that there is only 1 instance of our MasterObserver. We keep track of
080    // when the operation starts before the operation is executing.
081    this.createTableStartTime = System.currentTimeMillis();
082  }
083
084  @Override
085  public void postCreateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
086                              TableDescriptor desc, RegionInfo[] regions) throws IOException {
087    if (this.createTableStartTime > 0) {
088      long time = System.currentTimeMillis() - this.createTableStartTime;
089      LOG.info("Create table took: " + time);
090
091      // Update the timer metric for the create table operation duration.
092      createTableTimer.updateMillis(time);
093    }
094  }
095
096  @Override
097  public void preDisableTable(ObserverContext<MasterCoprocessorEnvironment> ctx, 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}