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