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