View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.hbtop.screen.top;
19  
20  import edu.umd.cs.findbugs.annotations.Nullable;
21  import java.util.ArrayList;
22  import java.util.EnumMap;
23  import java.util.List;
24  import java.util.Objects;
25  import java.util.concurrent.atomic.AtomicBoolean;
26  import java.util.concurrent.atomic.AtomicLong;
27  import org.apache.hadoop.hbase.classification.InterfaceAudience;
28  import org.apache.hadoop.hbase.hbtop.Record;
29  import org.apache.hadoop.hbase.hbtop.field.Field;
30  import org.apache.hadoop.hbase.hbtop.field.FieldInfo;
31  import org.apache.hadoop.hbase.hbtop.mode.Mode;
32  import org.apache.hadoop.hbase.hbtop.screen.Screen;
33  import org.apache.hadoop.hbase.hbtop.screen.ScreenView;
34  import org.apache.hadoop.hbase.hbtop.screen.field.FieldScreenPresenter;
35  import org.apache.hadoop.hbase.hbtop.screen.field.FieldScreenView;
36  import org.apache.hadoop.hbase.hbtop.screen.help.HelpScreenView;
37  import org.apache.hadoop.hbase.hbtop.screen.mode.ModeScreenPresenter;
38  import org.apache.hadoop.hbase.hbtop.screen.mode.ModeScreenView;
39  import org.apache.hadoop.hbase.hbtop.terminal.Terminal;
40  import org.apache.hadoop.hbase.hbtop.terminal.TerminalSize;
41  
42  /**
43   * The presentation logic for the top screen.
44   */
45  @InterfaceAudience.Private
46  public class TopScreenPresenter {
47    private final TopScreenView topScreenView;
48    private final AtomicLong refreshDelay;
49    private long lastRefreshTimestamp;
50  
51    private final AtomicBoolean adjustFieldLength = new AtomicBoolean(true);
52    private final TopScreenModel topScreenModel;
53    private int terminalLength;
54    private int horizontalScroll;
55    private final Paging paging = new Paging();
56  
57    private final EnumMap<Field, Boolean> fieldDisplayMap = new EnumMap<>(Field.class);
58    private final EnumMap<Field, Integer> fieldLengthMap = new EnumMap<>(Field.class);
59  
60    private final long numberOfIterations;
61    private long iterations;
62  
63    public TopScreenPresenter(TopScreenView topScreenView, long initialRefreshDelay,
64      TopScreenModel topScreenModel, @Nullable List<Field> initialFields, long numberOfIterations) {
65      this.topScreenView = Objects.requireNonNull(topScreenView);
66      this.refreshDelay = new AtomicLong(initialRefreshDelay);
67      this.topScreenModel = Objects.requireNonNull(topScreenModel);
68      this.numberOfIterations = numberOfIterations;
69  
70      initFieldDisplayMapAndFieldLengthMap(initialFields);
71    }
72  
73    public void init() {
74      updateTerminalLengthAndPageSize(topScreenView.getTerminalSize(), topScreenView.getPageSize());
75      topScreenView.hideCursor();
76    }
77  
78    private void updateTerminalLengthAndPageSize(@Nullable TerminalSize terminalSize,
79      @Nullable Integer pageSize) {
80      if (terminalSize != null) {
81        terminalLength = terminalSize.getColumns();
82      } else {
83        terminalLength = Integer.MAX_VALUE;
84      }
85      if (pageSize != null) {
86        paging.updatePageSize(pageSize);
87      } else {
88        paging.updatePageSize(Integer.MAX_VALUE);
89      }
90    }
91  
92    public long refresh(boolean force) {
93      if (!force) {
94        long delay = System.currentTimeMillis() - lastRefreshTimestamp;
95        if (delay < refreshDelay.get()) {
96          return refreshDelay.get() - delay;
97        }
98      }
99  
100     TerminalSize newTerminalSize = topScreenView.doResizeIfNecessary();
101     if (newTerminalSize != null) {
102       updateTerminalLengthAndPageSize(newTerminalSize, topScreenView.getPageSize());
103       topScreenView.clearTerminal();
104     }
105 
106     topScreenModel.refreshMetricsData();
107     paging.updateRecordsSize(topScreenModel.getRecords().size());
108 
109     adjustFieldLengthIfNeeded();
110 
111     topScreenView.showTopScreen(topScreenModel.getSummary(), getDisplayedHeaders(),
112       getDisplayedRecords(), getSelectedRecord());
113 
114     topScreenView.refreshTerminal();
115 
116     lastRefreshTimestamp = System.currentTimeMillis();
117     iterations++;
118     return refreshDelay.get();
119   }
120 
121   public void adjustFieldLength() {
122     adjustFieldLength.set(true);
123     refresh(true);
124   }
125 
126   private void adjustFieldLengthIfNeeded() {
127     if (adjustFieldLength.get()) {
128       adjustFieldLength.set(false);
129 
130       for (Field f : topScreenModel.getFields()) {
131         if (f.isAutoAdjust()) {
132           int maxLength = 0;
133           for (Record record : topScreenModel.getRecords()) {
134             int length = record.get(f).asString().length();
135             maxLength = Math.max(length, maxLength);
136           }
137           fieldLengthMap.put(f, Math.max(maxLength, f.getHeader().length()));
138         }
139       }
140     }
141   }
142 
143   private List<Header> getDisplayedHeaders() {
144     List<Field> displayFields = new ArrayList<>();
145     for (Field field : topScreenModel.getFields()) {
146       if (fieldDisplayMap.get(field)) {
147         displayFields.add(field);
148       }
149     }
150 
151     if (displayFields.isEmpty()) {
152       horizontalScroll = 0;
153     } else if (horizontalScroll > displayFields.size() - 1) {
154       horizontalScroll = displayFields.size() - 1;
155     }
156 
157     List<Header> ret = new ArrayList<>();
158 
159     int length = 0;
160     for (int i = horizontalScroll; i < displayFields.size(); i++) {
161       Field field = displayFields.get(i);
162       int fieldLength = fieldLengthMap.get(field);
163 
164       length += fieldLength + 1;
165       if (length > terminalLength) {
166         break;
167       }
168       ret.add(new Header(field, fieldLength));
169     }
170 
171     return ret;
172   }
173 
174   private List<Record> getDisplayedRecords() {
175     List<Record> ret = new ArrayList<>();
176     for (int i = paging.getPageStartPosition(); i < paging.getPageEndPosition(); i++) {
177       ret.add(topScreenModel.getRecords().get(i));
178     }
179     return ret;
180   }
181 
182   private Record getSelectedRecord() {
183     if (topScreenModel.getRecords().isEmpty()) {
184       return null;
185     }
186     return topScreenModel.getRecords().get(paging.getCurrentPosition());
187   }
188 
189   public void arrowUp() {
190     paging.arrowUp();
191     refresh(true);
192   }
193 
194   public void arrowDown() {
195     paging.arrowDown();
196     refresh(true);
197   }
198 
199   public void pageUp() {
200     paging.pageUp();
201     refresh(true);
202   }
203 
204   public void pageDown() {
205     paging.pageDown();
206     refresh(true);
207   }
208 
209   public void arrowLeft() {
210     if (horizontalScroll > 0) {
211       horizontalScroll -= 1;
212     }
213     refresh(true);
214   }
215 
216   public void arrowRight() {
217     if (horizontalScroll < getHeaderSize() - 1) {
218       horizontalScroll += 1;
219     }
220     refresh(true);
221   }
222 
223   public void home() {
224     if (horizontalScroll > 0) {
225       horizontalScroll = 0;
226     }
227     refresh(true);
228   }
229 
230   public void end() {
231     int headerSize = getHeaderSize();
232     horizontalScroll = headerSize == 0 ? 0 : headerSize - 1;
233     refresh(true);
234   }
235 
236   private int getHeaderSize() {
237     int size = 0;
238     for (Field field : topScreenModel.getFields()) {
239       if (fieldDisplayMap.get(field)) {
240         size++;
241       }
242     }
243     return size;
244   }
245 
246   public void switchSortOrder() {
247     topScreenModel.switchSortOrder();
248     refresh(true);
249   }
250 
251   public ScreenView transitionToHelpScreen(Screen screen, Terminal terminal) {
252     return new HelpScreenView(screen, terminal, refreshDelay.get(), topScreenView);
253   }
254 
255   public ScreenView transitionToModeScreen(Screen screen, Terminal terminal) {
256     return new ModeScreenView(screen, terminal, topScreenModel.getCurrentMode(),
257       new ModeScreenPresenter.ResultListener() {
258         @Override
259         public void accept(Mode mode) {
260           switchMode(mode);
261         }
262       }, topScreenView);
263   }
264 
265   public ScreenView transitionToFieldScreen(Screen screen, Terminal terminal) {
266     return new FieldScreenView(screen, terminal,
267       topScreenModel.getCurrentSortField(), topScreenModel.getFields(),
268       fieldDisplayMap,
269       new FieldScreenPresenter.ResultListener() {
270         @Override
271         public void accept(Field sortField, List<Field> fields,
272           EnumMap<Field, Boolean> fieldDisplayMap) {
273           topScreenModel.setSortFieldAndFields(sortField, fields);
274           TopScreenPresenter.this.fieldDisplayMap.clear();
275           TopScreenPresenter.this.fieldDisplayMap.putAll(fieldDisplayMap);
276         }
277       }, topScreenView);
278   }
279 
280   private void switchMode(Mode nextMode) {
281     topScreenModel.switchMode(nextMode, false, null);
282     reset();
283   }
284 
285   public void drillDown() {
286     Record selectedRecord = getSelectedRecord();
287     if (selectedRecord == null) {
288       return;
289     }
290     if (topScreenModel.drillDown(selectedRecord)) {
291       reset();
292       refresh(true);
293     }
294   }
295 
296   private void reset() {
297     initFieldDisplayMapAndFieldLengthMap(null);
298     adjustFieldLength.set(true);
299     paging.init();
300     horizontalScroll = 0;
301     topScreenView.clearTerminal();
302   }
303 
304   private void initFieldDisplayMapAndFieldLengthMap(@Nullable List<Field> initialFields) {
305     fieldDisplayMap.clear();
306     fieldLengthMap.clear();
307     for (FieldInfo fieldInfo : topScreenModel.getFieldInfos()) {
308       if (initialFields != null) {
309         fieldDisplayMap.put(fieldInfo.getField(), initialFields.contains(fieldInfo.getField()));
310       } else {
311         fieldDisplayMap.put(fieldInfo.getField(), fieldInfo.isDisplayByDefault());
312       }
313       fieldLengthMap.put(fieldInfo.getField(), fieldInfo.getDefaultLength());
314     }
315   }
316 
317   public ScreenView goToMessageMode(Screen screen, Terminal terminal, int row, String message) {
318     return new MessageModeScreenView(screen, terminal, row, message, topScreenView);
319   }
320 
321   public ScreenView goToInputModeForRefreshDelay(final Screen screen, final Terminal terminal,
322     final int row) {
323     return new InputModeScreenView(screen, terminal, row,
324       "Change refresh delay from " + (double) refreshDelay.get() / 1000 + " to", null,
325       new InputModeScreenPresenter.ResultListener() {
326         @Override
327         public ScreenView apply(String inputString) {
328           if (inputString.isEmpty()) {
329             return topScreenView;
330           }
331 
332           double delay;
333           try {
334             delay = Double.parseDouble(inputString);
335           } catch (NumberFormatException e) {
336             return goToMessageMode(screen, terminal, row, "Unacceptable floating point");
337           }
338 
339           refreshDelay.set((long) (delay * 1000));
340           return topScreenView;
341         }
342       });
343   }
344 
345   public ScreenView goToInputModeForFilter(final Screen screen, final Terminal terminal,
346     final int row, final boolean ignoreCase) {
347     return new InputModeScreenView(screen, terminal, row,
348       "add filter #" + (topScreenModel.getFilters().size() + 1) +
349         " (" + (ignoreCase ? "ignoring case" : "case sensitive") + ") as: [!]FLD?VAL",
350       topScreenModel.getFilterHistories(),
351       new InputModeScreenPresenter.ResultListener() {
352         @Override
353         public ScreenView apply(String inputString) {
354           if (inputString.isEmpty()) {
355             return topScreenView;
356           }
357 
358           if (!topScreenModel.addFilter(inputString, ignoreCase)) {
359             return goToMessageMode(screen, terminal, row, "Unacceptable filter expression");
360           }
361 
362           paging.init();
363           return topScreenView;
364         }
365       });
366   }
367 
368   public void clearFilters() {
369     topScreenModel.clearFilters();
370     paging.init();
371     refresh(true);
372   }
373 
374   public ScreenView goToFilterDisplayMode(Screen screen, Terminal terminal, int row) {
375     return new FilterDisplayModeScreenView(screen, terminal, row, topScreenModel.getFilters(),
376       topScreenView);
377   }
378 
379   public boolean isIterationFinished() {
380     return iterations >= numberOfIterations;
381   }
382 }