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 static org.apache.commons.lang3.time.DateFormatUtils.ISO_8601_EXTENDED_TIME_FORMAT;
021
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.Collections;
025import java.util.List;
026import java.util.Objects;
027import java.util.stream.Collectors;
028
029import org.apache.hadoop.hbase.ClusterMetrics;
030import org.apache.hadoop.hbase.client.Admin;
031import org.apache.hadoop.hbase.hbtop.Record;
032import org.apache.hadoop.hbase.hbtop.RecordFilter;
033import org.apache.hadoop.hbase.hbtop.field.Field;
034import org.apache.hadoop.hbase.hbtop.field.FieldInfo;
035import org.apache.hadoop.hbase.hbtop.field.FieldValue;
036import org.apache.hadoop.hbase.hbtop.mode.DrillDownInfo;
037import org.apache.hadoop.hbase.hbtop.mode.Mode;
038import org.apache.yetus.audience.InterfaceAudience;
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041
042
043/**
044 * The data and business logic for the top screen.
045 */
046@InterfaceAudience.Private
047public class TopScreenModel {
048
049  private static final Logger LOGGER = LoggerFactory.getLogger(TopScreenModel.class);
050
051  private final Admin admin;
052
053  private Mode currentMode;
054  private Field currentSortField;
055  private List<FieldInfo> fieldInfos;
056  private List<Field> fields;
057
058  private Summary summary;
059  private List<Record> records;
060
061  private final List<RecordFilter> filters = new ArrayList<>();
062  private final List<RecordFilter> pushDownFilters = new ArrayList<>();
063  private final List<String> filterHistories = new ArrayList<>();
064
065  private boolean ascendingSort;
066
067  public TopScreenModel(Admin admin, Mode initialMode) {
068    this.admin = Objects.requireNonNull(admin);
069    switchMode(Objects.requireNonNull(initialMode), null, false);
070  }
071
072  public void switchMode(Mode nextMode, List<RecordFilter> initialFilters,
073    boolean keepSortFieldAndSortOrderIfPossible) {
074
075    currentMode = nextMode;
076    fieldInfos = Collections.unmodifiableList(new ArrayList<>(currentMode.getFieldInfos()));
077    fields = Collections.unmodifiableList(currentMode.getFieldInfos().stream()
078      .map(FieldInfo::getField).collect(Collectors.toList()));
079
080    if (keepSortFieldAndSortOrderIfPossible) {
081      boolean match = fields.stream().anyMatch(f -> f == currentSortField);
082      if (!match) {
083        currentSortField = nextMode.getDefaultSortField();
084        ascendingSort = false;
085      }
086    } else {
087      currentSortField = nextMode.getDefaultSortField();
088      ascendingSort = false;
089    }
090
091    clearFilters();
092    if (initialFilters != null) {
093      filters.addAll(initialFilters);
094    }
095    decomposePushDownFilter();
096  }
097
098  public void setSortFieldAndFields(Field sortField, List<Field> fields) {
099    this.currentSortField = sortField;
100    this.fields = Collections.unmodifiableList(new ArrayList<>(fields));
101  }
102
103  /*
104   * HBTop only calls this from a single thread, and if that ever changes, this needs
105   * synchronization
106   */
107  public void refreshMetricsData() {
108    ClusterMetrics clusterMetrics;
109    try {
110      clusterMetrics = admin.getClusterMetrics();
111    } catch (Exception e) {
112      LOGGER.error("Unable to get cluster metrics", e);
113      return;
114    }
115
116    refreshSummary(clusterMetrics);
117    refreshRecords(clusterMetrics);
118  }
119
120  private void refreshSummary(ClusterMetrics clusterMetrics) {
121    String currentTime = ISO_8601_EXTENDED_TIME_FORMAT
122      .format(System.currentTimeMillis());
123    String version = clusterMetrics.getHBaseVersion();
124    String clusterId = clusterMetrics.getClusterId();
125    int liveServers = clusterMetrics.getLiveServerMetrics().size();
126    int deadServers = clusterMetrics.getDeadServerNames().size();
127    int regionCount = clusterMetrics.getRegionCount();
128    int ritCount = clusterMetrics.getRegionStatesInTransition().size();
129    double averageLoad = clusterMetrics.getAverageLoad();
130    long aggregateRequestPerSecond = clusterMetrics.getLiveServerMetrics().entrySet().stream()
131      .mapToLong(e -> e.getValue().getRequestCountPerSecond()).sum();
132
133    summary = new Summary(currentTime, version, clusterId, liveServers + deadServers,
134      liveServers, deadServers, regionCount, ritCount, averageLoad, aggregateRequestPerSecond);
135  }
136
137  private void refreshRecords(ClusterMetrics clusterMetrics) {
138    List<Record> records = currentMode.getRecords(clusterMetrics, pushDownFilters);
139
140    // Filter and sort
141    records = records.stream()
142      .filter(r -> filters.stream().allMatch(f -> f.execute(r)))
143      .sorted((recordLeft, recordRight) -> {
144        FieldValue left = recordLeft.get(currentSortField);
145        FieldValue right = recordRight.get(currentSortField);
146        return (ascendingSort ? 1 : -1) * left.compareTo(right);
147      }).collect(Collectors.toList());
148
149    this.records = Collections.unmodifiableList(records);
150  }
151
152  public void switchSortOrder() {
153    ascendingSort = !ascendingSort;
154  }
155
156  public boolean addFilter(String filterString, boolean ignoreCase) {
157    RecordFilter filter = RecordFilter.parse(filterString, fields, ignoreCase);
158    if (filter == null) {
159      return false;
160    }
161    filters.add(filter);
162    filterHistories.add(filterString);
163    return true;
164  }
165
166  public void clearFilters() {
167    pushDownFilters.clear();
168    filters.clear();
169  }
170
171  public boolean drillDown(Record selectedRecord) {
172    DrillDownInfo drillDownInfo = currentMode.drillDown(selectedRecord);
173    if (drillDownInfo == null) {
174      return false;
175    }
176    switchMode(drillDownInfo.getNextMode(), drillDownInfo.getInitialFilters(), true);
177    return true;
178  }
179
180  public Mode getCurrentMode() {
181    return currentMode;
182  }
183
184  public Field getCurrentSortField() {
185    return currentSortField;
186  }
187
188  public List<FieldInfo> getFieldInfos() {
189    return fieldInfos;
190  }
191
192  public List<Field> getFields() {
193    return fields;
194  }
195
196  public Summary getSummary() {
197    return summary;
198  }
199
200  public List<Record> getRecords() {
201    return records;
202  }
203
204  public List<RecordFilter> getFilters() {
205    return Collections.unmodifiableList(filters);
206  }
207
208  public List<String> getFilterHistories() {
209    return Collections.unmodifiableList(filterHistories);
210  }
211
212  private void decomposePushDownFilter() {
213    pushDownFilters.clear();
214    for (RecordFilter filter : filters) {
215      if (!fields.contains(filter.getField())) {
216        pushDownFilters.add(filter);
217      }
218    }
219    filters.removeAll(pushDownFilters);
220  }
221
222  public Collection<? extends RecordFilter> getPushDownFilters() {
223    return pushDownFilters;
224  }
225}