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.client.metrics;
019
020import java.util.ArrayList;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.concurrent.atomic.AtomicLong;
025import java.util.stream.Collectors;
026import org.apache.hadoop.hbase.ServerName;
027import org.apache.yetus.audience.InterfaceAudience;
028
029import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableMap;
030
031/**
032 * Provides server side metrics related to scan operations.
033 */
034@InterfaceAudience.Public
035@SuppressWarnings("checkstyle:VisibilityModifier") // See HBASE-27757
036public class ServerSideScanMetrics {
037  /**
038   * Hash to hold the String -> Atomic Long mappings for each metric
039   */
040  private final Map<String, AtomicLong> counters = new HashMap<>();
041  private final List<RegionScanMetricsData> regionScanMetricsData = new ArrayList<>(0);
042  protected RegionScanMetricsData currentRegionScanMetricsData = null;
043
044  /**
045   * If region level scan metrics are enabled, must call this method to start collecting metrics for
046   * the region before scanning the region.
047   */
048  public void moveToNextRegion() {
049    currentRegionScanMetricsData = new RegionScanMetricsData();
050    regionScanMetricsData.add(currentRegionScanMetricsData);
051    currentRegionScanMetricsData.createCounter(COUNT_OF_ROWS_SCANNED_KEY_METRIC_NAME);
052    currentRegionScanMetricsData.createCounter(COUNT_OF_ROWS_FILTERED_KEY_METRIC_NAME);
053    currentRegionScanMetricsData.createCounter(BLOCK_BYTES_SCANNED_KEY_METRIC_NAME);
054    currentRegionScanMetricsData.createCounter(FS_READ_TIME_METRIC_NAME);
055  }
056
057  /**
058   * Create a new counter with the specified name.
059   * @return {@link AtomicLong} instance for the counter with counterName
060   */
061  protected AtomicLong createCounter(String counterName) {
062    return ScanMetricsUtil.createCounter(counters, counterName);
063  }
064
065  public static final String COUNT_OF_ROWS_SCANNED_KEY_METRIC_NAME = "ROWS_SCANNED";
066  public static final String COUNT_OF_ROWS_FILTERED_KEY_METRIC_NAME = "ROWS_FILTERED";
067
068  public static final String BLOCK_BYTES_SCANNED_KEY_METRIC_NAME = "BLOCK_BYTES_SCANNED";
069
070  public static final String FS_READ_TIME_METRIC_NAME = "FS_READ_TIME";
071
072  /**
073   * number of rows filtered during scan RPC
074   */
075  public final AtomicLong countOfRowsFiltered =
076    createCounter(COUNT_OF_ROWS_FILTERED_KEY_METRIC_NAME);
077
078  /**
079   * number of rows scanned during scan RPC. Not every row scanned will be returned to the client
080   * since rows may be filtered.
081   */
082  public final AtomicLong countOfRowsScanned = createCounter(COUNT_OF_ROWS_SCANNED_KEY_METRIC_NAME);
083
084  public final AtomicLong countOfBlockBytesScanned =
085    createCounter(BLOCK_BYTES_SCANNED_KEY_METRIC_NAME);
086
087  public final AtomicLong fsReadTime = createCounter(FS_READ_TIME_METRIC_NAME);
088
089  /**
090   * Sets counter with counterName to passed in value, does nothing if counter does not exist. If
091   * region level scan metrics are enabled then sets the value of counter for the current region
092   * being scanned.
093   */
094  public void setCounter(String counterName, long value) {
095    ScanMetricsUtil.setCounter(counters, counterName, value);
096    if (this.currentRegionScanMetricsData != null) {
097      this.currentRegionScanMetricsData.setCounter(counterName, value);
098    }
099  }
100
101  /**
102   * Returns true if a counter exists with the counterName.
103   */
104  public boolean hasCounter(String counterName) {
105    return ScanMetricsUtil.hasCounter(counters, counterName);
106  }
107
108  /**
109   * Returns {@link AtomicLong} instance for this counter name, null if counter does not exist.
110   */
111  public AtomicLong getCounter(String counterName) {
112    return ScanMetricsUtil.getCounter(counters, counterName);
113  }
114
115  /**
116   * Increments the counter with counterName by delta, does nothing if counter does not exist. If
117   * region level scan metrics are enabled then increments the counter corresponding to the current
118   * region being scanned. Please see {@link #moveToNextRegion()}.
119   */
120  public void addToCounter(String counterName, long delta) {
121    ScanMetricsUtil.addToCounter(counters, counterName, delta);
122    if (this.currentRegionScanMetricsData != null) {
123      this.currentRegionScanMetricsData.addToCounter(counterName, delta);
124    }
125  }
126
127  /**
128   * Get all the values combined for all the regions since the last time this function was called.
129   * Calling this function will reset all AtomicLongs in the instance back to 0.
130   * @return A Map of String -> Long for metrics
131   */
132  public Map<String, Long> getMetricsMap() {
133    return getMetricsMap(true);
134  }
135
136  /**
137   * Get all the values combined for all the regions. If reset is true, we will reset all the
138   * AtomicLongs back to 0.
139   * @param reset whether to reset the AtomicLongs to 0.
140   * @return A Map of String -> Long for metrics
141   */
142  public Map<String, Long> getMetricsMap(boolean reset) {
143    return ImmutableMap.copyOf(ScanMetricsUtil.collectMetrics(counters, reset));
144  }
145
146  /**
147   * Get values grouped by each region scanned since the last time this was called. Calling this
148   * function will reset all region level scan metrics counters back to 0.
149   * @return A Map of region -> (Map of metric name -> Long) for metrics
150   */
151  public Map<ScanMetricsRegionInfo, Map<String, Long>> collectMetricsByRegion() {
152    return collectMetricsByRegion(true);
153  }
154
155  /**
156   * Get values grouped by each region scanned. If reset is true, will reset all the region level
157   * scan metrics counters back to 0.
158   * @param reset whether to reset region level scan metric counters to 0.
159   * @return A Map of region -> (Map of metric name -> Long) for metrics
160   */
161  public Map<ScanMetricsRegionInfo, Map<String, Long>> collectMetricsByRegion(boolean reset) {
162    // Create a builder
163    ImmutableMap.Builder<ScanMetricsRegionInfo, Map<String, Long>> builder = ImmutableMap.builder();
164    for (RegionScanMetricsData regionScanMetricsData : this.regionScanMetricsData) {
165      if (
166        regionScanMetricsData.getScanMetricsRegionInfo()
167            == ScanMetricsRegionInfo.EMPTY_SCAN_METRICS_REGION_INFO
168      ) {
169        continue;
170      }
171      builder.put(regionScanMetricsData.getScanMetricsRegionInfo(),
172        regionScanMetricsData.collectMetrics(reset));
173    }
174    return builder.build();
175  }
176
177  @Override
178  public String toString() {
179    return counters + "," + regionScanMetricsData.stream().map(RegionScanMetricsData::toString)
180      .collect(Collectors.joining(","));
181  }
182
183  /**
184   * Call this method after calling {@link #moveToNextRegion()} to populate server name and encoded
185   * region name details for the region being scanned and for which metrics are being collected at
186   * the moment.
187   */
188  public void initScanMetricsRegionInfo(String encodedRegionName, ServerName serverName) {
189    currentRegionScanMetricsData.initScanMetricsRegionInfo(encodedRegionName, serverName);
190  }
191}