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;
019
020import java.io.IOException;
021import java.security.PrivilegedAction;
022import java.util.EnumSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Optional;
026import java.util.concurrent.CompletableFuture;
027import java.util.concurrent.atomic.AtomicInteger;
028
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.hbase.ClusterMetrics.Option;
031import org.apache.hadoop.hbase.Waiter.Predicate;
032import org.apache.hadoop.hbase.client.Admin;
033import org.apache.hadoop.hbase.client.AsyncAdmin;
034import org.apache.hadoop.hbase.client.AsyncConnection;
035import org.apache.hadoop.hbase.client.ClusterConnectionFactory;
036import org.apache.hadoop.hbase.client.Connection;
037import org.apache.hadoop.hbase.client.ConnectionFactory;
038import org.apache.hadoop.hbase.client.Get;
039import org.apache.hadoop.hbase.client.Put;
040import org.apache.hadoop.hbase.client.RegionInfoBuilder;
041import org.apache.hadoop.hbase.client.RegionStatesCount;
042import org.apache.hadoop.hbase.client.Result;
043import org.apache.hadoop.hbase.client.Scan;
044import org.apache.hadoop.hbase.client.Table;
045import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
046import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
047import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
048import org.apache.hadoop.hbase.coprocessor.MasterObserver;
049import org.apache.hadoop.hbase.coprocessor.ObserverContext;
050import org.apache.hadoop.hbase.filter.FilterAllFilter;
051import org.apache.hadoop.hbase.master.HMaster;
052import org.apache.hadoop.hbase.regionserver.HRegionServer;
053import org.apache.hadoop.hbase.regionserver.MetricsUserAggregateFactory;
054import org.apache.hadoop.hbase.security.User;
055import org.apache.hadoop.hbase.security.UserProvider;
056import org.apache.hadoop.hbase.testclassification.MediumTests;
057import org.apache.hadoop.hbase.util.Bytes;
058import org.apache.hadoop.hbase.util.JVMClusterUtil.MasterThread;
059import org.apache.hadoop.hbase.util.JVMClusterUtil.RegionServerThread;
060import org.junit.AfterClass;
061import org.junit.Assert;
062import org.junit.BeforeClass;
063import org.junit.ClassRule;
064import org.junit.Test;
065import org.junit.experimental.categories.Category;
066
067@Category(MediumTests.class)
068public class TestClientClusterMetrics {
069
070  @ClassRule
071  public static final HBaseClassTestRule CLASS_RULE =
072      HBaseClassTestRule.forClass(TestClientClusterMetrics.class);
073
074  private static HBaseTestingUtil UTIL;
075  private static Admin ADMIN;
076  private final static int SLAVES = 5;
077  private final static int MASTERS = 3;
078  private static SingleProcessHBaseCluster CLUSTER;
079  private static HRegionServer DEAD;
080  private static final TableName TABLE_NAME = TableName.valueOf("test");
081  private static final byte[] CF = Bytes.toBytes("cf");
082
083
084  @BeforeClass
085  public static void setUpBeforeClass() throws Exception {
086    Configuration conf = HBaseConfiguration.create();
087    conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, MyObserver.class.getName());
088    UTIL = new HBaseTestingUtil(conf);
089    StartTestingClusterOption option = StartTestingClusterOption.builder()
090        .numMasters(MASTERS).numRegionServers(SLAVES).numDataNodes(SLAVES).build();
091    UTIL.startMiniCluster(option);
092    CLUSTER = UTIL.getHBaseCluster();
093    CLUSTER.waitForActiveAndReadyMaster();
094    ADMIN = UTIL.getAdmin();
095    // Kill one region server
096    List<RegionServerThread> rsts = CLUSTER.getLiveRegionServerThreads();
097    RegionServerThread rst = rsts.get(rsts.size() - 1);
098    DEAD = rst.getRegionServer();
099    DEAD.stop("Test dead servers metrics");
100    while (rst.isAlive()) {
101      Thread.sleep(500);
102    }
103  }
104
105  @Test
106  public void testDefaults() throws Exception {
107    ClusterMetrics origin = ADMIN.getClusterMetrics();
108    ClusterMetrics defaults = ADMIN.getClusterMetrics(EnumSet.allOf(Option.class));
109    Assert.assertEquals(origin.getHBaseVersion(), defaults.getHBaseVersion());
110    Assert.assertEquals(origin.getClusterId(), defaults.getClusterId());
111    Assert.assertEquals(origin.getAverageLoad(), defaults.getAverageLoad(), 0);
112    Assert.assertEquals(origin.getBackupMasterNames().size(),
113        defaults.getBackupMasterNames().size());
114    Assert.assertEquals(origin.getDeadServerNames().size(), defaults.getDeadServerNames().size());
115    Assert.assertEquals(origin.getRegionCount(), defaults.getRegionCount());
116    Assert.assertEquals(origin.getLiveServerMetrics().size(),
117        defaults.getLiveServerMetrics().size());
118    Assert.assertEquals(origin.getMasterInfoPort(), defaults.getMasterInfoPort());
119    Assert.assertEquals(origin.getServersName().size(), defaults.getServersName().size());
120    Assert.assertEquals(ADMIN.getRegionServers().size(), defaults.getServersName().size());
121  }
122
123  @Test
124  public void testAsyncClient() throws Exception {
125    try (AsyncConnection asyncConnect = ConnectionFactory.createAsyncConnection(
126      UTIL.getConfiguration()).get()) {
127      AsyncAdmin asyncAdmin = asyncConnect.getAdmin();
128      CompletableFuture<ClusterMetrics> originFuture =
129        asyncAdmin.getClusterMetrics();
130      CompletableFuture<ClusterMetrics> defaultsFuture =
131        asyncAdmin.getClusterMetrics(EnumSet.allOf(Option.class));
132      ClusterMetrics origin = originFuture.get();
133      ClusterMetrics defaults = defaultsFuture.get();
134      Assert.assertEquals(origin.getHBaseVersion(), defaults.getHBaseVersion());
135      Assert.assertEquals(origin.getClusterId(), defaults.getClusterId());
136      Assert.assertEquals(origin.getHBaseVersion(), defaults.getHBaseVersion());
137      Assert.assertEquals(origin.getClusterId(), defaults.getClusterId());
138      Assert.assertEquals(origin.getAverageLoad(), defaults.getAverageLoad(), 0);
139      Assert.assertEquals(origin.getBackupMasterNames().size(),
140        defaults.getBackupMasterNames().size());
141      Assert.assertEquals(origin.getDeadServerNames().size(), defaults.getDeadServerNames().size());
142      Assert.assertEquals(origin.getRegionCount(), defaults.getRegionCount());
143      Assert.assertEquals(origin.getLiveServerMetrics().size(),
144        defaults.getLiveServerMetrics().size());
145      Assert.assertEquals(origin.getMasterInfoPort(), defaults.getMasterInfoPort());
146      Assert.assertEquals(origin.getServersName().size(), defaults.getServersName().size());
147      origin.getTableRegionStatesCount().forEach(((tableName, regionStatesCount) -> {
148        RegionStatesCount defaultRegionStatesCount = defaults.getTableRegionStatesCount()
149          .get(tableName);
150        Assert.assertEquals(defaultRegionStatesCount, regionStatesCount);
151      }));
152    }
153  }
154
155  @Test
156  public void testLiveAndDeadServersStatus() throws Exception {
157    // Count the number of live regionservers
158    List<RegionServerThread> regionserverThreads = CLUSTER.getLiveRegionServerThreads();
159    int numRs = 0;
160    int len = regionserverThreads.size();
161    for (int i = 0; i < len; i++) {
162      if (regionserverThreads.get(i).isAlive()) {
163        numRs++;
164      }
165    }
166    // Depending on the (random) order of unit execution we may run this unit before the
167    // minicluster is fully up and recovered from the RS shutdown done during test init.
168    Waiter.waitFor(CLUSTER.getConfiguration(), 10 * 1000, 100, new Predicate<Exception>() {
169      @Override
170      public boolean evaluate() throws Exception {
171        ClusterMetrics metrics = ADMIN.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS));
172        Assert.assertNotNull(metrics);
173        return metrics.getRegionCount() > 0;
174      }
175    });
176    // Retrieve live servers and dead servers info.
177    EnumSet<Option> options =
178        EnumSet.of(Option.LIVE_SERVERS, Option.DEAD_SERVERS, Option.SERVERS_NAME);
179    ClusterMetrics metrics = ADMIN.getClusterMetrics(options);
180    Assert.assertNotNull(metrics);
181    // exclude a dead region server
182    Assert.assertEquals(SLAVES -1, numRs);
183    // live servers = nums of regionservers
184    // By default, HMaster don't carry any regions so it won't report its load.
185    // Hence, it won't be in the server list.
186    Assert.assertEquals(numRs, metrics.getLiveServerMetrics().size());
187    Assert.assertTrue(metrics.getRegionCount() > 0);
188    Assert.assertNotNull(metrics.getDeadServerNames());
189    Assert.assertEquals(1, metrics.getDeadServerNames().size());
190    ServerName deadServerName = metrics.getDeadServerNames().iterator().next();
191    Assert.assertEquals(DEAD.getServerName(), deadServerName);
192    Assert.assertNotNull(metrics.getServersName());
193    Assert.assertEquals(numRs, metrics.getServersName().size());
194  }
195
196  @Test
197  public void testRegionStatesCount() throws Exception {
198    Table table = UTIL.createTable(TABLE_NAME, CF);
199    table.put(new Put(Bytes.toBytes("k1"))
200      .addColumn(CF, Bytes.toBytes("q1"), Bytes.toBytes("v1")));
201    table.put(new Put(Bytes.toBytes("k2"))
202      .addColumn(CF, Bytes.toBytes("q2"), Bytes.toBytes("v2")));
203    table.put(new Put(Bytes.toBytes("k3"))
204      .addColumn(CF, Bytes.toBytes("q3"), Bytes.toBytes("v3")));
205
206    ClusterMetrics metrics = ADMIN.getClusterMetrics();
207    Assert.assertEquals(metrics.getTableRegionStatesCount().size(), 2);
208    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TableName.META_TABLE_NAME)
209      .getRegionsInTransition(), 0);
210    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TableName.META_TABLE_NAME)
211      .getOpenRegions(), 1);
212    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TableName.META_TABLE_NAME)
213      .getTotalRegions(), 1);
214    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TableName.META_TABLE_NAME)
215      .getClosedRegions(), 0);
216    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TableName.META_TABLE_NAME)
217      .getSplitRegions(), 0);
218    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TABLE_NAME)
219      .getRegionsInTransition(), 0);
220    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TABLE_NAME)
221      .getOpenRegions(), 1);
222    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TABLE_NAME)
223      .getTotalRegions(), 1);
224
225    UTIL.deleteTable(TABLE_NAME);
226  }
227
228  @Test
229  public void testRegionStatesWithSplit() throws Exception {
230    int startRowNum = 20;
231    int rowCount = 80;
232    Table table = UTIL.createTable(TABLE_NAME, CF);
233    table.put(new Put(Bytes.toBytes("k1"))
234      .addColumn(CF, Bytes.toBytes("q1"), Bytes.toBytes("v1")));
235    table.put(new Put(Bytes.toBytes("k2"))
236      .addColumn(CF, Bytes.toBytes("q2"), Bytes.toBytes("v2")));
237
238    insertData(TABLE_NAME, startRowNum, rowCount);
239
240    ClusterMetrics metrics = ADMIN.getClusterMetrics();
241    Assert.assertEquals(metrics.getTableRegionStatesCount().size(), 2);
242    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TableName.META_TABLE_NAME)
243      .getRegionsInTransition(), 0);
244    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TableName.META_TABLE_NAME)
245      .getOpenRegions(), 1);
246    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TableName.META_TABLE_NAME)
247      .getTotalRegions(), 1);
248    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TABLE_NAME)
249      .getRegionsInTransition(), 0);
250    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TABLE_NAME)
251      .getOpenRegions(), 1);
252    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TABLE_NAME)
253      .getTotalRegions(), 1);
254
255    int splitRowNum = startRowNum + rowCount / 2;
256    byte[] splitKey = Bytes.toBytes("" + splitRowNum);
257
258    // Split region of the table
259    ADMIN.split(TABLE_NAME, splitKey);
260
261    metrics = ADMIN.getClusterMetrics();
262    Assert.assertEquals(metrics.getTableRegionStatesCount().size(), 2);
263    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TableName.META_TABLE_NAME)
264      .getRegionsInTransition(), 0);
265    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TableName.META_TABLE_NAME)
266      .getOpenRegions(), 1);
267    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TableName.META_TABLE_NAME)
268      .getTotalRegions(), 1);
269    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TABLE_NAME)
270      .getRegionsInTransition(), 0);
271    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TABLE_NAME)
272      .getOpenRegions(), 2);
273    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TABLE_NAME)
274      .getTotalRegions(), 3);
275    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TABLE_NAME)
276      .getSplitRegions(), 1);
277    Assert.assertEquals(metrics.getTableRegionStatesCount().get(TABLE_NAME)
278      .getClosedRegions(), 0);
279
280    UTIL.deleteTable(TABLE_NAME);
281  }
282
283  @Test public void testMasterAndBackupMastersStatus() throws Exception {
284    // get all the master threads
285    List<MasterThread> masterThreads = CLUSTER.getMasterThreads();
286    int numActive = 0;
287    int activeIndex = 0;
288    ServerName activeName = null;
289    HMaster active = null;
290    for (int i = 0; i < masterThreads.size(); i++) {
291      if (masterThreads.get(i).getMaster().isActiveMaster()) {
292        numActive++;
293        activeIndex = i;
294        active = masterThreads.get(activeIndex).getMaster();
295        activeName = active.getServerName();
296      }
297    }
298    Assert.assertNotNull(active);
299    Assert.assertEquals(1, numActive);
300    Assert.assertEquals(MASTERS, masterThreads.size());
301    // Retrieve master and backup masters infos only.
302    EnumSet<Option> options = EnumSet.of(Option.MASTER, Option.BACKUP_MASTERS);
303    ClusterMetrics metrics = ADMIN.getClusterMetrics(options);
304    Assert.assertTrue(metrics.getMasterName().equals(activeName));
305    Assert.assertEquals(MASTERS - 1, metrics.getBackupMasterNames().size());
306  }
307
308  @Test public void testUserMetrics() throws Exception {
309    Configuration conf = UTIL.getConfiguration();
310    // If metrics for users is not enabled, this test doesn't  make sense.
311    if (!conf.getBoolean(MetricsUserAggregateFactory.METRIC_USER_ENABLED_CONF,
312        MetricsUserAggregateFactory.DEFAULT_METRIC_USER_ENABLED_CONF)) {
313      return;
314    }
315    User userFoo = User.createUserForTesting(conf, "FOO_USER_METRIC_TEST", new String[0]);
316    User userBar = User.createUserForTesting(conf, "BAR_USER_METRIC_TEST", new String[0]);
317    User userTest = User.createUserForTesting(conf, "TEST_USER_METRIC_TEST", new String[0]);
318    UTIL.createTable(TABLE_NAME, CF);
319    waitForUsersMetrics(0);
320    long writeMetaMetricBeforeNextuser = getMetaMetrics().getWriteRequestCount();
321    userFoo.runAs(new PrivilegedAction<Void>() {
322      @Override public Void run() {
323        try {
324          doPut();
325        } catch (IOException e) {
326          Assert.fail("Exception:" + e.getMessage());
327        }
328        return null;
329      }
330    });
331    waitForUsersMetrics(1);
332    long writeMetaMetricForUserFoo =
333        getMetaMetrics().getWriteRequestCount() - writeMetaMetricBeforeNextuser;
334    long readMetaMetricBeforeNextuser = getMetaMetrics().getReadRequestCount();
335    userBar.runAs(new PrivilegedAction<Void>() {
336      @Override public Void run() {
337        try {
338          doGet();
339        } catch (IOException e) {
340          Assert.fail("Exception:" + e.getMessage());
341        }
342        return null;
343      }
344    });
345    waitForUsersMetrics(2);
346    long readMetaMetricForUserBar =
347        getMetaMetrics().getReadRequestCount() - readMetaMetricBeforeNextuser;
348    long filteredMetaReqeust = getMetaMetrics().getFilteredReadRequestCount();
349    userTest.runAs(new PrivilegedAction<Void>() {
350      @Override public Void run() {
351        try {
352          Table table = createConnection(UTIL.getConfiguration()).getTable(TABLE_NAME);
353          for (Result result : table.getScanner(new Scan().setFilter(new FilterAllFilter()))) {
354            Assert.fail("Should have filtered all rows");
355          }
356        } catch (IOException e) {
357          Assert.fail("Exception:" + e.getMessage());
358        }
359        return null;
360      }
361    });
362    waitForUsersMetrics(3);
363    long filteredMetaReqeustForTestUser =
364        getMetaMetrics().getFilteredReadRequestCount() - filteredMetaReqeust;
365    Map<byte[], UserMetrics> userMap =
366        ADMIN.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS)).getLiveServerMetrics().values()
367            .iterator().next().getUserMetrics();
368    for (byte[] user : userMap.keySet()) {
369      switch (Bytes.toString(user)) {
370        case "FOO_USER_METRIC_TEST":
371          Assert.assertEquals(1,
372              userMap.get(user).getWriteRequestCount() - writeMetaMetricForUserFoo);
373          break;
374        case "BAR_USER_METRIC_TEST":
375          Assert
376              .assertEquals(1, userMap.get(user).getReadRequestCount() - readMetaMetricForUserBar);
377          Assert.assertEquals(0, userMap.get(user).getWriteRequestCount());
378          break;
379        case "TEST_USER_METRIC_TEST":
380          Assert.assertEquals(1,
381              userMap.get(user).getFilteredReadRequests() - filteredMetaReqeustForTestUser);
382          Assert.assertEquals(0, userMap.get(user).getWriteRequestCount());
383          break;
384        default:
385          //current user
386          Assert.assertEquals(UserProvider.instantiate(conf).getCurrent().getName(),
387              Bytes.toString(user));
388          //Read/write count because of Meta operations
389          Assert.assertTrue(userMap.get(user).getReadRequestCount() > 1);
390          break;
391      }
392    }
393    UTIL.deleteTable(TABLE_NAME);
394  }
395
396  private RegionMetrics getMetaMetrics() throws IOException {
397    for (ServerMetrics serverMetrics : ADMIN.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS))
398        .getLiveServerMetrics().values()) {
399      RegionMetrics metaMetrics = serverMetrics.getRegionMetrics()
400          .get(RegionInfoBuilder.FIRST_META_REGIONINFO.getRegionName());
401      if (metaMetrics != null) {
402        return metaMetrics;
403      }
404    }
405    Assert.fail("Should have find meta metrics");
406    return null;
407  }
408
409  private void waitForUsersMetrics(int noOfUsers) throws Exception {
410    //Sleep for metrics to get updated on master
411    Thread.sleep(5000);
412    Waiter.waitFor(CLUSTER.getConfiguration(), 10 * 1000, 100, new Predicate<Exception>() {
413      @Override public boolean evaluate() throws Exception {
414        Map<byte[], UserMetrics> metrics =
415            ADMIN.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS)).getLiveServerMetrics().values()
416                .iterator().next().getUserMetrics();
417        Assert.assertNotNull(metrics);
418        //including current user + noOfUsers
419        return metrics.keySet().size() > noOfUsers;
420      }
421    });
422  }
423
424  private void doPut() throws IOException {
425    Table table = createConnection(UTIL.getConfiguration()).getTable(TABLE_NAME);
426    table.put(new Put(Bytes.toBytes("a")).addColumn(CF, Bytes.toBytes("col1"), Bytes.toBytes("1")));
427
428  }
429
430  private void doGet() throws IOException {
431    Table table = createConnection(UTIL.getConfiguration()).getTable(TABLE_NAME);
432    table.get(new Get(Bytes.toBytes("a")).addColumn(CF, Bytes.toBytes("col1")));
433
434  }
435
436  private Connection createConnection(Configuration conf) throws IOException {
437    User user = UserProvider.instantiate(conf).getCurrent();
438    return ClusterConnectionFactory.createAsyncClusterConnection(conf, null, user).toConnection();
439  }
440
441  @Test
442  public void testOtherStatusInfos() throws Exception {
443    EnumSet<Option> options =
444        EnumSet.of(Option.MASTER_COPROCESSORS, Option.HBASE_VERSION,
445                   Option.CLUSTER_ID, Option.BALANCER_ON);
446    ClusterMetrics metrics = ADMIN.getClusterMetrics(options);
447    Assert.assertEquals(1, metrics.getMasterCoprocessorNames().size());
448    Assert.assertNotNull(metrics.getHBaseVersion());
449    Assert.assertNotNull(metrics.getClusterId());
450    Assert.assertTrue(metrics.getAverageLoad() == 0.0);
451    Assert.assertNotNull(metrics.getBalancerOn());
452  }
453
454  @AfterClass
455  public static void tearDownAfterClass() throws Exception {
456    if (ADMIN != null) {
457      ADMIN.close();
458    }
459    UTIL.shutdownMiniCluster();
460  }
461
462  @Test
463  public void testObserver() throws IOException {
464    int preCount = MyObserver.PRE_COUNT.get();
465    int postCount = MyObserver.POST_COUNT.get();
466    Assert.assertTrue(ADMIN.getClusterMetrics().getMasterCoprocessorNames().stream()
467        .anyMatch(s -> s.equals(MyObserver.class.getSimpleName())));
468    Assert.assertEquals(preCount + 1, MyObserver.PRE_COUNT.get());
469    Assert.assertEquals(postCount + 1, MyObserver.POST_COUNT.get());
470  }
471
472  private static void insertData(final TableName tableName, int startRow, int rowCount)
473      throws IOException {
474    Table t = UTIL.getConnection().getTable(tableName);
475    Put p;
476    for (int i = 0; i < rowCount; i++) {
477      p = new Put(Bytes.toBytes("" + (startRow + i)));
478      p.addColumn(CF, Bytes.toBytes("val1"), Bytes.toBytes(i));
479      t.put(p);
480    }
481  }
482
483  public static class MyObserver implements MasterCoprocessor, MasterObserver {
484    private static final AtomicInteger PRE_COUNT = new AtomicInteger(0);
485    private static final AtomicInteger POST_COUNT = new AtomicInteger(0);
486
487    @Override public Optional<MasterObserver> getMasterObserver() {
488      return Optional.of(this);
489    }
490
491    @Override public void preGetClusterMetrics(ObserverContext<MasterCoprocessorEnvironment> ctx)
492        throws IOException {
493      PRE_COUNT.incrementAndGet();
494    }
495
496    @Override public void postGetClusterMetrics(ObserverContext<MasterCoprocessorEnvironment> ctx,
497        ClusterMetrics metrics) throws IOException {
498      POST_COUNT.incrementAndGet();
499    }
500  }
501}