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", "[]"));

Reply via email to