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.hbtop.screen.top; 019 020import edu.umd.cs.findbugs.annotations.Nullable; 021import java.util.ArrayList; 022import java.util.EnumMap; 023import java.util.List; 024import java.util.Objects; 025import java.util.concurrent.atomic.AtomicBoolean; 026import java.util.concurrent.atomic.AtomicLong; 027import java.util.stream.Collectors; 028import org.apache.hadoop.hbase.hbtop.Record; 029import org.apache.hadoop.hbase.hbtop.RecordFilter; 030import org.apache.hadoop.hbase.hbtop.field.Field; 031import org.apache.hadoop.hbase.hbtop.field.FieldInfo; 032import org.apache.hadoop.hbase.hbtop.mode.Mode; 033import org.apache.hadoop.hbase.hbtop.screen.Screen; 034import org.apache.hadoop.hbase.hbtop.screen.ScreenView; 035import org.apache.hadoop.hbase.hbtop.screen.field.FieldScreenView; 036import org.apache.hadoop.hbase.hbtop.screen.help.HelpScreenView; 037import org.apache.hadoop.hbase.hbtop.screen.mode.ModeScreenView; 038import org.apache.hadoop.hbase.hbtop.terminal.Terminal; 039import org.apache.hadoop.hbase.hbtop.terminal.TerminalSize; 040import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 041import org.apache.yetus.audience.InterfaceAudience; 042 043/** 044 * The presentation logic for the top screen. 045 */ 046@InterfaceAudience.Private 047public class TopScreenPresenter { 048 private final TopScreenView topScreenView; 049 private final AtomicLong refreshDelay; 050 private long lastRefreshTimestamp; 051 052 private final AtomicBoolean adjustFieldLength = new AtomicBoolean(true); 053 private final TopScreenModel topScreenModel; 054 private int terminalLength; 055 private int horizontalScroll; 056 private final Paging paging = new Paging(); 057 058 private final EnumMap<Field, Boolean> fieldDisplayMap = new EnumMap<>(Field.class); 059 private final EnumMap<Field, Integer> fieldLengthMap = new EnumMap<>(Field.class); 060 061 private final long numberOfIterations; 062 private long iterations; 063 064 public TopScreenPresenter(TopScreenView topScreenView, long initialRefreshDelay, 065 TopScreenModel topScreenModel, @Nullable List<Field> initialFields, long numberOfIterations) { 066 this.topScreenView = Objects.requireNonNull(topScreenView); 067 this.refreshDelay = new AtomicLong(initialRefreshDelay); 068 this.topScreenModel = Objects.requireNonNull(topScreenModel); 069 this.numberOfIterations = numberOfIterations; 070 071 initFieldDisplayMapAndFieldLengthMap(initialFields); 072 } 073 074 public void init() { 075 updateTerminalLengthAndPageSize(topScreenView.getTerminalSize(), topScreenView.getPageSize()); 076 topScreenView.hideCursor(); 077 } 078 079 private void updateTerminalLengthAndPageSize(@Nullable TerminalSize terminalSize, 080 @Nullable Integer pageSize) { 081 if (terminalSize != null) { 082 terminalLength = terminalSize.getColumns(); 083 } else { 084 terminalLength = Integer.MAX_VALUE; 085 } 086 if (pageSize != null) { 087 paging.updatePageSize(pageSize); 088 } else { 089 paging.updatePageSize(Integer.MAX_VALUE); 090 } 091 } 092 093 public long refresh(boolean force) { 094 if (!force) { 095 long delay = EnvironmentEdgeManager.currentTime() - lastRefreshTimestamp; 096 if (delay < refreshDelay.get()) { 097 return refreshDelay.get() - delay; 098 } 099 } 100 101 TerminalSize newTerminalSize = topScreenView.doResizeIfNecessary(); 102 if (newTerminalSize != null) { 103 updateTerminalLengthAndPageSize(newTerminalSize, topScreenView.getPageSize()); 104 topScreenView.clearTerminal(); 105 } 106 107 topScreenModel.refreshMetricsData(); 108 paging.updateRecordsSize(topScreenModel.getRecords().size()); 109 110 adjustFieldLengthIfNeeded(); 111 112 topScreenView.showTopScreen(topScreenModel.getSummary(), getDisplayedHeaders(), 113 getDisplayedRecords(), getSelectedRecord()); 114 115 topScreenView.refreshTerminal(); 116 117 lastRefreshTimestamp = EnvironmentEdgeManager.currentTime(); 118 iterations++; 119 return refreshDelay.get(); 120 } 121 122 public void adjustFieldLength() { 123 adjustFieldLength.set(true); 124 refresh(true); 125 } 126 127 private void adjustFieldLengthIfNeeded() { 128 if (adjustFieldLength.get()) { 129 adjustFieldLength.set(false); 130 131 for (Field f : topScreenModel.getFields()) { 132 if (f.isAutoAdjust()) { 133 int maxLength = topScreenModel.getRecords().stream() 134 .map(r -> r.get(f).asString().length()).max(Integer::compareTo).orElse(0); 135 fieldLengthMap.put(f, Math.max(maxLength, f.getHeader().length())); 136 } 137 } 138 } 139 } 140 141 private List<Header> getDisplayedHeaders() { 142 List<Field> displayFields = 143 topScreenModel.getFields().stream().filter(fieldDisplayMap::get).collect(Collectors.toList()); 144 145 if (displayFields.isEmpty()) { 146 horizontalScroll = 0; 147 } else if (horizontalScroll > displayFields.size() - 1) { 148 horizontalScroll = displayFields.size() - 1; 149 } 150 151 List<Header> ret = new ArrayList<>(); 152 153 int length = 0; 154 for (int i = horizontalScroll; i < displayFields.size(); i++) { 155 Field field = displayFields.get(i); 156 int fieldLength = fieldLengthMap.get(field); 157 158 length += fieldLength + 1; 159 if (length > terminalLength) { 160 break; 161 } 162 ret.add(new Header(field, fieldLength)); 163 } 164 165 return ret; 166 } 167 168 private List<Record> getDisplayedRecords() { 169 List<Record> ret = new ArrayList<>(); 170 for (int i = paging.getPageStartPosition(); i < paging.getPageEndPosition(); i++) { 171 ret.add(topScreenModel.getRecords().get(i)); 172 } 173 return ret; 174 } 175 176 private Record getSelectedRecord() { 177 if (topScreenModel.getRecords().isEmpty()) { 178 return null; 179 } 180 return topScreenModel.getRecords().get(paging.getCurrentPosition()); 181 } 182 183 public void arrowUp() { 184 paging.arrowUp(); 185 refresh(true); 186 } 187 188 public void arrowDown() { 189 paging.arrowDown(); 190 refresh(true); 191 } 192 193 public void pageUp() { 194 paging.pageUp(); 195 refresh(true); 196 } 197 198 public void pageDown() { 199 paging.pageDown(); 200 refresh(true); 201 } 202 203 public void arrowLeft() { 204 if (horizontalScroll > 0) { 205 horizontalScroll -= 1; 206 } 207 refresh(true); 208 } 209 210 public void arrowRight() { 211 if (horizontalScroll < getHeaderSize() - 1) { 212 horizontalScroll += 1; 213 } 214 refresh(true); 215 } 216 217 public void home() { 218 if (horizontalScroll > 0) { 219 horizontalScroll = 0; 220 } 221 refresh(true); 222 } 223 224 public void end() { 225 int headerSize = getHeaderSize(); 226 horizontalScroll = headerSize == 0 ? 0 : headerSize - 1; 227 refresh(true); 228 } 229 230 private int getHeaderSize() { 231 return (int) topScreenModel.getFields().stream().filter(fieldDisplayMap::get).count(); 232 } 233 234 public void switchSortOrder() { 235 topScreenModel.switchSortOrder(); 236 refresh(true); 237 } 238 239 public ScreenView transitionToHelpScreen(Screen screen, Terminal terminal) { 240 return new HelpScreenView(screen, terminal, refreshDelay.get(), topScreenView); 241 } 242 243 public ScreenView transitionToModeScreen(Screen screen, Terminal terminal) { 244 return new ModeScreenView(screen, terminal, topScreenModel.getCurrentMode(), this::switchMode, 245 topScreenView); 246 } 247 248 public ScreenView transitionToFieldScreen(Screen screen, Terminal terminal) { 249 return new FieldScreenView(screen, terminal, topScreenModel.getCurrentSortField(), 250 topScreenModel.getFields(), fieldDisplayMap, (sortField, fields, fieldDisplayMap) -> { 251 topScreenModel.setSortFieldAndFields(sortField, fields); 252 this.fieldDisplayMap.clear(); 253 this.fieldDisplayMap.putAll(fieldDisplayMap); 254 }, topScreenView); 255 } 256 257 private void switchMode(Mode nextMode) { 258 topScreenModel.switchMode(nextMode, false, null); 259 reset(); 260 } 261 262 public void drillDown() { 263 Record selectedRecord = getSelectedRecord(); 264 if (selectedRecord == null) { 265 return; 266 } 267 if (topScreenModel.drillDown(selectedRecord)) { 268 reset(); 269 refresh(true); 270 } 271 } 272 273 private void reset() { 274 initFieldDisplayMapAndFieldLengthMap(null); 275 adjustFieldLength.set(true); 276 paging.init(); 277 horizontalScroll = 0; 278 topScreenView.clearTerminal(); 279 } 280 281 private void initFieldDisplayMapAndFieldLengthMap(@Nullable List<Field> initialFields) { 282 fieldDisplayMap.clear(); 283 fieldLengthMap.clear(); 284 for (FieldInfo fieldInfo : topScreenModel.getFieldInfos()) { 285 if (initialFields != null) { 286 fieldDisplayMap.put(fieldInfo.getField(), initialFields.contains(fieldInfo.getField())); 287 } else { 288 fieldDisplayMap.put(fieldInfo.getField(), fieldInfo.isDisplayByDefault()); 289 } 290 fieldLengthMap.put(fieldInfo.getField(), fieldInfo.getDefaultLength()); 291 } 292 } 293 294 public ScreenView goToMessageMode(Screen screen, Terminal terminal, int row, String message) { 295 return new MessageModeScreenView(screen, terminal, row, message, topScreenView); 296 } 297 298 public ScreenView goToInputModeForRefreshDelay(Screen screen, Terminal terminal, int row) { 299 return new InputModeScreenView(screen, terminal, row, 300 "Change refresh delay from " + (double) refreshDelay.get() / 1000 + " to", null, 301 (inputString) -> { 302 if (inputString.isEmpty()) { 303 return topScreenView; 304 } 305 306 double delay; 307 try { 308 delay = Double.parseDouble(inputString); 309 } catch (NumberFormatException e) { 310 return goToMessageMode(screen, terminal, row, "Unacceptable floating point"); 311 } 312 313 refreshDelay.set((long) (delay * 1000)); 314 return topScreenView; 315 }); 316 } 317 318 public ScreenView goToInputModeForFilter(Screen screen, Terminal terminal, int row, 319 boolean ignoreCase) { 320 return new InputModeScreenView(screen, terminal, row, 321 "add filter #" + (topScreenModel.getFilters().size() + 1) + " (" 322 + (ignoreCase ? "ignoring case" : "case sensitive") + ") as: [!]FLD?VAL", 323 topScreenModel.getFilterHistories(), (inputString) -> { 324 if (inputString.isEmpty()) { 325 return topScreenView; 326 } 327 328 if (!topScreenModel.addFilter(inputString, ignoreCase)) { 329 return goToMessageMode(screen, terminal, row, "Unacceptable filter expression"); 330 } 331 332 paging.init(); 333 return topScreenView; 334 }); 335 } 336 337 public void clearFilters() { 338 topScreenModel.clearFilters(); 339 paging.init(); 340 refresh(true); 341 } 342 343 public ScreenView goToFilterDisplayMode(Screen screen, Terminal terminal, int row) { 344 ArrayList<RecordFilter> filters = new ArrayList<>(topScreenModel.getFilters()); 345 filters.addAll(topScreenModel.getPushDownFilters()); 346 return new FilterDisplayModeScreenView(screen, terminal, row, filters, topScreenView); 347 } 348 349 public boolean isIterationFinished() { 350 return iterations >= numberOfIterations; 351 } 352}