/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.blur.shell;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import jline.Terminal;
import jline.console.ConsoleReader;
import org.apache.blur.thirdparty.thrift_0_9_0.TException;
import org.apache.blur.thirdparty.thrift_0_9_0.transport.TTransportException;
import org.apache.blur.thrift.BlurClientManager;
import org.apache.blur.thrift.Connection;
import org.apache.blur.thrift.generated.Blur;
import org.apache.blur.thrift.generated.Blur.Client;
import org.apache.blur.thrift.generated.BlurException;
import org.apache.blur.thrift.generated.Metric;
public class TopCommand extends Command {
private static final String HELP = ".help";
private static final String TOP = "top.";
private static final String LONGNAME = ".longname";
private static final String SHORTNAME = ".shortname";
private static final String UNKNOWN = "Unknown";
private static final String TOP_SHARD_SERVER_SHORTNAME = "top.SHARD_SERVER.shortname";
public enum SCREEN {
HELP, TOP
}
private static final double ONE_THOUSAND = 1000;
private static final double ONE_MILLION = ONE_THOUSAND * ONE_THOUSAND;
private static final double ONE_BILLION = ONE_THOUSAND * ONE_MILLION;
private static final double ONE_TRILLION = ONE_THOUSAND * ONE_BILLION;
private static final double ONE_QUADRILLION = ONE_THOUSAND * ONE_TRILLION;
private int _width = Integer.MAX_VALUE;
private int _height;
@Override
public void doit(PrintWriter out, Blur.Iface client, String[] args) throws CommandException, TException,
BlurException {
try {
doitInternal(out, client, args);
} finally {
ConsoleReader reader = this.getConsoleReader();
if (reader != null) {
reader.setPrompt(Main.PROMPT);
}
}
}
public void doitInternal(PrintWriter out, Blur.Iface client, String[] args) throws CommandException, TException,
BlurException {
AtomicBoolean quit = new AtomicBoolean();
AtomicBoolean help = new AtomicBoolean();
Properties properties = new Properties();
try {
properties.load(getClass().getResourceAsStream("top.properties"));
} catch (IOException e) {
if (Main.debug) {
e.printStackTrace();
}
throw new CommandException(e.getMessage());
}
String cluster;
if (args.length != 2) {
cluster = Main.getCluster(client, "Invalid args: " + help());
} else {
cluster = args[1];
}
Map<String, String> metricNames = new HashMap<String, String>();
Map<String, String> helpMap = new HashMap<String, String>();
Set<Object> keySet = properties.keySet();
for (Object k : keySet) {
String key = k.toString();
if (isShortName(key)) {
String shortName = getShortName(key, properties);
String longName = getLongName(getLongNameKey(key), properties);
longName = longName.replace("<CLUSTER_NAME>", cluster);
metricNames.put(shortName, longName);
} else if (isHelpName(key)) {
int indexOf = key.indexOf(HELP);
String strKey = key.substring(0, indexOf);
Object shortNameKey = properties.get(strKey + SHORTNAME);
Object helpMessage = properties.get(key);
if (shortNameKey != null && helpMessage != null) {
helpMap.put(shortNameKey.toString(), helpMessage.toString());
}
}
}
String labelsStr = properties.getProperty("top.columns");
String[] labels = resolveShortNames(labelsStr.split(","), properties);
String sizesStr = properties.getProperty("top.sizes");
Set<String> sizes = new HashSet<String>(Arrays.asList(resolveShortNames(sizesStr.split(","), properties)));
Set<String> keys = new HashSet<String>(metricNames.values());
ConsoleReader reader = this.getConsoleReader();
if (reader != null) {
Terminal terminal = reader.getTerminal();
_height = terminal.getHeight() - 2;
_width = terminal.getWidth() - 2;
try {
reader.setPrompt("");
reader.clearScreen();
} catch (IOException e) {
if (Main.debug) {
e.printStackTrace();
}
}
startCommandWatcher(reader, quit, help, this);
}
List<String> shardServerList = new ArrayList<String>(client.shardServerList(cluster));
Collections.sort(shardServerList);
Map<String, AtomicReference<Client>> shardClients = setupClients(shardServerList);
String shardServerLabel = properties.getProperty(TOP_SHARD_SERVER_SHORTNAME);
int longestServerName = Math.max(getSizeOfLongestKey(shardClients), shardServerLabel.length());
StringBuilder header = new StringBuilder("%" + longestServerName + "s");
for (int i = 1; i < labels.length; i++) {
header.append(" %10s");
}
do {
int lineCount = 0;
StringBuilder output = new StringBuilder();
if (quit.get()) {
return;
} else if (help.get()) {
showHelp(output, labels, helpMap);
} else {
output.append(truncate(String.format(header.toString(), (Object[]) labels)) + "\n");
lineCount++;
SERVER: for (Entry<String, AtomicReference<Client>> e : new TreeMap<String, AtomicReference<Client>>(
shardClients).entrySet()) {
String shardServer = e.getKey();
AtomicReference<Client> ref = e.getValue();
Map<String, Metric> metrics = getMetrics(shardServer, ref, keys);
if (metrics == null) {
String line = String.format("%" + longestServerName + "s*%n", shardServer);
output.append(line);
lineCount++;
if (tooLong(lineCount)) {
break SERVER;
}
} else {
Object[] cols = new Object[labels.length];
int c = 0;
cols[c++] = shardServer;
StringBuilder sb = new StringBuilder("%" + longestServerName + "s");
for (int i = 1; i < labels.length; i++) {
String mn = metricNames.get(labels[i]);
Metric metric = metrics.get(mn);
Double value;
if (metric == null) {
value = null;
} else {
Map<String, Double> doubleMap = metric.getDoubleMap();
value = doubleMap.get("oneMinuteRate");
if (value == null) {
value = doubleMap.get("value");
}
}
if (value == null) {
value = 0.0;
}
cols[c++] = humanize(value, sizes.contains(mn));
sb.append(" %10s");
}
output.append(truncate(String.format(sb.toString(), cols)) + "\n");
lineCount++;
if (tooLong(lineCount)) {
break SERVER;
}
}
}
}
if (reader != null) {
try {
reader.clearScreen();
} catch (IOException e) {
if (Main.debug) {
e.printStackTrace();
}
}
}
out.print(output.toString());
out.flush();
if (reader != null) {
try {
synchronized (this) {
wait(3000);
}
} catch (InterruptedException e) {
return;
}
Terminal terminal = reader.getTerminal();
_height = terminal.getHeight() - 2;
_width = terminal.getWidth() - 2;
List<String> currentShardServerList = new ArrayList<String>(client.shardServerList(cluster));
Collections.sort(currentShardServerList);
if (!shardServerList.equals(currentShardServerList)) {
close(shardClients);
shardClients = setupClients(shardServerList);
}
}
} while (reader != null);
}
private boolean tooLong(int lineCount) {
if (lineCount >= _height) {
return true;
}
return false;
}
private boolean isHelpName(String key) {
return key.endsWith(HELP);
}
private void close(Map<String, AtomicReference<Client>> shardClients) {
for (AtomicReference<Client> client : shardClients.values()) {
tryToClose(client);
}
}
private Map<String, AtomicReference<Client>> setupClients(List<String> shardServerList) {
Map<String, AtomicReference<Client>> shardClients = new ConcurrentHashMap<String, AtomicReference<Client>>();
for (String sc : shardServerList) {
AtomicReference<Client> ref = shardClients.get(sc);
if (ref == null) {
ref = new AtomicReference<Client>();
shardClients.put(sc, ref);
}
tryToConnect(sc, ref);
}
return shardClients;
}
private boolean tryToConnect(String sc, AtomicReference<Client> ref) {
try {
Client c = BlurClientManager.newClient(new Connection(sc));
ref.set(c);
return true;
} catch (TTransportException e) {
ref.set(null);
if (Main.debug) {
e.printStackTrace();
}
} catch (IOException e) {
ref.set(null);
if (Main.debug) {
e.printStackTrace();
}
}
return false;
}
private Map<String, Metric> getMetrics(String sc, AtomicReference<Client> ref, Set<String> keys) {
if (ref.get() == null) {
if (!tryToConnect(sc, ref)) {
return null;
}
}
try {
return ref.get().metrics(keys);
} catch (BlurException e) {
if (Main.debug) {
e.printStackTrace();
}
} catch (TException e) {
if (Main.debug) {
e.printStackTrace();
}
tryToClose(ref);
}
return null;
}
private void tryToClose(AtomicReference<Client> ref) {
Client client = ref.get();
if (client != null) {
ref.set(null);
try {
client.getInputProtocol().getTransport().close();
} catch (Exception e) {
if (Main.debug) {
e.printStackTrace();
}
}
}
}
private String[] resolveShortNames(String[] keys, Properties properties) {
String[] labels = new String[keys.length];
int i = 0;
for (String key : keys) {
String shortName = properties.getProperty(TOP + key + SHORTNAME);
labels[i++] = shortName;
}
return labels;
}
private String getLongNameKey(String key) {
return key.replace(SHORTNAME, LONGNAME);
}
private String getShortName(String key, Properties properties) {
return properties.getProperty(key, UNKNOWN);
}
private String getLongName(String key, Properties properties) {
return properties.getProperty(key, UNKNOWN);
}
private boolean isShortName(String key) {
return key.endsWith(SHORTNAME);
}
private void showHelp(StringBuilder output, Object[] labels, Map<String, String> helpMap) {
output.append("Help\n");
for (int i = 0; i < labels.length; i++) {
String shortName = (String) labels[i];
String helpMessage = helpMap.get(shortName);
output.append(String.format("%15s", shortName));
output.append(" - ");
output.append(helpMessage);
output.append('\n');
}
}
private void startCommandWatcher(final ConsoleReader reader, final AtomicBoolean quit, final AtomicBoolean help,
final Object lock) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) {
int readCharacter = reader.readCharacter();
if (readCharacter == 'q') {
quit.set(true);
synchronized (lock) {
lock.notify();
}
return;
} else if (readCharacter == 'h') {
help.set(!help.get());
synchronized (lock) {
lock.notify();
}
}
}
} catch (IOException e) {
if (Main.debug) {
e.printStackTrace();
}
}
}
});
thread.setDaemon(true);
thread.start();
}
private String truncate(String s) {
return s.substring(0, Math.min(_width, s.length()));
}
private String humanize(double value, boolean size) {
long v = (long) (value / ONE_QUADRILLION);
if (v > 0) {
return String.format("%7.2f%s", value / ONE_QUADRILLION, size ? "Q" : "P");
}
v = (long) (value / ONE_TRILLION);
if (v > 0) {
return String.format("%7.2f%s", value / ONE_TRILLION, "T");
}
v = (long) (value / ONE_BILLION);
if (v > 0) {
return String.format("%7.2f%s", value / ONE_BILLION, size ? "B" : "G");
}
v = (long) (value / ONE_MILLION);
if (v > 0) {
return String.format("%7.2f%s", value / ONE_MILLION, "M");
}
v = (long) (value / ONE_THOUSAND);
if (v > 0) {
return String.format("%7.2f%s", value / ONE_THOUSAND, "K");
}
return String.format("%7.2f", value);
}
private int getSizeOfLongestKey(Map<String, ?> map) {
int i = 0;
for (String s : map.keySet()) {
int length = s.length();
if (i < length) {
i = length;
}
}
return i;
}
@Override
public String description() {
return "Top for watching shard clusters.";
}
@Override
public String usage() {
return "[<cluster>]";
}
@Override
public String name() {
return "top";
}
}