This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new bd23d0679 perf(metrics): resolve dashboard resources once per export
(#5884)
bd23d0679 is described below
commit bd23d0679106449fc7cfc40c201758df2c68b6a8
Author: 健康宝 <[email protected]>
AuthorDate: Mon Oct 12 15:19:26 2026 +0800
perf(metrics): resolve dashboard resources once per export (#5884)
`getDashboardsArchive()` called `listDashboards()` once
(`GrafanaDashboardService.java:137`) and then `getDashboardJson(uid)` for every
entry (`:146`), and that path re-resolves the classpath pattern per uid:
`getDashboardJson` (`:120-130`) → `findResource` (`:156-163`) →
`resolveUniqueResources` (`:165-172`) → `resolveResources` (`:174-181`), which
runs a full `classpath*:grafana/*.json` scan, sort and de-duplication each
time. Exporting N dashboards therefore performed 1+N full scan [...]
`getDashboardsArchiveShouldResolveResourcesOnce` counts
`resolveResources()` invocations on an anonymous subclass and asserts exactly
one, so reverting to `getDashboardJson(uid)` — three dashboards, four
resolutions — goes red immediately; the performance contract is itself the
assertion. `getDashboardsArchiveShouldSkipUnreadableAssetsLikeTheVisibleList`
pins the pre-existing rule that assets skipped by `listDashboards` never enter
the archive. The one observable difference is that a [...]
---
.../metrics/grafana/GrafanaDashboardService.java | 21 ++++++++--
.../grafana/GrafanaDashboardServiceTest.java | 46 ++++++++++++++++++++++
2 files changed, 64 insertions(+), 3 deletions(-)
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardService.java
b/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardService.java
index f20043e1f..5da5bc1bd 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardService.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardService.java
@@ -33,6 +33,7 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -66,8 +67,12 @@ public class GrafanaDashboardService {
* Lists metadata for every bundled Grafana dashboard.
*/
public List<GrafanaDashboardInfo> listDashboards() {
+ return collectInfos(resolveUniqueResources());
+ }
+
+ private List<GrafanaDashboardInfo> collectInfos(List<Resource> resources) {
List<GrafanaDashboardInfo> infos = new ArrayList<>();
- for (Resource resource : resolveUniqueResources()) {
+ for (Resource resource : resources) {
String uid = uidOf(resource);
if (uid == null) {
continue;
@@ -134,16 +139,26 @@ public class GrafanaDashboardService {
* the same way as {@link #listDashboards()} so the archive matches the
visible dashboard list.
*/
public byte[] getDashboardsArchive() {
- List<GrafanaDashboardInfo> dashboards = listDashboards();
+ // Resolve the pattern once and derive both the visible list and the
archive bodies from
+ // it; looking each uid up again (as getDashboardJson does) re-scans
and re-sorts the
+ // whole classpath pattern for every archive entry.
+ List<Resource> resources = resolveUniqueResources();
+ List<GrafanaDashboardInfo> dashboards = collectInfos(resources);
if (dashboards.isEmpty()) {
throw new BusinessException(404, "No Grafana dashboards are
available");
}
+ Map<String, Resource> resourcesByUid = new HashMap<>();
+ for (Resource resource : resources) {
+ resourcesByUid.put(uidOf(resource), resource);
+ }
try (ByteArrayOutputStream output = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(output,
StandardCharsets.UTF_8)) {
for (GrafanaDashboardInfo dashboard : dashboards) {
ZipEntry entry = new ZipEntry(dashboard.uid() + ".json");
zip.putNextEntry(entry);
-
zip.write(getDashboardJson(dashboard.uid()).getBytes(StandardCharsets.UTF_8));
+ try (InputStream in =
resourcesByUid.get(dashboard.uid()).getInputStream()) {
+ in.transferTo(zip);
+ }
zip.closeEntry();
}
zip.finish();
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardServiceTest.java
b/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardServiceTest.java
index 71554c556..619d27629 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardServiceTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/grafana/GrafanaDashboardServiceTest.java
@@ -28,6 +28,7 @@ import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.ZipInputStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -128,6 +129,51 @@ class GrafanaDashboardServiceTest {
}
}
+ @Test
+ void getDashboardsArchiveShouldResolveResourcesOnce() {
+ List<Resource> resources = List.of(
+ resource("a.json",
"{\"uid\":\"a\",\"title\":\"A\",\"tags\":[\"rocketmq\"]}"),
+ resource("b.json",
"{\"uid\":\"b\",\"title\":\"B\",\"tags\":[\"rocketmq\"]}"),
+ resource("c.json",
"{\"uid\":\"c\",\"title\":\"C\",\"tags\":[\"rocketmq\"]}"));
+ AtomicInteger resolutions = new AtomicInteger();
+ GrafanaDashboardService service = new GrafanaDashboardService(new
ObjectMapper()) {
+ @Override
+ protected Resource[] resolveResources() {
+ resolutions.incrementAndGet();
+ return resources.toArray(new Resource[0]);
+ }
+ };
+
+ service.getDashboardsArchive();
+
+ assertEquals(1, resolutions.get(),
+ "archive export should resolve the pattern once, not once per
dashboard entry");
+ }
+
+ @Test
+ void getDashboardsArchiveShouldSkipUnreadableAssetsLikeTheVisibleList()
throws Exception {
+ GrafanaDashboardService service = serviceWithResources(
+ resource("a.json",
"{\"uid\":\"a\",\"title\":\"A\",\"tags\":[\"rocketmq\"]}"),
+ new ByteArrayResource(new byte[0]) {
+ @Override
+ public String getFilename() {
+ return "boom.json";
+ }
+
+ @Override
+ public InputStream getInputStream() throws
java.io.IOException {
+ throw new java.io.IOException("unreadable");
+ }
+ });
+
+ byte[] archive = service.getDashboardsArchive();
+
+ try (ZipInputStream zip = new ZipInputStream(new
ByteArrayInputStream(archive), StandardCharsets.UTF_8)) {
+ assertEquals("a.json", zip.getNextEntry().getName());
+ assertNull(zip.getNextEntry());
+ }
+ }
+
@Test
void getDashboardsArchiveShouldRejectEmptyDashboardSet() {
GrafanaDashboardService service =
serviceWithResources(resource("invalid.json", "[]"));