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    currentRegionScanMetricsData.createCounter(BYTES_READ_FROM_FS_METRIC_NAME);
056    currentRegionScanMetricsData.createCounter(BYTES_READ_FROM_BLOCK_CACHE_METRIC_NAME);
057    currentRegionScanMetricsData.createCounter(BYTES_READ_FROM_MEMSTORE_METRIC_NAME);
058    currentRegionScanMetricsData.createCounter(BLOCK_READ_OPS_COUNT_METRIC_NAME);
059  }
060
061  /**
062   * Create a new counter with the specified name.
063   * @return {@link AtomicLong} instance for the counter with counterName
064   */
065  protected AtomicLong createCounter(String counterName) {
066    return ScanMetricsUtil.createCounter(counters, counterName);
067  }
068
069  public static final String COUNT_OF_ROWS_SCANNED_KEY_METRIC_NAME = "ROWS_SCANNED";
070  public static final String COUNT_OF_ROWS_FILTERED_KEY_METRIC_NAME = "ROWS_FILTERED";
071
072  public static final String BLOCK_BYTES_SCANNED_KEY_METRIC_NAME = "BLOCK_BYTES_SCANNED";
073
074  public static final String FS_READ_TIME_METRIC_NAME = "FS_READ_TIME";
075  public static final String BYTES_READ_FROM_FS_METRIC_NAME = "BYTES_READ_FROM_FS";
076  public static final String BYTES_READ_FROM_BLOCK_CACHE_METRIC_NAME =
077    "BYTES_READ_FROM_BLOCK_CACHE";
078  public static final String BYTES_READ_FROM_MEMSTORE_METRIC_NAME = "BYTES_READ_FROM_MEMSTORE";
079  public static final String BLOCK_READ_OPS_COUNT_METRIC_NAME = "BLOCK_READ_OPS_COUNT";
080
081  /**
082   * number of rows filtered during scan RPC
083   */
084  public final AtomicLong countOfRowsFiltered =
085    createCounter(COUNT_OF_ROWS_FILTERED_KEY_METRIC_NAME);
086
087  /**
088   * number of rows scanned during scan RPC. Not every row scanned will be returned to the client
089   * since rows may be filtered.
090   */
091  public final AtomicLong countOfRowsScanned = createCounter(COUNT_OF_ROWS_SCANNED_KEY_METRIC_NAME);
092
093  public final AtomicLong countOfBlockBytesScanned =
094    createCounter(BLOCK_BYTES_SCANNED_KEY_METRIC_NAME);
095
096  public final AtomicLong fsReadTime = createCounter(FS_READ_TIME_METRIC_NAME);
097
098  public final AtomicLong bytesReadFromFs = createCounter(BYTES_READ_FROM_FS_METRIC_NAME);
099
100  public final AtomicLong bytesReadFromBlockCache =
101    createCounter(BYTES_READ_FROM_BLOCK_CACHE_METRIC_NAME);
102
103  public final AtomicLong bytesReadFromMemstore =
104    createCounter(BYTES_READ_FROM_MEMSTORE_METRIC_NAME);
105
106  public final AtomicLong blockReadOpsCount = createCounter(BLOCK_READ_OPS_COUNT_METRIC_NAME);
107
108  /**
109   * Sets counter with counterName to passed in value, does nothing if counter does not exist. If
110   * region level scan metrics are enabled then sets the value of counter for the current region
111   * being scanned.
112   */
113  public void setCounter(String counterName, long value) {
114    ScanMetricsUtil.setCounter(counters, counterName, value);
115    if (this.currentRegionScanMetricsData != null) {
116      this.currentRegionScanMetricsData.setCounter(counterName, value);
117    }
118  }
119
120  /**
121   * Returns true if a counter exists with the counterName.
122   */
123  public boolean hasCounter(String counterName) {
124    return ScanMetricsUtil.hasCounter(counters, counterName);
125  }
126
127  /**
128   * Returns {@link AtomicLong} instance for this counter name, null if counter does not exist.
129   */
130  public AtomicLong getCounter(String counterName) {
131    return ScanMetricsUtil.getCounter(counters, counterName);
132  }
133
134  /**
135   * Increments the counter with counterName by delta, does nothing if counter does not exist. If
136   * region level scan metrics are enabled then increments the counter corresponding to the current
137   * region being scanned. Please see {@link #moveToNextRegion()}.
138   */
139  public void addToCounter(String counterName, long delta) {
140    ScanMetricsUtil.addToCounter(counters, counterName, delta);
141    if (this.currentRegionScanMetricsData != null) {
142      this.currentRegionScanMetricsData.addToCounter(counterName, delta);
143    }
144  }
145
146  /**
147   * Get all the values combined for all the regions since the last time this function was called.
148   * Calling this function will reset all AtomicLongs in the instance back to 0.
149   * @return A Map of String -> Long for metrics
150   */
151  public Map<String, Long> getMetricsMap() {
152    return getMetricsMap(true);
153  }
154
155  /**
156   * Get all the values combined for all the regions. If reset is true, we will reset all the
157   * AtomicLongs back to 0.
158   * @param reset whether to reset the AtomicLongs to 0.
159   * @return A Map of String -> Long for metrics
160   */
161  public Map<String, Long> getMetricsMap(boolean reset) {
162    return ImmutableMap.copyOf(ScanMetricsUtil.collectMetrics(counters, reset));
163  }
164
165  /**
166   * Get values grouped by each region scanned since the last time this was called. Calling this
167   * function will reset all region level scan metrics counters back to 0.
168   * @return A Map of region -> (Map of metric name -> Long) for metrics
169   */
170  public Map<ScanMetricsRegionInfo, Map<String, Long>> collectMetricsByRegion() {
171    return collectMetricsByRegion(true);
172  }
173
174  /**
175   * Get values grouped by each region scanned. If reset is true, will reset all the region level
176   * scan metrics counters back to 0.
177   * @param reset whether to reset region level scan metric counters to 0.
178   * @return A Map of region -> (Map of metric name -> Long) for metrics
179   */
180  public Map<ScanMetricsRegionInfo, Map<String, Long>> collectMetricsByRegion(boolean reset) {
181    // Create a builder
182    ImmutableMap.Builder<ScanMetricsRegionInfo, Map<String, Long>> builder = ImmutableMap.builder();
183    for (RegionScanMetricsData regionScanMetricsData : this.regionScanMetricsData) {
184      if (
185        regionScanMetricsData.getScanMetricsRegionInfo()
186            == ScanMetricsRegionInfo.EMPTY_SCAN_METRICS_REGION_INFO
187      ) {
188        continue;
189      }
190      builder.put(regionScanMetricsData.getScanMetricsRegionInfo(),
191        regionScanMetricsData.collectMetrics(reset));
192    }
193    return builder.build();
194  }
195
196  @Override
197  public String toString() {
198    return counters + "," + regionScanMetricsData.stream().map(RegionScanMetricsData::toString)
199      .collect(Collectors.joining(","));
200  }
201
202  /**
203   * Call this method after calling {@link #moveToNextRegion()} to populate server name and encoded
204   * region name details for the region being scanned and for which metrics are being collected at
205   * the moment.
206   */
207  public void initScanMetricsRegionInfo(String encodedRegionName, ServerName serverName) {
208    currentRegionScanMetricsData.initScanMetricsRegionInfo(encodedRegionName, serverName);
209  }
210}