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.List;
023import java.util.stream.Collectors;
024import org.apache.hadoop.hbase.client.Admin;
025import org.apache.hadoop.hbase.hbtop.Record;
026import org.apache.hadoop.hbase.hbtop.RecordFilter;
027import org.apache.hadoop.hbase.hbtop.field.Field;
028import org.apache.hadoop.hbase.hbtop.mode.Mode;
029import org.apache.hadoop.hbase.hbtop.screen.AbstractScreenView;
030import org.apache.hadoop.hbase.hbtop.screen.Screen;
031import org.apache.hadoop.hbase.hbtop.screen.ScreenView;
032import org.apache.hadoop.hbase.hbtop.terminal.KeyPress;
033import org.apache.hadoop.hbase.hbtop.terminal.Terminal;
034import org.apache.hadoop.hbase.hbtop.terminal.TerminalPrinter;
035import org.apache.hadoop.hbase.hbtop.terminal.TerminalSize;
036import org.apache.yetus.audience.InterfaceAudience;
037
038/**
039 * The screen that provides a dynamic real-time view for the HBase metrics. This shows the metric
040 * {@link Summary} and the metric {@link Record}s. The summary and the metrics are updated
041 * periodically (3 seconds by default).
042 */
043@InterfaceAudience.Private
044public class TopScreenView extends AbstractScreenView {
045
046  private static final int SUMMARY_START_ROW = 0;
047  private static final int SUMMARY_ROW_NUM = 7;
048  private static final int MESSAGE_ROW = 7;
049  private static final int RECORD_HEADER_ROW = 8;
050  private static final int RECORD_START_ROW = 9;
051
052  private final TopScreenPresenter topScreenPresenter;
053  private Integer pageSize;
054
055  public TopScreenView(Screen screen, Terminal terminal, long initialRefreshDelay, Admin admin,
056    Mode initialMode, @Nullable List<Field> initialFields, @Nullable Field initialSortField,
057    @Nullable Boolean initialAscendingSort, @Nullable List<RecordFilter> initialFilters,
058    long numberOfIterations) {
059    super(screen, terminal);
060    this.topScreenPresenter =
061      new TopScreenPresenter(
062        this, initialRefreshDelay, new TopScreenModel(admin, initialMode, initialFields,
063          initialSortField, initialAscendingSort, initialFilters),
064        initialFields, numberOfIterations);
065  }
066
067  @Override
068  public void init() {
069    topScreenPresenter.init();
070    long delay = topScreenPresenter.refresh(true);
071    setTimer(delay);
072  }
073
074  @Nullable
075  @Override
076  public ScreenView handleTimer() {
077    long delay = topScreenPresenter.refresh(false);
078    setTimer(delay);
079    return topScreenPresenter.isIterationFinished() ? null : this;
080  }
081
082  @Nullable
083  @Override
084  public ScreenView handleKeyPress(KeyPress keyPress) {
085    switch (keyPress.getType()) {
086      case Enter:
087        topScreenPresenter.refresh(true);
088        return topScreenPresenter.isIterationFinished() ? null : this;
089
090      case ArrowUp:
091        topScreenPresenter.arrowUp();
092        return topScreenPresenter.isIterationFinished() ? null : this;
093
094      case ArrowDown:
095        topScreenPresenter.arrowDown();
096        return topScreenPresenter.isIterationFinished() ? null : this;
097
098      case ArrowLeft:
099        topScreenPresenter.arrowLeft();
100        return topScreenPresenter.isIterationFinished() ? null : this;
101
102      case ArrowRight:
103        topScreenPresenter.arrowRight();
104        return topScreenPresenter.isIterationFinished() ? null : this;
105
106      case PageUp:
107        topScreenPresenter.pageUp();
108        return topScreenPresenter.isIterationFinished() ? null : this;
109
110      case PageDown:
111        topScreenPresenter.pageDown();
112        return topScreenPresenter.isIterationFinished() ? null : this;
113
114      case Home:
115        topScreenPresenter.home();
116        return topScreenPresenter.isIterationFinished() ? null : this;
117
118      case End:
119        topScreenPresenter.end();
120        return topScreenPresenter.isIterationFinished() ? null : this;
121
122      case Escape:
123        return null;
124
125      default:
126        // Do nothing
127        break;
128    }
129
130    if (keyPress.getType() != KeyPress.Type.Character) {
131      return unknownCommandMessage();
132    }
133
134    assert keyPress.getCharacter() != null;
135    switch (keyPress.getCharacter()) {
136      case 'R':
137        topScreenPresenter.switchSortOrder();
138        break;
139
140      case 'f':
141        cancelTimer();
142        return topScreenPresenter.transitionToFieldScreen(getScreen(), getTerminal());
143
144      case 'm':
145        cancelTimer();
146        return topScreenPresenter.transitionToModeScreen(getScreen(), getTerminal());
147
148      case 'h':
149        cancelTimer();
150        return topScreenPresenter.transitionToHelpScreen(getScreen(), getTerminal());
151
152      case 'd':
153        cancelTimer();
154        return topScreenPresenter.goToInputModeForRefreshDelay(getScreen(), getTerminal(),
155          MESSAGE_ROW);
156
157      case 'o':
158        cancelTimer();
159        if (keyPress.isCtrl()) {
160          return topScreenPresenter.goToFilterDisplayMode(getScreen(), getTerminal(), MESSAGE_ROW);
161        }
162        return topScreenPresenter.goToInputModeForFilter(getScreen(), getTerminal(), MESSAGE_ROW,
163          true);
164
165      case 'O':
166        cancelTimer();
167        return topScreenPresenter.goToInputModeForFilter(getScreen(), getTerminal(), MESSAGE_ROW,
168          false);
169
170      case '=':
171        topScreenPresenter.clearFilters();
172        break;
173
174      case 'X':
175        topScreenPresenter.adjustFieldLength();
176        break;
177
178      case 'i':
179        topScreenPresenter.drillDown();
180        break;
181
182      case 'q':
183        return null;
184
185      default:
186        return unknownCommandMessage();
187    }
188    return this;
189  }
190
191  @Nullable
192  @Override
193  public TerminalSize getTerminalSize() {
194    TerminalSize terminalSize = super.getTerminalSize();
195    if (terminalSize == null) {
196      return null;
197    }
198    updatePageSize(terminalSize);
199    return terminalSize;
200  }
201
202  @Nullable
203  @Override
204  public TerminalSize doResizeIfNecessary() {
205    TerminalSize terminalSize = super.doResizeIfNecessary();
206    if (terminalSize == null) {
207      return null;
208    }
209    updatePageSize(terminalSize);
210    return terminalSize;
211  }
212
213  private void updatePageSize(TerminalSize terminalSize) {
214    pageSize = terminalSize.getRows() - SUMMARY_ROW_NUM - 2;
215    if (pageSize < 0) {
216      pageSize = 0;
217    }
218  }
219
220  @Nullable
221  public Integer getPageSize() {
222    return pageSize;
223  }
224
225  public void showTopScreen(Summary summary, List<Header> headers, List<Record> records,
226    Record selectedRecord) {
227    showSummary(summary);
228    clearMessage();
229    showHeaders(headers);
230    showRecords(headers, records, selectedRecord);
231  }
232
233  private void showSummary(Summary summary) {
234    TerminalPrinter printer = getTerminalPrinter(SUMMARY_START_ROW);
235    printer.print(String.format("HBase hbtop - %s", summary.getCurrentTime())).endOfLine();
236    printer.print(String.format("Version: %s", summary.getVersion())).endOfLine();
237    printer.print(String.format("Cluster ID: %s", summary.getClusterId())).endOfLine();
238    printer.print("RegionServer(s): ").startBold().print(Integer.toString(summary.getServers()))
239      .stopBold().print(" total, ").startBold().print(Integer.toString(summary.getLiveServers()))
240      .stopBold().print(" live, ").startBold().print(Integer.toString(summary.getDeadServers()))
241      .stopBold().print(" dead").endOfLine();
242    printer.print("RegionCount: ").startBold().print(Integer.toString(summary.getRegionCount()))
243      .stopBold().print(" total, ").startBold().print(Integer.toString(summary.getRitCount()))
244      .stopBold().print(" rit").endOfLine();
245    printer.print("Average Cluster Load: ").startBold()
246      .print(String.format("%.2f", summary.getAverageLoad())).stopBold().endOfLine();
247    printer.print("Aggregate Request/s: ").startBold()
248      .print(Long.toString(summary.getAggregateRequestPerSecond())).stopBold().endOfLine();
249  }
250
251  private void showRecords(List<Header> headers, List<Record> records, Record selectedRecord) {
252    TerminalPrinter printer = getTerminalPrinter(RECORD_START_ROW);
253    int size;
254    if (pageSize != null) {
255      size = pageSize;
256    } else {
257      size = records.size();
258    }
259    List<String> buf = new ArrayList<>(headers.size());
260    for (int i = 0; i < size; i++) {
261      if (i < records.size()) {
262        Record record = records.get(i);
263        buf.clear();
264        for (Header header : headers) {
265          String value = "";
266          if (record.containsKey(header.getField())) {
267            value = record.get(header.getField()).asString();
268          }
269
270          buf.add(limitLineLength(String.format(header.format(), value), header.getLength()));
271        }
272
273        String recordString = String.join(" ", buf);
274        if (!recordString.isEmpty()) {
275          recordString += " ";
276        }
277
278        if (record == selectedRecord) {
279          printer.startHighlight().print(recordString).stopHighlight().endOfLine();
280        } else {
281          printer.print(recordString).endOfLine();
282        }
283      } else {
284        printer.endOfLine();
285      }
286    }
287  }
288
289  private void showHeaders(List<Header> headers) {
290    String header = headers.stream().map(h -> String.format(h.format(), h.getField().getHeader()))
291      .collect(Collectors.joining(" "));
292
293    if (!header.isEmpty()) {
294      header += " ";
295    }
296
297    getTerminalPrinter(RECORD_HEADER_ROW).startHighlight().print(header).stopHighlight()
298      .endOfLine();
299  }
300
301  private String limitLineLength(String line, int length) {
302    if (line.length() > length) {
303      return line.substring(0, length - 1) + "+";
304    }
305    return line;
306  }
307
308  private void clearMessage() {
309    getTerminalPrinter(MESSAGE_ROW).print("").endOfLine();
310  }
311
312  private ScreenView unknownCommandMessage() {
313    cancelTimer();
314    return topScreenPresenter.goToMessageMode(getScreen(), getTerminal(), MESSAGE_ROW,
315      "Unknown command - try 'h' for help");
316  }
317}