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.yetus.audience.InterfaceAudience; 041 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 = System.currentTimeMillis() - 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 = System.currentTimeMillis(); 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()) 135 .max(Integer::compareTo).orElse(0); 136 fieldLengthMap.put(f, Math.max(maxLength, f.getHeader().length())); 137 } 138 } 139 } 140 } 141 142 private List<Header> getDisplayedHeaders() { 143 List<Field> displayFields = 144 topScreenModel.getFields().stream() 145 .filter(fieldDisplayMap::get).collect(Collectors.toList()); 146 147 if (displayFields.isEmpty()) { 148 horizontalScroll = 0; 149 } else if (horizontalScroll > displayFields.size() - 1) { 150 horizontalScroll = displayFields.size() - 1; 151 } 152 153 List<Header> ret = new ArrayList<>(); 154 155 int length = 0; 156 for (int i = horizontalScroll; i < displayFields.size(); i++) { 157 Field field = displayFields.get(i); 158 int fieldLength = fieldLengthMap.get(field); 159 160 length += fieldLength + 1; 161 if (length > terminalLength) { 162 break; 163 } 164 ret.add(new Header(field, fieldLength)); 165 } 166 167 return ret; 168 } 169 170 private List<Record> getDisplayedRecords() { 171 List<Record> ret = new ArrayList<>(); 172 for (int i = paging.getPageStartPosition(); i < paging.getPageEndPosition(); i++) { 173 ret.add(topScreenModel.getRecords().get(i)); 174 } 175 return ret; 176 } 177 178 private Record getSelectedRecord() { 179 if (topScreenModel.getRecords().isEmpty()) { 180 return null; 181 } 182 return topScreenModel.getRecords().get(paging.getCurrentPosition()); 183 } 184 185 public void arrowUp() { 186 paging.arrowUp(); 187 refresh(true); 188 } 189 190 public void arrowDown() { 191 paging.arrowDown(); 192 refresh(true); 193 } 194 195 public void pageUp() { 196 paging.pageUp(); 197 refresh(true); 198 } 199 200 public void pageDown() { 201 paging.pageDown(); 202 refresh(true); 203 } 204 205 public void arrowLeft() { 206 if (horizontalScroll > 0) { 207 horizontalScroll -= 1; 208 } 209 refresh(true); 210 } 211 212 public void arrowRight() { 213 if (horizontalScroll < getHeaderSize() - 1) { 214 horizontalScroll += 1; 215 } 216 refresh(true); 217 } 218 219 public void home() { 220 if (horizontalScroll > 0) { 221 horizontalScroll = 0; 222 } 223 refresh(true); 224 } 225 226 public void end() { 227 int headerSize = getHeaderSize(); 228 horizontalScroll = headerSize == 0 ? 0 : headerSize - 1; 229 refresh(true); 230 } 231 232 private int getHeaderSize() { 233 return (int) topScreenModel.getFields().stream() 234 .filter(fieldDisplayMap::get).count(); 235 } 236 237 public void switchSortOrder() { 238 topScreenModel.switchSortOrder(); 239 refresh(true); 240 } 241 242 public ScreenView transitionToHelpScreen(Screen screen, Terminal terminal) { 243 return new HelpScreenView(screen, terminal, refreshDelay.get(), topScreenView); 244 } 245 246 public ScreenView transitionToModeScreen(Screen screen, Terminal terminal) { 247 return new ModeScreenView(screen, terminal, topScreenModel.getCurrentMode(), this::switchMode, 248 topScreenView); 249 } 250 251 public ScreenView transitionToFieldScreen(Screen screen, Terminal terminal) { 252 return new FieldScreenView(screen, terminal, 253 topScreenModel.getCurrentSortField(), topScreenModel.getFields(), 254 fieldDisplayMap, 255 (sortField, fields, fieldDisplayMap) -> { 256 topScreenModel.setSortFieldAndFields(sortField, fields); 257 this.fieldDisplayMap.clear(); 258 this.fieldDisplayMap.putAll(fieldDisplayMap); 259 }, topScreenView); 260 } 261 262 private void switchMode(Mode nextMode) { 263 topScreenModel.switchMode(nextMode, false, null); 264 reset(); 265 } 266 267 public void drillDown() { 268 Record selectedRecord = getSelectedRecord(); 269 if (selectedRecord == null) { 270 return; 271 } 272 if (topScreenModel.drillDown(selectedRecord)) { 273 reset(); 274 refresh(true); 275 } 276 } 277 278 private void reset() { 279 initFieldDisplayMapAndFieldLengthMap(null); 280 adjustFieldLength.set(true); 281 paging.init(); 282 horizontalScroll = 0; 283 topScreenView.clearTerminal(); 284 } 285 286 private void initFieldDisplayMapAndFieldLengthMap(@Nullable List<Field> initialFields) { 287 fieldDisplayMap.clear(); 288 fieldLengthMap.clear(); 289 for (FieldInfo fieldInfo : topScreenModel.getFieldInfos()) { 290 if (initialFields != null) { 291 fieldDisplayMap.put(fieldInfo.getField(), initialFields.contains(fieldInfo.getField())); 292 } else { 293 fieldDisplayMap.put(fieldInfo.getField(), fieldInfo.isDisplayByDefault()); 294 } 295 fieldLengthMap.put(fieldInfo.getField(), fieldInfo.getDefaultLength()); 296 } 297 } 298 299 public ScreenView goToMessageMode(Screen screen, Terminal terminal, int row, String message) { 300 return new MessageModeScreenView(screen, terminal, row, message, topScreenView); 301 } 302 303 public ScreenView goToInputModeForRefreshDelay(Screen screen, Terminal terminal, int row) { 304 return new InputModeScreenView(screen, terminal, row, 305 "Change refresh delay from " + (double) refreshDelay.get() / 1000 + " to", null, 306 (inputString) -> { 307 if (inputString.isEmpty()) { 308 return topScreenView; 309 } 310 311 double delay; 312 try { 313 delay = Double.parseDouble(inputString); 314 } catch (NumberFormatException e) { 315 return goToMessageMode(screen, terminal, row, "Unacceptable floating point"); 316 } 317 318 refreshDelay.set((long) (delay * 1000)); 319 return topScreenView; 320 }); 321 } 322 323 public ScreenView goToInputModeForFilter(Screen screen, Terminal terminal, int row, 324 boolean ignoreCase) { 325 return new InputModeScreenView(screen, terminal, row, 326 "add filter #" + (topScreenModel.getFilters().size() + 1) + 327 " (" + (ignoreCase ? "ignoring case" : "case sensitive") + ") as: [!]FLD?VAL", 328 topScreenModel.getFilterHistories(), 329 (inputString) -> { 330 if (inputString.isEmpty()) { 331 return topScreenView; 332 } 333 334 if (!topScreenModel.addFilter(inputString, ignoreCase)) { 335 return goToMessageMode(screen, terminal, row, "Unacceptable filter expression"); 336 } 337 338 paging.init(); 339 return topScreenView; 340 }); 341 } 342 343 public void clearFilters() { 344 topScreenModel.clearFilters(); 345 paging.init(); 346 refresh(true); 347 } 348 349 public ScreenView goToFilterDisplayMode(Screen screen, Terminal terminal, int row) { 350 ArrayList<RecordFilter> filters = new ArrayList<>(topScreenModel.getFilters()); 351 filters.addAll(topScreenModel.getPushDownFilters()); 352 return new FilterDisplayModeScreenView(screen, terminal, row, filters, topScreenView); 353 } 354 355 public boolean isIterationFinished() { 356 return iterations >= numberOfIterations; 357 } 358}