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}