Skip to content

Commit be4807a

Browse files
committed
cli: Fix for display of name column
1 parent f82e90d commit be4807a

5 files changed

Lines changed: 121 additions & 47 deletions

File tree

cli/src/main/java/org/ebean/monitor/cli/Charts.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,9 @@ static String unit(TopCommand.Sort sort) {
4242
};
4343
}
4444

45-
/** A row's display label: the group value, falling back to the label tag. */
45+
/** A row's single-line chart identity: {@code name:label}, with fallbacks. */
4646
private static String rowLabel(TopGroup r) {
47-
if (r.group() != null) {
48-
return r.group();
49-
}
50-
return r.label() == null ? "" : r.label();
47+
return Display.chartLabel(r);
5148
}
5249

5350
/**
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.ebean.monitor.cli;
2+
3+
import org.ebean.monitor.v1.model.TopGroup;
4+
5+
/**
6+
* Shared rendering of a metric's display identity.
7+
*
8+
* <p>In the v2 metric model the family {@code name} is the primary identity
9+
* (e.g. {@code ebean.txn}, {@code ebean.query}) and {@code label} is the
10+
* secondary discriminator (e.g. {@code readonly}, {@code DMessage.findMessages}).
11+
* Tables render these as separate {@code NAME} and {@code LABEL} columns; where a
12+
* single column is required (a chart bar) they are joined as {@code name:label}.
13+
*/
14+
final class Display {
15+
16+
private Display() {
17+
}
18+
19+
/** Single-line identity for charts: {@code name:label}, with sensible fallbacks. */
20+
static String chartLabel(TopGroup r) {
21+
return join(r.name(), r.label(), r.group());
22+
}
23+
24+
/** Join a metric's {@code name} and {@code label} into a single {@code name:label} line. */
25+
static String join(String name, String label, String fallback) {
26+
if (name == null || name.isBlank()) {
27+
if (label != null && !label.isBlank()) {
28+
return label;
29+
}
30+
return fallback == null ? "" : fallback;
31+
}
32+
if (label == null || label.isBlank() || label.equals(name)) {
33+
return name;
34+
}
35+
return name + ":" + label;
36+
}
37+
}

cli/src/main/java/org/ebean/monitor/cli/Interactive.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,27 @@
3434
final class Interactive {
3535

3636
private static final int BAR_WIDTH = 24;
37+
private static final int NAME_MAX = 40;
3738
private static final int LABEL_MAX = 70;
3839
private static final int HASH_SHORT = 12;
3940

4041
/** Which ranked list is being shown — drives the per-mode columns. */
4142
enum Mode { TOP, MISSING }
4243

43-
/** A flattened ranked row: enough to render and to drive the row actions. */
44-
record Row(String app, String hash, String label, double value, String unit,
44+
/**
45+
* A flattened ranked row: enough to render and to drive the row actions.
46+
*
47+
* <p>{@code name} is the v2 metric family (display only). {@code label} carries
48+
* the aggregation dimension value and doubles as the drill-down filter key, so it
49+
* must stay the raw value (not a name-decorated display string).
50+
*/
51+
record Row(String app, String hash, String name, String label, double value, String unit,
4552
long count, long total, long mean, long max,
4653
boolean planCapable, Long captureCount, String lastCaptured) {
4754

4855
/** Compact constructor for tests that only need the identity + ranked value. */
4956
Row(String app, String hash, String label, double value, String unit) {
50-
this(app, hash, label, value, unit, 0, 0, 0, 0, false, null, null);
57+
this(app, hash, null, label, value, unit, 0, 0, 0, 0, false, null, null);
5158
}
5259
}
5360

@@ -212,7 +219,7 @@ private static List<Row> toTopRows(List<TopGroup> src, By by) {
212219
List<Row> out = new ArrayList<>(src.size());
213220
for (TopGroup r : src) {
214221
String label = r.label() != null ? r.label() : r.group();
215-
out.add(new Row(r.app(), r.key(), label, measure(r, by), unit,
222+
out.add(new Row(r.app(), r.key(), r.name(), label, measure(r, by), unit,
216223
nz(r.count()), nz(r.totalMicros()), nz(r.meanMicros()), nz(r.maxMicros()),
217224
Boolean.TRUE.equals(r.planCapable()), null, null));
218225
}
@@ -223,7 +230,7 @@ private static List<Row> toMissingRows(List<MissingPlanMetric> src, By by) {
223230
String unit = by.unit();
224231
List<Row> out = new ArrayList<>(src.size());
225232
for (MissingPlanMetric m : src) {
226-
out.add(new Row(m.app(), m.key(), m.label(), measure(m, by), unit,
233+
out.add(new Row(m.app(), m.key(), null, m.label(), measure(m, by), unit,
227234
m.count(), m.totalMicros(), m.meanMicros(), m.maxMicros(),
228235
false, m.captureCount(), m.lastCapturedAt() == null ? "never" : m.lastCapturedAt().toString()));
229236
}
@@ -512,7 +519,7 @@ private boolean drillGroup(Row group) {
512519
return true;
513520
}
514521
if (!hashRows.isEmpty()) {
515-
return drillLoop(group.label(), toTopRows(hashRows, by));
522+
return drillLoop(Display.join(group.name(), group.label(), group.label()), toTopRows(hashRows, by));
516523
}
517524
}
518525
// No windowed activity (or no drill fetcher): list the catalog so the
@@ -531,9 +538,9 @@ private boolean drillGroup(Row group) {
531538
final List<Row> rows = new ArrayList<>(metrics.size());
532539
for (AppMetric m : metrics) {
533540
final String lbl = m.label() != null ? m.label() : m.name();
534-
rows.add(new Row(app, m.key(), lbl, 0, "us", 0, 0, 0, 0, true, null, null));
541+
rows.add(new Row(app, m.key(), m.name(), lbl, 0, "us", 0, 0, 0, 0, true, null, null));
535542
}
536-
return drillLoop(group.label(), rows);
543+
return drillLoop(Display.join(group.name(), group.label(), group.label()), rows);
537544
}
538545

539546
/** A nested list of the individual queries under a drilled group. */
@@ -990,6 +997,7 @@ private List<Col> preColumns() {
990997
if (showApp) {
991998
cols.add(new Col("APP", false, r -> r.app() == null ? "" : r.app()));
992999
}
1000+
cols.add(new Col("NAME", false, r -> r.name() == null ? "" : tail(r.name(), NAME_MAX)));
9931001
cols.add(new Col("LABEL", false, r -> tail(r.label(), LABEL_MAX)));
9941002
cols.add(new Col("HASH", false, r -> shortHash(r.hash())));
9951003
cols.add(new Col("COUNT", true, r -> num(r.count())));

cli/src/main/java/org/ebean/monitor/cli/TopCommand.java

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.ebean.monitor.cli;
22

33
import java.util.List;
4+
import java.util.Locale;
45
import java.util.concurrent.Callable;
56

67
import org.ebean.monitor.v1.model.TopGroup;
@@ -115,7 +116,6 @@ public Integer call() {
115116
if (env == null) {
116117
env = ConfigDefaults.envOrNull();
117118
}
118-
final boolean byHash = "hash".equalsIgnoreCase(by);
119119
final boolean gauge = sort == Sort.value;
120120
try (Insight insight = Insight.open(conn)) {
121121
java.util.function.Function<String, List<TopGroup>> fetch = ob -> (app == null)
@@ -140,57 +140,88 @@ public Integer call() {
140140
Charts.printPareto(rows, sort);
141141
return 0;
142142
}
143-
printTable(rows, byHash, gauge);
143+
printTable(rows, gauge);
144144
return 0;
145145
}
146146
}
147147

148-
private void printTable(List<TopGroup> rows, boolean byHash, boolean gauge) {
148+
private void printTable(List<TopGroup> rows, boolean gauge) {
149+
final boolean byHash = "hash".equalsIgnoreCase(by);
150+
final boolean byName = "name".equalsIgnoreCase(by);
151+
// A "tag" dimension is any grouping other than name/label/hash (e.g. type, kind):
152+
// NAME and LABEL aren't single-valued, so the tag value gets its own column.
153+
final boolean tagDim = !byHash && !byName && !"label".equalsIgnoreCase(by);
154+
final String tagHeader = by.toUpperCase(Locale.ROOT);
155+
149156
int appWidth = "APP".length();
150-
int groupWidth = (byHash ? "LABEL" : "GROUP").length();
157+
int nameWidth = "NAME".length();
158+
int labelWidth = "LABEL".length();
159+
int tagWidth = tagHeader.length();
151160
for (TopGroup r : rows) {
152161
if (r.app() != null) {
153162
appWidth = Math.max(appWidth, r.app().length());
154163
}
155-
String groupValue = byHash ? (r.label() == null ? r.group() : r.label()) : r.group();
156-
if (groupValue != null) {
157-
groupWidth = Math.max(groupWidth, groupValue.length());
164+
nameWidth = Math.max(nameWidth, nv(r.name()).length());
165+
labelWidth = Math.max(labelWidth, nv(r.label()).length());
166+
if (tagDim) {
167+
tagWidth = Math.max(tagWidth, nv(r.group()).length());
158168
}
159169
}
170+
171+
String idFmt = "%-" + appWidth + "s %-" + nameWidth + "s %-" + labelWidth + "s"
172+
+ (tagDim ? " %-" + tagWidth + "s" : "");
173+
160174
if (gauge) {
161-
String headFmt = "%-" + appWidth + "s %-" + groupWidth + "s %16s %8s %6s%n";
162-
String rowFmt = "%-" + appWidth + "s %-" + groupWidth + "s %16s %8d %6d%n";
163-
System.out.printf(headFmt, "APP", "GROUP", "VALUE", "WINDOW", "HASHES");
175+
System.out.printf(idFmt + " %16s %8s %6s%n", idHead(tagDim, tagHeader, "VALUE", "WINDOW", "HASHES"));
164176
for (TopGroup r : rows) {
165-
System.out.printf(rowFmt, nv(r.app()), nv(r.group()),
166-
r.value() == null ? "" : String.format("%,.0f", r.value()),
167-
r.windowMinutes(), r.hashCount());
177+
System.out.printf(idFmt + " %16s %8d %6d%n", idRow(r, tagDim,
178+
r.value() == null ? "" : String.format("%,.0f", r.value()), r.windowMinutes(), r.hashCount()));
168179
}
169180
return;
170181
}
171182
if (byHash) {
172-
String headFmt = "%-" + appWidth + "s %-" + groupWidth + "s %10s %16s %14s %14s %8s %5s %s%n";
173-
String rowFmt = "%-" + appWidth + "s %-" + groupWidth + "s %10d %16d %14d %14d %8d %5s %s%n";
174-
System.out.printf(headFmt,
175-
"APP", "LABEL", "COUNT", "TOTAL(us)", "MEAN(us)", "MAX(us)", "WINDOW", "PLAN", "HASH");
183+
System.out.printf(idFmt + " %10s %16s %14s %14s %8s %5s %s%n",
184+
idHead(false, tagHeader, "COUNT", "TOTAL(us)", "MEAN(us)", "MAX(us)", "WINDOW", "PLAN", "HASH"));
176185
for (TopGroup r : rows) {
177-
System.out.printf(rowFmt,
178-
nv(r.app()), nv(r.label() == null ? r.group() : r.label()),
186+
System.out.printf(idFmt + " %10d %16d %14d %14d %8d %5s %s%n", idRow(r, false,
179187
z(r.count()), z(r.totalMicros()), z(r.meanMicros()), z(r.maxMicros()),
180-
r.windowMinutes(), Boolean.TRUE.equals(r.planCapable()) ? "yes" : "no", nv(r.key()));
188+
r.windowMinutes(), Boolean.TRUE.equals(r.planCapable()) ? "yes" : "no", nv(r.key())));
181189
}
182190
return;
183191
}
184-
String headFmt = "%-" + appWidth + "s %-" + groupWidth + "s %10s %16s %14s %14s %8s %6s%n";
185-
String rowFmt = "%-" + appWidth + "s %-" + groupWidth + "s %10d %16d %14d %14d %8d %6d%n";
186-
System.out.printf(headFmt,
187-
"APP", "GROUP", "COUNT", "TOTAL(us)", "MEAN(us)", "MAX(us)", "WINDOW", "HASHES");
192+
System.out.printf(idFmt + " %10s %16s %14s %14s %8s %6s%n",
193+
idHead(tagDim, tagHeader, "COUNT", "TOTAL(us)", "MEAN(us)", "MAX(us)", "WINDOW", "HASHES"));
188194
for (TopGroup r : rows) {
189-
System.out.printf(rowFmt,
190-
nv(r.app()), nv(r.group()),
195+
System.out.printf(idFmt + " %10d %16d %14d %14d %8d %6d%n", idRow(r, tagDim,
191196
z(r.count()), z(r.totalMicros()), z(r.meanMicros()), z(r.maxMicros()),
192-
r.windowMinutes(), r.hashCount());
197+
r.windowMinutes(), r.hashCount()));
198+
}
199+
}
200+
201+
/** Header cells: APP, NAME, LABEL, [tag], then the supplied measure headers. */
202+
private static Object[] idHead(boolean tagDim, String tagHeader, Object... rest) {
203+
List<Object> cells = new java.util.ArrayList<>();
204+
cells.add("APP");
205+
cells.add("NAME");
206+
cells.add("LABEL");
207+
if (tagDim) {
208+
cells.add(tagHeader);
209+
}
210+
java.util.Collections.addAll(cells, rest);
211+
return cells.toArray();
212+
}
213+
214+
/** Row cells: app, name, label, [tag value], then the supplied measure values. */
215+
private static Object[] idRow(TopGroup r, boolean tagDim, Object... rest) {
216+
List<Object> cells = new java.util.ArrayList<>();
217+
cells.add(nv(r.app()));
218+
cells.add(nv(r.name()));
219+
cells.add(nv(r.label()));
220+
if (tagDim) {
221+
cells.add(nv(r.group()));
193222
}
223+
java.util.Collections.addAll(cells, rest);
224+
return cells.toArray();
194225
}
195226

196227
private static String nv(@Nullable String s) {

cli/src/test/java/org/ebean/monitor/cli/InteractiveRenderTest.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,24 @@ class InteractiveRenderTest {
1414

1515
private static final String HASH = "a2e2082df04620910f8fa034561b3346";
1616

17-
private static Interactive.Row top(String app, String hash, String label, long mean) {
18-
return new Interactive.Row(app, hash, label, mean, "us", 1000, 5_000_000, mean, 120_000, true, null, null);
17+
private static Interactive.Row top(String app, String hash, String name, String label, long mean) {
18+
return new Interactive.Row(app, hash, name, label, mean, "us", 1000, 5_000_000, mean, 120_000, true, null, null);
1919
}
2020

2121
@Test
2222
void renderList_topMode_showsCoreColumnsValuesAndShortHash() {
2323
var i = Interactive.forRender(Interactive.Mode.TOP, true);
2424
List<Interactive.Row> rows = List.of(
25-
top("central-access", HASH, "orm.A.find", 93203),
26-
top("central-access", "h2", "orm.B.find", 12612));
25+
top("central-access", HASH, "ebean.query", "orm.A.find", 93203),
26+
top("central-access", "h2", "ebean.query", "orm.B.find", 12612));
2727

2828
String out = i.renderList("Top 2 by mean", rows);
2929
String header = headerLine(out.split("\n"));
3030

31-
assertThat(header).contains("#").contains("APP").contains("LABEL")
31+
assertThat(header).contains("#").contains("APP").contains("NAME").contains("LABEL")
3232
.contains("COUNT").contains("TOTAL(us)").contains("MEAN(us)").contains("MAX(us)")
3333
.contains("PLAN").contains("HASH").contains("chart");
34+
assertThat(out).contains("ebean.query"); // v2 family name column
3435
assertThat(out).contains("orm.A.find");
3536
assertThat(out).contains("93,203"); // grouped digits, no unit suffix in columns
3637
assertThat(out).contains("a2e2082df046"); // 12-char short hash
@@ -41,14 +42,14 @@ void renderList_topMode_showsCoreColumnsValuesAndShortHash() {
4142
@Test
4243
void renderList_singleApp_hidesAppColumn() {
4344
var i = Interactive.forRender(Interactive.Mode.TOP, false);
44-
String out = i.renderList("Top 1 by mean", List.of(top("central-access", HASH, "orm.A.find", 5)));
45+
String out = i.renderList("Top 1 by mean", List.of(top("central-access", HASH, "ebean.query", "orm.A.find", 5)));
4546
assertThat(headerLine(out.split("\n"))).doesNotContain("APP");
4647
}
4748

4849
@Test
4950
void renderList_missingMode_showsCapturesAndCaptured() {
5051
var i = Interactive.forRender(Interactive.Mode.MISSING, false);
51-
Interactive.Row r = new Interactive.Row("app", HASH, "orm.CDriver.driver_all_since",
52+
Interactive.Row r = new Interactive.Row("app", HASH, null, "orm.CDriver.driver_all_since",
5253
0, "us", 0, 0, 0, 0, false, 0L, null);
5354

5455
String out = i.renderList("Missing plans 1 by total", List.of(r));

0 commit comments

Comments
 (0)