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 edu.umd.cs.findbugs.annotations.Nullable;
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.Collections;
026import java.util.List;
027import java.util.Objects;
028import java.util.stream.Collectors;
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, @Nullable List<Field> initialFields,
068    @Nullable Field initialSortField, @Nullable Boolean initialAscendingSort,
069    @Nullable List<RecordFilter> initialFilters) {
070    this.admin = Objects.requireNonNull(admin);
071    switchMode(Objects.requireNonNull(initialMode), initialSortField, false, initialFields,
072      initialAscendingSort, initialFilters);
073  }
074
075  public void switchMode(Mode nextMode, boolean keepSortFieldAndSortOrderIfPossible,
076    List<RecordFilter> initialFilters) {
077    switchMode(nextMode, null, keepSortFieldAndSortOrderIfPossible, null, null, initialFilters);
078  }
079
080  public void switchMode(Mode nextMode, Field initialSortField,
081    boolean keepSortFieldAndSortOrderIfPossible, @Nullable List<Field> initialFields,
082    @Nullable Boolean initialAscendingSort, @Nullable List<RecordFilter> initialFilters) {
083    currentMode = nextMode;
084    fieldInfos = Collections.unmodifiableList(new ArrayList<>(currentMode.getFieldInfos()));
085
086    if (initialFields != null) {
087      List<Field> tmp = new ArrayList<>(initialFields);
088      tmp.addAll(currentMode.getFieldInfos().stream().map(FieldInfo::getField)
089        .filter(f -> !initialFields.contains(f))
090        .collect(Collectors.toList()));
091      fields = Collections.unmodifiableList(tmp);
092    } else {
093      fields = Collections.unmodifiableList(currentMode.getFieldInfos().stream()
094        .map(FieldInfo::getField).collect(Collectors.toList()));
095    }
096
097    if (keepSortFieldAndSortOrderIfPossible) {
098      boolean match = fields.stream().anyMatch(f -> f == currentSortField);
099      if (!match) {
100        if (initialSortField != null && initialAscendingSort != null) {
101          currentSortField = initialSortField;
102          ascendingSort = initialAscendingSort;
103        } else {
104          currentSortField = nextMode.getDefaultSortField();
105          ascendingSort = false;
106        }
107      }
108    } else {
109      if (initialSortField != null && initialAscendingSort != null) {
110        currentSortField = initialSortField;
111        ascendingSort = initialAscendingSort;
112      } else {
113        currentSortField = nextMode.getDefaultSortField();
114        ascendingSort = false;
115      }
116    }
117
118    clearFilters();
119    if (initialFilters != null) {
120      filters.addAll(initialFilters);
121    }
122    decomposePushDownFilter();
123  }
124
125  public void setSortFieldAndFields(Field sortField, List<Field> fields) {
126    this.currentSortField = sortField;
127    this.fields = Collections.unmodifiableList(new ArrayList<>(fields));
128  }
129
130  /*
131   * HBTop only calls this from a single thread, and if that ever changes, this needs
132   * synchronization
133   */
134  public void refreshMetricsData() {
135    ClusterMetrics clusterMetrics;
136    try {
137      clusterMetrics = admin.getClusterMetrics();
138    } catch (Exception e) {
139      LOGGER.error("Unable to get cluster metrics", e);
140      return;
141    }
142
143    refreshSummary(clusterMetrics);
144    refreshRecords(clusterMetrics);
145  }
146
147  private void refreshSummary(ClusterMetrics clusterMetrics) {
148    String currentTime = ISO_8601_EXTENDED_TIME_FORMAT
149      .format(System.currentTimeMillis());
150    String version = clusterMetrics.getHBaseVersion();
151    String clusterId = clusterMetrics.getClusterId();
152    int liveServers = clusterMetrics.getLiveServerMetrics().size();
153    int deadServers = clusterMetrics.getDeadServerNames().size();
154    int regionCount = clusterMetrics.getRegionCount();
155    int ritCount = clusterMetrics.getRegionStatesInTransition().size();
156    double averageLoad = clusterMetrics.getAverageLoad();
157    long aggregateRequestPerSecond = clusterMetrics.getLiveServerMetrics().entrySet().stream()
158      .mapToLong(e -> e.getValue().getRequestCountPerSecond()).sum();
159
160    summary = new Summary(currentTime, version, clusterId, liveServers + deadServers,
161      liveServers, deadServers, regionCount, ritCount, averageLoad, aggregateRequestPerSecond);
162  }
163
164  private void refreshRecords(ClusterMetrics clusterMetrics) {
165    List<Record> records = currentMode.getRecords(clusterMetrics, pushDownFilters);
166
167    // Filter and sort
168    records = records.stream()
169      .filter(r -> filters.stream().allMatch(f -> f.execute(r)))
170      .sorted((recordLeft, recordRight) -> {
171        FieldValue left = recordLeft.get(currentSortField);
172        FieldValue right = recordRight.get(currentSortField);
173        return (ascendingSort ? 1 : -1) * left.compareTo(right);
174      }).collect(Collectors.toList());
175
176    this.records = Collections.unmodifiableList(records);
177  }
178
179  public void switchSortOrder() {
180    ascendingSort = !ascendingSort;
181  }
182
183  public boolean addFilter(String filterString, boolean ignoreCase) {
184    RecordFilter filter = RecordFilter.parse(filterString, fields, ignoreCase);
185    if (filter == null) {
186      return false;
187    }
188    filters.add(filter);
189    filterHistories.add(filterString);
190    return true;
191  }
192
193  public void clearFilters() {
194    pushDownFilters.clear();
195    filters.clear();
196  }
197
198  public boolean drillDown(Record selectedRecord) {
199    DrillDownInfo drillDownInfo = currentMode.drillDown(selectedRecord);
200    if (drillDownInfo == null) {
201      return false;
202    }
203    switchMode(drillDownInfo.getNextMode(), true, drillDownInfo.getInitialFilters());
204    return true;
205  }
206
207  public Mode getCurrentMode() {
208    return currentMode;
209  }
210
211  public Field getCurrentSortField() {
212    return currentSortField;
213  }
214
215  public List<FieldInfo> getFieldInfos() {
216    return fieldInfos;
217  }
218
219  public List<Field> getFields() {
220    return fields;
221  }
222
223  public Summary getSummary() {
224    return summary;
225  }
226
227  public List<Record> getRecords() {
228    return records;
229  }
230
231  public List<RecordFilter> getFilters() {
232    return Collections.unmodifiableList(filters);
233  }
234
235  public List<String> getFilterHistories() {
236    return Collections.unmodifiableList(filterHistories);
237  }
238
239  private void decomposePushDownFilter() {
240    pushDownFilters.clear();
241    for (RecordFilter filter : filters) {
242      if (!fields.contains(filter.getField())) {
243        pushDownFilters.add(filter);
244      }
245    }
246    filters.removeAll(pushDownFilters);
247  }
248
249  public Collection<? extends RecordFilter> getPushDownFilters() {
250    return pushDownFilters;
251  }
252}