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