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