From c91c42aabaa30d38c88a8a4ddd7b5ba2a99e40eb Mon Sep 17 00:00:00 2001 From: gongdewei Date: Wed, 21 Jul 2021 16:53:05 +0800 Subject: [PATCH] truncate string value, polish object renderer --- .../arthas/core/command/model/ObjectVO.java | 4 +- .../core/util/object/ObjectInspector.java | 39 +++++--- .../core/util/object/ObjectRenderer.java | 98 ++++++++++--------- .../arthas/core/model/ObjectRenderTest.java | 72 +++++++++++++- 4 files changed, 152 insertions(+), 61 deletions(-) diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/ObjectVO.java b/core/src/main/java/com/taobao/arthas/core/command/model/ObjectVO.java index 58ab88dd..383758d6 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/model/ObjectVO.java +++ b/core/src/main/java/com/taobao/arthas/core/command/model/ObjectVO.java @@ -22,13 +22,13 @@ public class ObjectVO { //map entry key protected ObjectVO key; - // string, collection/array + //collection/array size private Integer size; //complex object's fields protected List fields; - //exceed number limit + // whether exceed number limit private Boolean exceedLimit; private Integer objectNumberLimit; diff --git a/core/src/main/java/com/taobao/arthas/core/util/object/ObjectInspector.java b/core/src/main/java/com/taobao/arthas/core/util/object/ObjectInspector.java index fa457fbc..04be9efc 100644 --- a/core/src/main/java/com/taobao/arthas/core/util/object/ObjectInspector.java +++ b/core/src/main/java/com/taobao/arthas/core/util/object/ObjectInspector.java @@ -22,23 +22,20 @@ public class ObjectInspector { public static final int DEFAULT_OBJECT_NUMBER_LIMIT = 500; public static final int DEFAULT_ARRAY_LEN_LIMIT = 100; + public static final int DEFAULT_STRING_LEN_LIMIT = 4096; - private int objectNumberLimit; - private int arrayLenLimit; + + private int objectNumberLimit = DEFAULT_OBJECT_NUMBER_LIMIT; + private int arrayLenLimit = DEFAULT_ARRAY_LEN_LIMIT; + private int stringLenLimit = DEFAULT_STRING_LEN_LIMIT; //子对象数量 private int objectCount; public ObjectInspector() { - this(DEFAULT_OBJECT_NUMBER_LIMIT, DEFAULT_ARRAY_LEN_LIMIT); } public ObjectInspector(int objectNumberLimit) { - this(objectNumberLimit, DEFAULT_ARRAY_LEN_LIMIT); - } - - public ObjectInspector(int objectNumberLimit, int arrayLenLimit) { - this.setObjectNumberLimit(objectNumberLimit); - this.setArrayLenLimit(arrayLenLimit); + this.objectNumberLimit = objectNumberLimit; } public ObjectVO inspect(Object object, int expand) { @@ -51,11 +48,24 @@ public class ObjectInspector { return objectVO; } catch (ObjectTooLargeException e) { // unreachable statement - return new ObjectVO(object != null ? object.getClass().getSimpleName() : "", "..."); + return new ObjectVO(object != null ? object.getClass().getSimpleName() : "", e.getMessage()); } } private ObjectVO inspectObject(Object obj, int deep, int expand) throws ObjectTooLargeException { + ObjectVO objectVO = this.inspectObject0(obj, deep, expand); + if (objectVO != null && objectVO.getValue() != null && objectVO.getValue() instanceof String) { + String stringValue = (String) objectVO.getValue(); + if (stringValue.length() > stringLenLimit) { + // truncate string value + objectVO.setValue(stringValue.substring(0, stringLenLimit) + "...(truncated " + + (stringValue.length() - stringLenLimit) + " chars)"); + } + } + return objectVO; + } + + private ObjectVO inspectObject0(Object obj, int deep, int expand) throws ObjectTooLargeException { checkObjectAmount(); if (null == obj) { @@ -588,8 +598,15 @@ public class ObjectInspector { this.arrayLenLimit = arrayLenLimit < 10 ? 10 :arrayLenLimit; } + public int getStringLenLimit() { + return stringLenLimit; + } - // --------------- static methods --------------------// + public void setStringLenLimit(int stringLenLimit) { + this.stringLenLimit = stringLenLimit < 100 ? 100 : stringLenLimit; + } + +// --------------- static methods --------------------// private static Object[] toArray(int[] arrays, int limit) { limit = Math.min(arrays.length, limit); diff --git a/core/src/main/java/com/taobao/arthas/core/util/object/ObjectRenderer.java b/core/src/main/java/com/taobao/arthas/core/util/object/ObjectRenderer.java index 51a3079d..04fc1283 100644 --- a/core/src/main/java/com/taobao/arthas/core/util/object/ObjectRenderer.java +++ b/core/src/main/java/com/taobao/arthas/core/util/object/ObjectRenderer.java @@ -1,6 +1,7 @@ package com.taobao.arthas.core.util.object; import com.taobao.arthas.core.command.model.ObjectVO; +import com.taobao.arthas.core.util.StringUtils; import java.util.Collection; @@ -29,30 +30,25 @@ public abstract class ObjectRenderer { return; } if (vo.getKey() != null) { - //kv entry - render(vo.getKey(), deep, sb); - sb.append(" : "); - render((ObjectVO) vo.getValue(), deep, sb); + renderKeyValueEntry(vo, deep, sb); return; } if (vo.getName() != null) { + // object field name sb.append(vo.getName()).append('='); } + if (vo.getType() != null) { - //object + //object type sb.append('@').append(vo.getType()).append('['); } + // object value start int nextDeep = deep+1; if (vo.getFields() != null) { - //fields - sb.append('\n'); - for (ObjectVO field : vo.getFields()) { - renderTab(sb, nextDeep); - render(field, nextDeep, sb); - sb.append(",\n"); - } + // complex object: fields is not null + renderComplexObject(vo, nextDeep, sb); } else { //value if (vo.getSize() != null) { @@ -68,6 +64,8 @@ public abstract class ObjectRenderer { renderValue(vo, deep, sb); } } + + // object value end if (vo.getType() != null) { if (sb.charAt(sb.length() - 1) == '\n') { renderTab(sb, deep); @@ -76,6 +74,25 @@ public abstract class ObjectRenderer { } } + private static void renderComplexObject(ObjectVO vo, int deep, StringBuffer sb) { + sb.append('\n'); + for (ObjectVO field : vo.getFields()) { + if (StringUtils.isEmpty(field.getName())) { + throw new IllegalArgumentException("Complex object's field name is empty: " + sb + "... "); + } + renderTab(sb, deep); + render(field, deep, sb); + sb.append(",\n"); + } + } + + private static void renderKeyValueEntry(ObjectVO vo, int deep, StringBuffer sb) { + //kv entry + render(vo.getKey(), deep, sb); + sb.append(" : "); + render((ObjectVO) vo.getValue(), deep, sb); + } + private static StringBuffer renderTab(StringBuffer sb, int deep) { for (int i = 0; i < deep; i++) { sb.append(TAB); @@ -91,47 +108,40 @@ public abstract class ObjectRenderer { sb.append('\n'); Collection collection = (Collection) value; for (Object e : collection) { - if (e instanceof ObjectVO) { - ObjectVO objectVO = (ObjectVO) e; - renderTab(sb, nextDeep); - render(objectVO, nextDeep, sb); - sb.append(",\n"); - } else { - renderTab(sb, nextDeep); - sb.append(e).append(",\n"); - } - } - //如果没有完全显示所有元素,则添加省略提示 - int count = collection.size(); - if (vo.getSize() > count) { - String msg = count + " out of " + vo.getSize() + " displayed, " + (vo.getSize() - count) + " remaining.\n"; - renderTab(sb, nextDeep); - sb.append(msg); + renderElement(e, nextDeep, sb); } + renderSize(vo, collection.size(), nextDeep, sb); } else if (value instanceof Object[]) { sb.append('\n'); Object[] objs = (Object[]) value; for (int i = 0; i < objs.length; i++) { - Object obj = objs[i]; - if (obj instanceof ObjectVO) { - ObjectVO objectVO = (ObjectVO) obj; - renderTab(sb, nextDeep); - render(objectVO, nextDeep, sb); - sb.append(",\n"); - } else { - renderTab(sb, nextDeep); - sb.append(obj).append(",\n"); - } - } - //如果没有完全显示所有元素,则添加省略提示 - int count = objs.length; - if (vo.getSize() > count) { - String msg = count + " out of " + vo.getSize() + " displayed, " + (vo.getSize() - count) + " remaining.\n"; - renderTab(sb, nextDeep).append(msg); + renderElement(objs[i], nextDeep, sb); } + renderSize(vo, objs.length, nextDeep, sb); } else { sb.append(value); } } + private static void renderElement(Object obj, int deep, StringBuffer sb) { + if (obj instanceof ObjectVO) { + ObjectVO objectVO = (ObjectVO) obj; + renderTab(sb, deep); + render(objectVO, deep, sb); + sb.append(",\n"); + } else { + renderTab(sb, deep); + sb.append(obj).append(",\n"); + } + } + + private static void renderSize(ObjectVO vo, int elemCount, int deep, StringBuffer sb) { + //如果没有完全显示所有元素,则添加省略提示 + if (vo.getSize() > elemCount) { + String msg = elemCount + " out of " + vo.getSize() + " displayed, " + (vo.getSize() - elemCount) + " remaining.\n"; + renderTab(sb, deep); + sb.append(msg); + } + } + } diff --git a/core/src/test/java/com/taobao/arthas/core/model/ObjectRenderTest.java b/core/src/test/java/com/taobao/arthas/core/model/ObjectRenderTest.java index ee96d8a0..317b097c 100644 --- a/core/src/test/java/com/taobao/arthas/core/model/ObjectRenderTest.java +++ b/core/src/test/java/com/taobao/arthas/core/model/ObjectRenderTest.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -26,6 +27,7 @@ public class ObjectRenderTest { static int arrayLenLimit = 15; static int objectNumLimit = 30; + static int stringLenLimit = 100; @Test public void testSimpleObjects() { @@ -357,7 +359,8 @@ public class ObjectRenderTest { @Test public void testObjectTooLargeException() { NestedClass nestedClass = new NestedClass(100); - ObjectInspector objectInspector = new ObjectInspector(10, arrayLenLimit); + ObjectInspector objectInspector = new ObjectInspector(10); + objectInspector.setArrayLenLimit(arrayLenLimit); ObjectVO objectVO = objectInspector.inspect(nestedClass, 4); printObject(objectVO); @@ -380,6 +383,64 @@ public class ObjectRenderTest { "] Number of objects exceeds limit: 10")); } + @Test + public void testTruncateStringValue() { + + String str = "This is a very long string."; + StringBuilder sb = new StringBuilder(str.length()*10); + for (int i = 0; i < 10; i++) { + sb.append(str); + } + String longString = sb.toString(); + + // string + ObjectVO objectVO = inspectObject(longString, 1); + printObject(objectVO); + Assert.assertTrue(ObjectRenderer.render(objectVO).contains("(truncated 170 chars)")); + + // array + Object[] objects = new Object[]{longString}; + objectVO = inspectObject(objects, 2); + printObject(objectVO); + Assert.assertTrue(ObjectRenderer.render(objectVO).contains("(truncated 170 chars)")); + + // collection + List list = new ArrayList(); + list.add(longString); + objectVO = inspectObject(objects, 2); + printObject(objectVO); + Assert.assertTrue(ObjectRenderer.render(objectVO).contains("(truncated 170 chars)")); + + // map + Map map = new HashMap(); + map.put("longString", longString); + objectVO = inspectObject(map, 2); + printObject(objectVO); + Assert.assertTrue(ObjectRenderer.render(objectVO).contains("(truncated 170 chars)")); + + // complex object + SonBean sonBean = new SonBean(); + sonBean.setJ(longString); + objectVO = inspectObject(sonBean, 2); + printObject(objectVO); + Assert.assertTrue(ObjectRenderer.render(objectVO).contains("(truncated 170 chars)")); + + // nest map + Map nestMap = new HashMap(); + nestMap.put("sonBean", sonBean); + objectVO = inspectObject(nestMap, 3); + printObject(objectVO); + Assert.assertTrue(ObjectRenderer.render(objectVO).contains("(truncated 170 chars)")); + + // nest list + List nestList = new ArrayList(); + nestList.add(nestMap); + objectVO = inspectObject(nestList, 3); + printObject(objectVO); + Assert.assertTrue(ObjectRenderer.render(objectVO).contains("(truncated 170 chars)")); + + } + /** * 显示基类属性值 */ @@ -419,12 +480,15 @@ public class ObjectRenderTest { } } - private ObjectInspector newInspector() { - return new ObjectInspector(objectNumLimit, arrayLenLimit); + private ObjectInspector newInspector(int objectNumLimit, int arrayLenLimit, int stringLenLimit) { + ObjectInspector objectInspector = new ObjectInspector(objectNumLimit); + objectInspector.setArrayLenLimit(arrayLenLimit); + objectInspector.setStringLenLimit(stringLenLimit); + return objectInspector; } private ObjectVO inspectObject(Object object, int expand) { - return new ObjectInspector(objectNumLimit, arrayLenLimit).inspect(object, expand); + return newInspector(objectNumLimit, arrayLenLimit, stringLenLimit).inspect(object, expand); } private void testSimpleArray(Object array, String testJson, String testString) {