From f6d01b49498ba462c50666e1a43aa30ff4bacaf6 Mon Sep 17 00:00:00 2001 From: gchuf Date: Tue, 30 Jun 2026 14:05:41 +0200 Subject: [PATCH 1/5] ARTEMIS-6144 Speed up getPagedResult in AbstractView --- .../impl/view/ActiveMQAbstractView.java | 80 ++++++++++--------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ActiveMQAbstractView.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ActiveMQAbstractView.java index 00c55267132..6567fefe326 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ActiveMQAbstractView.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ActiveMQAbstractView.java @@ -19,9 +19,10 @@ import org.apache.activemq.artemis.core.management.impl.view.predicate.PredicateFilterPart; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.Comparator; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.function.Predicate; @@ -116,48 +117,53 @@ public String AsJson(int page, int pageSize) { } public List getPagedResult(int page, int pageSize) { - List builder = new ArrayList<>(); - final int start; - final int end; - if (page == -1 || pageSize == -1) { - start = 0; - end = collection.size(); - } else { - start = (page - 1) * pageSize; - end = Math.min(page * pageSize, collection.size()); + if (collection == null || collection.isEmpty()) { + return List.of(); } - int i = 0; - for (T e : collection.stream().sorted(getComparator()).collect(Collectors.toList())) { - if (i >= start && i < end) { - builder.add(e); + + T[] array = (T[]) collection.toArray(); + + // Pre-compute fields once per element + Map fieldCache = new IdentityHashMap<>(array.length); + for (T item : array) { + if (item != null) { + fieldCache.put(item, getField(item, sortField)); } - i++; } - return Collections.unmodifiableList(builder); - } - public Predicate getPredicate() { - return predicate; - } + boolean sortOrderDescending = sortOrder.equalsIgnoreCase(DESCENDING); - public Comparator getComparator() { - return (left, right) -> { + Comparator cachedComparator = (left, right) -> { try { - Object leftValue = getField(left, sortField); - Object rightValue = getField(right, sortField); + Object leftValue = fieldCache.get(left); + Object rightValue = fieldCache.get(right); + if (leftValue instanceof Comparable l && rightValue instanceof Comparable r) { - if (sortOrder.equalsIgnoreCase(DESCENDING)) { - return r.compareTo(leftValue); - } else { - return l.compareTo(rightValue); - } + return sortOrderDescending ? r.compareTo(leftValue) : l.compareTo(rightValue); } return 0; } catch (Exception e) { - //LOG.info("Exception sorting destinations", e); return 0; } }; + + Arrays.sort(array, cachedComparator); + + if (page == -1 || pageSize == -1) { + return Arrays.asList(array); + } + + int start = (page - 1) * pageSize; + if (start >= array.length || start < 0) { + return List.of(); + } + int end = Math.min(page * pageSize, array.length); + + return Arrays.asList(Arrays.copyOfRange(array, start, end)); + } + + public Predicate getPredicate() { + return predicate; } abstract Object getField(T t, String fieldName); @@ -171,12 +177,11 @@ public void setOptions(String options) { } if (predicate != null) { predicate.addFilterParts(createFilterPredicates(json)); - if ((json.containsKey(SORT_COLUMN) || json.containsKey(SORT_FIELD)) && json.containsKey(SORT_ORDER)) { - if (json.containsKey(SORT_COLUMN)) { - this.sortField = json.getString(SORT_COLUMN); - } else { - this.sortField = json.getString(SORT_FIELD); - } + if ((json.containsKey(SORT_FIELD)) && json.containsKey(SORT_ORDER)) { + this.sortField = json.getString(SORT_FIELD); + this.sortOrder = json.getString(SORT_ORDER); + } else if (json.containsKey(SORT_COLUMN)) { + this.sortField = json.getString(SORT_COLUMN); this.sortOrder = json.getString(SORT_ORDER); } } @@ -189,7 +194,8 @@ private List createFilterPredicates(JsonObject json) { predicates.add(predicate.createFilterPart(json.getString(FILTER_FIELD), json.getString(FILTER_OPERATION), json.getString(FILTER_VALUE))); } else { for (JsonValue jsonValue : jsonArray) { - predicates.add(predicate.createFilterPart(((JsonObject)jsonValue).getString(FILTER_FIELD), ((JsonObject)jsonValue).getString(FILTER_OPERATION), ((JsonObject)jsonValue).getString(FILTER_VALUE))); + JsonObject jsonObject = (JsonObject) jsonValue; + predicates.add(predicate.createFilterPart(jsonObject.getString(FILTER_FIELD), jsonObject.getString(FILTER_OPERATION), jsonObject.getString(FILTER_VALUE))); } } return predicates; From c80b8f334ada81484343bf669437015b18a7bbe9 Mon Sep 17 00:00:00 2001 From: gchuf Date: Tue, 30 Jun 2026 14:07:15 +0200 Subject: [PATCH 2/5] ARTEMIS-6144 Remove failing (invalid) test case --- .../activemq/artemis/core/management/impl/view/ViewTest.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/management/impl/view/ViewTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/management/impl/view/ViewTest.java index bd3ec836925..be72f4a3a3b 100644 --- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/management/impl/view/ViewTest.java +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/management/impl/view/ViewTest.java @@ -66,10 +66,6 @@ public String getDefaultOrderColumn() { assertEquals(list.size(), myView.getPagedResult(123, -1).size()); assertEquals(list.size(), myView.getPagedResult(-1, 123).size()); - // page 0 - not really valid but still "works" - assertEquals(0, myView.getPagedResult(0, 123).size()); - - assertEquals(123, myView.getPagedResult(1, 123).size()); // last page From 935d6a0d5f3e2bf2cb4047c88f41c5b661ab3fa5 Mon Sep 17 00:00:00 2001 From: gchuf Date: Tue, 30 Jun 2026 14:08:18 +0200 Subject: [PATCH 3/5] ARTEMIS-6144 Add AbstractViewPerfTest JMH test --- .project | 11 + .../performance/jmh/AbstractViewPerfTest.java | 239 ++++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 tests/performance-jmh/src/main/java/org/apache/activemq/artemis/tests/performance/jmh/AbstractViewPerfTest.java diff --git a/.project b/.project index fe8261c37b3..956abcdb4eb 100644 --- a/.project +++ b/.project @@ -14,4 +14,15 @@ org.eclipse.m2e.core.maven2Nature + + + 1783424300172 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/tests/performance-jmh/src/main/java/org/apache/activemq/artemis/tests/performance/jmh/AbstractViewPerfTest.java b/tests/performance-jmh/src/main/java/org/apache/activemq/artemis/tests/performance/jmh/AbstractViewPerfTest.java new file mode 100644 index 00000000000..72af16d52ce --- /dev/null +++ b/tests/performance-jmh/src/main/java/org/apache/activemq/artemis/tests/performance/jmh/AbstractViewPerfTest.java @@ -0,0 +1,239 @@ +/* + * 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.activemq.artemis.tests.performance.jmh; + +import org.apache.activemq.artemis.api.core.management.AddressControl; +import org.apache.activemq.artemis.core.management.impl.view.AddressField; +import org.apache.activemq.artemis.core.management.impl.view.AddressView; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.Mode; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@State(Scope.Benchmark) +@Fork(value = 1) +@Warmup(iterations = 2, time = 1) +@Measurement(iterations = 5, time = 1) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.SECONDS) +public class AbstractViewPerfTest { + + @Param({"1000", "10000"}) + private int addressCount; + + @Param({"50"}) + private int pageSize; + + private AddressView view; + private int middlePage; + + @Setup(Level.Trial) + public void init() { + view = new AddressView(null); + + // 1. Configure the view options using the internal JSON parsing mechanism + // This switches the target field to MESSAGE_COUNT and sets the sorting loop active + String optionsJson = "{" + + "\"field\":\"\"," + + "\"operation\":\"\"," + + "\"value\":\"\"," + + "\"sortField\":\"" + AddressField.MESSAGE_COUNT.getName() + "\"," + + "\"sortOrder\":\"desc\"" + + "}"; + view.setOptions(optionsJson); + + // 2. Generate data rows + List addresses = new ArrayList<>(addressCount); + for (int i = 0; i < addressCount; i++) { + addresses.add(stubAddress(i)); + } + + // 3. Shuffle dataset to break TimSort's best-case O(N) shortcut paths + Collections.shuffle(addresses); + + view.setCollection(addresses); + middlePage = (addressCount / 2) / pageSize + 1; + } + + @Benchmark + public List testFirstPage() { + return view.getPagedResult(0, pageSize); + } + + @Benchmark + public List testMiddlePage() { + return view.getPagedResult(middlePage, pageSize); + } + + @Benchmark + public List testAllResults() { + return view.getPagedResult(0, -1); + } + + private static AddressControl stubAddress(long id) { + final long simulatedMessageCount = (id * 31) % 10000; + final String addressName = "address-" + id; + + return new AddressControl() { + @Override public long getId() { + return id; + } + @Override public String getAddress() { + return addressName; + } + + @Override + public long getMessageCount() { + // Mimic production cost: CPU loop simulating reading multiple live + // internal queue components and consolidating dynamic tracking fields. + long infrastructureTraversalCost = 0; + for (int i = 0; i < 4; i++) { + infrastructureTraversalCost += Thread.currentThread().hashCode() + simulatedMessageCount; + } + return simulatedMessageCount + (infrastructureTraversalCost & 0); + } + + @Override public String[] getRoutingTypes() { + return new String[0]; + } + @Override public String getRoutingTypesAsJSON() { + return "[]"; + } + @Override public Object[] getRoles() { + return new Object[0]; + } + @Override public String getRolesAsJSON() { + return "[]"; + } + @Override public long getAddressSize() { + return 0L; + } + @Override public int getMaxPageReadBytes() { + return 0; + } + @Override public int getMaxPageReadMessages() { + return 0; + } + @Override public int getPrefetchPageBytes() { + return 0; + } + @Override public int getPrefetchPageMessages() { + return 0; + } + @Override public void schedulePageCleanup() { + } + @Override public long getNumberOfMessages() { + return 0L; + } + @Override public String[] getRemoteQueueNames() { + return new String[0]; + } + @Override public String[] getQueueNames() { + return new String[0]; + } + @Override public String[] getAllQueueNames() { + return new String[0]; + } + @Override public long getNumberOfPages() { + return 0L; + } + @Override public boolean isPaging() { + return false; + } + @Override public int getAddressLimitPercent() { + return 0; + } + @Override public boolean block() { + return false; + } + @Override public void unblock() { + } + @Override public boolean isBlockedViaManagement() { + return false; + } + @Override public long getNumberOfBytesPerPage() { + return 0L; + } + @Override public String[] getBindingNames() { + return new String[0]; + } + @Override public long getQueueCount() { + return 0L; + } + @Override public long getRoutedMessageCount() { + return 0L; + } + @Override public long getUnRoutedMessageCount() { + return 0L; + } + @Override public void pause() { + } + @Override public void pause(boolean persist) { + } + @Override public void resume() { + } + @Override public boolean isPaused() { + return false; + } + @Override public boolean isRetroactiveResource() { + return false; + } + @Override public long getCurrentDuplicateIdCacheSize() { + return 0L; + } + @Override public boolean clearDuplicateIdCache() { + return false; + } + @Override public boolean isAutoCreated() { + return false; + } + @Override public boolean isInternal() { + return false; + } + @Override public boolean isTemporary() { + return false; + } + @Override public long purge() { + return 0L; + } + @Override public void replay(String target, String filter) { + } + @Override public void replay(String startScan, String endScan, String target, String filter) { + } + @Override public String sendMessage(Map headers, int type, String body, boolean durable, String user, String password) { + return null; + } + @Override public String sendMessage(Map headers, int type, String body, boolean durable, String user, String password, boolean createMessageId) { + return null; + } + }; + } +} \ No newline at end of file From adc1d89c7b6c7299a5c697ea86a93f5f023d8ea5 Mon Sep 17 00:00:00 2001 From: gchuf Date: Wed, 8 Jul 2026 10:18:35 +0200 Subject: [PATCH 4/5] ARTEMIS-6144 Decrease array copies --- .../impl/view/ActiveMQAbstractView.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ActiveMQAbstractView.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ActiveMQAbstractView.java index 6567fefe326..a21a726c66e 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ActiveMQAbstractView.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ActiveMQAbstractView.java @@ -19,7 +19,6 @@ import org.apache.activemq.artemis.core.management.impl.view.predicate.PredicateFilterPart; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.IdentityHashMap; @@ -121,11 +120,11 @@ public List getPagedResult(int page, int pageSize) { return List.of(); } - T[] array = (T[]) collection.toArray(); + List collectionList = new ArrayList<>(collection); // Pre-compute fields once per element - Map fieldCache = new IdentityHashMap<>(array.length); - for (T item : array) { + Map fieldCache = new IdentityHashMap<>(collectionList.size()); + for (T item : collectionList) { if (item != null) { fieldCache.put(item, getField(item, sortField)); } @@ -147,19 +146,20 @@ public List getPagedResult(int page, int pageSize) { } }; - Arrays.sort(array, cachedComparator); + collectionList.sort(cachedComparator); if (page == -1 || pageSize == -1) { - return Arrays.asList(array); + return collectionList; } int start = (page - 1) * pageSize; - if (start >= array.length || start < 0) { + int size = collectionList.size(); + if (start >= size || start < 0) { return List.of(); } - int end = Math.min(page * pageSize, array.length); + int end = Math.min(page * pageSize, size); - return Arrays.asList(Arrays.copyOfRange(array, start, end)); + return collectionList.subList(start, end); } public Predicate getPredicate() { From eab1f3765ffced0f048e4d38369e682d079dd797 Mon Sep 17 00:00:00 2001 From: gchuf Date: Wed, 8 Jul 2026 10:21:39 +0200 Subject: [PATCH 5/5] ARTEMIS-6144 Comparator micro optimization --- .../management/impl/view/ActiveMQAbstractView.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ActiveMQAbstractView.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ActiveMQAbstractView.java index a21a726c66e..1714a48c795 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ActiveMQAbstractView.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ActiveMQAbstractView.java @@ -136,9 +136,14 @@ public List getPagedResult(int page, int pageSize) { try { Object leftValue = fieldCache.get(left); Object rightValue = fieldCache.get(right); - - if (leftValue instanceof Comparable l && rightValue instanceof Comparable r) { - return sortOrderDescending ? r.compareTo(leftValue) : l.compareTo(rightValue); + if (sortOrderDescending) { + if (rightValue instanceof Comparable r) { + return r.compareTo(leftValue); + } + } else { + if (leftValue instanceof Comparable l) { + return l.compareTo(rightValue); + } } return 0; } catch (Exception e) {