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/**
040 * The screen that provides a dynamic real-time view for the HBase metrics.
041 *
042 * This shows the metric {@link Summary} and the metric {@link Record}s. The summary and the
043 * metrics are updated periodically (3 seconds by default).
044 */
045@InterfaceAudience.Private
046public class TopScreenView extends AbstractScreenView {
047
048  private static final int SUMMARY_START_ROW = 0;
049  private static final int SUMMARY_ROW_NUM = 7;
050  private static final int MESSAGE_ROW = 7;
051  private static final int RECORD_HEADER_ROW = 8;
052  private static final int RECORD_START_ROW = 9;
053
054  private final TopScreenPresenter topScreenPresenter;
055  private Integer pageSize;
056
057  public TopScreenView(Screen screen, Terminal terminal, long initialRefreshDelay, Admin admin,
058    Mode initialMode, @Nullable List<Field> initialFields, @Nullable Field initialSortField,
059    @Nullable Boolean initialAscendingSort, @Nullable List<RecordFilter> initialFilters,
060    long numberOfIterations) {
061    super(screen, terminal);
062    this.topScreenPresenter = new TopScreenPresenter(this, initialRefreshDelay,
063      new TopScreenModel(admin, initialMode, initialFields, initialSortField,
064        initialAscendingSort, initialFilters), 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): ")
239      .startBold().print(Integer.toString(summary.getServers())).stopBold()
240      .print(" total, ")
241      .startBold().print(Integer.toString(summary.getLiveServers())).stopBold()
242      .print(" live, ")
243      .startBold().print(Integer.toString(summary.getDeadServers())).stopBold()
244      .print(" dead").endOfLine();
245    printer.print("RegionCount: ")
246      .startBold().print(Integer.toString(summary.getRegionCount())).stopBold()
247      .print(" total, ")
248      .startBold().print(Integer.toString(summary.getRitCount())).stopBold()
249      .print(" rit").endOfLine();
250    printer.print("Average Cluster Load: ")
251      .startBold().print(String.format("%.2f", summary.getAverageLoad())).stopBold().endOfLine();
252    printer.print("Aggregate Request/s: ")
253      .startBold().print(Long.toString(summary.getAggregateRequestPerSecond())).stopBold()
254      .endOfLine();
255  }
256
257  private void showRecords(List<Header> headers, List<Record> records, Record selectedRecord) {
258    TerminalPrinter printer = getTerminalPrinter(RECORD_START_ROW);
259    int size;
260    if (pageSize != null) {
261      size = pageSize;
262    } else {
263      size = records.size();
264    }
265    List<String> buf = new ArrayList<>(headers.size());
266    for (int i = 0; i < size; i++) {
267      if(i < records.size()) {
268        Record record = records.get(i);
269        buf.clear();
270        for (Header header : headers) {
271          String value = "";
272          if (record.containsKey(header.getField())) {
273            value = record.get(header.getField()).asString();
274          }
275
276          buf.add(limitLineLength(String.format(header.format(), value), header.getLength()));
277        }
278
279        String recordString = String.join(" ", buf);
280        if (!recordString.isEmpty()) {
281          recordString += " ";
282        }
283
284        if (record == selectedRecord) {
285          printer.startHighlight().print(recordString).stopHighlight().endOfLine();
286        } else {
287          printer.print(recordString).endOfLine();
288        }
289      } else {
290        printer.endOfLine();
291      }
292    }
293  }
294
295  private void showHeaders(List<Header> headers) {
296    String header = headers.stream()
297      .map(h -> String.format(h.format(), h.getField().getHeader()))
298      .collect(Collectors.joining(" "));
299
300    if (!header.isEmpty()) {
301      header += " ";
302    }
303
304    getTerminalPrinter(RECORD_HEADER_ROW).startHighlight().print(header).stopHighlight()
305      .endOfLine();
306  }
307
308  private String limitLineLength(String line, int length) {
309    if (line.length() > length) {
310      return line.substring(0, length - 1) + "+";
311    }
312    return line;
313  }
314
315  private void clearMessage() {
316    getTerminalPrinter(MESSAGE_ROW).print("").endOfLine();
317  }
318
319  private ScreenView unknownCommandMessage() {
320    cancelTimer();
321    return topScreenPresenter.goToMessageMode(getScreen(), getTerminal(), MESSAGE_ROW,
322      "Unknown command - try 'h' for help");
323  }
324}