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}