You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gn...@apache.org on 2016/10/17 09:59:25 UTC
[1/2] karaf git commit: [KARAF-4775] Implement a thread top command
Repository: karaf
Updated Branches:
refs/heads/master ec5e9b75d -> 887770ed4
[KARAF-4775] Implement a thread top command
Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/aebb6fd0
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/aebb6fd0
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/aebb6fd0
Branch: refs/heads/master
Commit: aebb6fd04e7cf30cfac2775c812077e2d3431ff7
Parents: ec5e9b7
Author: Guillaume Nodet <gn...@apache.org>
Authored: Mon Oct 17 11:48:28 2016 +0200
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Mon Oct 17 11:51:41 2016 +0200
----------------------------------------------------------------------
.../org/apache/karaf/main/ConfigProperties.java | 17 +-
.../karaf/shell/commands/impl/TTopAction.java | 68 +++
.../karaf/shell/commands/impl/top/TTop.java | 593 +++++++++++++++++++
.../services/org/apache/karaf/shell/commands | 1 +
4 files changed, 678 insertions(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/karaf/blob/aebb6fd0/main/src/main/java/org/apache/karaf/main/ConfigProperties.java
----------------------------------------------------------------------
diff --git a/main/src/main/java/org/apache/karaf/main/ConfigProperties.java b/main/src/main/java/org/apache/karaf/main/ConfigProperties.java
index 0383191..b8ef054 100644
--- a/main/src/main/java/org/apache/karaf/main/ConfigProperties.java
+++ b/main/src/main/java/org/apache/karaf/main/ConfigProperties.java
@@ -21,6 +21,8 @@ package org.apache.karaf.main;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadMXBean;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
@@ -135,6 +137,8 @@ public class ConfigProperties {
private static final String KARAF_DELAY_CONSOLE = "karaf.delay.console";
+ private static final String KARAF_THREAD_MONITORING = "karaf.thread.monitoring";
+
private static final String PROPERTY_LOCK_CLASS_DEFAULT = SimpleFileLock.class.getName();
private static final String SECURITY_PROVIDERS = "org.apache.karaf.security.providers";
@@ -175,6 +179,7 @@ public class ConfigProperties {
String shutdownCommand;
String startupMessage;
boolean delayConsoleStart;
+ boolean threadMonitoring;
public ConfigProperties() throws Exception {
this.karafHome = Utils.getKarafHome(ConfigProperties.class, PROP_KARAF_HOME, ENV_KARAF_HOME);
@@ -222,7 +227,8 @@ public class ConfigProperties {
this.shutdownCommand = props.getProperty(KARAF_SHUTDOWN_COMMAND);
this.startupMessage = props.getProperty(KARAF_STARTUP_MESSAGE, "Apache Karaf starting up. Press Enter to open the shell now...");
this.delayConsoleStart = Boolean.parseBoolean(props.getProperty(KARAF_DELAY_CONSOLE, "false"));
- System.setProperty(KARAF_DELAY_CONSOLE, new Boolean(this.delayConsoleStart).toString());
+ this.threadMonitoring = Boolean.parseBoolean(props.getProperty(KARAF_THREAD_MONITORING, "false"));
+ System.setProperty(KARAF_DELAY_CONSOLE, Boolean.toString(this.delayConsoleStart));
}
public void performInit() throws Exception {
@@ -265,6 +271,15 @@ public class ConfigProperties {
System.err.println("WARN: can't update etc/config.properties with the generated command shutdown. We advise to manually add the karaf.shutdown.command property.");
}
}
+ if (threadMonitoring) {
+ ThreadMXBean threadsBean = ManagementFactory.getThreadMXBean();
+ if (threadsBean.isThreadCpuTimeSupported()) {
+ threadsBean.setThreadCpuTimeEnabled(true);
+ }
+ if (threadsBean.isThreadContentionMonitoringSupported()) {
+ threadsBean.setThreadContentionMonitoringEnabled(true);
+ }
+ }
}
private String getPropertyOrFail(String propertyName) {
http://git-wip-us.apache.org/repos/asf/karaf/blob/aebb6fd0/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/TTopAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/TTopAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/TTopAction.java
new file mode 100644
index 0000000..4790c6c
--- /dev/null
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/TTopAction.java
@@ -0,0 +1,68 @@
+/*
+ * 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.karaf.shell.commands.impl;
+
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.commands.impl.top.TTop;
+import org.jline.terminal.Terminal;
+
+import java.util.Arrays;
+
+
+/**
+ * Display threads infos in a table.
+ */
+@Command(scope = "shell", name = "ttop", description = "Display threads information")
+@Service
+public class TTopAction implements Action {
+
+ @Option(name = "--order" , aliases = { "-o" }, description = "Comma separated list of sorting keys")
+ String order;
+
+ @Option(name = "--stats" , aliases = { "-t" }, description = "Comma separated list of stats to display")
+ String stats;
+
+ @Option(name = "--seconds" , aliases = { "-s" }, description = "Delay between updates in seconds")
+ Integer seconds;
+
+ @Option(name = "--millis" , aliases = { "-m" }, description = "Delay between updates in milliseconds")
+ Integer millis;
+
+ @Option(name = "--nthreads", aliases = { "-n" }, description = "Only display up to NTHREADS threads")
+ int nthreads = -1;
+
+ @Reference
+ Session session;
+
+ @Override
+ public Object execute() throws Exception {
+ TTop ttop = new TTop((Terminal) session.get(".jline.terminal"));
+ ttop.sort = order != null ? Arrays.asList(order.split(",")) : null;
+ ttop.delay = seconds != null ? seconds * 1000 : ttop.delay;
+ ttop.delay = millis != null ? millis : ttop.delay;
+ ttop.stats = stats != null ? Arrays.asList(stats.split(",")) : null;
+ ttop.nthreads = nthreads;
+ ttop.run();
+ return null;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/aebb6fd0/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/top/TTop.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/top/TTop.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/top/TTop.java
new file mode 100644
index 0000000..f3ba964
--- /dev/null
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/top/TTop.java
@@ -0,0 +1,593 @@
+/*
+ * 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.karaf.shell.commands.impl.top;
+
+import org.jline.keymap.BindingReader;
+import org.jline.keymap.KeyMap;
+import org.jline.terminal.Attributes;
+import org.jline.terminal.Size;
+import org.jline.terminal.Terminal;
+import org.jline.utils.*;
+
+import java.io.IOException;
+import java.lang.management.*;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static org.apache.karaf.shell.commands.impl.top.TTop.Align.Left;
+import static org.apache.karaf.shell.commands.impl.top.TTop.Align.Right;
+
+/**
+ * Thread Top implementation based on jline.
+ *
+ * TODO: option modification at runtime (such as implemented in less) is not currently supported
+ * TODO: one possible addition would be to detect deadlock threads and display them in a specific way
+ */
+public class TTop {
+
+ public static final String STAT_UPTIME = "uptime";
+
+ public static final String STAT_TID = "tid";
+ public static final String STAT_NAME = "name";
+ public static final String STAT_STATE = "state";
+ public static final String STAT_BLOCKED_TIME = "blocked_time";
+ public static final String STAT_BLOCKED_COUNT = "blocked_count";
+ public static final String STAT_WAITED_TIME = "waited_time";
+ public static final String STAT_WAITED_COUNT = "waited_count";
+ public static final String STAT_LOCK_NAME = "lock_name";
+ public static final String STAT_LOCK_OWNER_ID = "lock_owner_id";
+ public static final String STAT_LOCK_OWNER_NAME = "lock_owner_name";
+ public static final String STAT_USER_TIME = "user_time";
+ public static final String STAT_USER_TIME_PERC = "user_time_perc";
+ public static final String STAT_CPU_TIME = "cpu_time";
+ public static final String STAT_CPU_TIME_PERC = "cpu_time_perc";
+
+ public List<String> sort;
+ public long delay;
+ public List<String> stats;
+ public int nthreads;
+
+ public enum Align {
+ Left, Right
+ };
+
+ public enum Operation {
+ INCREASE_DELAY,
+ DECREASE_DELAY,
+ HELP,
+ EXIT,
+ CLEAR,
+ REVERSE
+ }
+
+ private final Map<String, Column> columns = new LinkedHashMap<>();
+ private final Terminal terminal;
+ private final Display display;
+ private final BindingReader bindingReader;
+ private final KeyMap<Operation> keys;
+ private final Size size = new Size();
+
+ private Comparator<Map<String, Comparable>> comparator;
+
+ // Internal cache data
+ private Map<Long, Map<String, Object>> previous = new HashMap<>();
+ private Map<Long, Map<String, Long>> changes = new HashMap<>();
+ private Map<String, Integer> widths = new HashMap<>();
+
+
+ public TTop(Terminal terminal) {
+ this.terminal = terminal;
+ this.display = new Display(terminal, true);
+ this.bindingReader = new BindingReader(terminal.reader());
+
+ DecimalFormatSymbols dfs = new DecimalFormatSymbols();
+ dfs.setDecimalSeparator('.');
+ DecimalFormat perc = new DecimalFormat("0.00%", dfs);
+
+ register(STAT_TID, Right, "TID", o -> String.format("%3d", (Long) o));
+ register(STAT_NAME, Left, "NAME", padcut(40));
+ register(STAT_STATE, Left, "STATE", o -> o.toString().toLowerCase());
+ register(STAT_BLOCKED_TIME, Right, "T-BLOCKED", o -> millis((Long) o));
+ register(STAT_BLOCKED_COUNT, Right, "#-BLOCKED", Object::toString);
+ register(STAT_WAITED_TIME, Right, "T-WAITED", o -> millis((Long) o));
+ register(STAT_WAITED_COUNT, Right, "#-WAITED", Object::toString);
+ register(STAT_LOCK_NAME, Left, "LOCK-NAME", Object::toString);
+ register(STAT_LOCK_OWNER_ID, Right, "LOCK-OWNER-ID", id -> ((Long) id) >= 0 ? id.toString() : "");
+ register(STAT_LOCK_OWNER_NAME, Left, "LOCK-OWNER-NAME", name -> name != null ? name.toString() : "");
+ register(STAT_USER_TIME, Right, "T-USR", o -> nanos((Long) o));
+ register(STAT_CPU_TIME, Right, "T-CPU", o -> nanos((Long) o));
+ register(STAT_USER_TIME_PERC, Right, "%-USR", perc::format);
+ register(STAT_CPU_TIME_PERC, Right, "%-CPU", perc::format);
+
+ keys = new KeyMap<>();
+ bindKeys(keys);
+ }
+
+ public KeyMap<Operation> getKeys() {
+ return keys;
+ }
+
+ public void run() throws IOException, InterruptedException {
+ comparator = buildComparator(sort);
+ delay = delay > 0 ? Math.max(delay, 100) : 1000;
+ if (stats == null || stats.isEmpty()) {
+ stats = Arrays.asList(STAT_TID, STAT_NAME, STAT_STATE, STAT_CPU_TIME, STAT_LOCK_OWNER_ID);
+ }
+
+ Boolean isThreadContentionMonitoringEnabled = null;
+ ThreadMXBean threadsBean = ManagementFactory.getThreadMXBean();
+ if (stats.contains(STAT_BLOCKED_TIME)
+ || stats.contains(STAT_BLOCKED_COUNT)
+ || stats.contains(STAT_WAITED_TIME)
+ || stats.contains(STAT_WAITED_COUNT)) {
+ if (threadsBean.isThreadContentionMonitoringSupported()) {
+ isThreadContentionMonitoringEnabled = threadsBean.isThreadContentionMonitoringEnabled();
+ if (!isThreadContentionMonitoringEnabled) {
+ threadsBean.setThreadContentionMonitoringEnabled(true);
+ }
+ } else {
+ stats.removeAll(Arrays.asList(STAT_BLOCKED_TIME, STAT_BLOCKED_COUNT, STAT_WAITED_TIME, STAT_WAITED_COUNT));
+ }
+ }
+ Boolean isThreadCpuTimeEnabled = null;
+ if (stats.contains(STAT_USER_TIME) || stats.contains(STAT_CPU_TIME)) {
+ if (threadsBean.isThreadCpuTimeSupported()) {
+ isThreadCpuTimeEnabled = threadsBean.isThreadCpuTimeEnabled();
+ if (!isThreadCpuTimeEnabled) {
+ threadsBean.setThreadCpuTimeEnabled(true);
+ }
+ } else {
+ stats.removeAll(Arrays.asList(STAT_USER_TIME, STAT_CPU_TIME));
+ }
+ }
+
+ size.copy(terminal.getSize());
+ Terminal.SignalHandler prevHandler = terminal.handle(Terminal.Signal.WINCH, this::handle);
+ Attributes attr = terminal.enterRawMode();
+ try {
+
+ // Use alternate buffer
+ terminal.puts(InfoCmp.Capability.enter_ca_mode);
+ terminal.puts(InfoCmp.Capability.keypad_xmit);
+ terminal.puts(InfoCmp.Capability.cursor_invisible);
+ terminal.writer().flush();
+
+ long t0 = System.currentTimeMillis();
+
+ Operation op;
+ do {
+ display();
+ checkInterrupted();
+
+ op = null;
+
+ long delta = ((System.currentTimeMillis() - t0) / delay + 1) * delay + t0 - System.currentTimeMillis();
+ int ch = bindingReader.peekCharacter(delta);
+ if (ch == -1) {
+ op = Operation.EXIT;
+ } else if (ch != NonBlockingReader.READ_EXPIRED) {
+ op = bindingReader.readBinding(keys, null, false);
+ }
+ if (op == null) {
+ continue;
+ }
+
+ switch (op) {
+ case INCREASE_DELAY:
+ delay = delay * 2;
+ t0 = System.currentTimeMillis();
+ break;
+ case DECREASE_DELAY:
+ delay = Math.max(delay / 2, 16);
+ t0 = System.currentTimeMillis();
+ break;
+ case CLEAR:
+ display.clear();
+ break;
+ case REVERSE:
+ comparator = comparator.reversed();
+ break;
+ }
+ } while (op != Operation.EXIT);
+ } catch (InterruptedException ie) {
+ // Do nothing
+ } finally {
+ terminal.setAttributes(attr);
+ if (prevHandler != null) {
+ terminal.handle(Terminal.Signal.WINCH, prevHandler);
+ }
+ // Use main buffer
+ terminal.puts(InfoCmp.Capability.exit_ca_mode);
+ terminal.puts(InfoCmp.Capability.keypad_local);
+ terminal.puts(InfoCmp.Capability.cursor_visible);
+ terminal.writer().flush();
+
+ if (isThreadContentionMonitoringEnabled != null) {
+ threadsBean.setThreadContentionMonitoringEnabled(isThreadContentionMonitoringEnabled);
+ }
+ if (isThreadCpuTimeEnabled != null) {
+ threadsBean.setThreadCpuTimeEnabled(isThreadCpuTimeEnabled);
+ }
+ }
+ }
+
+ private void handle(Terminal.Signal signal) {
+ int prevw = size.getColumns();
+ size.copy(terminal.getSize());
+ try {
+ if (size.getColumns() < prevw) {
+ display.clear();
+ }
+ display();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+
+ private List<Map<String, Comparable>> infos() {
+ long ctime = ManagementFactory.getRuntimeMXBean().getUptime();
+ Long ptime = (Long) previous.computeIfAbsent(-1L, id -> new HashMap<>()).put(STAT_UPTIME, ctime);
+ long delta = ptime != null ? ctime - ptime : 0L;
+
+ ThreadMXBean threadsBean = ManagementFactory.getThreadMXBean();
+ ThreadInfo[] infos = threadsBean.dumpAllThreads(false, false);
+ List<Map<String, Comparable>> threads = new ArrayList<>();
+ for (ThreadInfo ti : infos) {
+ Map<String, Comparable> t = new HashMap<>();
+ t.put(STAT_TID, ti.getThreadId());
+ t.put(STAT_NAME, ti.getThreadName());
+ t.put(STAT_STATE, ti.getThreadState());
+ if (threadsBean.isThreadContentionMonitoringEnabled()) {
+ t.put(STAT_BLOCKED_TIME, ti.getBlockedTime());
+ t.put(STAT_BLOCKED_COUNT, ti.getBlockedCount());
+ t.put(STAT_WAITED_TIME, ti.getWaitedTime());
+ t.put(STAT_WAITED_COUNT, ti.getWaitedCount());
+ }
+ t.put(STAT_LOCK_NAME, ti.getLockName());
+ t.put(STAT_LOCK_OWNER_ID, ti.getLockOwnerId());
+ t.put(STAT_LOCK_OWNER_NAME, ti.getLockOwnerName());
+ if (threadsBean.isThreadCpuTimeSupported() && threadsBean.isThreadCpuTimeEnabled()) {
+ long tid = ti.getThreadId(), t0, t1;
+ // Cpu
+ t1 = threadsBean.getThreadCpuTime(tid);
+ t0 = (Long) previous.computeIfAbsent(tid, id -> new HashMap<>()).getOrDefault(STAT_CPU_TIME, t1);
+ t.put(STAT_CPU_TIME, t1);
+ t.put(STAT_CPU_TIME_PERC, (delta != 0) ? (t1 - t0) / ((double) delta * 1000000) : 0.0d);
+ // User
+ t1 = threadsBean.getThreadUserTime(tid);
+ t0 = (Long) previous.computeIfAbsent(tid, id -> new HashMap<>()).getOrDefault(STAT_USER_TIME, t1);
+ t.put(STAT_USER_TIME, t1);
+ t.put(STAT_USER_TIME_PERC, (delta != 0) ? (t1 - t0) / ((double) delta * 1000000) : 0.0d);
+ }
+ threads.add(t);
+ }
+ return threads;
+ }
+
+ private void align(AttributedStringBuilder sb, String val, int width, Align align) {
+ if (align == Align.Left) {
+ sb.append(val);
+ for (int i = 0; i < width - val.length(); i++) {
+ sb.append(' ');
+ }
+ } else {
+ for (int i = 0; i < width - val.length(); i++) {
+ sb.append(' ');
+ }
+ sb.append(val);
+ }
+ }
+
+ private void display() throws IOException {
+ long now = System.currentTimeMillis();
+
+ display.resize(size.getRows(), size.getColumns());
+
+ List<AttributedString> lines = new ArrayList<>();
+ AttributedStringBuilder sb = new AttributedStringBuilder(size.getColumns());
+
+ // Top headers
+ sb.style(sb.style().bold());
+ sb.append("ttop");
+ sb.style(sb.style().boldOff());
+ sb.append(" - ");
+ sb.append(String.format("%8tT", new Date()));
+ sb.append(".");
+
+
+ OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean();
+ String osinfo = "OS: " + os.getName() + " " + os.getVersion() + ", " + os.getArch() + ", " + os.getAvailableProcessors() + " cpus.";
+ if (sb.length() + 1 + osinfo.length() < size.getColumns()) {
+ sb.append(" ");
+ } else {
+ lines.add(sb.toAttributedString());
+ sb.setLength(0);
+ }
+ sb.append(osinfo);
+
+ ClassLoadingMXBean cl = ManagementFactory.getClassLoadingMXBean();
+ String clsinfo = "Classes: " + cl.getLoadedClassCount() + " loaded, " + cl.getUnloadedClassCount() + " unloaded, " + cl.getTotalLoadedClassCount() + " loaded total.";
+ if (sb.length() + 1 + clsinfo.length() < size.getColumns()) {
+ sb.append(" ");
+ } else {
+ lines.add(sb.toAttributedString());
+ sb.setLength(0);
+ }
+ sb.append(clsinfo);
+
+ ThreadMXBean th = ManagementFactory.getThreadMXBean();
+ String thinfo = "Threads: " + th.getThreadCount() + ", peak: " + th.getPeakThreadCount() + ", started: " + th.getTotalStartedThreadCount() + ".";
+ if (sb.length() + 1 + thinfo.length() < size.getColumns()) {
+ sb.append(" ");
+ } else {
+ lines.add(sb.toAttributedString());
+ sb.setLength(0);
+ }
+ sb.append(thinfo);
+
+ MemoryMXBean me = ManagementFactory.getMemoryMXBean();
+ String meinfo = "Memory: " + "heap: " + memory(me.getHeapMemoryUsage().getUsed(), me.getHeapMemoryUsage().getMax())
+ + ", non heap: " + memory(me.getNonHeapMemoryUsage().getUsed(), me.getNonHeapMemoryUsage().getMax()) + ".";
+ if (sb.length() + 1 + meinfo.length() < size.getColumns()) {
+ sb.append(" ");
+ } else {
+ lines.add(sb.toAttributedString());
+ sb.setLength(0);
+ }
+ sb.append(meinfo);
+
+ StringBuilder sbc = new StringBuilder();
+ sbc.append("GC: ");
+ boolean first = true;
+ for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
+ if (first) {
+ first = false;
+ } else {
+ sbc.append(", ");
+ }
+ long count = gc.getCollectionCount();
+ long time = gc.getCollectionTime();
+ sbc.append(gc.getName()).append(": ")
+ .append(Long.toString(count)).append(" col. / ")
+ .append(String.format("%d", time / 1000))
+ .append(".")
+ .append(String.format("%03d", time % 1000))
+ .append(" s");
+ }
+ sbc.append(".");
+ if (sb.length() + 1 + sbc.length() < size.getColumns()) {
+ sb.append(" ");
+ } else {
+ lines.add(sb.toAttributedString());
+ sb.setLength(0);
+ }
+ sb.append(sbc);
+ lines.add(sb.toAttributedString());
+ sb.setLength(0);
+
+ lines.add(sb.toAttributedString());
+
+ // Threads
+ List<Map<String, Comparable>> threads = infos();
+ Collections.sort(threads, comparator);
+ int nb = Math.min(size.getRows() - lines.size() - 2, nthreads > 0 ? nthreads : threads.size());
+ // Compute values
+ List<Map<String, String>> values = threads.subList(0, nb).stream()
+ .map(thread -> stats.stream()
+ .collect(Collectors.toMap(
+ Function.identity(),
+ key -> columns.get(key).format.apply(thread.get(key)))))
+ .collect(Collectors.toList());
+ for (String key : stats) {
+ int width = values.stream().mapToInt(map -> map.get(key).length()).max().orElse(0);
+ widths.put(key, Math.max(columns.get(key).header.length(), Math.max(width, widths.getOrDefault(key, 0))));
+ }
+ List<String> cstats;
+ if (widths.values().stream().mapToInt(Integer::intValue).sum() + stats.size() - 1 < size.getColumns()) {
+ cstats = stats;
+ } else {
+ cstats = new ArrayList<>();
+ int sz = 0;
+ for (String stat : stats) {
+ int nsz = sz;
+ if (nsz > 0) {
+ nsz++;
+ }
+ nsz += widths.get(stat);
+ if (nsz < size.getColumns()) {
+ sz = nsz;
+ cstats.add(stat);
+ } else {
+ break;
+ }
+ }
+ }
+ // Headers
+ for (String key : cstats) {
+ if (sb.length() > 0) {
+ sb.append(" ");
+ }
+ Column col = columns.get(key);
+ align(sb, col.header, widths.get(key), col.align);
+ }
+ lines.add(sb.toAttributedString());
+ sb.setLength(0);
+ // Threads
+ for (int i = 0; i < nb; i++) {
+ Map<String, Comparable> thread = threads.get(i);
+ long tid = (Long) thread.get(STAT_TID);
+ for (String key : cstats) {
+ if (sb.length() > 0) {
+ sb.append(" ");
+ }
+ long last;
+ Object cur = thread.get(key);
+ Object prv = previous.computeIfAbsent(tid, id -> new HashMap<>()).put(key, cur);
+ if (prv != null && !prv.equals(cur)) {
+ changes.computeIfAbsent(tid, id -> new HashMap<>()).put(key, now);
+ last = now;
+ } else {
+ last = changes.computeIfAbsent(tid, id -> new HashMap<>()).getOrDefault(key, 0L);
+ }
+ long fade = delay * 24;
+ if (now - last < fade) {
+ int r = (int) ((now - last) / (fade / 24));
+ sb.style(sb.style().foreground(255 - r).background(9));
+ }
+ align(sb, values.get(i).get(key), widths.get(key), columns.get(key).align);
+ sb.style(sb.style().backgroundOff().foregroundOff());
+ }
+ lines.add(sb.toAttributedString());
+ sb.setLength(0);
+ }
+
+ display.update(lines, 0);
+ terminal.flush();
+ }
+
+ private Comparator<Map<String, Comparable>> buildComparator(List<String> sort) {
+ if (sort == null || sort.isEmpty()) {
+ sort = Collections.singletonList(STAT_TID);
+ }
+ Comparator<Map<String, Comparable>> comparator = null;
+ for (String key : sort) {
+ boolean asc = true;
+ if (key.startsWith("+")) {
+ key = key.substring(1);
+ } else if (key.startsWith("-")) {
+ key = key.substring(1);
+ asc = false;
+ }
+ if (!columns.containsKey(key)) {
+ throw new IllegalArgumentException("Unsupported sort key: " + key);
+ }
+ String fkey = key;
+ Comparator<Map<String, Comparable>> comp = Comparator.comparing(
+ (Function<Map<String, Comparable>, Comparable>) m -> m.get(fkey),
+ asc ? Comparator.reverseOrder() : Comparator.naturalOrder());
+ if (comparator != null) {
+ comparator = comparator.thenComparing(comp);
+ } else {
+ comparator = comp;
+ }
+ }
+ return comparator;
+ }
+
+ private void register(String name, Align align, String header, Function<Object, String> format) {
+ columns.put(name, new Column(name, align, header, format));
+ }
+
+ private static String nanos(long nanos) {
+ return millis(nanos / 1_000_000L);
+ }
+
+ private static String millis(long millis) {
+ long secs = millis / 1_000;
+ millis = millis % 1000;
+ long mins = secs / 60;
+ secs = secs % 60;
+ long hours = mins / 60;
+ mins = mins % 60;
+ if (hours > 0) {
+ return String.format("%d:%02d:%02d.%03d", hours, mins, secs, millis);
+ } else if (mins > 0) {
+ return String.format("%d:%02d.%03d", mins, secs, millis);
+ } else {
+ return String.format("%d.%03d", secs, millis);
+ }
+ }
+ private static Function<Object, String> padcut(int nb) {
+ return o -> padcut(o.toString(), nb);
+ }
+ private static String padcut(String str, int nb) {
+ if (str.length() <= nb) {
+ StringBuilder sb = new StringBuilder(nb);
+ sb.append(str);
+ while (sb.length() < nb) {
+ sb.append(' ');
+ }
+ return sb.toString();
+ } else {
+ StringBuilder sb = new StringBuilder(nb);
+ sb.append(str, 0, nb - 3);
+ sb.append("...");
+ return sb.toString();
+ }
+ }
+ private static String memory(long cur, long max) {
+ if (max > 0) {
+ String smax = humanReadableByteCount(max, false);
+ String cmax = humanReadableByteCount(cur, false);
+ StringBuilder sb = new StringBuilder(smax.length() * 2 + 3);
+ for (int i = cmax.length(); i < smax.length(); i++) {
+ sb.append(' ');
+ }
+ sb.append(cmax).append(" / ").append(smax);
+ return sb.toString();
+ } else {
+ return humanReadableByteCount(cur, false);
+ }
+ }
+
+ private static String humanReadableByteCount(long bytes, boolean si) {
+ int unit = si ? 1000 : 1024;
+ if (bytes < 1024) return bytes + " B";
+ int exp = (int) (Math.log(bytes) / Math.log(1024));
+ String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i");
+ return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
+ }
+
+ /**
+ * This is for long running commands to be interrupted by ctrl-c
+ */
+ private void checkInterrupted() throws InterruptedException {
+ Thread.yield();
+ if (Thread.currentThread().isInterrupted()) {
+ throw new InterruptedException();
+ }
+ }
+
+ private void bindKeys(KeyMap<Operation> map) {
+ map.bind(Operation.HELP, "h", "?");
+ map.bind(Operation.EXIT, "q", ":q", "Q", ":Q", "ZZ");
+ map.bind(Operation.INCREASE_DELAY, "+");
+ map.bind(Operation.DECREASE_DELAY, "-");
+ map.bind(Operation.CLEAR, KeyMap.ctrl('L'));
+ map.bind(Operation.REVERSE, "r");
+ }
+
+ private static class Column {
+ final String name;
+ final Align align;
+ final String header;
+ final Function<Object, String> format;
+
+ Column(String name, Align align, String header, Function<Object, String> format) {
+ this.name = name;
+ this.align = align;
+ this.header = header;
+ this.format = format;
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/aebb6fd0/shell/commands/src/main/resources/META-INF/services/org/apache/karaf/shell/commands
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/resources/META-INF/services/org/apache/karaf/shell/commands b/shell/commands/src/main/resources/META-INF/services/org/apache/karaf/shell/commands
index 4e18e04..686ce64 100644
--- a/shell/commands/src/main/resources/META-INF/services/org/apache/karaf/shell/commands
+++ b/shell/commands/src/main/resources/META-INF/services/org/apache/karaf/shell/commands
@@ -41,5 +41,6 @@ org.apache.karaf.shell.commands.impl.SourceAction
org.apache.karaf.shell.commands.impl.TacAction
org.apache.karaf.shell.commands.impl.TailAction
org.apache.karaf.shell.commands.impl.ThreadsAction
+org.apache.karaf.shell.commands.impl.TTopAction
org.apache.karaf.shell.commands.impl.WatchAction
org.apache.karaf.shell.commands.impl.WcAction
[2/2] karaf git commit: Fix small indentation problem in JsonWriter
Posted by gn...@apache.org.
Fix small indentation problem in JsonWriter
Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/887770ed
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/887770ed
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/887770ed
Branch: refs/heads/master
Commit: 887770ed417df4a16ad808b983b1f8a24409a0b4
Parents: aebb6fd
Author: Guillaume Nodet <gn...@apache.org>
Authored: Wed Oct 12 11:14:56 2016 +0200
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Mon Oct 17 11:59:01 2016 +0200
----------------------------------------------------------------------
.../java/org/apache/karaf/features/internal/util/JsonWriter.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/karaf/blob/887770ed/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java b/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java
index b65ea27..0135980 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/util/JsonWriter.java
@@ -88,7 +88,7 @@ public final class JsonWriter {
if (indent >= 0) {
writer.append(' ');
}
- write(writer, entry.getValue(), indent + 1);
+ write(writer, entry.getValue(), indent >= 0 ? indent + 1 : -1);
}
if (indent >= 0) {
indent(writer, indent);