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}