From 5c7cce7a973313d3a169a40ae7d1501ad433798d Mon Sep 17 00:00:00 2001 From: Wu Sheng Date: Mon, 20 Apr 2026 19:17:16 +0800 Subject: [PATCH 01/14] SWIP-12 Stage 1: Layer enums, component-libraries, listener layer mapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add WECHAT_MINI_PROGRAM(48) and ALIPAY_MINI_PROGRAM(49) to Layer.java. - Register WeChat-MiniProgram(10002) and AliPay-MiniProgram(10003) in component-libraries.yml so topology renders the SDK's exit spans with proper component names. - CommonAnalysisListener now takes IComponentLibraryCatalogService via constructor, resolves the two mini-program component ids at construction time, and extends identifyServiceLayer(SpanLayer) to also accept componentId: mini-program component ids map to the two new layers, FAAS continues to map to Layer.FAAS, everything else remains Layer.GENERAL. - RPCAnalysisListener (4 callsites) and EndpointDepFromCrossThreadAnalysisListener (1 callsite) pass span.getComponentId() through. Their Factory classes resolve IComponentLibraryCatalogService from CoreModule. No SPI change — the existing SegmentParserListenerManager pipeline picks up the new mapping automatically. - Tests updated with a mock catalog stubbed to return 10002 / 10003 so span componentId == 0 doesn't accidentally collide with the new mini-program layers. --- .../listener/CommonAnalysisListener.java | 28 +++++++++++++--- ...intDepFromCrossThreadAnalysisListener.java | 21 +++++++++--- .../parser/listener/RPCAnalysisListener.java | 29 +++++++++++++---- .../oap/server/core/analysis/Layer.java | 12 ++++++- ...epFromCrossThreadAnalysisListenerTest.java | 8 ++++- .../listener/RPCAnalysisListenerTest.java | 32 +++++++++++++------ .../main/resources/component-libraries.yml | 6 ++++ 7 files changed, 109 insertions(+), 27 deletions(-) diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/CommonAnalysisListener.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/CommonAnalysisListener.java index 8ff8baae4a49..9762782a025b 100644 --- a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/CommonAnalysisListener.java +++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/CommonAnalysisListener.java @@ -20,20 +20,38 @@ import org.apache.skywalking.apm.network.language.agent.v3.SpanLayer; import org.apache.skywalking.oap.server.core.analysis.Layer; +import org.apache.skywalking.oap.server.core.config.IComponentLibraryCatalogService; /** * The common logic for specific listeners. */ abstract class CommonAnalysisListener { + + private final int wechatMiniProgramComponentId; + private final int alipayMiniProgramComponentId; + + protected CommonAnalysisListener(IComponentLibraryCatalogService componentLibraryCatalogService) { + this.wechatMiniProgramComponentId = componentLibraryCatalogService.getComponentId("WeChat-MiniProgram"); + this.alipayMiniProgramComponentId = componentLibraryCatalogService.getComponentId("AliPay-MiniProgram"); + } + /** - * Identify the layer of span's service/instance owner. Such as ${@link Layer#FAAS} and ${@link Layer#GENERAL}. + * Identify the layer of span's service/instance owner. Such as ${@link Layer#FAAS} and ${@link Layer#GENERAL}. + * + * @param spanLayer the span's declared layer, used as a fallback signal (e.g. FAAS). + * @param componentId the span's component id, takes precedence when it maps to a known + * client-side SDK layer (WeChat / Alipay mini-program). */ - protected Layer identifyServiceLayer(SpanLayer spanLayer) { + protected Layer identifyServiceLayer(SpanLayer spanLayer, int componentId) { + if (componentId == wechatMiniProgramComponentId) { + return Layer.WECHAT_MINI_PROGRAM; + } + if (componentId == alipayMiniProgramComponentId) { + return Layer.ALIPAY_MINI_PROGRAM; + } if (SpanLayer.FAAS.equals(spanLayer)) { - // function as a Service return Layer.FAAS; - } else { - return Layer.GENERAL; } + return Layer.GENERAL; } } diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/EndpointDepFromCrossThreadAnalysisListener.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/EndpointDepFromCrossThreadAnalysisListener.java index 08fcc0da8da5..f71fb1a48240 100644 --- a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/EndpointDepFromCrossThreadAnalysisListener.java +++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/EndpointDepFromCrossThreadAnalysisListener.java @@ -20,7 +20,6 @@ import java.util.ArrayList; import java.util.List; -import lombok.RequiredArgsConstructor; import org.apache.skywalking.apm.network.language.agent.v3.SegmentObject; import org.apache.skywalking.apm.network.language.agent.v3.SegmentReference; import org.apache.skywalking.apm.network.language.agent.v3.SpanLayer; @@ -30,6 +29,7 @@ import org.apache.skywalking.oap.server.core.CoreModule; import org.apache.skywalking.oap.server.core.analysis.Layer; import org.apache.skywalking.oap.server.core.analysis.TimeBucket; +import org.apache.skywalking.oap.server.core.config.IComponentLibraryCatalogService; import org.apache.skywalking.oap.server.core.config.NamingControl; import org.apache.skywalking.oap.server.core.source.DetectPoint; import org.apache.skywalking.oap.server.core.source.SourceReceiver; @@ -42,7 +42,6 @@ * * @since 9.0.0 */ -@RequiredArgsConstructor public class EndpointDepFromCrossThreadAnalysisListener extends CommonAnalysisListener implements ExitAnalysisListener, LocalAnalysisListener { private final SourceReceiver sourceReceiver; private final AnalyzerModuleConfig config; @@ -50,6 +49,16 @@ public class EndpointDepFromCrossThreadAnalysisListener extends CommonAnalysisLi private final List depBuilders = new ArrayList<>(10); + public EndpointDepFromCrossThreadAnalysisListener(final SourceReceiver sourceReceiver, + final AnalyzerModuleConfig config, + final NamingControl namingControl, + final IComponentLibraryCatalogService componentLibraryCatalogService) { + super(componentLibraryCatalogService); + this.sourceReceiver = sourceReceiver; + this.config = config; + this.namingControl = namingControl; + } + @Override public boolean containsPoint(final Point point) { return Point.Exit.equals(point) || Point.Local.equals(point); @@ -102,7 +111,7 @@ private void parseRefForEndpointDependency(final SpanObject span, final SegmentO sourceBuilder.setDestEndpointName(span.getOperationName()); sourceBuilder.setDestServiceInstanceName(segmentObject.getServiceInstance()); sourceBuilder.setDestServiceName(segmentObject.getService()); - sourceBuilder.setDestLayer(identifyServiceLayer(span.getSpanLayer())); + sourceBuilder.setDestLayer(identifyServiceLayer(span.getSpanLayer(), span.getComponentId())); sourceBuilder.setDetectPoint(DetectPoint.SERVER); sourceBuilder.setComponentId(span.getComponentId()); setPublicAttrs(sourceBuilder, span); @@ -136,17 +145,21 @@ public void build() { public static class Factory implements AnalysisListenerFactory { private final SourceReceiver sourceReceiver; private final NamingControl namingControl; + private final IComponentLibraryCatalogService componentLibraryCatalogService; public Factory(ModuleManager moduleManager) { this.sourceReceiver = moduleManager.find(CoreModule.NAME).provider().getService(SourceReceiver.class); this.namingControl = moduleManager.find(CoreModule.NAME) .provider() .getService(NamingControl.class); + this.componentLibraryCatalogService = moduleManager.find(CoreModule.NAME) + .provider() + .getService(IComponentLibraryCatalogService.class); } @Override public AnalysisListener create(final ModuleManager moduleManager, final AnalyzerModuleConfig config) { - return new EndpointDepFromCrossThreadAnalysisListener(sourceReceiver, config, namingControl); + return new EndpointDepFromCrossThreadAnalysisListener(sourceReceiver, config, namingControl, componentLibraryCatalogService); } } } diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/RPCAnalysisListener.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/RPCAnalysisListener.java index f4b2b9f19e1b..162fb1fcff1e 100644 --- a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/RPCAnalysisListener.java +++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/RPCAnalysisListener.java @@ -20,7 +20,6 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.skywalking.apm.network.language.agent.v3.SegmentObject; import org.apache.skywalking.apm.network.language.agent.v3.SegmentReference; @@ -37,6 +36,7 @@ import org.apache.skywalking.oap.server.core.analysis.TimeBucket; import org.apache.skywalking.oap.server.core.analysis.manual.networkalias.NetworkAddressAlias; import org.apache.skywalking.oap.server.core.cache.NetworkAddressAliasCache; +import org.apache.skywalking.oap.server.core.config.IComponentLibraryCatalogService; import org.apache.skywalking.oap.server.core.config.NamingControl; import org.apache.skywalking.oap.server.core.source.DetectPoint; import org.apache.skywalking.oap.server.core.source.EndpointRelation; @@ -55,7 +55,6 @@ * RPCAnalysisListener detects all RPC relative statistics. */ @Slf4j -@RequiredArgsConstructor public class RPCAnalysisListener extends CommonAnalysisListener implements EntryAnalysisListener, ExitAnalysisListener, LocalAnalysisListener { private final List callingInTraffic = new ArrayList<>(10); private final List callingOutTraffic = new ArrayList<>(10); @@ -66,6 +65,18 @@ public class RPCAnalysisListener extends CommonAnalysisListener implements Entry private final NetworkAddressAliasCache networkAddressAliasCache; private final NamingControl namingControl; + public RPCAnalysisListener(final SourceReceiver sourceReceiver, + final AnalyzerModuleConfig config, + final NetworkAddressAliasCache networkAddressAliasCache, + final NamingControl namingControl, + final IComponentLibraryCatalogService componentLibraryCatalogService) { + super(componentLibraryCatalogService); + this.sourceReceiver = sourceReceiver; + this.config = config; + this.networkAddressAliasCache = networkAddressAliasCache; + this.namingControl = namingControl; + } + @Override public boolean containsPoint(Point point) { return Point.Entry.equals(point) || Point.Exit.equals(point) || Point.Local.equals(point); @@ -119,7 +130,7 @@ public void parseEntry(SpanObject span, SegmentObject segmentObject) { sourceBuilder.setDestEndpointName(span.getOperationName()); sourceBuilder.setDestServiceInstanceName(segmentObject.getServiceInstance()); sourceBuilder.setDestServiceName(segmentObject.getService()); - sourceBuilder.setDestLayer(identifyServiceLayer(span.getSpanLayer())); + sourceBuilder.setDestLayer(identifyServiceLayer(span.getSpanLayer(), span.getComponentId())); sourceBuilder.setDetectPoint(DetectPoint.SERVER); sourceBuilder.setComponentId(span.getComponentId()); setPublicAttrs(sourceBuilder, span); @@ -134,7 +145,7 @@ public void parseEntry(SpanObject span, SegmentObject segmentObject) { sourceBuilder.setSourceLayer(Layer.VIRTUAL_MQ); sourceBuilder.setDestServiceInstanceName(segmentObject.getServiceInstance()); sourceBuilder.setDestServiceName(segmentObject.getService()); - sourceBuilder.setDestLayer(identifyServiceLayer(span.getSpanLayer())); + sourceBuilder.setDestLayer(identifyServiceLayer(span.getSpanLayer(), span.getComponentId())); sourceBuilder.setDetectPoint(DetectPoint.SERVER); sourceBuilder.setComponentId(span.getComponentId()); setPublicAttrs(sourceBuilder, span); @@ -147,7 +158,7 @@ public void parseEntry(SpanObject span, SegmentObject segmentObject) { sourceBuilder.setSourceLayer(Layer.UNDEFINED); sourceBuilder.setDestServiceInstanceName(segmentObject.getServiceInstance()); sourceBuilder.setDestServiceName(segmentObject.getService()); - sourceBuilder.setDestLayer(identifyServiceLayer(span.getSpanLayer())); + sourceBuilder.setDestLayer(identifyServiceLayer(span.getSpanLayer(), span.getComponentId())); sourceBuilder.setDestEndpointName(span.getOperationName()); sourceBuilder.setDetectPoint(DetectPoint.SERVER); sourceBuilder.setComponentId(span.getComponentId()); @@ -178,7 +189,7 @@ public void parseExit(SpanObject span, SegmentObject segmentObject) { sourceBuilder.setSourceServiceName(segmentObject.getService()); sourceBuilder.setSourceServiceInstanceName(segmentObject.getServiceInstance()); - sourceBuilder.setSourceLayer(identifyServiceLayer(span.getSpanLayer())); + sourceBuilder.setSourceLayer(identifyServiceLayer(span.getSpanLayer(), span.getComponentId())); final NetworkAddressAlias networkAddressAlias = networkAddressAliasCache.get(networkAddress); if (networkAddressAlias == null) { @@ -377,6 +388,7 @@ public static class Factory implements AnalysisListenerFactory { private final SourceReceiver sourceReceiver; private final NetworkAddressAliasCache networkAddressAliasCache; private final NamingControl namingControl; + private final IComponentLibraryCatalogService componentLibraryCatalogService; public Factory(ModuleManager moduleManager) { this.sourceReceiver = moduleManager.find(CoreModule.NAME).provider().getService(SourceReceiver.class); @@ -386,12 +398,15 @@ public Factory(ModuleManager moduleManager) { this.namingControl = moduleManager.find(CoreModule.NAME) .provider() .getService(NamingControl.class); + this.componentLibraryCatalogService = moduleManager.find(CoreModule.NAME) + .provider() + .getService(IComponentLibraryCatalogService.class); } @Override public AnalysisListener create(ModuleManager moduleManager, AnalyzerModuleConfig config) { return new RPCAnalysisListener( - sourceReceiver, config, networkAddressAliasCache, namingControl); + sourceReceiver, config, networkAddressAliasCache, namingControl, componentLibraryCatalogService); } } } diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/Layer.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/Layer.java index d4edc1b523b1..207698a413d1 100644 --- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/Layer.java +++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/Layer.java @@ -283,7 +283,17 @@ public enum Layer { /** * iOS/iPadOS app monitoring via OpenTelemetry Swift SDK */ - IOS(47, true); + IOS(47, true), + + /** + * WeChat Mini Program monitoring via mini-program-monitor SDK + */ + WECHAT_MINI_PROGRAM(48, true), + + /** + * Alipay Mini Program monitoring via mini-program-monitor SDK + */ + ALIPAY_MINI_PROGRAM(49, true); private final int value; /** diff --git a/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/EndpointDepFromCrossThreadAnalysisListenerTest.java b/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/EndpointDepFromCrossThreadAnalysisListenerTest.java index ab9f9e5cdba0..b3bdfd83e07e 100644 --- a/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/EndpointDepFromCrossThreadAnalysisListenerTest.java +++ b/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/EndpointDepFromCrossThreadAnalysisListenerTest.java @@ -31,6 +31,7 @@ import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.EndpointDepFromCrossThreadAnalysisListener; import org.apache.skywalking.oap.server.core.analysis.IDManager; import org.apache.skywalking.oap.server.core.analysis.Layer; +import org.apache.skywalking.oap.server.core.config.IComponentLibraryCatalogService; import org.apache.skywalking.oap.server.core.config.NamingControl; import org.apache.skywalking.oap.server.core.config.group.EndpointNameGrouping; import org.apache.skywalking.oap.server.core.source.Endpoint; @@ -50,6 +51,8 @@ public class EndpointDepFromCrossThreadAnalysisListenerTest { @Mock private static AnalyzerModuleConfig CONFIG; + @Mock + private static IComponentLibraryCatalogService COMPONENT_LIBRARY_CATALOG; private static NamingControl NAMING_CONTROL = new NamingControl( 70, 100, @@ -63,6 +66,8 @@ public class EndpointDepFromCrossThreadAnalysisListenerTest { public void init() throws Exception { MockitoAnnotations.openMocks(this).close(); + when(COMPONENT_LIBRARY_CATALOG.getComponentId("WeChat-MiniProgram")).thenReturn(10002); + when(COMPONENT_LIBRARY_CATALOG.getComponentId("AliPay-MiniProgram")).thenReturn(10003); final UninstrumentedGatewaysConfig uninstrumentedGatewaysConfig = Mockito.mock( UninstrumentedGatewaysConfig.class); when(uninstrumentedGatewaysConfig.isAddressConfiguredAsGateway(any())).thenReturn(false); @@ -75,7 +80,8 @@ public void testEndpointDependency() { EndpointDepFromCrossThreadAnalysisListener listener = new EndpointDepFromCrossThreadAnalysisListener( mockReceiver, CONFIG, - NAMING_CONTROL + NAMING_CONTROL, + COMPONENT_LIBRARY_CATALOG ); final long startTime = System.currentTimeMillis(); diff --git a/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/RPCAnalysisListenerTest.java b/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/RPCAnalysisListenerTest.java index f31323d33e88..f5fece8d6b0d 100644 --- a/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/RPCAnalysisListenerTest.java +++ b/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/RPCAnalysisListenerTest.java @@ -35,6 +35,7 @@ import org.apache.skywalking.oap.server.core.analysis.IDManager; import org.apache.skywalking.oap.server.core.analysis.manual.networkalias.NetworkAddressAlias; import org.apache.skywalking.oap.server.core.cache.NetworkAddressAliasCache; +import org.apache.skywalking.oap.server.core.config.IComponentLibraryCatalogService; import org.apache.skywalking.oap.server.core.config.NamingControl; import org.apache.skywalking.oap.server.core.config.group.EndpointNameGrouping; import org.apache.skywalking.oap.server.core.source.Endpoint; @@ -71,6 +72,8 @@ public class RPCAnalysisListenerTest { private static NetworkAddressAliasCache CACHE; @Mock private static NetworkAddressAliasCache CACHE2; + @Mock + private static IComponentLibraryCatalogService COMPONENT_LIBRARY_CATALOG; private static NamingControl NAMING_CONTROL = new NamingControl( 70, 100, @@ -82,6 +85,8 @@ public class RPCAnalysisListenerTest { public void init() { MockitoAnnotations.initMocks(this); + when(COMPONENT_LIBRARY_CATALOG.getComponentId("WeChat-MiniProgram")).thenReturn(10002); + when(COMPONENT_LIBRARY_CATALOG.getComponentId("AliPay-MiniProgram")).thenReturn(10003); when(CACHE.get(any())).thenReturn(null); final NetworkAddressAlias networkAddressAlias = new NetworkAddressAlias(); final String serviceId = IDManager.ServiceID.buildId("target-service", true); @@ -101,7 +106,8 @@ public void testContainsPoint() { new MockReceiver(), CONFIG, CACHE, - NAMING_CONTROL + NAMING_CONTROL, + COMPONENT_LIBRARY_CATALOG ); Assertions.assertTrue(listener.containsPoint(AnalysisListener.Point.Entry)); Assertions.assertTrue(listener.containsPoint(AnalysisListener.Point.Local)); @@ -120,7 +126,8 @@ public void testEntrySpanWithoutRef() { mockReceiver, CONFIG, CACHE, - NAMING_CONTROL + NAMING_CONTROL, + COMPONENT_LIBRARY_CATALOG ); final long startTime = System.currentTimeMillis(); @@ -182,7 +189,8 @@ public void testEntrySpanRef() { mockReceiver, CONFIG, CACHE, - NAMING_CONTROL + NAMING_CONTROL, + COMPONENT_LIBRARY_CATALOG ); final long startTime = System.currentTimeMillis(); @@ -248,7 +256,8 @@ public void testEntrySpanMQRef() { mockReceiver, CONFIG, CACHE, - NAMING_CONTROL + NAMING_CONTROL, + COMPONENT_LIBRARY_CATALOG ); final long startTime = System.currentTimeMillis(); @@ -307,7 +316,8 @@ public void testParseLocalLogicSpan() { mockReceiver, CONFIG, CACHE, - NAMING_CONTROL + NAMING_CONTROL, + COMPONENT_LIBRARY_CATALOG ); final long startTime = System.currentTimeMillis(); @@ -350,7 +360,8 @@ public void testParseSpanWithLogicEndpointTag() { mockReceiver, CONFIG, CACHE, - NAMING_CONTROL + NAMING_CONTROL, + COMPONENT_LIBRARY_CATALOG ); final long startTime = System.currentTimeMillis(); @@ -395,7 +406,8 @@ public void testExitSpanWithoutAlias() { mockReceiver, CONFIG, CACHE, - NAMING_CONTROL + NAMING_CONTROL, + COMPONENT_LIBRARY_CATALOG ); final long startTime = System.currentTimeMillis(); @@ -436,7 +448,8 @@ public void testExitSpanWithAlias() { mockReceiver, CONFIG, CACHE2, - NAMING_CONTROL + NAMING_CONTROL, + COMPONENT_LIBRARY_CATALOG ); final long startTime = System.currentTimeMillis(); @@ -475,7 +488,8 @@ public void testMQEntryWithoutRef() { mockReceiver, CONFIG, CACHE, - NAMING_CONTROL + NAMING_CONTROL, + COMPONENT_LIBRARY_CATALOG ); final long startTime = System.currentTimeMillis(); diff --git a/oap-server/server-starter/src/main/resources/component-libraries.yml b/oap-server/server-starter/src/main/resources/component-libraries.yml index da8617c71f8f..634252762dd1 100644 --- a/oap-server/server-starter/src/main/resources/component-libraries.yml +++ b/oap-server/server-starter/src/main/resources/component-libraries.yml @@ -844,6 +844,12 @@ JavaScript: ajax: id: 10001 languages: JavaScript +WeChat-MiniProgram: + id: 10002 + languages: JavaScript +AliPay-MiniProgram: + id: 10003 + languages: JavaScript # Rust components # [11000, 12000) for Rust agent From 42fad98d51880b0d681c6309b788e97374756a81 Mon Sep 17 00:00:00 2001 From: Wu Sheng Date: Mon, 20 Apr 2026 19:23:13 +0800 Subject: [PATCH 02/14] SWIP-12 Stage 2: MAL, LAL, log-MAL rules for mini-program signals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - otel-rules/miniprogram/ ships 4 MAL files: per-platform (WeChat / Alipay) x per-scope (service / instance). Each has a top-level filter gating on miniprogram_platform so the two layers do not double-count. Service-scoped rules avoid service_instance_id fragmentation (mirrors the ios-metrickit.yaml precedent) and also emit endpoint-scoped variants via the chained .endpoint(...) override (APISIX pattern) so per-page dashboards populate from the same file. Alipay drops route / script / package_load / first_paint which are WeChat-only. - lal/miniprogram.yaml uses layer:auto — one rule produces two layers via script-side dispatch on miniprogram.platform. Corrected the SWIP draft's ternary-in-layer (LAL grammar does not allow ternary in valueAccess) to if / else-if, and the draft's nested metrics block form to the flat form the grammar actually accepts. Instance source is sourceAttribute("service.instance.id") — matches what OTLP metrics key on, so logs land on the same OAP instance entity when operators follow the recommended serviceInstance == serviceVersion pattern. - log-mal-rules/miniprogram.yaml aggregates the LAL metrics{}-block miniprogram_error_count samples into meter_wechat_mp_error_count / meter_alipay_mp_error_count, one YAML doc per layer with its own top-level filter. - application.yml appends miniprogram/* to enabledOtelMetricsRules, miniprogram to lalFiles and malFiles. The config-dump e2e expected file (cases/storage/expected/config-dump.yml) must mirror this — tracked for Stage 5. --- .../src/main/resources/application.yml | 6 +- .../src/main/resources/lal/miniprogram.yaml | 68 +++++++++++++++++++ .../resources/log-mal-rules/miniprogram.yaml | 33 +++++++++ .../alipay-mini-program-instance.yaml | 32 +++++++++ .../miniprogram/alipay-mini-program.yaml | 44 ++++++++++++ .../wechat-mini-program-instance.yaml | 39 +++++++++++ .../miniprogram/wechat-mini-program.yaml | 50 ++++++++++++++ 7 files changed, 269 insertions(+), 3 deletions(-) create mode 100644 oap-server/server-starter/src/main/resources/lal/miniprogram.yaml create mode 100644 oap-server/server-starter/src/main/resources/log-mal-rules/miniprogram.yaml create mode 100644 oap-server/server-starter/src/main/resources/otel-rules/miniprogram/alipay-mini-program-instance.yaml create mode 100644 oap-server/server-starter/src/main/resources/otel-rules/miniprogram/alipay-mini-program.yaml create mode 100644 oap-server/server-starter/src/main/resources/otel-rules/miniprogram/wechat-mini-program-instance.yaml create mode 100644 oap-server/server-starter/src/main/resources/otel-rules/miniprogram/wechat-mini-program.yaml diff --git a/oap-server/server-starter/src/main/resources/application.yml b/oap-server/server-starter/src/main/resources/application.yml index 83e149a7f652..3feb093ad925 100644 --- a/oap-server/server-starter/src/main/resources/application.yml +++ b/oap-server/server-starter/src/main/resources/application.yml @@ -237,8 +237,8 @@ agent-analyzer: log-analyzer: selector: ${SW_LOG_ANALYZER:default} default: - lalFiles: ${SW_LOG_LAL_FILES:envoy-als,mesh-dp,mysql-slowsql,pgsql-slowsql,redis-slowsql,k8s-service,nginx,envoy-ai-gateway,default} - malFiles: ${SW_LOG_MAL_FILES:"nginx"} + lalFiles: ${SW_LOG_LAL_FILES:envoy-als,mesh-dp,mysql-slowsql,pgsql-slowsql,redis-slowsql,k8s-service,nginx,envoy-ai-gateway,miniprogram,default} + malFiles: ${SW_LOG_MAL_FILES:"nginx,miniprogram"} event-analyzer: selector: ${SW_EVENT_ANALYZER:default} @@ -390,7 +390,7 @@ receiver-otel: selector: ${SW_OTEL_RECEIVER:default} default: enabledHandlers: ${SW_OTEL_RECEIVER_ENABLED_HANDLERS:"otlp-traces,otlp-metrics,otlp-logs"} - enabledOtelMetricsRules: ${SW_OTEL_RECEIVER_ENABLED_OTEL_METRICS_RULES:"apisix,nginx/*,k8s/*,istio-controlplane,vm,mysql/*,postgresql/*,oap,aws-eks/*,windows,aws-s3/*,aws-dynamodb/*,aws-gateway/*,redis/*,elasticsearch/*,rabbitmq/*,mongodb/*,kafka/*,pulsar/*,bookkeeper/*,rocketmq/*,clickhouse/*,activemq/*,kong/*,flink/*,banyandb/*,envoy-ai-gateway/*,ios/*"} + enabledOtelMetricsRules: ${SW_OTEL_RECEIVER_ENABLED_OTEL_METRICS_RULES:"apisix,nginx/*,k8s/*,istio-controlplane,vm,mysql/*,postgresql/*,oap,aws-eks/*,windows,aws-s3/*,aws-dynamodb/*,aws-gateway/*,redis/*,elasticsearch/*,rabbitmq/*,mongodb/*,kafka/*,pulsar/*,bookkeeper/*,rocketmq/*,clickhouse/*,activemq/*,kong/*,flink/*,banyandb/*,envoy-ai-gateway/*,ios/*,miniprogram/*"} receiver-zipkin: selector: ${SW_RECEIVER_ZIPKIN:-} diff --git a/oap-server/server-starter/src/main/resources/lal/miniprogram.yaml b/oap-server/server-starter/src/main/resources/lal/miniprogram.yaml new file mode 100644 index 000000000000..5a55667c9215 --- /dev/null +++ b/oap-server/server-starter/src/main/resources/lal/miniprogram.yaml @@ -0,0 +1,68 @@ +# 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. + +# WeChat / Alipay Mini Program error-log processing. +# One rule covers both runtimes — layer is decided script-side from the +# miniprogram.platform resource attribute, using the SWIP-11 layer:auto +# mechanism. Also emits a `miniprogram_error_count` counter sample for +# every error log, picked up by log-mal-rules/miniprogram.yaml and +# aggregated into per-platform `error_count` metrics. + +rules: + - name: miniprogram-errors + layer: auto + dsl: | + filter { + def platform = sourceAttribute("miniprogram.platform") + if (platform != "wechat" && platform != "alipay") { + abort {} + } + if (tag("exception.type") == null) { + abort {} + } + + extractor { + if (platform == "wechat") { + layer "WECHAT_MINI_PROGRAM" + } else if (platform == "alipay") { + layer "ALIPAY_MINI_PROGRAM" + } + + # Instance source matches what OTLP metrics use, so logs aggregate under + # the same OAP instance entity when operators follow the recommended + # serviceInstance == serviceVersion pattern (SDK >= v0.4.0). If absent, + # sourceAttribute() returns null/empty -> TrafficSinkListener skips the + # instance traffic, matching MAL's empty-dim behavior. + instance sourceAttribute("service.instance.id") + endpoint tag("miniprogram.page.path") + + tag 'platform': platform + tag 'exception.type': tag("exception.type") + tag 'exception.stacktrace': tag("exception.stacktrace") + tag 'http.method': tag("http.request.method") + tag 'http.status': tag("http.response.status_code") + tag 'server.address': tag("server.address") + + metrics { + timestamp log.timestamp as Long + labels service_name: log.service, exception_type: tag("exception.type"), miniprogram_platform: platform + name "miniprogram_error_count" + value 1 + } + } + + sink { + } + } diff --git a/oap-server/server-starter/src/main/resources/log-mal-rules/miniprogram.yaml b/oap-server/server-starter/src/main/resources/log-mal-rules/miniprogram.yaml new file mode 100644 index 000000000000..40196031851d --- /dev/null +++ b/oap-server/server-starter/src/main/resources/log-mal-rules/miniprogram.yaml @@ -0,0 +1,33 @@ +# 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. + +# Log-MAL rules for WeChat / Alipay Mini Program error counters. +# Consumes `miniprogram_error_count` samples emitted by the LAL rule +# (lal/miniprogram.yaml, metrics{} block), one per error log, and +# aggregates per-platform into `meter_wechat_mp_error_count` / +# `meter_alipay_mp_error_count`. The top-level filter gates each doc +# to the correct platform so the two rules do not double-count. + +metricPrefix: meter_wechat_mp +filter: "{ tags -> tags.miniprogram_platform == 'wechat' }" +metricsRules: + - name: error_count + exp: miniprogram_error_count.sum(['service_name', 'exception_type']).downsampling(SUM).service(['service_name'], Layer.WECHAT_MINI_PROGRAM) +--- +metricPrefix: meter_alipay_mp +filter: "{ tags -> tags.miniprogram_platform == 'alipay' }" +metricsRules: + - name: error_count + exp: miniprogram_error_count.sum(['service_name', 'exception_type']).downsampling(SUM).service(['service_name'], Layer.ALIPAY_MINI_PROGRAM) diff --git a/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/alipay-mini-program-instance.yaml b/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/alipay-mini-program-instance.yaml new file mode 100644 index 000000000000..2d50f94b0220 --- /dev/null +++ b/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/alipay-mini-program-instance.yaml @@ -0,0 +1,32 @@ +# 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. + +# Alipay Mini Program instance-level (per app version) metrics. +# Mirrors alipay-mini-program.yaml scoped to service instance. Meaningful only +# when the operator follows the recommended serviceInstance == serviceVersion +# pattern (see SWIP-12 §2) — otherwise the service_instance_id label is empty +# and no instance traffic is produced. + +expSuffix: instance(['service_name'], ['service_instance_id'], Layer.ALIPAY_MINI_PROGRAM) +metricPrefix: meter_alipay_mp_instance +filter: "{ tags -> tags.miniprogram_platform == 'alipay' }" + +metricsRules: + - name: app_launch_duration + exp: miniprogram_app_launch_duration.avg(['service_name', 'service_instance_id']) + - name: first_render_duration + exp: miniprogram_first_render_duration.avg(['service_name', 'service_instance_id']) + - name: request_duration_percentile + exp: miniprogram_request_duration_histogram.sum(['service_name', 'service_instance_id', 'le']).histogram().histogram_percentile([50,75,90,95,99]) diff --git a/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/alipay-mini-program.yaml b/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/alipay-mini-program.yaml new file mode 100644 index 000000000000..7787d1499e60 --- /dev/null +++ b/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/alipay-mini-program.yaml @@ -0,0 +1,44 @@ +# 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. + +# Alipay Mini Program service-level metrics from the mini-program-monitor SDK. +# Alipay's base library does not expose the PerformanceObserver events WeChat +# does (route/script/packageLoad/first_paint), so those metrics are absent here +# — the SDK falls back to lifecycle hooks for app_launch / first_render, which +# are approximations rather than renderer-authoritative timings. +# Aggregated across all app versions (instances). + +expSuffix: service(['service_name'], Layer.ALIPAY_MINI_PROGRAM) +metricPrefix: meter_alipay_mp +filter: "{ tags -> tags.miniprogram_platform == 'alipay' }" + +metricsRules: + # Perf (service-wide averages) + - name: app_launch_duration + exp: miniprogram_app_launch_duration.avg(['service_name']) + - name: first_render_duration + exp: miniprogram_first_render_duration.avg(['service_name']) + + # Request latency percentile + - name: request_duration_percentile + exp: miniprogram_request_duration_histogram.sum(['service_name', 'le']).histogram().histogram_percentile([50,75,90,95,99]) + + # Per-page (endpoint-scoped) variants — chained .endpoint(...) overrides the expSuffix. + - name: endpoint_app_launch_duration + exp: miniprogram_app_launch_duration.avg(['service_name', 'miniprogram_page_path']).endpoint(['service_name'], ['miniprogram_page_path'], Layer.ALIPAY_MINI_PROGRAM) + - name: endpoint_first_render_duration + exp: miniprogram_first_render_duration.avg(['service_name', 'miniprogram_page_path']).endpoint(['service_name'], ['miniprogram_page_path'], Layer.ALIPAY_MINI_PROGRAM) + - name: endpoint_request_duration_percentile + exp: miniprogram_request_duration_histogram.sum(['service_name', 'miniprogram_page_path', 'le']).histogram().histogram_percentile([50,75,90,95,99]).endpoint(['service_name'], ['miniprogram_page_path'], Layer.ALIPAY_MINI_PROGRAM) diff --git a/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/wechat-mini-program-instance.yaml b/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/wechat-mini-program-instance.yaml new file mode 100644 index 000000000000..83f2f2bc88bf --- /dev/null +++ b/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/wechat-mini-program-instance.yaml @@ -0,0 +1,39 @@ +# 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. + +# WeChat Mini Program instance-level (per app version) metrics. +# Same metrics as wechat-mini-program.yaml but scoped to the service instance +# (operator's recommended pattern: set serviceInstance to service.version). +# When serviceInstance is unset, `service_instance_id` label is empty and no +# instance traffic is produced; per-instance dashboards are meaningful only +# when operators follow the recommended pattern (see SWIP-12 §2). + +expSuffix: instance(['service_name'], ['service_instance_id'], Layer.WECHAT_MINI_PROGRAM) +metricPrefix: meter_wechat_mp_instance +filter: "{ tags -> tags.miniprogram_platform == 'wechat' }" + +metricsRules: + - name: app_launch_duration + exp: miniprogram_app_launch_duration.avg(['service_name', 'service_instance_id']) + - name: first_render_duration + exp: miniprogram_first_render_duration.avg(['service_name', 'service_instance_id']) + - name: route_duration + exp: miniprogram_route_duration.avg(['service_name', 'service_instance_id']) + - name: script_duration + exp: miniprogram_script_duration.avg(['service_name', 'service_instance_id']) + - name: package_load_duration + exp: miniprogram_package_load_duration.avg(['service_name', 'service_instance_id']) + - name: request_duration_percentile + exp: miniprogram_request_duration_histogram.sum(['service_name', 'service_instance_id', 'le']).histogram().histogram_percentile([50,75,90,95,99]) diff --git a/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/wechat-mini-program.yaml b/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/wechat-mini-program.yaml new file mode 100644 index 000000000000..325ff9635926 --- /dev/null +++ b/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/wechat-mini-program.yaml @@ -0,0 +1,50 @@ +# 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. + +# WeChat Mini Program service-level metrics from the mini-program-monitor SDK. +# Filter gates the rule to traffic carrying miniprogram.platform == "wechat" so +# the WeChat and Alipay rules do not double-count when both runtimes report to +# the same OAP. Metrics named `meter_wechat_mp_*` power the WeChat dashboards. +# Aggregated across all app versions (instances). + +expSuffix: service(['service_name'], Layer.WECHAT_MINI_PROGRAM) +metricPrefix: meter_wechat_mp +filter: "{ tags -> tags.miniprogram_platform == 'wechat' }" + +metricsRules: + # Perf (service-wide averages) + - name: app_launch_duration + exp: miniprogram_app_launch_duration.avg(['service_name']) + - name: first_render_duration + exp: miniprogram_first_render_duration.avg(['service_name']) + # first_paint.time is an epoch-ms timestamp (not a duration) — not aggregated. + - name: route_duration + exp: miniprogram_route_duration.avg(['service_name']) + - name: script_duration + exp: miniprogram_script_duration.avg(['service_name']) + - name: package_load_duration + exp: miniprogram_package_load_duration.avg(['service_name']) + + # Request latency percentile + - name: request_duration_percentile + exp: miniprogram_request_duration_histogram.sum(['service_name', 'le']).histogram().histogram_percentile([50,75,90,95,99]) + + # Per-page (endpoint-scoped) variants — chained .endpoint(...) overrides the expSuffix. + - name: endpoint_app_launch_duration + exp: miniprogram_app_launch_duration.avg(['service_name', 'miniprogram_page_path']).endpoint(['service_name'], ['miniprogram_page_path'], Layer.WECHAT_MINI_PROGRAM) + - name: endpoint_first_render_duration + exp: miniprogram_first_render_duration.avg(['service_name', 'miniprogram_page_path']).endpoint(['service_name'], ['miniprogram_page_path'], Layer.WECHAT_MINI_PROGRAM) + - name: endpoint_request_duration_percentile + exp: miniprogram_request_duration_histogram.sum(['service_name', 'miniprogram_page_path', 'le']).histogram().histogram_percentile([50,75,90,95,99]).endpoint(['service_name'], ['miniprogram_page_path'], Layer.WECHAT_MINI_PROGRAM) From c97125cce402795de1e8f5a6ec7c7658940ea6b0 Mon Sep 17 00:00:00 2001 From: Wu Sheng Date: Mon, 20 Apr 2026 19:27:34 +0800 Subject: [PATCH 03/14] SWIP-12 Stage 3: mini-program UI dashboards and menu entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - wechat_mini_program/ and alipay_mini_program/ each ship 4 JSON templates: -root (isRoot=true service list), -service (Tab with Overview / Instance / Endpoint / Trace / Log), -instance (per-version perf), -endpoint (per-page perf). UITemplateInitializer auto-discovers these folders from Layer enum names, so the new Layer entries from Stage 1 wire them up automatically. - Folder names use Layer.name().toLowerCase() (underscores, not hyphens) — hyphenated folders would be silently skipped by the discovery pass. - WeChat dashboards include the full perf set (launch, render, route, script, package_load, request percentile). Alipay drops the WeChat-only metrics and labels launch/render as lifecycle-based approximations so operators don't compare them head-to-head. - Per-service dashboard adds a Trace tab — mini-program outbound spans are native segments and queryable in the normal trace UI, unlike iOS where traces go to Zipkin. - menu.yaml extends the existing Mobile group (added by SWIP-11 iOS) with two new entries pointing to the per-layer root templates. UITemplateCheckerTest passes. --- .../alipay_mini_program-endpoint.json | 169 ++++++++ .../alipay_mini_program-instance.json | 182 +++++++++ .../alipay_mini_program-root.json | 76 ++++ .../alipay_mini_program-service.json | 316 ++++++++++++++ .../ui-initialized-templates/menu.yaml | 10 + .../wechat_mini_program-endpoint.json | 169 ++++++++ .../wechat_mini_program-instance.json | 248 +++++++++++ .../wechat_mini_program-root.json | 76 ++++ .../wechat_mini_program-service.json | 385 ++++++++++++++++++ 9 files changed, 1631 insertions(+) create mode 100644 oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-endpoint.json create mode 100644 oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-instance.json create mode 100644 oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-root.json create mode 100644 oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-service.json create mode 100644 oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-endpoint.json create mode 100644 oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-instance.json create mode 100644 oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-root.json create mode 100644 oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-service.json diff --git a/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-endpoint.json b/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-endpoint.json new file mode 100644 index 000000000000..5309025e396f --- /dev/null +++ b/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-endpoint.json @@ -0,0 +1,169 @@ +[ + { + "id": "Alipay-Mini-Program-Endpoint", + "configuration": { + "children": [ + { + "x": 0, + "y": 0, + "w": 24, + "h": 49, + "i": "12", + "type": "Tab", + "children": [ + { + "name": "Overview", + "children": [ + { + "x": 0, + "y": 0, + "w": 12, + "h": 15, + "i": "0", + "type": "Widget", + "widget": { + "title": "App Launch Duration on this Page (approx, ms)", + "tips": "Lifecycle-based approximation for this page as entry" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "meter_alipay_mp_endpoint_app_launch_duration" + ] + }, + { + "x": 12, + "y": 0, + "w": 12, + "h": 15, + "i": "1", + "type": "Widget", + "widget": { + "title": "First Render Duration (approx, ms)", + "tips": "Lifecycle-based approximation for this page" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "meter_alipay_mp_endpoint_first_render_duration" + ] + }, + { + "x": 0, + "y": 15, + "w": 24, + "h": 15, + "i": "2", + "type": "Widget", + "widget": { + "title": "Request Duration Percentile (ms)", + "tips": "Latency distribution of outbound requests initiated from this page" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "relabels(meter_alipay_mp_endpoint_request_duration_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='P50,P75,P90,P95,P99')" + ], + "metricConfig": [ + { + "label": "P50, P75, P90, P95, P99" + } + ] + }, + { + "x": 0, + "y": 30, + "w": 8, + "h": 15, + "i": "3", + "type": "Widget", + "widget": { + "title": "Outbound Load (calls / min)" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "endpoint_cpm" + ] + }, + { + "x": 8, + "y": 30, + "w": 8, + "h": 15, + "i": "4", + "type": "Widget", + "widget": { + "title": "Outbound Avg Response Time (ms)" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "endpoint_resp_time" + ] + }, + { + "x": 16, + "y": 30, + "w": 8, + "h": 15, + "i": "5", + "type": "Widget", + "widget": { + "title": "Outbound Success Rate (%)" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "endpoint_sla/100" + ] + } + ] + } + ] + } + ], + "layer": "ALIPAY_MINI_PROGRAM", + "entity": "Endpoint", + "name": "Alipay-Mini-Program-Endpoint", + "id": "Alipay-Mini-Program-Endpoint", + "isRoot": false + } + } +] diff --git a/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-instance.json b/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-instance.json new file mode 100644 index 000000000000..bd588e919802 --- /dev/null +++ b/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-instance.json @@ -0,0 +1,182 @@ +[ + { + "id": "Alipay-Mini-Program-Instance", + "configuration": { + "children": [ + { + "x": 0, + "y": 0, + "w": 24, + "h": 64, + "i": "12", + "type": "Tab", + "children": [ + { + "name": "Overview", + "children": [ + { + "x": 0, + "y": 0, + "w": 12, + "h": 15, + "i": "0", + "type": "Widget", + "widget": { + "title": "App Launch Duration (approx, ms)", + "tips": "Lifecycle-based approximation for this release" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "meter_alipay_mp_instance_app_launch_duration" + ] + }, + { + "x": 12, + "y": 0, + "w": 12, + "h": 15, + "i": "1", + "type": "Widget", + "widget": { + "title": "First Render Duration (approx, ms)", + "tips": "Lifecycle-based approximation for this release" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "meter_alipay_mp_instance_first_render_duration" + ] + }, + { + "x": 0, + "y": 15, + "w": 24, + "h": 15, + "i": "2", + "type": "Widget", + "widget": { + "title": "Request Duration Percentile (ms)", + "tips": "P50 / P75 / P90 / P95 / P99 for this release" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "relabels(meter_alipay_mp_instance_request_duration_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='P50,P75,P90,P95,P99')" + ], + "metricConfig": [ + { + "label": "P50, P75, P90, P95, P99" + } + ] + }, + { + "x": 0, + "y": 30, + "w": 8, + "h": 15, + "i": "3", + "type": "Widget", + "widget": { + "title": "Outbound Load (calls / min)" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "service_instance_cpm" + ] + }, + { + "x": 8, + "y": 30, + "w": 8, + "h": 15, + "i": "4", + "type": "Widget", + "widget": { + "title": "Outbound Avg Response Time (ms)" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "service_instance_resp_time" + ] + }, + { + "x": 16, + "y": 30, + "w": 8, + "h": 15, + "i": "5", + "type": "Widget", + "widget": { + "title": "Outbound Success Rate (%)" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "service_instance_sla/100" + ] + } + ] + }, + { + "name": "Log", + "children": [ + { + "x": 0, + "y": 0, + "w": 24, + "h": 60, + "i": "0", + "type": "Log" + } + ] + } + ] + } + ], + "layer": "ALIPAY_MINI_PROGRAM", + "entity": "ServiceInstance", + "name": "Alipay-Mini-Program-Instance", + "id": "Alipay-Mini-Program-Instance", + "isRoot": false + } + } +] diff --git a/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-root.json b/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-root.json new file mode 100644 index 000000000000..0cb27b29a6d2 --- /dev/null +++ b/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-root.json @@ -0,0 +1,76 @@ +[ + { + "id": "Alipay-Mini-Program-Root", + "configuration": { + "children": [ + { + "x": 0, + "y": 2, + "w": 24, + "h": 46, + "i": "0", + "type": "Widget", + "graph": { + "type": "ServiceList", + "dashboardName": "Alipay-Mini-Program-Service", + "fontSize": 12, + "showXAxis": false, + "showYAxis": false, + "showGroup": true + }, + "expressions": [ + "avg(meter_alipay_mp_app_launch_duration)", + "avg(meter_alipay_mp_first_render_duration)", + "avg(meter_alipay_mp_request_duration_percentile{p='95'})", + "avg(service_cpm)" + ], + "subExpressions": [ + "meter_alipay_mp_app_launch_duration", + "meter_alipay_mp_first_render_duration", + "meter_alipay_mp_request_duration_percentile{p='95'}", + "service_cpm" + ], + "metricConfig": [ + { + "label": "Launch Duration", + "unit": "ms" + }, + { + "label": "First Render", + "unit": "ms" + }, + { + "label": "Request P95", + "unit": "ms" + }, + { + "label": "Outbound Load", + "unit": "calls / min" + } + ] + }, + { + "x": 0, + "y": 0, + "w": 24, + "h": 2, + "i": "100", + "type": "Text", + "graph": { + "fontColor": "theme", + "backgroundColor": "theme", + "content": "Alipay Mini Program monitoring via mini-program-monitor SDK. OTLP metrics (lifecycle-based launch / render approximations + request-duration histogram), OTLP logs (JS / promise / ajax errors), and native trace segments for outbound HTTP requests with sw8 propagation to backends.", + "fontSize": 14, + "textAlign": "left", + "url": "https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-alipay-mini-program-monitoring/" + } + } + ], + "id": "Alipay-Mini-Program-Root", + "layer": "ALIPAY_MINI_PROGRAM", + "entity": "All", + "name": "Alipay-Mini-Program-Root", + "isRoot": true + } + } +] diff --git a/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-service.json b/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-service.json new file mode 100644 index 000000000000..1cb39043848b --- /dev/null +++ b/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-service.json @@ -0,0 +1,316 @@ +[ + { + "id": "Alipay-Mini-Program-Service", + "configuration": { + "children": [ + { + "x": 0, + "y": 0, + "w": 24, + "h": 64, + "i": "4", + "type": "Tab", + "children": [ + { + "name": "Overview", + "children": [ + { + "x": 0, + "y": 0, + "w": 12, + "h": 15, + "i": "0", + "type": "Widget", + "widget": { + "title": "App Launch Duration (approx, ms)", + "tips": "Lifecycle-based approximation: App.onLaunch → App.onShow delta. Not a renderer-authoritative timing. Do not compare head-to-head with WeChat values." + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "meter_alipay_mp_app_launch_duration" + ] + }, + { + "x": 12, + "y": 0, + "w": 12, + "h": 15, + "i": "1", + "type": "Widget", + "widget": { + "title": "First Render Duration (approx, ms)", + "tips": "Lifecycle-based approximation: Page.onLoad → Page.onReady delta. Not a renderer-authoritative timing." + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "meter_alipay_mp_first_render_duration" + ] + }, + { + "x": 0, + "y": 15, + "w": 24, + "h": 15, + "i": "2", + "type": "Widget", + "widget": { + "title": "Request Duration Percentile (ms)", + "tips": "miniprogram.request.duration histogram — P50 / P75 / P90 / P95 / P99" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "relabels(meter_alipay_mp_request_duration_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='P50,P75,P90,P95,P99')" + ], + "metricConfig": [ + { + "label": "P50, P75, P90, P95, P99" + } + ] + }, + { + "x": 0, + "y": 30, + "w": 8, + "h": 15, + "i": "3", + "type": "Widget", + "widget": { + "title": "Outbound Load (calls / min)", + "tips": "Outbound HTTP requests observed by the mini program, derived from native trace segments" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "service_cpm" + ] + }, + { + "x": 8, + "y": 30, + "w": 8, + "h": 15, + "i": "4", + "type": "Widget", + "widget": { + "title": "Outbound Avg Response Time (ms)", + "tips": "Client-observed average latency of outbound HTTP requests" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "service_resp_time" + ] + }, + { + "x": 16, + "y": 30, + "w": 8, + "h": 15, + "i": "5", + "type": "Widget", + "widget": { + "title": "Outbound Success Rate (%)", + "tips": "Percentage of outbound HTTP requests with status code < 400" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "service_sla/100" + ] + }, + { + "x": 0, + "y": 45, + "w": 24, + "h": 15, + "i": "6", + "type": "Widget", + "widget": { + "title": "Error Count", + "tips": "Counted from LAL-processed error logs, aggregated per service" + }, + "graph": { + "type": "Bar", + "showBackground": true + }, + "expressions": [ + "meter_alipay_mp_error_count" + ] + } + ] + }, + { + "name": "Instance", + "children": [ + { + "x": 0, + "y": 0, + "w": 24, + "h": 60, + "i": "0", + "type": "Widget", + "widget": { + "title": "App Version Breakdown", + "tips": "Each instance represents a release / version (service.instance.id — recommended pattern: service.version). Compare across releases." + }, + "graph": { + "type": "InstanceList", + "dashboardName": "Alipay-Mini-Program-Instance", + "fontSize": 12 + }, + "expressions": [ + "avg(meter_alipay_mp_instance_app_launch_duration)", + "avg(meter_alipay_mp_instance_first_render_duration)", + "avg(meter_alipay_mp_instance_request_duration_percentile{p='95'})", + "avg(service_instance_cpm)" + ], + "subExpressions": [ + "meter_alipay_mp_instance_app_launch_duration", + "meter_alipay_mp_instance_first_render_duration", + "meter_alipay_mp_instance_request_duration_percentile{p='95'}", + "service_instance_cpm" + ], + "metricConfig": [ + { + "label": "Launch Duration", + "unit": "ms" + }, + { + "label": "First Render", + "unit": "ms" + }, + { + "label": "Request P95", + "unit": "ms" + }, + { + "label": "Outbound Load", + "unit": "calls / min" + } + ] + } + ] + }, + { + "name": "Endpoint", + "children": [ + { + "x": 0, + "y": 0, + "w": 24, + "h": 60, + "i": "0", + "type": "Widget", + "widget": { + "title": "Pages", + "tips": "In-app pages (miniprogram.page.path). Click a row to drill into per-page perf + errors." + }, + "graph": { + "type": "EndpointList", + "dashboardName": "Alipay-Mini-Program-Endpoint", + "fontSize": 12, + "showXAxis": false, + "showYAxis": false + }, + "expressions": [ + "avg(meter_alipay_mp_endpoint_first_render_duration)", + "avg(meter_alipay_mp_endpoint_request_duration_percentile{p='95'})", + "avg(endpoint_cpm)" + ], + "subExpressions": [ + "meter_alipay_mp_endpoint_first_render_duration", + "meter_alipay_mp_endpoint_request_duration_percentile{p='95'}", + "endpoint_cpm" + ], + "metricConfig": [ + { + "label": "First Render", + "unit": "ms" + }, + { + "label": "Request P95", + "unit": "ms" + }, + { + "label": "Outbound Load", + "unit": "calls / min" + } + ] + } + ] + }, + { + "name": "Trace", + "children": [ + { + "x": 0, + "y": 0, + "w": 24, + "h": 60, + "i": "0", + "type": "Trace" + } + ] + }, + { + "name": "Log", + "children": [ + { + "x": 0, + "y": 0, + "w": 24, + "h": 60, + "i": "0", + "type": "Log" + } + ] + } + ] + } + ], + "layer": "ALIPAY_MINI_PROGRAM", + "entity": "Service", + "name": "Alipay-Mini-Program-Service", + "isRoot": false + } + } +] diff --git a/oap-server/server-starter/src/main/resources/ui-initialized-templates/menu.yaml b/oap-server/server-starter/src/main/resources/ui-initialized-templates/menu.yaml index a5eca99cfbb4..444fed281046 100644 --- a/oap-server/server-starter/src/main/resources/ui-initialized-templates/menu.yaml +++ b/oap-server/server-starter/src/main/resources/ui-initialized-templates/menu.yaml @@ -252,6 +252,16 @@ menus: description: iOS/iPadOS app monitoring via OpenTelemetry Swift SDK. Provides HTTP performance, MetricKit daily stats, and crash diagnostics. documentLink: https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-ios-monitoring/ i18nKey: mobile_ios + - title: WeChat Mini Program + layer: WECHAT_MINI_PROGRAM + description: WeChat Mini Program monitoring via the mini-program-monitor SDK. Provides launch / render / route / script / package-load perf, request latency percentiles, error counts, and per-page breakdowns — plus trace drill-down for outbound HTTP requests. + documentLink: https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-wechat-mini-program-monitoring/ + i18nKey: mobile_wechat_mini_program + - title: Alipay Mini Program + layer: ALIPAY_MINI_PROGRAM + description: Alipay Mini Program monitoring via the mini-program-monitor SDK. Provides lifecycle-based launch / render approximations, request latency percentiles, error counts, and per-page breakdowns — plus trace drill-down for outbound HTTP requests. + documentLink: https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-alipay-mini-program-monitoring/ + i18nKey: mobile_alipay_mini_program - title: GenAI icon: gen_ai description: Generative AI (GenAI) observability provides comprehensive monitoring and performance insights for various AI services and large language models (LLM). diff --git a/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-endpoint.json b/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-endpoint.json new file mode 100644 index 000000000000..4ee8db977ebd --- /dev/null +++ b/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-endpoint.json @@ -0,0 +1,169 @@ +[ + { + "id": "WeChat-Mini-Program-Endpoint", + "configuration": { + "children": [ + { + "x": 0, + "y": 0, + "w": 24, + "h": 49, + "i": "12", + "type": "Tab", + "children": [ + { + "name": "Overview", + "children": [ + { + "x": 0, + "y": 0, + "w": 12, + "h": 15, + "i": "0", + "type": "Widget", + "widget": { + "title": "App Launch Duration on this Page (ms)", + "tips": "Launch duration observed when this page was the entry" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "meter_wechat_mp_endpoint_app_launch_duration" + ] + }, + { + "x": 12, + "y": 0, + "w": 12, + "h": 15, + "i": "1", + "type": "Widget", + "widget": { + "title": "First Render Duration (ms)", + "tips": "First render time for this page" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "meter_wechat_mp_endpoint_first_render_duration" + ] + }, + { + "x": 0, + "y": 15, + "w": 24, + "h": 15, + "i": "2", + "type": "Widget", + "widget": { + "title": "Request Duration Percentile (ms)", + "tips": "Latency distribution of outbound requests initiated from this page" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "relabels(meter_wechat_mp_endpoint_request_duration_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='P50,P75,P90,P95,P99')" + ], + "metricConfig": [ + { + "label": "P50, P75, P90, P95, P99" + } + ] + }, + { + "x": 0, + "y": 30, + "w": 8, + "h": 15, + "i": "3", + "type": "Widget", + "widget": { + "title": "Outbound Load (calls / min)" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "endpoint_cpm" + ] + }, + { + "x": 8, + "y": 30, + "w": 8, + "h": 15, + "i": "4", + "type": "Widget", + "widget": { + "title": "Outbound Avg Response Time (ms)" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "endpoint_resp_time" + ] + }, + { + "x": 16, + "y": 30, + "w": 8, + "h": 15, + "i": "5", + "type": "Widget", + "widget": { + "title": "Outbound Success Rate (%)" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "endpoint_sla/100" + ] + } + ] + } + ] + } + ], + "layer": "WECHAT_MINI_PROGRAM", + "entity": "Endpoint", + "name": "WeChat-Mini-Program-Endpoint", + "id": "WeChat-Mini-Program-Endpoint", + "isRoot": false + } + } +] diff --git a/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-instance.json b/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-instance.json new file mode 100644 index 000000000000..3718c5cbaecf --- /dev/null +++ b/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-instance.json @@ -0,0 +1,248 @@ +[ + { + "id": "WeChat-Mini-Program-Instance", + "configuration": { + "children": [ + { + "x": 0, + "y": 0, + "w": 24, + "h": 64, + "i": "12", + "type": "Tab", + "children": [ + { + "name": "Overview", + "children": [ + { + "x": 0, + "y": 0, + "w": 8, + "h": 15, + "i": "0", + "type": "Widget", + "widget": { + "title": "App Launch Duration (ms)", + "tips": "Launch duration for this release" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "meter_wechat_mp_instance_app_launch_duration" + ] + }, + { + "x": 8, + "y": 0, + "w": 8, + "h": 15, + "i": "1", + "type": "Widget", + "widget": { + "title": "First Render Duration (ms)", + "tips": "First render duration for this release" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "meter_wechat_mp_instance_first_render_duration" + ] + }, + { + "x": 16, + "y": 0, + "w": 8, + "h": 15, + "i": "2", + "type": "Widget", + "widget": { + "title": "Package Load Duration (ms)" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "meter_wechat_mp_instance_package_load_duration" + ] + }, + { + "x": 0, + "y": 15, + "w": 12, + "h": 15, + "i": "3", + "type": "Widget", + "widget": { + "title": "Route Duration (ms)" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "meter_wechat_mp_instance_route_duration" + ] + }, + { + "x": 12, + "y": 15, + "w": 12, + "h": 15, + "i": "4", + "type": "Widget", + "widget": { + "title": "Script Duration (ms)" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "meter_wechat_mp_instance_script_duration" + ] + }, + { + "x": 0, + "y": 30, + "w": 24, + "h": 15, + "i": "5", + "type": "Widget", + "widget": { + "title": "Request Duration Percentile (ms)", + "tips": "P50 / P75 / P90 / P95 / P99 for this release" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "relabels(meter_wechat_mp_instance_request_duration_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='P50,P75,P90,P95,P99')" + ], + "metricConfig": [ + { + "label": "P50, P75, P90, P95, P99" + } + ] + }, + { + "x": 0, + "y": 45, + "w": 8, + "h": 15, + "i": "6", + "type": "Widget", + "widget": { + "title": "Outbound Load (calls / min)" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "service_instance_cpm" + ] + }, + { + "x": 8, + "y": 45, + "w": 8, + "h": 15, + "i": "7", + "type": "Widget", + "widget": { + "title": "Outbound Avg Response Time (ms)" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "service_instance_resp_time" + ] + }, + { + "x": 16, + "y": 45, + "w": 8, + "h": 15, + "i": "8", + "type": "Widget", + "widget": { + "title": "Outbound Success Rate (%)" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "service_instance_sla/100" + ] + } + ] + }, + { + "name": "Log", + "children": [ + { + "x": 0, + "y": 0, + "w": 24, + "h": 60, + "i": "0", + "type": "Log" + } + ] + } + ] + } + ], + "layer": "WECHAT_MINI_PROGRAM", + "entity": "ServiceInstance", + "name": "WeChat-Mini-Program-Instance", + "id": "WeChat-Mini-Program-Instance", + "isRoot": false + } + } +] diff --git a/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-root.json b/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-root.json new file mode 100644 index 000000000000..6938213e8ec8 --- /dev/null +++ b/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-root.json @@ -0,0 +1,76 @@ +[ + { + "id": "WeChat-Mini-Program-Root", + "configuration": { + "children": [ + { + "x": 0, + "y": 2, + "w": 24, + "h": 46, + "i": "0", + "type": "Widget", + "graph": { + "type": "ServiceList", + "dashboardName": "WeChat-Mini-Program-Service", + "fontSize": 12, + "showXAxis": false, + "showYAxis": false, + "showGroup": true + }, + "expressions": [ + "avg(meter_wechat_mp_app_launch_duration)", + "avg(meter_wechat_mp_first_render_duration)", + "avg(meter_wechat_mp_request_duration_percentile{p='95'})", + "avg(service_cpm)" + ], + "subExpressions": [ + "meter_wechat_mp_app_launch_duration", + "meter_wechat_mp_first_render_duration", + "meter_wechat_mp_request_duration_percentile{p='95'}", + "service_cpm" + ], + "metricConfig": [ + { + "label": "Launch Duration", + "unit": "ms" + }, + { + "label": "First Render", + "unit": "ms" + }, + { + "label": "Request P95", + "unit": "ms" + }, + { + "label": "Outbound Load", + "unit": "calls / min" + } + ] + }, + { + "x": 0, + "y": 0, + "w": 24, + "h": 2, + "i": "100", + "type": "Text", + "graph": { + "fontColor": "theme", + "backgroundColor": "theme", + "content": "WeChat Mini Program monitoring via mini-program-monitor SDK. OTLP metrics (perf gauges + request-duration histogram), OTLP logs (JS / promise / ajax / pageNotFound errors), and native trace segments for outbound HTTP requests with sw8 propagation to backends.", + "fontSize": 14, + "textAlign": "left", + "url": "https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-wechat-mini-program-monitoring/" + } + } + ], + "id": "WeChat-Mini-Program-Root", + "layer": "WECHAT_MINI_PROGRAM", + "entity": "All", + "name": "WeChat-Mini-Program-Root", + "isRoot": true + } + } +] diff --git a/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-service.json b/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-service.json new file mode 100644 index 000000000000..bb17dd548cf0 --- /dev/null +++ b/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-service.json @@ -0,0 +1,385 @@ +[ + { + "id": "WeChat-Mini-Program-Service", + "configuration": { + "children": [ + { + "x": 0, + "y": 0, + "w": 24, + "h": 64, + "i": "4", + "type": "Tab", + "children": [ + { + "name": "Overview", + "children": [ + { + "x": 0, + "y": 0, + "w": 8, + "h": 15, + "i": "0", + "type": "Widget", + "widget": { + "title": "App Launch Duration (ms)", + "tips": "Mini program start-up time from wx.getPerformance() entries, averaged fleet-wide" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "meter_wechat_mp_app_launch_duration" + ] + }, + { + "x": 8, + "y": 0, + "w": 8, + "h": 15, + "i": "1", + "type": "Widget", + "widget": { + "title": "First Render Duration (ms)", + "tips": "PerformanceObserver firstRender entry averaged fleet-wide" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "meter_wechat_mp_first_render_duration" + ] + }, + { + "x": 16, + "y": 0, + "w": 8, + "h": 15, + "i": "2", + "type": "Widget", + "widget": { + "title": "Package Load Duration (ms)", + "tips": "PerformanceObserver loadPackage entry" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "meter_wechat_mp_package_load_duration" + ] + }, + { + "x": 0, + "y": 15, + "w": 12, + "h": 15, + "i": "3", + "type": "Widget", + "widget": { + "title": "Route Duration (ms)", + "tips": "PerformanceObserver navigation / route entry" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "meter_wechat_mp_route_duration" + ] + }, + { + "x": 12, + "y": 15, + "w": 12, + "h": 15, + "i": "4", + "type": "Widget", + "widget": { + "title": "Script Duration (ms)", + "tips": "PerformanceObserver script entry" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "meter_wechat_mp_script_duration" + ] + }, + { + "x": 0, + "y": 30, + "w": 24, + "h": 15, + "i": "5", + "type": "Widget", + "widget": { + "title": "Request Duration Percentile (ms)", + "tips": "miniprogram.request.duration histogram — P50 / P75 / P90 / P95 / P99" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "relabels(meter_wechat_mp_request_duration_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='P50,P75,P90,P95,P99')" + ], + "metricConfig": [ + { + "label": "P50, P75, P90, P95, P99" + } + ] + }, + { + "x": 0, + "y": 45, + "w": 8, + "h": 15, + "i": "6", + "type": "Widget", + "widget": { + "title": "Outbound Load (calls / min)", + "tips": "Outbound HTTP requests observed by the mini program, derived from native trace segments" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "service_cpm" + ] + }, + { + "x": 8, + "y": 45, + "w": 8, + "h": 15, + "i": "7", + "type": "Widget", + "widget": { + "title": "Outbound Avg Response Time (ms)", + "tips": "Client-observed average latency of outbound HTTP requests" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "service_resp_time" + ] + }, + { + "x": 16, + "y": 45, + "w": 8, + "h": 15, + "i": "8", + "type": "Widget", + "widget": { + "title": "Outbound Success Rate (%)", + "tips": "Percentage of outbound HTTP requests with status code < 400" + }, + "graph": { + "type": "Line", + "step": false, + "smooth": false, + "showSymbol": true, + "showXAxis": true, + "showYAxis": true + }, + "expressions": [ + "service_sla/100" + ] + }, + { + "x": 0, + "y": 60, + "w": 24, + "h": 15, + "i": "9", + "type": "Widget", + "widget": { + "title": "Error Count", + "tips": "Counted from LAL-processed error logs, aggregated per service" + }, + "graph": { + "type": "Bar", + "showBackground": true + }, + "expressions": [ + "meter_wechat_mp_error_count" + ] + } + ] + }, + { + "name": "Instance", + "children": [ + { + "x": 0, + "y": 0, + "w": 24, + "h": 60, + "i": "0", + "type": "Widget", + "widget": { + "title": "App Version Breakdown", + "tips": "Each instance represents a release / version (service.instance.id — recommended pattern: service.version). Compare across releases." + }, + "graph": { + "type": "InstanceList", + "dashboardName": "WeChat-Mini-Program-Instance", + "fontSize": 12 + }, + "expressions": [ + "avg(meter_wechat_mp_instance_app_launch_duration)", + "avg(meter_wechat_mp_instance_first_render_duration)", + "avg(meter_wechat_mp_instance_request_duration_percentile{p='95'})", + "avg(service_instance_cpm)" + ], + "subExpressions": [ + "meter_wechat_mp_instance_app_launch_duration", + "meter_wechat_mp_instance_first_render_duration", + "meter_wechat_mp_instance_request_duration_percentile{p='95'}", + "service_instance_cpm" + ], + "metricConfig": [ + { + "label": "Launch Duration", + "unit": "ms" + }, + { + "label": "First Render", + "unit": "ms" + }, + { + "label": "Request P95", + "unit": "ms" + }, + { + "label": "Outbound Load", + "unit": "calls / min" + } + ] + } + ] + }, + { + "name": "Endpoint", + "children": [ + { + "x": 0, + "y": 0, + "w": 24, + "h": 60, + "i": "0", + "type": "Widget", + "widget": { + "title": "Pages", + "tips": "In-app pages (miniprogram.page.path). Click a row to drill into per-page perf + errors." + }, + "graph": { + "type": "EndpointList", + "dashboardName": "WeChat-Mini-Program-Endpoint", + "fontSize": 12, + "showXAxis": false, + "showYAxis": false + }, + "expressions": [ + "avg(meter_wechat_mp_endpoint_first_render_duration)", + "avg(meter_wechat_mp_endpoint_request_duration_percentile{p='95'})", + "avg(endpoint_cpm)" + ], + "subExpressions": [ + "meter_wechat_mp_endpoint_first_render_duration", + "meter_wechat_mp_endpoint_request_duration_percentile{p='95'}", + "endpoint_cpm" + ], + "metricConfig": [ + { + "label": "First Render", + "unit": "ms" + }, + { + "label": "Request P95", + "unit": "ms" + }, + { + "label": "Outbound Load", + "unit": "calls / min" + } + ] + } + ] + }, + { + "name": "Trace", + "children": [ + { + "x": 0, + "y": 0, + "w": 24, + "h": 60, + "i": "0", + "type": "Trace" + } + ] + }, + { + "name": "Log", + "children": [ + { + "x": 0, + "y": 0, + "w": 24, + "h": 60, + "i": "0", + "type": "Log" + } + ] + } + ] + } + ], + "layer": "WECHAT_MINI_PROGRAM", + "entity": "Service", + "name": "WeChat-Mini-Program-Service", + "isRoot": false + } + } +] From 79616170f6e2684455a4764ccdb9efde3884e8ec Mon Sep 17 00:00:00 2001 From: Wu Sheng Date: Mon, 20 Apr 2026 19:30:57 +0800 Subject: [PATCH 04/14] SWIP-12 Stage 4: user-facing docs, menu entry, changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docs/en/setup/backend/backend-wechat-mini-program-monitoring.md and backend-alipay-mini-program-monitoring.md — prerequisites, OAP configuration (when defaults are overridden), SDK setup snippet, metric catalog per platform, error-log categories, entity model, SDK version compatibility (v0.2.x / v0.3.x / v0.4.0+), dashboard overview, and platform-specific limitations. - docs/menu.yml — two new entries under Mobile Monitoring pointing at the new setup guides. - docs/en/changes/changes.md — entry under OAP Server summarizing the layer / componentId / listener mapping / rules work, and an entry under Documentation for the two new guides. docs/en/swip/readme.md already lists SWIP-12 under Accepted (from the earlier design PR) and docs/en/security/README.md's Client-Side Monitoring section already covers mini-programs — both untouched. --- docs/en/changes/changes.md | 2 + .../backend-alipay-mini-program-monitoring.md | 167 +++++++++++++++++ .../backend-wechat-mini-program-monitoring.md | 169 ++++++++++++++++++ docs/menu.yml | 4 + 4 files changed, 342 insertions(+) create mode 100644 docs/en/setup/backend/backend-alipay-mini-program-monitoring.md create mode 100644 docs/en/setup/backend/backend-wechat-mini-program-monitoring.md diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md index 44d8371be5e0..592540ee98f8 100644 --- a/docs/en/changes/changes.md +++ b/docs/en/changes/changes.md @@ -35,6 +35,7 @@ * Add iOS/iPadOS app monitoring via OpenTelemetry Swift SDK (SWIP-11). Includes the `IOS` layer, `IOSHTTPSpanListener` for outbound HTTP client metrics (supports OTel Swift `.old`/`.stable`/`.httpDup` semantic-convention modes via stable-then-legacy attribute fallback), `IOSMetricKitSpanListener` for daily MetricKit metrics (exit counts split by foreground/background, app-launch / hang-time percentile histograms with finite 30 s overflow ceiling), LAL rules for crash/hang diagnostics, Mobile menu, and iOS dashboards. * Fix LAL `layer: auto` mode dropping logs after extractor set the layer. Codegen now propagates `layer "..."` assignments to `LogMetadata.layer` so `FilterSpec.doSink()` sees the script-decided layer. * Fix MetricKit histogram percentile metrics being reported at 1000× their true value — the listener now marks its `SampleFamily` with `defaultHistogramBucketUnit(MILLISECONDS)` so MAL's default SECONDS→MS rescale of `le` labels is not applied. +* Add WeChat and Alipay Mini Program monitoring via the SkyAPM mini-program-monitor SDK (SWIP-12). Two new layers (`WECHAT_MINI_PROGRAM`, `ALIPAY_MINI_PROGRAM`); two new JavaScript componentIds (`WeChat-MiniProgram: 10002`, `AliPay-MiniProgram: 10003`); componentId-driven layer mapping in `CommonAnalysisListener` so native trace segments land on the correct layer with no new SPI; MAL rules per platform × scope under `otel-rules/miniprogram/` including chained `.endpoint(...)` per-page variants; LAL `layer: auto` rule that produces both layers via `miniprogram.platform` dispatch and emits error-count samples; log-MAL rule for per-layer `error_count`; per-layer menu entries and service / instance / endpoint dashboards with a dedicated Trace tab. #### UI * Add mobile menu icon and i18n labels for the iOS layer. @@ -43,6 +44,7 @@ #### Documentation * Update LAL documentation with `sourceAttribute()` function and `layer: auto` mode. * Add iOS app monitoring setup documentation. +* Add WeChat / Alipay Mini Program monitoring setup documentation, plus a client-side-monitoring section in the security guide covering public-internet ingress (OTLP + `/v3/segments`) for mobile / browser / mini-program SDKs. All issues and pull requests are [here](https://github.com/apache/skywalking/issues?q=milestone:10.5.0) diff --git a/docs/en/setup/backend/backend-alipay-mini-program-monitoring.md b/docs/en/setup/backend/backend-alipay-mini-program-monitoring.md new file mode 100644 index 000000000000..52fffcc731d1 --- /dev/null +++ b/docs/en/setup/backend/backend-alipay-mini-program-monitoring.md @@ -0,0 +1,167 @@ +# Alipay Mini Program Monitoring + +SkyWalking supports Alipay Mini Program monitoring via the +[SkyAPM mini-program-monitor SDK](https://github.com/SkyAPM/mini-program-monitor) — the same +SDK used for [WeChat](backend-wechat-mini-program-monitoring.md). The SDK detects the Alipay +runtime and adapts the platform tag accordingly; the OAP-side rules then route traffic to the +Alipay layer. + +Three data streams are captured: + +1. **OTLP logs** — JS errors, promise rejections, and AJAX failures (no `pageNotFound` — the + Alipay base library does not expose `my.onPageNotFound`) +2. **OTLP metrics** — app launch and first render durations (lifecycle-based approximations + on Alipay, not renderer-authoritative) and a delta histogram of outbound request durations +3. **SkyWalking native trace segments** (opt-in) — one `SegmentObject` per sampled outbound + `my.request`, with an `sw8` header injected on the wire so downstream backend services + join the same trace + +No new receiver is required — the SDK speaks OTLP/HTTP and the SkyWalking native v3 protocol. + +## Prerequisites + +- mini-program-monitor **≥ v0.4.0** is the recommended baseline. v0.3.x still works but with + the legacy per-device `serviceInstance` behavior (see [Compatibility](#compatibility)). +- SkyWalking OAP with the changes from SWIP-12 — OTLP HTTP receiver enabled (default on core + REST port 12800), the two new component ids registered in `component-libraries.yml`, and the + four MAL rules under `otel-rules/miniprogram/` enabled. + +## OAP Configuration + +Append `miniprogram/*` to `enabledOtelMetricsRules` and `miniprogram` to `lalFiles` and +`malFiles` in `application.yml` (preserve the existing defaults — don't replace them). These +entries are included in the shipped defaults, so this section is only relevant if you have +overridden the defaults via `SW_OTEL_RECEIVER_ENABLED_OTEL_METRICS_RULES`, `SW_LOG_LAL_FILES`, +or `SW_LOG_MAL_FILES`: + +```yaml +receiver-otel: + selector: ${SW_OTEL_RECEIVER:default} + default: + enabledHandlers: ${SW_OTEL_RECEIVER_ENABLED_HANDLERS:"otlp-traces,otlp-metrics,otlp-logs"} + enabledOtelMetricsRules: ${SW_OTEL_RECEIVER_ENABLED_OTEL_METRICS_RULES:",miniprogram/*"} + +log-analyzer: + selector: ${SW_LOG_ANALYZER:default} + default: + lalFiles: ${SW_LOG_LAL_FILES:",miniprogram"} + malFiles: ${SW_LOG_MAL_FILES:",miniprogram"} +``` + +Native trace segments (`/v3/segments`) need no additional config — the existing trace receiver +handles them and assigns the `ALIPAY_MINI_PROGRAM` layer automatically based on the SDK's +`AliPay-MiniProgram` componentId (10003). + +## Mini Program Setup + +```js +// app.js +const { init } = require('mini-program-monitor'); +App({ + onLaunch() { + init({ + service: 'my-mini-program', + serviceVersion: 'v1.2.0', + serviceInstance: 'v1.2.0', // version-scoped, recommended pattern + collector: 'https://', + platform: 'alipay', // optional — auto-detected from runtime + enable: { tracing: true }, // opt-in: SkyWalking native segments + }); + }, +}); +``` + +> **Security:** mini-program SDKs send telemetry from end-user devices over the public +> internet. See the [Security Notice](../../security/README.md) for deployment guidelines. + +## Metrics + +All MAL metrics are prefixed `meter_alipay_mp_*` (service-scope) or +`meter_alipay_mp_instance_*` (instance-scope). Endpoint-scoped variants surface as +`meter_alipay_mp_endpoint_*`. + +| Metric | Scope | Source | +|----------------------------------|--------------------------------|-------------------------------------------------------| +| `app_launch_duration` | service / instance / endpoint | App.onLaunch → App.onShow lifecycle delta (approx) | +| `first_render_duration` | service / instance / endpoint | Page.onLoad → Page.onReady lifecycle delta (approx) | +| `request_duration_percentile` | service / instance / endpoint | `miniprogram.request.duration` histogram (P50–P99) | +| `error_count` | service | LAL-derived counter — log-MAL rule aggregates per exception type | + +> **Precision caveat:** on Alipay the perf values come from lifecycle hooks, not the native +> PerformanceObserver entries WeChat uses (the Alipay base library doesn't expose them). +> These are approximations of time-to-interactive, not authoritative renderer timings. Do +> not compare WeChat and Alipay perf values directly. + +WeChat-only metrics (`route_duration`, `script_duration`, `package_load_duration`, +`first_paint.time`) are absent from Alipay. The Alipay dashboard omits the Navigation row +entirely. + +Outbound HTTP metrics come "for free" from the existing `core.oal` rules once the component-id +mapping assigns the ALIPAY_MINI_PROGRAM layer to segments: `service_cpm`, `service_resp_time`, +`service_sla`, `service_percentile`, `service_apdex`, `service_instance_cpm`, +`service_instance_resp_time`, `service_instance_sla`, `endpoint_cpm`, `endpoint_resp_time`, +`endpoint_sla`, `endpoint_percentile`, plus `ServiceRelation` / `EndpointRelation` topology +edges to the backend services the mini-program calls. + +## Error Logs + +The LAL rule `lal/miniprogram.yaml` uses `layer: auto` mode and dispatches on the +`miniprogram.platform` resource attribute — one rule file produces both the +`WECHAT_MINI_PROGRAM` and `ALIPAY_MINI_PROGRAM` layers. For each error log, the extractor +emits a `miniprogram_error_count` counter sample, which the log-MAL rule in +`log-mal-rules/miniprogram.yaml` aggregates into the per-layer `error_count` metric. + +Error categories (`exception.type` tag): + +| Category | Source | +|--------------------|------------------------------------------------------------------| +| `js` | `my.onError` — unhandled JS errors | +| `promise` | `my.onUnhandledRejection` — unhandled promise rejections | +| `ajax` | `my.request` failures (network + non-2xx) | + +## Entities + +| SkyWalking Entity | Source | Cardinality | Rationale | +|-------------------|-----------------------------------|-------------------|--------------------------------------------------------| +| Service | `service.name` | 1 per app | Fleet-wide app health | +| ServiceInstance | `service.instance.id` (pattern: set to `service.version`) | tens per app | Version regression / rollout monitoring | +| Endpoint | `miniprogram.page.path` | dozens per app | Which in-app page is slow / error-prone | + +Per-device `service.instance.id` is intentionally not used as an aggregation dimension — +unbounded cardinality means millions of entities on any real user base. The SDK (≥ v0.4.0) +no longer auto-generates a device id; operators set `serviceInstance` to a version-scoped +value. + +## Compatibility + +- **SDK ≤ v0.2.x** emits `componentId = 10001` (ajax-inherited). Its segments resolve to + `Layer.GENERAL` and do not benefit from this integration's layer / topology work. +- **SDK ≤ v0.3.x** auto-generates `service.instance.id = mp-{random}` per session, creating + one OAP instance entity per device — usually undesirable. Operators on v0.3.x can avoid this + by passing `init({ serviceInstance: serviceVersion })` explicitly. +- **SDK ≥ v0.4.0** leaves `service.instance.id` unset by default. The three signal pipelines + then handle absence differently: native segments produce a literal `-` instance entity; OTLP + logs and metrics create no instance entity at all. Per-instance dashboards are meaningful + only when the operator sets `serviceInstance`. +- The recommended pattern (SDK docs + e2e CI) is to set `serviceInstance` to a version-scoped + value. + +## Dashboards + +- **Alipay Mini Program** in the **Mobile** menu group — service list landing page. +- **Per-service** dashboard — launch / render durations (approx), request latency percentile, + outbound traffic (load, avg latency, success rate), error count, plus tabs for Instance, + Endpoint, Trace, and Log drill-down. +- **Per-instance (version)** dashboard — same metric set scoped to a release. +- **Per-endpoint (page)** dashboard — per-page perf, outbound traffic, request latency + percentile. + +## Limitations + +- Alipay perf metrics are lifecycle-based approximations, not renderer-authoritative. +- No `pageNotFound` error category — the Alipay base library does not expose + `my.onPageNotFound`. +- WebSocket, memory-warning, and network-status-change signals are not instrumented by the + current SDK. +- Device-level per-user aggregation is not supported by design — `serviceInstance` is intended + to be a version-scoped identifier. diff --git a/docs/en/setup/backend/backend-wechat-mini-program-monitoring.md b/docs/en/setup/backend/backend-wechat-mini-program-monitoring.md new file mode 100644 index 000000000000..d8e467e97e80 --- /dev/null +++ b/docs/en/setup/backend/backend-wechat-mini-program-monitoring.md @@ -0,0 +1,169 @@ +# WeChat Mini Program Monitoring + +SkyWalking supports WeChat Mini Program monitoring via the +[SkyAPM mini-program-monitor SDK](https://github.com/SkyAPM/mini-program-monitor). +The SDK emits three data streams over standard protocols: + +1. **OTLP logs** — JS errors, promise rejections, AJAX failures, and `pageNotFound` events +2. **OTLP metrics** — app launch, first render, route, script, and sub-package load durations; + a `first_paint.time` epoch-ms timestamp; and a delta histogram of outbound request durations +3. **SkyWalking native trace segments** (opt-in) — one `SegmentObject` per sampled outbound + `wx.request`, with an `sw8` header injected on the wire so downstream backend services join + the same trace + +No new receiver is required — the SDK speaks OTLP/HTTP and the SkyWalking native v3 protocol. + +## Prerequisites + +- mini-program-monitor **≥ v0.4.0** is the recommended baseline. v0.3.x still works but with + the legacy per-device `serviceInstance` behavior (see [Compatibility](#compatibility)). +- SkyWalking OAP with the changes from SWIP-12 — OTLP HTTP receiver enabled (default on core + REST port 12800), the two new component ids registered in `component-libraries.yml`, and the + four MAL rules under `otel-rules/miniprogram/` enabled. + +## OAP Configuration + +Append `miniprogram/*` to `enabledOtelMetricsRules` and `miniprogram` to `lalFiles` and +`malFiles` in `application.yml` (preserve the existing defaults — don't replace them). These +entries are included in the shipped defaults, so this section is only relevant if you have +overridden the defaults via `SW_OTEL_RECEIVER_ENABLED_OTEL_METRICS_RULES`, `SW_LOG_LAL_FILES`, +or `SW_LOG_MAL_FILES`: + +```yaml +receiver-otel: + selector: ${SW_OTEL_RECEIVER:default} + default: + enabledHandlers: ${SW_OTEL_RECEIVER_ENABLED_HANDLERS:"otlp-traces,otlp-metrics,otlp-logs"} + enabledOtelMetricsRules: ${SW_OTEL_RECEIVER_ENABLED_OTEL_METRICS_RULES:",miniprogram/*"} + +log-analyzer: + selector: ${SW_LOG_ANALYZER:default} + default: + lalFiles: ${SW_LOG_LAL_FILES:",miniprogram"} + malFiles: ${SW_LOG_MAL_FILES:",miniprogram"} +``` + +`miniprogram/*` picks up all four MAL files under +`otel-rules/miniprogram/` (per-platform × per-scope, see [Metrics](#metrics)). Native trace +segments (`/v3/segments`) need no additional config — the existing trace receiver handles them +and assigns the `WECHAT_MINI_PROGRAM` layer automatically based on the SDK's +`WeChat-MiniProgram` componentId (10002). + +## Mini Program Setup + +```js +// app.js +const { init } = require('mini-program-monitor'); +App({ + onLaunch() { + init({ + service: 'my-mini-program', + serviceVersion: 'v1.2.0', + // Recommended: set serviceInstance to a version-scoped value (mirroring + // service.version or a release tag). Leaving it unset means OTLP metrics + logs + // do not produce an instance entity at all (native segments produce a literal '-'); + // per-version / per-release dashboards need this field populated. + serviceInstance: 'v1.2.0', + collector: 'https://', + platform: 'wechat', // optional — auto-detected from runtime + enable: { tracing: true }, // opt-in: SkyWalking native segments + }); + }, +}); +``` + +> **Security:** mini-program SDKs send telemetry from end-user devices over the public +> internet. See the [Security Notice](../../security/README.md) for deployment guidelines. + +## Metrics + +All MAL metrics are prefixed `meter_wechat_mp_*` (service-scope) or +`meter_wechat_mp_instance_*` (instance-scope). Endpoint-scoped variants, where present, use +the chained `.endpoint(...)` override inside the service-scope file and surface as +`meter_wechat_mp_endpoint_*`. + +| Metric | Scope | Source | +|----------------------------------|----------------------|-------------------------------------------------------| +| `app_launch_duration` | service / instance / endpoint | `wx.getPerformance()` launch entry | +| `first_render_duration` | service / instance / endpoint | PerformanceObserver `firstRender` entry | +| `route_duration` | service / instance | PerformanceObserver `navigation` / `route` entry | +| `script_duration` | service / instance | PerformanceObserver `script` entry | +| `package_load_duration` | service / instance | PerformanceObserver `loadPackage` entry | +| `request_duration_percentile` | service / instance / endpoint | `miniprogram.request.duration` histogram (P50–P99) | +| `error_count` | service | LAL-derived counter — log-MAL rule aggregates per exception type | + +`first_paint.time` is emitted by the SDK as a wall-clock epoch-ms timestamp (not a duration). +It is passed through as a raw gauge sample but not aggregated by MAL — surface it only on +per-page trace / log views where the absolute timestamp has meaning. + +Outbound HTTP metrics come "for free" from the existing `core.oal` rules once the component-id +mapping assigns the WECHAT_MINI_PROGRAM layer to segments. Under the WeChat Mini Program layer +you get: `service_cpm`, `service_resp_time`, `service_sla`, `service_percentile`, `service_apdex`, +`service_instance_cpm`, `service_instance_resp_time`, `service_instance_sla`, +`service_instance_percentile`, `endpoint_cpm`, `endpoint_resp_time`, `endpoint_sla`, +`endpoint_percentile`, plus `ServiceRelation` / `EndpointRelation` topology edges to the +backend services the mini-program calls. + +## Error Logs + +The LAL rule `lal/miniprogram.yaml` uses `layer: auto` mode. It dispatches on the +`miniprogram.platform` resource attribute — one rule file produces both the `WECHAT_MINI_PROGRAM` +and `ALIPAY_MINI_PROGRAM` layers. For each error log, the extractor also emits a +`miniprogram_error_count` counter sample, which the log-MAL rule in `log-mal-rules/miniprogram.yaml` +aggregates into the per-layer `error_count` metric. + +Error categories (`exception.type` tag): + +| Category | Source | +|--------------------|------------------------------------------------------------------| +| `js` | `wx.onError` — unhandled JS errors | +| `promise` | `wx.onUnhandledRejection` — unhandled promise rejections | +| `ajax` | `wx.request` failures (network + non-2xx) | +| `pageNotFound` | `wx.onPageNotFound` — page not found (WeChat only) | + +## Entities + +| SkyWalking Entity | Source | Cardinality | Rationale | +|-------------------|-----------------------------------|-------------------|--------------------------------------------------------| +| Service | `service.name` | 1 per app | Fleet-wide app health | +| ServiceInstance | `service.instance.id` (pattern: set to `service.version`) | tens per app | Version regression / rollout monitoring | +| Endpoint | `miniprogram.page.path` | dozens per app | Which in-app page is slow / error-prone | + +Per-device `service.instance.id` is intentionally not used as an aggregation dimension — +unbounded cardinality means millions of entities on any real user base. The SDK (≥ v0.4.0) +no longer auto-generates a device id; operators set `serviceInstance` to a version-scoped value. + +## Compatibility + +- **SDK ≤ v0.2.x** emits `componentId = 10001` (ajax-inherited). Its segments resolve to + `Layer.GENERAL` and do not benefit from this integration's layer / topology work. OTLP + metrics + logs still flow through MAL / LAL because they key on the `miniprogram.platform` + resource attribute, which v0.2 already emits. +- **SDK ≤ v0.3.x** auto-generates `service.instance.id = mp-{random}` per session, creating + one OAP instance entity per device — usually undesirable. Operators on v0.3.x can avoid this + by passing `init({ serviceInstance: serviceVersion })` explicitly. +- **SDK ≥ v0.4.0** leaves `service.instance.id` unset by default. The three signal pipelines + then handle absence differently: native segments produce a literal `-` instance entity; OTLP + logs and metrics create no instance entity at all. Per-instance dashboards are meaningful + only when the operator sets `serviceInstance`. +- The recommended pattern (SDK docs + e2e CI) is to set `serviceInstance` to a version-scoped + value (mirroring `service.version` or a release tag). Then all three signal pipelines + aggregate under the same OAP instance entity. + +## Dashboards + +- **WeChat Mini Program** in the **Mobile** menu group — service list landing page. +- **Per-service** dashboard — launch / render / route / script / package-load durations, + request latency percentile, outbound traffic (load, avg latency, success rate), error count, + plus tabs for Instance, Endpoint, Trace, and Log drill-down. +- **Per-instance (version)** dashboard — same metric set scoped to a release. +- **Per-endpoint (page)** dashboard — per-page perf, outbound traffic, request latency + percentile. + +## Limitations + +- WebSocket, memory-warning, and network-status-change signals are not instrumented by the + current SDK. +- Device-level per-user aggregation is not supported by design — `serviceInstance` is intended + to be a version-scoped identifier. +- `first_paint.time` is an epoch-ms timestamp, not a duration. It is not aggregated by MAL. diff --git a/docs/menu.yml b/docs/menu.yml index d2477663b6fd..f806393dee1c 100644 --- a/docs/menu.yml +++ b/docs/menu.yml @@ -108,6 +108,10 @@ catalog: catalog: - name: "iOS" path: "/en/setup/backend/backend-ios-monitoring" + - name: "WeChat Mini Program" + path: "/en/setup/backend/backend-wechat-mini-program-monitoring" + - name: "Alipay Mini Program" + path: "/en/setup/backend/backend-alipay-mini-program-monitoring" - name: "Gateway Monitoring" catalog: - name: "Nginx Monitoring" From 8b28b5f5422ef05bc0156598f49614a7ba7d0d2e Mon Sep 17 00:00:00 2001 From: Wu Sheng Date: Mon, 20 Apr 2026 19:35:49 +0800 Subject: [PATCH 05/14] SWIP-12 Stage 5: e2e cases (WeChat + Alipay), CI matrix, config-dump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - test/e2e-v2/cases/miniprogram/{wechat,alipay}/: docker-compose.yml boots oap+banyandb with per-platform MAL rules, LAL file, log-MAL file wired via env vars. e2e.yaml drives hand-crafted OTLP metric and OTLP error-log payloads via curl (iOS-style, no external sim image dependency — the skyapm sim images are referenced in SWIP §10 for the showcase but not pinned for OAP CI). Verify cases cover: * service registered under the correct layer (WECHAT_MINI_PROGRAM / ALIPAY_MINI_PROGRAM) * service-scoped MAL metric (app_launch_duration, first_render_duration) populated * log-MAL derived meter_*_mp_error_count non-zero * instance metric populated when serviceInstance == serviceVersion * LAL-persisted error log visible in logs query Native-segment layer mapping is covered by the unit tests updated in Stage 1 (RPCAnalysisListenerTest / EndpointDepFromCrossThreadAnalysisListenerTest). - .github/workflows/skywalking.yaml registers both cases in the e2e matrix next to iOS Monitoring. - test/e2e-v2/cases/storage/expected/config-dump.yml mirrors the application.yml default changes from Stage 2 (enabledOtelMetricsRules, lalFiles, malFiles). Storage e2e diffs /debugging/config/dump against this file. --- .github/workflows/skywalking.yaml | 4 + .../miniprogram/alipay/docker-compose.yml | 39 ++++++++++ test/e2e-v2/cases/miniprogram/alipay/e2e.yaml | 74 +++++++++++++++++++ .../miniprogram/alipay/expected/logs.yml | 39 ++++++++++ .../alipay/expected/metrics-has-value.yml | 30 ++++++++ .../miniprogram/alipay/expected/service.yml | 24 ++++++ .../miniprogram/wechat/docker-compose.yml | 39 ++++++++++ test/e2e-v2/cases/miniprogram/wechat/e2e.yaml | 74 +++++++++++++++++++ .../miniprogram/wechat/expected/logs.yml | 39 ++++++++++ .../wechat/expected/metrics-has-value.yml | 30 ++++++++ .../miniprogram/wechat/expected/service.yml | 24 ++++++ .../cases/storage/expected/config-dump.yml | 6 +- 12 files changed, 419 insertions(+), 3 deletions(-) create mode 100644 test/e2e-v2/cases/miniprogram/alipay/docker-compose.yml create mode 100644 test/e2e-v2/cases/miniprogram/alipay/e2e.yaml create mode 100644 test/e2e-v2/cases/miniprogram/alipay/expected/logs.yml create mode 100644 test/e2e-v2/cases/miniprogram/alipay/expected/metrics-has-value.yml create mode 100644 test/e2e-v2/cases/miniprogram/alipay/expected/service.yml create mode 100644 test/e2e-v2/cases/miniprogram/wechat/docker-compose.yml create mode 100644 test/e2e-v2/cases/miniprogram/wechat/e2e.yaml create mode 100644 test/e2e-v2/cases/miniprogram/wechat/expected/logs.yml create mode 100644 test/e2e-v2/cases/miniprogram/wechat/expected/metrics-has-value.yml create mode 100644 test/e2e-v2/cases/miniprogram/wechat/expected/service.yml diff --git a/.github/workflows/skywalking.yaml b/.github/workflows/skywalking.yaml index 247b17ff2f9f..88f19ea1ed54 100644 --- a/.github/workflows/skywalking.yaml +++ b/.github/workflows/skywalking.yaml @@ -642,6 +642,10 @@ jobs: config: test/e2e-v2/cases/zipkin-virtual-genai/e2e.yaml - name: iOS Monitoring config: test/e2e-v2/cases/ios/e2e.yaml + - name: WeChat Mini Program Monitoring + config: test/e2e-v2/cases/miniprogram/wechat/e2e.yaml + - name: Alipay Mini Program Monitoring + config: test/e2e-v2/cases/miniprogram/alipay/e2e.yaml - name: Nginx config: test/e2e-v2/cases/nginx/e2e.yaml diff --git a/test/e2e-v2/cases/miniprogram/alipay/docker-compose.yml b/test/e2e-v2/cases/miniprogram/alipay/docker-compose.yml new file mode 100644 index 000000000000..670b5807f6bc --- /dev/null +++ b/test/e2e-v2/cases/miniprogram/alipay/docker-compose.yml @@ -0,0 +1,39 @@ +# 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. + +services: + oap: + extends: + file: ../../../script/docker-compose/base-compose.yml + service: oap + ports: + - 12800 + environment: + SW_OTEL_RECEIVER: default + SW_OTEL_RECEIVER_ENABLED_HANDLERS: "otlp-traces,otlp-metrics,otlp-logs" + SW_OTEL_RECEIVER_ENABLED_OTEL_METRICS_RULES: "miniprogram/alipay-mini-program,miniprogram/alipay-mini-program-instance" + SW_LOG_LAL_FILES: "miniprogram,default" + SW_LOG_MAL_FILES: "miniprogram" + SW_LOG_ANALYZER: default + + banyandb: + extends: + file: ../../../script/docker-compose/base-compose.yml + service: banyandb + ports: + - 17912 + +networks: + e2e: diff --git a/test/e2e-v2/cases/miniprogram/alipay/e2e.yaml b/test/e2e-v2/cases/miniprogram/alipay/e2e.yaml new file mode 100644 index 000000000000..4f1235c1b6dd --- /dev/null +++ b/test/e2e-v2/cases/miniprogram/alipay/e2e.yaml @@ -0,0 +1,74 @@ +# 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. + +# Alipay Mini Program e2e test — mirrors the WeChat case with +# miniprogram.platform=alipay so the platform-gated filter on the +# MAL / LAL rules routes to the ALIPAY_MINI_PROGRAM layer. WeChat-only +# metrics (route / script / package_load / first_paint.time) are not +# emitted and not verified. + +setup: + env: compose + file: docker-compose.yml + timeout: 20m + init-system-environment: ../../../script/env + steps: + - name: set PATH + command: export PATH=/tmp/skywalking-infra-e2e/bin:$PATH + - name: install yq + command: bash test/e2e-v2/script/prepare/setup-e2e-shell/install.sh yq + - name: install swctl + command: bash test/e2e-v2/script/prepare/setup-e2e-shell/install.sh swctl + - name: send Alipay Mini Program JS error log (OTLP) + command: | + TS_NS=$(date +%s)000000000 + set -e + curl -sS -f --retry 30 --retry-delay 5 --retry-connrefused --retry-all-errors --max-time 10 \ + -X POST -H 'Content-Type: application/json' \ + http://${oap_host}:${oap_12800}/v1/logs \ + -d "{\"resourceLogs\":[{\"resource\":{\"attributes\":[{\"key\":\"service.name\",\"value\":{\"stringValue\":\"alipay-mp-demo\"}},{\"key\":\"service.version\",\"value\":{\"stringValue\":\"v1.2.0\"}},{\"key\":\"service.instance.id\",\"value\":{\"stringValue\":\"v1.2.0\"}},{\"key\":\"miniprogram.platform\",\"value\":{\"stringValue\":\"alipay\"}}]},\"scopeLogs\":[{\"scope\":{\"name\":\"mini-program-monitor\"},\"logRecords\":[{\"timeUnixNano\":\"$TS_NS\",\"severityNumber\":17,\"severityText\":\"ERROR\",\"body\":{\"stringValue\":\"TypeError: Cannot read property 'foo' of undefined\"},\"attributes\":[{\"key\":\"exception.type\",\"value\":{\"stringValue\":\"js\"}},{\"key\":\"exception.stacktrace\",\"value\":{\"stringValue\":\"at pages/index/index.js:42\"}},{\"key\":\"miniprogram.page.path\",\"value\":{\"stringValue\":\"pages/index/index\"}}]}]}]}]}" + +trigger: + action: http + interval: 3s + times: 10 + url: http://${oap_host}:${oap_12800}/v1/metrics + method: POST + headers: + Content-Type: application/json + body: '{"resourceMetrics":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"alipay-mp-demo"}},{"key":"service.version","value":{"stringValue":"v1.2.0"}},{"key":"service.instance.id","value":{"stringValue":"v1.2.0"}},{"key":"miniprogram.platform","value":{"stringValue":"alipay"}}]},"scopeMetrics":[{"scope":{"name":"mini-program-monitor"},"metrics":[{"name":"miniprogram.app_launch.duration","unit":"ms","gauge":{"dataPoints":[{"timeUnixNano":"0","asDouble":220.0,"attributes":[{"key":"miniprogram.page.path","value":{"stringValue":"pages/index/index"}}]}]}},{"name":"miniprogram.first_render.duration","unit":"ms","gauge":{"dataPoints":[{"timeUnixNano":"0","asDouble":110.0,"attributes":[{"key":"miniprogram.page.path","value":{"stringValue":"pages/index/index"}}]}]}}]}]}]}' + +verify: + retry: + count: 20 + interval: 10s + cases: + # Service registered under ALIPAY_MINI_PROGRAM layer + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql service ly ALIPAY_MINI_PROGRAM + expected: expected/service.yml + # MAL service-scoped metric populated + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_alipay_mp_app_launch_duration --service-name=alipay-mp-demo + expected: expected/metrics-has-value.yml + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_alipay_mp_first_render_duration --service-name=alipay-mp-demo + expected: expected/metrics-has-value.yml + # Log-MAL derived error count non-zero on the error-log traffic + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_alipay_mp_error_count --service-name=alipay-mp-demo + expected: expected/metrics-has-value.yml + # Instance metric populated (serviceInstance == serviceVersion == v1.2.0 per recommended pattern) + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_alipay_mp_instance_app_launch_duration --service-name=alipay-mp-demo --instance-name=v1.2.0 + expected: expected/metrics-has-value.yml + # Error log persisted via LAL (miniprogram rule, layer:auto -> ALIPAY_MINI_PROGRAM) + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql logs list --service-name=alipay-mp-demo + expected: expected/logs.yml diff --git a/test/e2e-v2/cases/miniprogram/alipay/expected/logs.yml b/test/e2e-v2/cases/miniprogram/alipay/expected/logs.yml new file mode 100644 index 000000000000..6d65a8e4a9fc --- /dev/null +++ b/test/e2e-v2/cases/miniprogram/alipay/expected/logs.yml @@ -0,0 +1,39 @@ +# 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. + +errorreason: null +debuggingtrace: null +logs: + {{- contains .logs }} + - servicename: alipay-mp-demo + serviceid: {{ notEmpty .serviceid }} + serviceinstancename: "v1.2.0" + serviceinstanceid: {{ notEmpty .serviceinstanceid }} + endpointname: "pages/index/index" + endpointid: {{ notEmpty .endpointid }} + traceid: null + timestamp: {{ gt .timestamp 0 }} + contenttype: TEXT + content: {{ notEmpty .content }} + tags: + {{- contains .tags }} + - key: platform + value: alipay + - key: exception.type + value: js + - key: exception.stacktrace + value: "at pages/index/index.js:42" + {{- end }} + {{- end }} diff --git a/test/e2e-v2/cases/miniprogram/alipay/expected/metrics-has-value.yml b/test/e2e-v2/cases/miniprogram/alipay/expected/metrics-has-value.yml new file mode 100644 index 000000000000..ced9ce289197 --- /dev/null +++ b/test/e2e-v2/cases/miniprogram/alipay/expected/metrics-has-value.yml @@ -0,0 +1,30 @@ +# 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. + +debuggingtrace: null +type: TIME_SERIES_VALUES +results: + {{- contains .results }} + - metric: + labels: [] + values: + {{- contains .values }} + - id: {{ notEmpty .id }} + value: {{ notEmpty .value }} + traceid: null + owner: null + {{- end}} + {{- end}} +error: null diff --git a/test/e2e-v2/cases/miniprogram/alipay/expected/service.yml b/test/e2e-v2/cases/miniprogram/alipay/expected/service.yml new file mode 100644 index 000000000000..7fbfda52f6ba --- /dev/null +++ b/test/e2e-v2/cases/miniprogram/alipay/expected/service.yml @@ -0,0 +1,24 @@ +# 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. + +{{- contains . }} +- id: {{ b64enc "alipay-mp-demo" }}.1 + name: alipay-mp-demo + group: "" + shortname: alipay-mp-demo + layers: + - ALIPAY_MINI_PROGRAM + normal: true +{{- end }} diff --git a/test/e2e-v2/cases/miniprogram/wechat/docker-compose.yml b/test/e2e-v2/cases/miniprogram/wechat/docker-compose.yml new file mode 100644 index 000000000000..1cacb4ef6067 --- /dev/null +++ b/test/e2e-v2/cases/miniprogram/wechat/docker-compose.yml @@ -0,0 +1,39 @@ +# 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. + +services: + oap: + extends: + file: ../../../script/docker-compose/base-compose.yml + service: oap + ports: + - 12800 + environment: + SW_OTEL_RECEIVER: default + SW_OTEL_RECEIVER_ENABLED_HANDLERS: "otlp-traces,otlp-metrics,otlp-logs" + SW_OTEL_RECEIVER_ENABLED_OTEL_METRICS_RULES: "miniprogram/wechat-mini-program,miniprogram/wechat-mini-program-instance" + SW_LOG_LAL_FILES: "miniprogram,default" + SW_LOG_MAL_FILES: "miniprogram" + SW_LOG_ANALYZER: default + + banyandb: + extends: + file: ../../../script/docker-compose/base-compose.yml + service: banyandb + ports: + - 17912 + +networks: + e2e: diff --git a/test/e2e-v2/cases/miniprogram/wechat/e2e.yaml b/test/e2e-v2/cases/miniprogram/wechat/e2e.yaml new file mode 100644 index 000000000000..80b1de089608 --- /dev/null +++ b/test/e2e-v2/cases/miniprogram/wechat/e2e.yaml @@ -0,0 +1,74 @@ +# 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. + +# WeChat Mini Program e2e test — drives OTLP metrics + OTLP error log +# payloads via curl (iOS-style, no external sim image dependency) and +# verifies service / metric / log observable under the WECHAT_MINI_PROGRAM +# layer. Native-segment handling is covered by the layer-mapping unit +# tests in RPCAnalysisListenerTest / EndpointDepFromCrossThreadAnalysisListenerTest. + +setup: + env: compose + file: docker-compose.yml + timeout: 20m + init-system-environment: ../../../script/env + steps: + - name: set PATH + command: export PATH=/tmp/skywalking-infra-e2e/bin:$PATH + - name: install yq + command: bash test/e2e-v2/script/prepare/setup-e2e-shell/install.sh yq + - name: install swctl + command: bash test/e2e-v2/script/prepare/setup-e2e-shell/install.sh swctl + - name: send WeChat Mini Program JS error log (OTLP) + command: | + TS_NS=$(date +%s)000000000 + set -e + curl -sS -f --retry 30 --retry-delay 5 --retry-connrefused --retry-all-errors --max-time 10 \ + -X POST -H 'Content-Type: application/json' \ + http://${oap_host}:${oap_12800}/v1/logs \ + -d "{\"resourceLogs\":[{\"resource\":{\"attributes\":[{\"key\":\"service.name\",\"value\":{\"stringValue\":\"wechat-mp-demo\"}},{\"key\":\"service.version\",\"value\":{\"stringValue\":\"v1.2.0\"}},{\"key\":\"service.instance.id\",\"value\":{\"stringValue\":\"v1.2.0\"}},{\"key\":\"miniprogram.platform\",\"value\":{\"stringValue\":\"wechat\"}}]},\"scopeLogs\":[{\"scope\":{\"name\":\"mini-program-monitor\"},\"logRecords\":[{\"timeUnixNano\":\"$TS_NS\",\"severityNumber\":17,\"severityText\":\"ERROR\",\"body\":{\"stringValue\":\"TypeError: Cannot read property 'foo' of undefined\"},\"attributes\":[{\"key\":\"exception.type\",\"value\":{\"stringValue\":\"js\"}},{\"key\":\"exception.stacktrace\",\"value\":{\"stringValue\":\"at pages/index/index.js:42\"}},{\"key\":\"miniprogram.page.path\",\"value\":{\"stringValue\":\"pages/index/index\"}}]}]}]}]}" + +trigger: + action: http + interval: 3s + times: 10 + url: http://${oap_host}:${oap_12800}/v1/metrics + method: POST + headers: + Content-Type: application/json + body: '{"resourceMetrics":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"wechat-mp-demo"}},{"key":"service.version","value":{"stringValue":"v1.2.0"}},{"key":"service.instance.id","value":{"stringValue":"v1.2.0"}},{"key":"miniprogram.platform","value":{"stringValue":"wechat"}}]},"scopeMetrics":[{"scope":{"name":"mini-program-monitor"},"metrics":[{"name":"miniprogram.app_launch.duration","unit":"ms","gauge":{"dataPoints":[{"timeUnixNano":"0","asDouble":180.0,"attributes":[{"key":"miniprogram.page.path","value":{"stringValue":"pages/index/index"}}]}]}},{"name":"miniprogram.first_render.duration","unit":"ms","gauge":{"dataPoints":[{"timeUnixNano":"0","asDouble":95.0,"attributes":[{"key":"miniprogram.page.path","value":{"stringValue":"pages/index/index"}}]}]}}]}]}]}' + +verify: + retry: + count: 20 + interval: 10s + cases: + # Service registered under WECHAT_MINI_PROGRAM layer + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql service ly WECHAT_MINI_PROGRAM + expected: expected/service.yml + # MAL service-scoped metric populated + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_wechat_mp_app_launch_duration --service-name=wechat-mp-demo + expected: expected/metrics-has-value.yml + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_wechat_mp_first_render_duration --service-name=wechat-mp-demo + expected: expected/metrics-has-value.yml + # Log-MAL derived error count non-zero on the error-log traffic + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_wechat_mp_error_count --service-name=wechat-mp-demo + expected: expected/metrics-has-value.yml + # Instance metric populated (serviceInstance == serviceVersion == v1.2.0 per recommended pattern) + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_wechat_mp_instance_app_launch_duration --service-name=wechat-mp-demo --instance-name=v1.2.0 + expected: expected/metrics-has-value.yml + # Error log persisted via LAL (miniprogram rule, layer:auto -> WECHAT_MINI_PROGRAM) + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql logs list --service-name=wechat-mp-demo + expected: expected/logs.yml diff --git a/test/e2e-v2/cases/miniprogram/wechat/expected/logs.yml b/test/e2e-v2/cases/miniprogram/wechat/expected/logs.yml new file mode 100644 index 000000000000..c353a7bf2591 --- /dev/null +++ b/test/e2e-v2/cases/miniprogram/wechat/expected/logs.yml @@ -0,0 +1,39 @@ +# 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. + +errorreason: null +debuggingtrace: null +logs: + {{- contains .logs }} + - servicename: wechat-mp-demo + serviceid: {{ notEmpty .serviceid }} + serviceinstancename: "v1.2.0" + serviceinstanceid: {{ notEmpty .serviceinstanceid }} + endpointname: "pages/index/index" + endpointid: {{ notEmpty .endpointid }} + traceid: null + timestamp: {{ gt .timestamp 0 }} + contenttype: TEXT + content: {{ notEmpty .content }} + tags: + {{- contains .tags }} + - key: platform + value: wechat + - key: exception.type + value: js + - key: exception.stacktrace + value: "at pages/index/index.js:42" + {{- end }} + {{- end }} diff --git a/test/e2e-v2/cases/miniprogram/wechat/expected/metrics-has-value.yml b/test/e2e-v2/cases/miniprogram/wechat/expected/metrics-has-value.yml new file mode 100644 index 000000000000..ced9ce289197 --- /dev/null +++ b/test/e2e-v2/cases/miniprogram/wechat/expected/metrics-has-value.yml @@ -0,0 +1,30 @@ +# 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. + +debuggingtrace: null +type: TIME_SERIES_VALUES +results: + {{- contains .results }} + - metric: + labels: [] + values: + {{- contains .values }} + - id: {{ notEmpty .id }} + value: {{ notEmpty .value }} + traceid: null + owner: null + {{- end}} + {{- end}} +error: null diff --git a/test/e2e-v2/cases/miniprogram/wechat/expected/service.yml b/test/e2e-v2/cases/miniprogram/wechat/expected/service.yml new file mode 100644 index 000000000000..c0c68074391f --- /dev/null +++ b/test/e2e-v2/cases/miniprogram/wechat/expected/service.yml @@ -0,0 +1,24 @@ +# 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. + +{{- contains . }} +- id: {{ b64enc "wechat-mp-demo" }}.1 + name: wechat-mp-demo + group: "" + shortname: wechat-mp-demo + layers: + - WECHAT_MINI_PROGRAM + normal: true +{{- end }} diff --git a/test/e2e-v2/cases/storage/expected/config-dump.yml b/test/e2e-v2/cases/storage/expected/config-dump.yml index dadfc04c07a9..16bb839cec28 100644 --- a/test/e2e-v2/cases/storage/expected/config-dump.yml +++ b/test/e2e-v2/cases/storage/expected/config-dump.yml @@ -75,7 +75,7 @@ query.graphql.enableLogTestTool=false envoy-metric.default.gRPCSslCertChainPath= receiver-ebpf.default.gRPCPort=0 promql.default.buildInfoRevision= -receiver-otel.default.enabledOtelMetricsRules=apisix,nginx/*,k8s/*,istio-controlplane,vm,mysql/*,postgresql/*,oap,aws-eks/*,windows,aws-s3/*,aws-dynamodb/*,aws-gateway/*,redis/*,elasticsearch/*,rabbitmq/*,mongodb/*,kafka/*,pulsar/*,bookkeeper/*,rocketmq/*,clickhouse/*,activemq/*,kong/*,flink/*,banyandb/*,envoy-ai-gateway/*,ios/* +receiver-otel.default.enabledOtelMetricsRules=apisix,nginx/*,k8s/*,istio-controlplane,vm,mysql/*,postgresql/*,oap,aws-eks/*,windows,aws-s3/*,aws-dynamodb/*,aws-gateway/*,redis/*,elasticsearch/*,rabbitmq/*,mongodb/*,kafka/*,pulsar/*,bookkeeper/*,rocketmq/*,clickhouse/*,activemq/*,kong/*,flink/*,banyandb/*,envoy-ai-gateway/*,ios/*,miniprogram/* core.default.syncPeriodHttpUriRecognitionPattern=10 core.default.enableHierarchy=true event-analyzer.provider=default @@ -143,7 +143,7 @@ storage.mysql.properties.dataSource.useServerPrepStmts=true core.default.gRPCThreadPoolSize=-1 status-query.default.keywords4MaskingSecretsOfConfig=user,password,token,accessKey,secretKey,authentication storage.mysql.maxSizeOfBatchSql=2000 -log-analyzer.default.malFiles=nginx +log-analyzer.default.malFiles=nginx,miniprogram telemetry.prometheus.port=1234 core.default.recordDataTTL=3 core.default.maxHttpUrisNumberPerService=3000 @@ -196,7 +196,7 @@ promql.default.buildInfoBranch= envoy-metric.default.alsHTTPAnalysis= core.default.maxMessageSize=52428800 core.default.dataKeeperExecutePeriod=5 -log-analyzer.default.lalFiles=envoy-als,mesh-dp,mysql-slowsql,pgsql-slowsql,redis-slowsql,k8s-service,nginx,envoy-ai-gateway,default +log-analyzer.default.lalFiles=envoy-als,mesh-dp,mysql-slowsql,pgsql-slowsql,redis-slowsql,k8s-service,nginx,envoy-ai-gateway,miniprogram,default promql.default.buildInfoBuildUser=****** aws-firehose.default.enableTLS=false ai-pipeline.default.baselineServerPort=18080 From 2f0d5f0398742ef01942edb88e028c015bcb1b88 Mon Sep 17 00:00:00 2001 From: Wu Sheng Date: Mon, 20 Apr 2026 23:10:46 +0800 Subject: [PATCH 06/14] SWIP-12: unwind trace layer-mapping; fix MAL histogram units and CPM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses review: mini-programs are client-side (edge), same as browser — exit-only segments, no inbound analysis, so OAL server-side metrics (service_cpm / resp_time / sla / endpoint_*) don't populate. The earlier componentId-driven layer override in CommonAnalysisListener was wrong. Changes: - CommonAnalysisListener / RPCAnalysisListener / EndpointDepFromCrossThreadAnalysisListener reverted to pre-Stage-1 state — no IComponentLibraryCatalogService injection, no componentId arg on identifyServiceLayer, @RequiredArgsConstructor restored, 5 callsites back to single-arg form. Listener unit tests revert too. Layer enums (WECHAT_MINI_PROGRAM=48, ALIPAY_MINI_PROGRAM=49) and component-libraries.yml entries (10002/10003) stay — they are registration only and still power topology tooltip / UI rendering. - MAL rules corrected: * bucket-family name miniprogram_request_duration_histogram ~> miniprogram_request_duration (PrometheusMetricConverter emits the bucket family without a _histogram suffix). * .histogram() ~> .histogram("le", TimeUnit.MILLISECONDS) — SDK emits bucket bounds in ms; default MAL rescale (SECONDS, scale=1000) would inflate 1000x. * Added request_cpm per-scope rule from miniprogram_request_duration_count.sum([...]). Histogram is DELTA per-flush (fits mini-program's intermittent/offline sessions), so MAL's 1-minute bucketing sums per-flush counts into requests-per-minute directly — no .rate() / .increase() needed. * alipay rules mirror wechat, with Alipay-only metric subset. - MALCodegenHelper.ENUM_FQCN registers TimeUnit so rule YAML can write TimeUnit.MILLISECONDS (one line, enables other future rules too). - Dashboards rewritten to pull MAL-only metrics. Removed service_cpm / service_resp_time / service_sla / service_instance_cpm / endpoint_cpm etc. (won't populate without inbound analysis). Added Request Load widget backed by meter_*_mp_request_cpm. Trace tab kept (segments are stored, queryable via standard trace UI). - Setup docs drop the "come for free from core.oal" paragraph and state the browser-parity pattern explicitly. - SWIP-12 §3a retitled "Native Trace Segments — Client-Side Pattern (Browser Parity)" with the correct behavior. §6 revised: standard pipeline, no componentId override, no listener extension. UITemplateCheckerTest + checkstyle + full install/javadoc all pass. --- .../backend-alipay-mini-program-monitoring.md | 16 ++- .../backend-wechat-mini-program-monitoring.md | 17 ++- docs/en/swip/SWIP-12.md | 117 ++++++----------- .../listener/CommonAnalysisListener.java | 28 +--- ...intDepFromCrossThreadAnalysisListener.java | 21 +-- .../parser/listener/RPCAnalysisListener.java | 29 +--- .../v2/compiler/MALCodegenHelper.java | 1 + ...epFromCrossThreadAnalysisListenerTest.java | 8 +- .../listener/RPCAnalysisListenerTest.java | 32 ++--- .../alipay-mini-program-instance.yaml | 4 +- .../miniprogram/alipay-mini-program.yaml | 12 +- .../wechat-mini-program-instance.yaml | 4 +- .../miniprogram/wechat-mini-program.yaml | 14 +- .../alipay_mini_program-endpoint.json | 71 ++-------- .../alipay_mini_program-instance.json | 71 ++-------- .../alipay_mini_program-root.json | 16 +-- .../alipay_mini_program-service.json | 124 ++++++------------ .../wechat_mini_program-endpoint.json | 72 ++-------- .../wechat_mini_program-instance.json | 73 ++--------- .../wechat_mini_program-root.json | 16 +-- .../wechat_mini_program-service.json | 124 ++++++------------ 21 files changed, 260 insertions(+), 610 deletions(-) diff --git a/docs/en/setup/backend/backend-alipay-mini-program-monitoring.md b/docs/en/setup/backend/backend-alipay-mini-program-monitoring.md index 52fffcc731d1..225161e3955a 100644 --- a/docs/en/setup/backend/backend-alipay-mini-program-monitoring.md +++ b/docs/en/setup/backend/backend-alipay-mini-program-monitoring.md @@ -96,12 +96,16 @@ WeChat-only metrics (`route_duration`, `script_duration`, `package_load_duration `first_paint.time`) are absent from Alipay. The Alipay dashboard omits the Navigation row entirely. -Outbound HTTP metrics come "for free" from the existing `core.oal` rules once the component-id -mapping assigns the ALIPAY_MINI_PROGRAM layer to segments: `service_cpm`, `service_resp_time`, -`service_sla`, `service_percentile`, `service_apdex`, `service_instance_cpm`, -`service_instance_resp_time`, `service_instance_sla`, `endpoint_cpm`, `endpoint_resp_time`, -`endpoint_sla`, `endpoint_percentile`, plus `ServiceRelation` / `EndpointRelation` topology -edges to the backend services the mini-program calls. +Mini-program native trace segments are client-side (exit-only) — the same shape as browser +JS-agent traces. They flow through OAP's standard `RPCAnalysisListener` pipeline and produce +`ServiceRelation` / `ServiceInstanceRelation` edges to the backend services the mini-program +calls (so topology shows the outbound dependency), but do **not** produce +`service_cpm` / `service_resp_time` / `service_sla` / `endpoint_cpm` / `endpoint_resp_time` +under the mini-program layer — those come from inbound (entry-span) analysis, which +mini-programs don't have. The mini-program service / instance / endpoint entities are +created by MAL (OTLP metrics) and LAL (OTLP logs) instead. All request-load / latency +metrics on the dashboard come from the `miniprogram.request.duration` histogram's +`_count` + bucket families. ## Error Logs diff --git a/docs/en/setup/backend/backend-wechat-mini-program-monitoring.md b/docs/en/setup/backend/backend-wechat-mini-program-monitoring.md index d8e467e97e80..b97a3c1856a7 100644 --- a/docs/en/setup/backend/backend-wechat-mini-program-monitoring.md +++ b/docs/en/setup/backend/backend-wechat-mini-program-monitoring.md @@ -96,13 +96,16 @@ the chained `.endpoint(...)` override inside the service-scope file and surface It is passed through as a raw gauge sample but not aggregated by MAL — surface it only on per-page trace / log views where the absolute timestamp has meaning. -Outbound HTTP metrics come "for free" from the existing `core.oal` rules once the component-id -mapping assigns the WECHAT_MINI_PROGRAM layer to segments. Under the WeChat Mini Program layer -you get: `service_cpm`, `service_resp_time`, `service_sla`, `service_percentile`, `service_apdex`, -`service_instance_cpm`, `service_instance_resp_time`, `service_instance_sla`, -`service_instance_percentile`, `endpoint_cpm`, `endpoint_resp_time`, `endpoint_sla`, -`endpoint_percentile`, plus `ServiceRelation` / `EndpointRelation` topology edges to the -backend services the mini-program calls. +Mini-program native trace segments are client-side (exit-only) — the same shape as browser +JS-agent traces. They flow through OAP's standard `RPCAnalysisListener` pipeline and produce +`ServiceRelation` / `ServiceInstanceRelation` edges to the backend services the mini-program +calls (so topology shows the outbound dependency), but do **not** produce +`service_cpm` / `service_resp_time` / `service_sla` / `endpoint_cpm` / `endpoint_resp_time` +under the mini-program layer — those come from inbound (entry-span) analysis, which +mini-programs don't have. The mini-program service / instance / endpoint entities are +created by MAL (OTLP metrics) and LAL (OTLP logs) instead. All request-load / latency +metrics on the dashboard come from the `miniprogram.request.duration` histogram's +`_count` + bucket families. ## Error Logs diff --git a/docs/en/swip/SWIP-12.md b/docs/en/swip/SWIP-12.md index 9fe8de7fa800..e539e8f29b32 100644 --- a/docs/en/swip/SWIP-12.md +++ b/docs/en/swip/SWIP-12.md @@ -168,33 +168,30 @@ expose `PerformanceObserver` entries for the same events. These are approximatio compare WeChat and Alipay perf values directly; this is documented in the per-platform doc pages. -### 3a. OAL / Topology Metrics Emerge From the Layer Mapping +### 3a. Native Trace Segments — Client-Side Pattern (Browser Parity) The SDK posts Exit spans (`spanType=Exit`, `spanLayer=Http`) to `/v3/segments` via the -SkyWalking native protocol. After §6's `identifyServiceLayer` mapping assigns -`Layer.WECHAT_MINI_PROGRAM` / `Layer.ALIPAY_MINI_PROGRAM`, the native trace pipeline's -`RPCAnalysisListener` emits `Service`, `ServiceInstance`, `Endpoint` sources — plus -`ServiceRelation` / `ServiceInstanceRelation` / `EndpointRelation` for the -mini-program → backend call edge — all tagged with the mini-program layer. - -`core.oal` then produces, for free, under each mini-program layer: - -| Metric family | Scope | Source | -|---|---|---| -| `service_cpm`, `service_resp_time`, `service_sla`, `service_percentile`, `service_apdex` | Service | OAL on `Service` source | -| `service_instance_cpm`, `service_instance_resp_time`, `service_instance_sla`, `service_instance_percentile` | ServiceInstance | OAL on `ServiceInstance` source | -| `endpoint_cpm`, `endpoint_resp_time`, `endpoint_sla`, `endpoint_percentile` | Endpoint (page path) | OAL on `Endpoint` source | -| Topology edges to backend services | ServiceRelation / EndpointRelation | `sw8` propagation + backend receives the trace | - -These are **in addition to** the MAL-produced `meter_wechat_mp_*` / -`meter_alipay_mp_*` metrics from §4. The service dashboard panels should mix both — -outbound request latency from OAL (`service_resp_time`, `service_percentile`) keyed on -observed response time, plus the SDK's own per-page perf gauges from MAL. +SkyWalking native protocol. Mini-programs are client-side (edge) platforms — same shape +as browser JS-agent traces — so segments only ever carry exit spans and are processed +by OAP's standard `RPCAnalysisListener` pipeline with **no componentId-based layer +override**. `parseExit` fires, produces `ServiceRelation` / `ServiceInstanceRelation` +edges to the backend services the mini-program calls (topology shows the outbound +dependency, carrying `sw8` propagation for cross-trace joining), and does **not** +call `toService()` / `toEndpoint()`. + +So under the mini-program layer, `service_cpm` / `service_resp_time` / `service_sla` / +`service_percentile` / `service_apdex` / `endpoint_cpm` / `endpoint_resp_time` / +`endpoint_sla` / `endpoint_percentile` are **not populated** — those come from inbound +(entry-span) analysis, which mini-programs don't have. The mini-program service / +instance / endpoint entities are created by MAL (OTLP metrics) and LAL (OTLP logs) +instead, and the dashboards' request-load / latency metrics all come from the +`miniprogram.request.duration` histogram (`_count` family for CPM, bucket family for +percentiles). Topology note: mini-programs are leaf sources — they issue outbound requests but never -receive inbound traffic. So each mini-program service has outbound edges (to backend -APIs carrying `sw8`) but no upstream. This is correct by construction for client-side -platforms. +receive inbound traffic. Each mini-program service has outbound edges but no upstream. +This is correct by construction for client-side platforms and matches how OAP handles +browser agent traces. ### 3b. Error-Count Metric — Log-MAL Rule @@ -421,60 +418,30 @@ The rule sets the layer script-side based on `miniprogram.platform`, so **one ru produces two layers**. Error counts per service / instance / endpoint / exception.type can be derived via existing OAL log-metric machinery. -### 6. Trace Segment Handling — Component-Driven Layer Mapping +### 6. Trace Segment Handling — Standard Pipeline, No Componentid Override The SDK posts `SegmentObject` directly to `/v3/segments` (SkyWalking native protocol). -These segments are parsed by the normal trace pipeline — no new SPI is needed. - -Service-layer assignment already lives in -`CommonAnalysisListener.identifyServiceLayer(SpanLayer)` (a `protected` instance method -on the abstract base shared by `RPCAnalysisListener` and -`EndpointDepFromCrossThreadAnalysisListener` in the agent-analyzer module — -`SegmentAnalysisListener` does **not** extend `CommonAnalysisListener`, it has its own -service-meta path). Today it -maps `SpanLayer.FAAS → Layer.FAAS` and everything else to `Layer.GENERAL`. Extend it to -also accept the span's `componentId` (which the SDK already sets on every outbound span) -and dispatch to the mini-program layers. - -The component name → id mapping lives in `component-libraries.yml` and is exposed only -at runtime via `IComponentLibraryCatalogService` -(`ComponentLibraryCatalogService.java:84-104`) — there are no auto-generated Java -constants. So the listener's abstract base resolves the two ids once at construction -time and caches them as `int` fields: - -```java -abstract class CommonAnalysisListener { - private final int wechatMiniProgramComponentId; - private final int alipayMiniProgramComponentId; - - protected CommonAnalysisListener(IComponentLibraryCatalogService catalog) { - this.wechatMiniProgramComponentId = catalog.getComponentId("WeChat-MiniProgram"); - this.alipayMiniProgramComponentId = catalog.getComponentId("AliPay-MiniProgram"); - } - - protected Layer identifyServiceLayer(SpanLayer spanLayer, int componentId) { - if (componentId == wechatMiniProgramComponentId) { - return Layer.WECHAT_MINI_PROGRAM; - } - if (componentId == alipayMiniProgramComponentId) { - return Layer.ALIPAY_MINI_PROGRAM; - } - if (SpanLayer.FAAS.equals(spanLayer)) { - return Layer.FAAS; - } - return Layer.GENERAL; - } -} -``` - -`IComponentLibraryCatalogService` is already a `CoreModule` service, so wiring it into -the listener factories is one constructor parameter. Each of the 5 existing call sites -in `RPCAnalysisListener` (×4) and `EndpointDepFromCrossThreadAnalysisListener` (×1) -adds `span.getComponentId()` to its current `identifyServiceLayer(span.getSpanLayer())` -call. - -No new SPI, no new listener registration — all the work happens inside the existing -`SegmentParserListenerManager` pipeline. +These segments are parsed by the normal trace pipeline — no new SPI, no listener +extension, and **no componentId → layer override** in `CommonAnalysisListener`. + +Rationale: mini-programs are client-side (edge) platforms. They carry only exit spans, +same shape as browser JS-agent traces. OAP's existing `RPCAnalysisListener.parseExit` +already handles that: it emits `ServiceRelation` / `ServiceInstanceRelation` edges to +the backend services the mini-program calls (so outbound topology works, with `sw8` +joining the downstream trace), and never calls `toService()` / `toEndpoint()` for exit +spans — so no mini-program Service / Endpoint entity is created from trace analysis. + +The mini-program Service / ServiceInstance / Endpoint entities are created separately +by MAL (OTLP metrics, §4) and LAL (OTLP logs, §5). That's exactly how browser +monitoring works: traces run through the general pipeline while the browser receiver +plugin creates Browser-layer entities via its own dispatcher. + +This means `CommonAnalysisListener.identifyServiceLayer(SpanLayer)` stays unchanged; +the 5 callsites in `RPCAnalysisListener` / `EndpointDepFromCrossThreadAnalysisListener` +keep their current signatures; listener factories need no new service injection. +`component-libraries.yml` still registers `WeChat-MiniProgram: 10002` / +`AliPay-MiniProgram: 10003` (§7) so topology tooltips and UI renderers show the +component name — but those ids don't drive layer assignment. **Persistence:** default `true` — unlike iOS MetricKit spans (which represent 24-hour windows and must be suppressed), mini-program segments are real outgoing HTTP spans diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/CommonAnalysisListener.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/CommonAnalysisListener.java index 9762782a025b..8ff8baae4a49 100644 --- a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/CommonAnalysisListener.java +++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/CommonAnalysisListener.java @@ -20,38 +20,20 @@ import org.apache.skywalking.apm.network.language.agent.v3.SpanLayer; import org.apache.skywalking.oap.server.core.analysis.Layer; -import org.apache.skywalking.oap.server.core.config.IComponentLibraryCatalogService; /** * The common logic for specific listeners. */ abstract class CommonAnalysisListener { - - private final int wechatMiniProgramComponentId; - private final int alipayMiniProgramComponentId; - - protected CommonAnalysisListener(IComponentLibraryCatalogService componentLibraryCatalogService) { - this.wechatMiniProgramComponentId = componentLibraryCatalogService.getComponentId("WeChat-MiniProgram"); - this.alipayMiniProgramComponentId = componentLibraryCatalogService.getComponentId("AliPay-MiniProgram"); - } - /** - * Identify the layer of span's service/instance owner. Such as ${@link Layer#FAAS} and ${@link Layer#GENERAL}. - * - * @param spanLayer the span's declared layer, used as a fallback signal (e.g. FAAS). - * @param componentId the span's component id, takes precedence when it maps to a known - * client-side SDK layer (WeChat / Alipay mini-program). + * Identify the layer of span's service/instance owner. Such as ${@link Layer#FAAS} and ${@link Layer#GENERAL}. */ - protected Layer identifyServiceLayer(SpanLayer spanLayer, int componentId) { - if (componentId == wechatMiniProgramComponentId) { - return Layer.WECHAT_MINI_PROGRAM; - } - if (componentId == alipayMiniProgramComponentId) { - return Layer.ALIPAY_MINI_PROGRAM; - } + protected Layer identifyServiceLayer(SpanLayer spanLayer) { if (SpanLayer.FAAS.equals(spanLayer)) { + // function as a Service return Layer.FAAS; + } else { + return Layer.GENERAL; } - return Layer.GENERAL; } } diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/EndpointDepFromCrossThreadAnalysisListener.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/EndpointDepFromCrossThreadAnalysisListener.java index f71fb1a48240..08fcc0da8da5 100644 --- a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/EndpointDepFromCrossThreadAnalysisListener.java +++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/EndpointDepFromCrossThreadAnalysisListener.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.List; +import lombok.RequiredArgsConstructor; import org.apache.skywalking.apm.network.language.agent.v3.SegmentObject; import org.apache.skywalking.apm.network.language.agent.v3.SegmentReference; import org.apache.skywalking.apm.network.language.agent.v3.SpanLayer; @@ -29,7 +30,6 @@ import org.apache.skywalking.oap.server.core.CoreModule; import org.apache.skywalking.oap.server.core.analysis.Layer; import org.apache.skywalking.oap.server.core.analysis.TimeBucket; -import org.apache.skywalking.oap.server.core.config.IComponentLibraryCatalogService; import org.apache.skywalking.oap.server.core.config.NamingControl; import org.apache.skywalking.oap.server.core.source.DetectPoint; import org.apache.skywalking.oap.server.core.source.SourceReceiver; @@ -42,6 +42,7 @@ * * @since 9.0.0 */ +@RequiredArgsConstructor public class EndpointDepFromCrossThreadAnalysisListener extends CommonAnalysisListener implements ExitAnalysisListener, LocalAnalysisListener { private final SourceReceiver sourceReceiver; private final AnalyzerModuleConfig config; @@ -49,16 +50,6 @@ public class EndpointDepFromCrossThreadAnalysisListener extends CommonAnalysisLi private final List depBuilders = new ArrayList<>(10); - public EndpointDepFromCrossThreadAnalysisListener(final SourceReceiver sourceReceiver, - final AnalyzerModuleConfig config, - final NamingControl namingControl, - final IComponentLibraryCatalogService componentLibraryCatalogService) { - super(componentLibraryCatalogService); - this.sourceReceiver = sourceReceiver; - this.config = config; - this.namingControl = namingControl; - } - @Override public boolean containsPoint(final Point point) { return Point.Exit.equals(point) || Point.Local.equals(point); @@ -111,7 +102,7 @@ private void parseRefForEndpointDependency(final SpanObject span, final SegmentO sourceBuilder.setDestEndpointName(span.getOperationName()); sourceBuilder.setDestServiceInstanceName(segmentObject.getServiceInstance()); sourceBuilder.setDestServiceName(segmentObject.getService()); - sourceBuilder.setDestLayer(identifyServiceLayer(span.getSpanLayer(), span.getComponentId())); + sourceBuilder.setDestLayer(identifyServiceLayer(span.getSpanLayer())); sourceBuilder.setDetectPoint(DetectPoint.SERVER); sourceBuilder.setComponentId(span.getComponentId()); setPublicAttrs(sourceBuilder, span); @@ -145,21 +136,17 @@ public void build() { public static class Factory implements AnalysisListenerFactory { private final SourceReceiver sourceReceiver; private final NamingControl namingControl; - private final IComponentLibraryCatalogService componentLibraryCatalogService; public Factory(ModuleManager moduleManager) { this.sourceReceiver = moduleManager.find(CoreModule.NAME).provider().getService(SourceReceiver.class); this.namingControl = moduleManager.find(CoreModule.NAME) .provider() .getService(NamingControl.class); - this.componentLibraryCatalogService = moduleManager.find(CoreModule.NAME) - .provider() - .getService(IComponentLibraryCatalogService.class); } @Override public AnalysisListener create(final ModuleManager moduleManager, final AnalyzerModuleConfig config) { - return new EndpointDepFromCrossThreadAnalysisListener(sourceReceiver, config, namingControl, componentLibraryCatalogService); + return new EndpointDepFromCrossThreadAnalysisListener(sourceReceiver, config, namingControl); } } } diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/RPCAnalysisListener.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/RPCAnalysisListener.java index 162fb1fcff1e..f4b2b9f19e1b 100644 --- a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/RPCAnalysisListener.java +++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/RPCAnalysisListener.java @@ -20,6 +20,7 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.skywalking.apm.network.language.agent.v3.SegmentObject; import org.apache.skywalking.apm.network.language.agent.v3.SegmentReference; @@ -36,7 +37,6 @@ import org.apache.skywalking.oap.server.core.analysis.TimeBucket; import org.apache.skywalking.oap.server.core.analysis.manual.networkalias.NetworkAddressAlias; import org.apache.skywalking.oap.server.core.cache.NetworkAddressAliasCache; -import org.apache.skywalking.oap.server.core.config.IComponentLibraryCatalogService; import org.apache.skywalking.oap.server.core.config.NamingControl; import org.apache.skywalking.oap.server.core.source.DetectPoint; import org.apache.skywalking.oap.server.core.source.EndpointRelation; @@ -55,6 +55,7 @@ * RPCAnalysisListener detects all RPC relative statistics. */ @Slf4j +@RequiredArgsConstructor public class RPCAnalysisListener extends CommonAnalysisListener implements EntryAnalysisListener, ExitAnalysisListener, LocalAnalysisListener { private final List callingInTraffic = new ArrayList<>(10); private final List callingOutTraffic = new ArrayList<>(10); @@ -65,18 +66,6 @@ public class RPCAnalysisListener extends CommonAnalysisListener implements Entry private final NetworkAddressAliasCache networkAddressAliasCache; private final NamingControl namingControl; - public RPCAnalysisListener(final SourceReceiver sourceReceiver, - final AnalyzerModuleConfig config, - final NetworkAddressAliasCache networkAddressAliasCache, - final NamingControl namingControl, - final IComponentLibraryCatalogService componentLibraryCatalogService) { - super(componentLibraryCatalogService); - this.sourceReceiver = sourceReceiver; - this.config = config; - this.networkAddressAliasCache = networkAddressAliasCache; - this.namingControl = namingControl; - } - @Override public boolean containsPoint(Point point) { return Point.Entry.equals(point) || Point.Exit.equals(point) || Point.Local.equals(point); @@ -130,7 +119,7 @@ public void parseEntry(SpanObject span, SegmentObject segmentObject) { sourceBuilder.setDestEndpointName(span.getOperationName()); sourceBuilder.setDestServiceInstanceName(segmentObject.getServiceInstance()); sourceBuilder.setDestServiceName(segmentObject.getService()); - sourceBuilder.setDestLayer(identifyServiceLayer(span.getSpanLayer(), span.getComponentId())); + sourceBuilder.setDestLayer(identifyServiceLayer(span.getSpanLayer())); sourceBuilder.setDetectPoint(DetectPoint.SERVER); sourceBuilder.setComponentId(span.getComponentId()); setPublicAttrs(sourceBuilder, span); @@ -145,7 +134,7 @@ public void parseEntry(SpanObject span, SegmentObject segmentObject) { sourceBuilder.setSourceLayer(Layer.VIRTUAL_MQ); sourceBuilder.setDestServiceInstanceName(segmentObject.getServiceInstance()); sourceBuilder.setDestServiceName(segmentObject.getService()); - sourceBuilder.setDestLayer(identifyServiceLayer(span.getSpanLayer(), span.getComponentId())); + sourceBuilder.setDestLayer(identifyServiceLayer(span.getSpanLayer())); sourceBuilder.setDetectPoint(DetectPoint.SERVER); sourceBuilder.setComponentId(span.getComponentId()); setPublicAttrs(sourceBuilder, span); @@ -158,7 +147,7 @@ public void parseEntry(SpanObject span, SegmentObject segmentObject) { sourceBuilder.setSourceLayer(Layer.UNDEFINED); sourceBuilder.setDestServiceInstanceName(segmentObject.getServiceInstance()); sourceBuilder.setDestServiceName(segmentObject.getService()); - sourceBuilder.setDestLayer(identifyServiceLayer(span.getSpanLayer(), span.getComponentId())); + sourceBuilder.setDestLayer(identifyServiceLayer(span.getSpanLayer())); sourceBuilder.setDestEndpointName(span.getOperationName()); sourceBuilder.setDetectPoint(DetectPoint.SERVER); sourceBuilder.setComponentId(span.getComponentId()); @@ -189,7 +178,7 @@ public void parseExit(SpanObject span, SegmentObject segmentObject) { sourceBuilder.setSourceServiceName(segmentObject.getService()); sourceBuilder.setSourceServiceInstanceName(segmentObject.getServiceInstance()); - sourceBuilder.setSourceLayer(identifyServiceLayer(span.getSpanLayer(), span.getComponentId())); + sourceBuilder.setSourceLayer(identifyServiceLayer(span.getSpanLayer())); final NetworkAddressAlias networkAddressAlias = networkAddressAliasCache.get(networkAddress); if (networkAddressAlias == null) { @@ -388,7 +377,6 @@ public static class Factory implements AnalysisListenerFactory { private final SourceReceiver sourceReceiver; private final NetworkAddressAliasCache networkAddressAliasCache; private final NamingControl namingControl; - private final IComponentLibraryCatalogService componentLibraryCatalogService; public Factory(ModuleManager moduleManager) { this.sourceReceiver = moduleManager.find(CoreModule.NAME).provider().getService(SourceReceiver.class); @@ -398,15 +386,12 @@ public Factory(ModuleManager moduleManager) { this.namingControl = moduleManager.find(CoreModule.NAME) .provider() .getService(NamingControl.class); - this.componentLibraryCatalogService = moduleManager.find(CoreModule.NAME) - .provider() - .getService(IComponentLibraryCatalogService.class); } @Override public AnalysisListener create(ModuleManager moduleManager, AnalyzerModuleConfig config) { return new RPCAnalysisListener( - sourceReceiver, config, networkAddressAliasCache, namingControl, componentLibraryCatalogService); + sourceReceiver, config, networkAddressAliasCache, namingControl); } } } diff --git a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALCodegenHelper.java b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALCodegenHelper.java index 208cd50c6b93..44457575de21 100644 --- a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALCodegenHelper.java +++ b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALCodegenHelper.java @@ -53,6 +53,7 @@ private MALCodegenHelper() { "org.apache.skywalking.oap.meter.analyzer.v2.dsl.tagOpt.K8sRetagType"); ENUM_FQCN.put("DownsamplingType", "org.apache.skywalking.oap.meter.analyzer.v2.dsl.DownsamplingType"); + ENUM_FQCN.put("TimeUnit", "java.util.concurrent.TimeUnit"); CLOSURE_CLASS_FQCN = new HashMap<>(); CLOSURE_CLASS_FQCN.put("ProcessRegistry", diff --git a/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/EndpointDepFromCrossThreadAnalysisListenerTest.java b/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/EndpointDepFromCrossThreadAnalysisListenerTest.java index b3bdfd83e07e..ab9f9e5cdba0 100644 --- a/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/EndpointDepFromCrossThreadAnalysisListenerTest.java +++ b/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/EndpointDepFromCrossThreadAnalysisListenerTest.java @@ -31,7 +31,6 @@ import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.EndpointDepFromCrossThreadAnalysisListener; import org.apache.skywalking.oap.server.core.analysis.IDManager; import org.apache.skywalking.oap.server.core.analysis.Layer; -import org.apache.skywalking.oap.server.core.config.IComponentLibraryCatalogService; import org.apache.skywalking.oap.server.core.config.NamingControl; import org.apache.skywalking.oap.server.core.config.group.EndpointNameGrouping; import org.apache.skywalking.oap.server.core.source.Endpoint; @@ -51,8 +50,6 @@ public class EndpointDepFromCrossThreadAnalysisListenerTest { @Mock private static AnalyzerModuleConfig CONFIG; - @Mock - private static IComponentLibraryCatalogService COMPONENT_LIBRARY_CATALOG; private static NamingControl NAMING_CONTROL = new NamingControl( 70, 100, @@ -66,8 +63,6 @@ public class EndpointDepFromCrossThreadAnalysisListenerTest { public void init() throws Exception { MockitoAnnotations.openMocks(this).close(); - when(COMPONENT_LIBRARY_CATALOG.getComponentId("WeChat-MiniProgram")).thenReturn(10002); - when(COMPONENT_LIBRARY_CATALOG.getComponentId("AliPay-MiniProgram")).thenReturn(10003); final UninstrumentedGatewaysConfig uninstrumentedGatewaysConfig = Mockito.mock( UninstrumentedGatewaysConfig.class); when(uninstrumentedGatewaysConfig.isAddressConfiguredAsGateway(any())).thenReturn(false); @@ -80,8 +75,7 @@ public void testEndpointDependency() { EndpointDepFromCrossThreadAnalysisListener listener = new EndpointDepFromCrossThreadAnalysisListener( mockReceiver, CONFIG, - NAMING_CONTROL, - COMPONENT_LIBRARY_CATALOG + NAMING_CONTROL ); final long startTime = System.currentTimeMillis(); diff --git a/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/RPCAnalysisListenerTest.java b/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/RPCAnalysisListenerTest.java index f5fece8d6b0d..f31323d33e88 100644 --- a/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/RPCAnalysisListenerTest.java +++ b/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/RPCAnalysisListenerTest.java @@ -35,7 +35,6 @@ import org.apache.skywalking.oap.server.core.analysis.IDManager; import org.apache.skywalking.oap.server.core.analysis.manual.networkalias.NetworkAddressAlias; import org.apache.skywalking.oap.server.core.cache.NetworkAddressAliasCache; -import org.apache.skywalking.oap.server.core.config.IComponentLibraryCatalogService; import org.apache.skywalking.oap.server.core.config.NamingControl; import org.apache.skywalking.oap.server.core.config.group.EndpointNameGrouping; import org.apache.skywalking.oap.server.core.source.Endpoint; @@ -72,8 +71,6 @@ public class RPCAnalysisListenerTest { private static NetworkAddressAliasCache CACHE; @Mock private static NetworkAddressAliasCache CACHE2; - @Mock - private static IComponentLibraryCatalogService COMPONENT_LIBRARY_CATALOG; private static NamingControl NAMING_CONTROL = new NamingControl( 70, 100, @@ -85,8 +82,6 @@ public class RPCAnalysisListenerTest { public void init() { MockitoAnnotations.initMocks(this); - when(COMPONENT_LIBRARY_CATALOG.getComponentId("WeChat-MiniProgram")).thenReturn(10002); - when(COMPONENT_LIBRARY_CATALOG.getComponentId("AliPay-MiniProgram")).thenReturn(10003); when(CACHE.get(any())).thenReturn(null); final NetworkAddressAlias networkAddressAlias = new NetworkAddressAlias(); final String serviceId = IDManager.ServiceID.buildId("target-service", true); @@ -106,8 +101,7 @@ public void testContainsPoint() { new MockReceiver(), CONFIG, CACHE, - NAMING_CONTROL, - COMPONENT_LIBRARY_CATALOG + NAMING_CONTROL ); Assertions.assertTrue(listener.containsPoint(AnalysisListener.Point.Entry)); Assertions.assertTrue(listener.containsPoint(AnalysisListener.Point.Local)); @@ -126,8 +120,7 @@ public void testEntrySpanWithoutRef() { mockReceiver, CONFIG, CACHE, - NAMING_CONTROL, - COMPONENT_LIBRARY_CATALOG + NAMING_CONTROL ); final long startTime = System.currentTimeMillis(); @@ -189,8 +182,7 @@ public void testEntrySpanRef() { mockReceiver, CONFIG, CACHE, - NAMING_CONTROL, - COMPONENT_LIBRARY_CATALOG + NAMING_CONTROL ); final long startTime = System.currentTimeMillis(); @@ -256,8 +248,7 @@ public void testEntrySpanMQRef() { mockReceiver, CONFIG, CACHE, - NAMING_CONTROL, - COMPONENT_LIBRARY_CATALOG + NAMING_CONTROL ); final long startTime = System.currentTimeMillis(); @@ -316,8 +307,7 @@ public void testParseLocalLogicSpan() { mockReceiver, CONFIG, CACHE, - NAMING_CONTROL, - COMPONENT_LIBRARY_CATALOG + NAMING_CONTROL ); final long startTime = System.currentTimeMillis(); @@ -360,8 +350,7 @@ public void testParseSpanWithLogicEndpointTag() { mockReceiver, CONFIG, CACHE, - NAMING_CONTROL, - COMPONENT_LIBRARY_CATALOG + NAMING_CONTROL ); final long startTime = System.currentTimeMillis(); @@ -406,8 +395,7 @@ public void testExitSpanWithoutAlias() { mockReceiver, CONFIG, CACHE, - NAMING_CONTROL, - COMPONENT_LIBRARY_CATALOG + NAMING_CONTROL ); final long startTime = System.currentTimeMillis(); @@ -448,8 +436,7 @@ public void testExitSpanWithAlias() { mockReceiver, CONFIG, CACHE2, - NAMING_CONTROL, - COMPONENT_LIBRARY_CATALOG + NAMING_CONTROL ); final long startTime = System.currentTimeMillis(); @@ -488,8 +475,7 @@ public void testMQEntryWithoutRef() { mockReceiver, CONFIG, CACHE, - NAMING_CONTROL, - COMPONENT_LIBRARY_CATALOG + NAMING_CONTROL ); final long startTime = System.currentTimeMillis(); diff --git a/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/alipay-mini-program-instance.yaml b/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/alipay-mini-program-instance.yaml index 2d50f94b0220..9016e1477d8a 100644 --- a/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/alipay-mini-program-instance.yaml +++ b/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/alipay-mini-program-instance.yaml @@ -28,5 +28,7 @@ metricsRules: exp: miniprogram_app_launch_duration.avg(['service_name', 'service_instance_id']) - name: first_render_duration exp: miniprogram_first_render_duration.avg(['service_name', 'service_instance_id']) + - name: request_cpm + exp: miniprogram_request_duration_count.sum(['service_name', 'service_instance_id']) - name: request_duration_percentile - exp: miniprogram_request_duration_histogram.sum(['service_name', 'service_instance_id', 'le']).histogram().histogram_percentile([50,75,90,95,99]) + exp: miniprogram_request_duration.sum(['service_name', 'service_instance_id', 'le']).histogram("le", TimeUnit.MILLISECONDS).histogram_percentile([50,75,90,95,99]) diff --git a/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/alipay-mini-program.yaml b/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/alipay-mini-program.yaml index 7787d1499e60..c56957f2e5c1 100644 --- a/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/alipay-mini-program.yaml +++ b/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/alipay-mini-program.yaml @@ -31,14 +31,20 @@ metricsRules: - name: first_render_duration exp: miniprogram_first_render_duration.avg(['service_name']) - # Request latency percentile + # Request load (calls per minute) — delta counter. + - name: request_cpm + exp: miniprogram_request_duration_count.sum(['service_name']) + + # Request latency percentile — bucket `le` labels already in ms. - name: request_duration_percentile - exp: miniprogram_request_duration_histogram.sum(['service_name', 'le']).histogram().histogram_percentile([50,75,90,95,99]) + exp: miniprogram_request_duration.sum(['service_name', 'le']).histogram("le", TimeUnit.MILLISECONDS).histogram_percentile([50,75,90,95,99]) # Per-page (endpoint-scoped) variants — chained .endpoint(...) overrides the expSuffix. - name: endpoint_app_launch_duration exp: miniprogram_app_launch_duration.avg(['service_name', 'miniprogram_page_path']).endpoint(['service_name'], ['miniprogram_page_path'], Layer.ALIPAY_MINI_PROGRAM) - name: endpoint_first_render_duration exp: miniprogram_first_render_duration.avg(['service_name', 'miniprogram_page_path']).endpoint(['service_name'], ['miniprogram_page_path'], Layer.ALIPAY_MINI_PROGRAM) + - name: endpoint_request_cpm + exp: miniprogram_request_duration_count.sum(['service_name', 'miniprogram_page_path']).endpoint(['service_name'], ['miniprogram_page_path'], Layer.ALIPAY_MINI_PROGRAM) - name: endpoint_request_duration_percentile - exp: miniprogram_request_duration_histogram.sum(['service_name', 'miniprogram_page_path', 'le']).histogram().histogram_percentile([50,75,90,95,99]).endpoint(['service_name'], ['miniprogram_page_path'], Layer.ALIPAY_MINI_PROGRAM) + exp: miniprogram_request_duration.sum(['service_name', 'miniprogram_page_path', 'le']).histogram("le", TimeUnit.MILLISECONDS).histogram_percentile([50,75,90,95,99]).endpoint(['service_name'], ['miniprogram_page_path'], Layer.ALIPAY_MINI_PROGRAM) diff --git a/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/wechat-mini-program-instance.yaml b/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/wechat-mini-program-instance.yaml index 83f2f2bc88bf..9c2526a97399 100644 --- a/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/wechat-mini-program-instance.yaml +++ b/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/wechat-mini-program-instance.yaml @@ -35,5 +35,7 @@ metricsRules: exp: miniprogram_script_duration.avg(['service_name', 'service_instance_id']) - name: package_load_duration exp: miniprogram_package_load_duration.avg(['service_name', 'service_instance_id']) + - name: request_cpm + exp: miniprogram_request_duration_count.sum(['service_name', 'service_instance_id']) - name: request_duration_percentile - exp: miniprogram_request_duration_histogram.sum(['service_name', 'service_instance_id', 'le']).histogram().histogram_percentile([50,75,90,95,99]) + exp: miniprogram_request_duration.sum(['service_name', 'service_instance_id', 'le']).histogram("le", TimeUnit.MILLISECONDS).histogram_percentile([50,75,90,95,99]) diff --git a/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/wechat-mini-program.yaml b/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/wechat-mini-program.yaml index 325ff9635926..0b6ef27b1e45 100644 --- a/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/wechat-mini-program.yaml +++ b/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/wechat-mini-program.yaml @@ -37,14 +37,22 @@ metricsRules: - name: package_load_duration exp: miniprogram_package_load_duration.avg(['service_name']) - # Request latency percentile + # Request load (calls per minute) — delta counter, MAL's 1-minute bucketing + # sums per-flush counts into requests-per-minute directly. + - name: request_cpm + exp: miniprogram_request_duration_count.sum(['service_name']) + + # Request latency percentile — bucket `le` labels already in ms, MILLISECONDS + # unit stops MAL's default SECONDS → MS rescale (× 1000). - name: request_duration_percentile - exp: miniprogram_request_duration_histogram.sum(['service_name', 'le']).histogram().histogram_percentile([50,75,90,95,99]) + exp: miniprogram_request_duration.sum(['service_name', 'le']).histogram("le", TimeUnit.MILLISECONDS).histogram_percentile([50,75,90,95,99]) # Per-page (endpoint-scoped) variants — chained .endpoint(...) overrides the expSuffix. - name: endpoint_app_launch_duration exp: miniprogram_app_launch_duration.avg(['service_name', 'miniprogram_page_path']).endpoint(['service_name'], ['miniprogram_page_path'], Layer.WECHAT_MINI_PROGRAM) - name: endpoint_first_render_duration exp: miniprogram_first_render_duration.avg(['service_name', 'miniprogram_page_path']).endpoint(['service_name'], ['miniprogram_page_path'], Layer.WECHAT_MINI_PROGRAM) + - name: endpoint_request_cpm + exp: miniprogram_request_duration_count.sum(['service_name', 'miniprogram_page_path']).endpoint(['service_name'], ['miniprogram_page_path'], Layer.WECHAT_MINI_PROGRAM) - name: endpoint_request_duration_percentile - exp: miniprogram_request_duration_histogram.sum(['service_name', 'miniprogram_page_path', 'le']).histogram().histogram_percentile([50,75,90,95,99]).endpoint(['service_name'], ['miniprogram_page_path'], Layer.WECHAT_MINI_PROGRAM) + exp: miniprogram_request_duration.sum(['service_name', 'miniprogram_page_path', 'le']).histogram("le", TimeUnit.MILLISECONDS).histogram_percentile([50,75,90,95,99]).endpoint(['service_name'], ['miniprogram_page_path'], Layer.WECHAT_MINI_PROGRAM) diff --git a/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-endpoint.json b/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-endpoint.json index 5309025e396f..974edbcda838 100644 --- a/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-endpoint.json +++ b/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-endpoint.json @@ -22,8 +22,7 @@ "i": "0", "type": "Widget", "widget": { - "title": "App Launch Duration on this Page (approx, ms)", - "tips": "Lifecycle-based approximation for this page as entry" + "title": "App Launch Duration on this Page (approx, ms)" }, "graph": { "type": "Line", @@ -45,8 +44,7 @@ "i": "1", "type": "Widget", "widget": { - "title": "First Render Duration (approx, ms)", - "tips": "Lifecycle-based approximation for this page" + "title": "First Render Duration (approx, ms)" }, "graph": { "type": "Line", @@ -68,8 +66,7 @@ "i": "2", "type": "Widget", "widget": { - "title": "Request Duration Percentile (ms)", - "tips": "Latency distribution of outbound requests initiated from this page" + "title": "Request Load (calls / min)" }, "graph": { "type": "Line", @@ -80,45 +77,18 @@ "showYAxis": true }, "expressions": [ - "relabels(meter_alipay_mp_endpoint_request_duration_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='P50,P75,P90,P95,P99')" - ], - "metricConfig": [ - { - "label": "P50, P75, P90, P95, P99" - } + "meter_alipay_mp_endpoint_request_cpm" ] }, { "x": 0, "y": 30, - "w": 8, + "w": 24, "h": 15, "i": "3", "type": "Widget", "widget": { - "title": "Outbound Load (calls / min)" - }, - "graph": { - "type": "Line", - "step": false, - "smooth": false, - "showSymbol": true, - "showXAxis": true, - "showYAxis": true - }, - "expressions": [ - "endpoint_cpm" - ] - }, - { - "x": 8, - "y": 30, - "w": 8, - "h": 15, - "i": "4", - "type": "Widget", - "widget": { - "title": "Outbound Avg Response Time (ms)" + "title": "Request Duration Percentile (ms)" }, "graph": { "type": "Line", @@ -129,29 +99,12 @@ "showYAxis": true }, "expressions": [ - "endpoint_resp_time" - ] - }, - { - "x": 16, - "y": 30, - "w": 8, - "h": 15, - "i": "5", - "type": "Widget", - "widget": { - "title": "Outbound Success Rate (%)" - }, - "graph": { - "type": "Line", - "step": false, - "smooth": false, - "showSymbol": true, - "showXAxis": true, - "showYAxis": true - }, - "expressions": [ - "endpoint_sla/100" + "relabels(meter_alipay_mp_endpoint_request_duration_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='P50,P75,P90,P95,P99')" + ], + "metricConfig": [ + { + "label": "P50, P75, P90, P95, P99" + } ] } ] diff --git a/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-instance.json b/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-instance.json index bd588e919802..993d92e89220 100644 --- a/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-instance.json +++ b/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-instance.json @@ -22,8 +22,7 @@ "i": "0", "type": "Widget", "widget": { - "title": "App Launch Duration (approx, ms)", - "tips": "Lifecycle-based approximation for this release" + "title": "App Launch Duration (approx, ms)" }, "graph": { "type": "Line", @@ -45,8 +44,7 @@ "i": "1", "type": "Widget", "widget": { - "title": "First Render Duration (approx, ms)", - "tips": "Lifecycle-based approximation for this release" + "title": "First Render Duration (approx, ms)" }, "graph": { "type": "Line", @@ -68,8 +66,7 @@ "i": "2", "type": "Widget", "widget": { - "title": "Request Duration Percentile (ms)", - "tips": "P50 / P75 / P90 / P95 / P99 for this release" + "title": "Request Load (calls / min)" }, "graph": { "type": "Line", @@ -80,45 +77,18 @@ "showYAxis": true }, "expressions": [ - "relabels(meter_alipay_mp_instance_request_duration_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='P50,P75,P90,P95,P99')" - ], - "metricConfig": [ - { - "label": "P50, P75, P90, P95, P99" - } + "meter_alipay_mp_instance_request_cpm" ] }, { "x": 0, "y": 30, - "w": 8, + "w": 24, "h": 15, "i": "3", "type": "Widget", "widget": { - "title": "Outbound Load (calls / min)" - }, - "graph": { - "type": "Line", - "step": false, - "smooth": false, - "showSymbol": true, - "showXAxis": true, - "showYAxis": true - }, - "expressions": [ - "service_instance_cpm" - ] - }, - { - "x": 8, - "y": 30, - "w": 8, - "h": 15, - "i": "4", - "type": "Widget", - "widget": { - "title": "Outbound Avg Response Time (ms)" + "title": "Request Duration Percentile (ms)" }, "graph": { "type": "Line", @@ -129,29 +99,12 @@ "showYAxis": true }, "expressions": [ - "service_instance_resp_time" - ] - }, - { - "x": 16, - "y": 30, - "w": 8, - "h": 15, - "i": "5", - "type": "Widget", - "widget": { - "title": "Outbound Success Rate (%)" - }, - "graph": { - "type": "Line", - "step": false, - "smooth": false, - "showSymbol": true, - "showXAxis": true, - "showYAxis": true - }, - "expressions": [ - "service_instance_sla/100" + "relabels(meter_alipay_mp_instance_request_duration_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='P50,P75,P90,P95,P99')" + ], + "metricConfig": [ + { + "label": "P50, P75, P90, P95, P99" + } ] } ] diff --git a/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-root.json b/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-root.json index 0cb27b29a6d2..d9946679e4c6 100644 --- a/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-root.json +++ b/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-root.json @@ -19,18 +19,22 @@ "showGroup": true }, "expressions": [ + "avg(meter_alipay_mp_request_cpm)", "avg(meter_alipay_mp_app_launch_duration)", "avg(meter_alipay_mp_first_render_duration)", - "avg(meter_alipay_mp_request_duration_percentile{p='95'})", - "avg(service_cpm)" + "avg(meter_alipay_mp_request_duration_percentile{p='95'})" ], "subExpressions": [ + "meter_alipay_mp_request_cpm", "meter_alipay_mp_app_launch_duration", "meter_alipay_mp_first_render_duration", - "meter_alipay_mp_request_duration_percentile{p='95'}", - "service_cpm" + "meter_alipay_mp_request_duration_percentile{p='95'}" ], "metricConfig": [ + { + "label": "Request Load", + "unit": "calls / min" + }, { "label": "Launch Duration", "unit": "ms" @@ -42,10 +46,6 @@ { "label": "Request P95", "unit": "ms" - }, - { - "label": "Outbound Load", - "unit": "calls / min" } ] }, diff --git a/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-service.json b/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-service.json index 1cb39043848b..a51ab28b47d3 100644 --- a/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-service.json +++ b/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-service.json @@ -63,13 +63,13 @@ { "x": 0, "y": 15, - "w": 24, + "w": 12, "h": 15, "i": "2", "type": "Widget", "widget": { - "title": "Request Duration Percentile (ms)", - "tips": "miniprogram.request.duration histogram — P50 / P75 / P90 / P95 / P99" + "title": "Request Load (calls / min)", + "tips": "Outbound HTTP request count derived from miniprogram.request.duration histogram _count family" }, "graph": { "type": "Line", @@ -80,70 +80,38 @@ "showYAxis": true }, "expressions": [ - "relabels(meter_alipay_mp_request_duration_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='P50,P75,P90,P95,P99')" - ], - "metricConfig": [ - { - "label": "P50, P75, P90, P95, P99" - } + "meter_alipay_mp_request_cpm" ] }, { - "x": 0, - "y": 30, - "w": 8, + "x": 12, + "y": 15, + "w": 12, "h": 15, "i": "3", "type": "Widget", "widget": { - "title": "Outbound Load (calls / min)", - "tips": "Outbound HTTP requests observed by the mini program, derived from native trace segments" + "title": "Error Count", + "tips": "Counted from LAL-processed error logs (js / promise / ajax), aggregated per service" }, "graph": { - "type": "Line", - "step": false, - "smooth": false, - "showSymbol": true, - "showXAxis": true, - "showYAxis": true + "type": "Bar", + "showBackground": true }, "expressions": [ - "service_cpm" + "meter_alipay_mp_error_count" ] }, { - "x": 8, + "x": 0, "y": 30, - "w": 8, + "w": 24, "h": 15, "i": "4", "type": "Widget", "widget": { - "title": "Outbound Avg Response Time (ms)", - "tips": "Client-observed average latency of outbound HTTP requests" - }, - "graph": { - "type": "Line", - "step": false, - "smooth": false, - "showSymbol": true, - "showXAxis": true, - "showYAxis": true - }, - "expressions": [ - "service_resp_time" - ] - }, - { - "x": 16, - "y": 30, - "w": 8, - "h": 15, - "i": "5", - "type": "Widget", - "widget": { - "title": "Outbound Success Rate (%)", - "tips": "Percentage of outbound HTTP requests with status code < 400" + "title": "Request Duration Percentile (ms)", + "tips": "miniprogram.request.duration histogram — P50 / P75 / P90 / P95 / P99" }, "graph": { "type": "Line", @@ -154,26 +122,12 @@ "showYAxis": true }, "expressions": [ - "service_sla/100" - ] - }, - { - "x": 0, - "y": 45, - "w": 24, - "h": 15, - "i": "6", - "type": "Widget", - "widget": { - "title": "Error Count", - "tips": "Counted from LAL-processed error logs, aggregated per service" - }, - "graph": { - "type": "Bar", - "showBackground": true - }, - "expressions": [ - "meter_alipay_mp_error_count" + "relabels(meter_alipay_mp_request_duration_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='P50,P75,P90,P95,P99')" + ], + "metricConfig": [ + { + "label": "P50, P75, P90, P95, P99" + } ] } ] @@ -198,18 +152,22 @@ "fontSize": 12 }, "expressions": [ + "avg(meter_alipay_mp_instance_request_cpm)", "avg(meter_alipay_mp_instance_app_launch_duration)", "avg(meter_alipay_mp_instance_first_render_duration)", - "avg(meter_alipay_mp_instance_request_duration_percentile{p='95'})", - "avg(service_instance_cpm)" + "avg(meter_alipay_mp_instance_request_duration_percentile{p='95'})" ], "subExpressions": [ + "meter_alipay_mp_instance_request_cpm", "meter_alipay_mp_instance_app_launch_duration", "meter_alipay_mp_instance_first_render_duration", - "meter_alipay_mp_instance_request_duration_percentile{p='95'}", - "service_instance_cpm" + "meter_alipay_mp_instance_request_duration_percentile{p='95'}" ], "metricConfig": [ + { + "label": "Request Load", + "unit": "calls / min" + }, { "label": "Launch Duration", "unit": "ms" @@ -221,10 +179,6 @@ { "label": "Request P95", "unit": "ms" - }, - { - "label": "Outbound Load", - "unit": "calls / min" } ] } @@ -242,7 +196,7 @@ "type": "Widget", "widget": { "title": "Pages", - "tips": "In-app pages (miniprogram.page.path). Click a row to drill into per-page perf + errors." + "tips": "In-app pages (miniprogram.page.path). Click a row to drill into per-page perf." }, "graph": { "type": "EndpointList", @@ -252,16 +206,20 @@ "showYAxis": false }, "expressions": [ + "avg(meter_alipay_mp_endpoint_request_cpm)", "avg(meter_alipay_mp_endpoint_first_render_duration)", - "avg(meter_alipay_mp_endpoint_request_duration_percentile{p='95'})", - "avg(endpoint_cpm)" + "avg(meter_alipay_mp_endpoint_request_duration_percentile{p='95'})" ], "subExpressions": [ + "meter_alipay_mp_endpoint_request_cpm", "meter_alipay_mp_endpoint_first_render_duration", - "meter_alipay_mp_endpoint_request_duration_percentile{p='95'}", - "endpoint_cpm" + "meter_alipay_mp_endpoint_request_duration_percentile{p='95'}" ], "metricConfig": [ + { + "label": "Request Load", + "unit": "calls / min" + }, { "label": "First Render", "unit": "ms" @@ -269,10 +227,6 @@ { "label": "Request P95", "unit": "ms" - }, - { - "label": "Outbound Load", - "unit": "calls / min" } ] } diff --git a/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-endpoint.json b/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-endpoint.json index 4ee8db977ebd..325f62b4fb69 100644 --- a/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-endpoint.json +++ b/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-endpoint.json @@ -22,8 +22,7 @@ "i": "0", "type": "Widget", "widget": { - "title": "App Launch Duration on this Page (ms)", - "tips": "Launch duration observed when this page was the entry" + "title": "App Launch Duration on this Page (ms)" }, "graph": { "type": "Line", @@ -45,8 +44,7 @@ "i": "1", "type": "Widget", "widget": { - "title": "First Render Duration (ms)", - "tips": "First render time for this page" + "title": "First Render Duration (ms)" }, "graph": { "type": "Line", @@ -68,8 +66,8 @@ "i": "2", "type": "Widget", "widget": { - "title": "Request Duration Percentile (ms)", - "tips": "Latency distribution of outbound requests initiated from this page" + "title": "Request Load (calls / min)", + "tips": "Outbound HTTP request count initiated from this page" }, "graph": { "type": "Line", @@ -80,45 +78,18 @@ "showYAxis": true }, "expressions": [ - "relabels(meter_wechat_mp_endpoint_request_duration_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='P50,P75,P90,P95,P99')" - ], - "metricConfig": [ - { - "label": "P50, P75, P90, P95, P99" - } + "meter_wechat_mp_endpoint_request_cpm" ] }, { "x": 0, "y": 30, - "w": 8, + "w": 24, "h": 15, "i": "3", "type": "Widget", "widget": { - "title": "Outbound Load (calls / min)" - }, - "graph": { - "type": "Line", - "step": false, - "smooth": false, - "showSymbol": true, - "showXAxis": true, - "showYAxis": true - }, - "expressions": [ - "endpoint_cpm" - ] - }, - { - "x": 8, - "y": 30, - "w": 8, - "h": 15, - "i": "4", - "type": "Widget", - "widget": { - "title": "Outbound Avg Response Time (ms)" + "title": "Request Duration Percentile (ms)" }, "graph": { "type": "Line", @@ -129,29 +100,12 @@ "showYAxis": true }, "expressions": [ - "endpoint_resp_time" - ] - }, - { - "x": 16, - "y": 30, - "w": 8, - "h": 15, - "i": "5", - "type": "Widget", - "widget": { - "title": "Outbound Success Rate (%)" - }, - "graph": { - "type": "Line", - "step": false, - "smooth": false, - "showSymbol": true, - "showXAxis": true, - "showYAxis": true - }, - "expressions": [ - "endpoint_sla/100" + "relabels(meter_wechat_mp_endpoint_request_duration_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='P50,P75,P90,P95,P99')" + ], + "metricConfig": [ + { + "label": "P50, P75, P90, P95, P99" + } ] } ] diff --git a/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-instance.json b/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-instance.json index 3718c5cbaecf..240d73c55ac1 100644 --- a/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-instance.json +++ b/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-instance.json @@ -22,8 +22,7 @@ "i": "0", "type": "Widget", "widget": { - "title": "App Launch Duration (ms)", - "tips": "Launch duration for this release" + "title": "App Launch Duration (ms)" }, "graph": { "type": "Line", @@ -45,8 +44,7 @@ "i": "1", "type": "Widget", "widget": { - "title": "First Render Duration (ms)", - "tips": "First render duration for this release" + "title": "First Render Duration (ms)" }, "graph": { "type": "Line", @@ -134,8 +132,8 @@ "i": "5", "type": "Widget", "widget": { - "title": "Request Duration Percentile (ms)", - "tips": "P50 / P75 / P90 / P95 / P99 for this release" + "title": "Request Load (calls / min)", + "tips": "Outbound HTTP request count for this release" }, "graph": { "type": "Line", @@ -146,67 +144,19 @@ "showYAxis": true }, "expressions": [ - "relabels(meter_wechat_mp_instance_request_duration_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='P50,P75,P90,P95,P99')" - ], - "metricConfig": [ - { - "label": "P50, P75, P90, P95, P99" - } + "meter_wechat_mp_instance_request_cpm" ] }, { "x": 0, "y": 45, - "w": 8, + "w": 24, "h": 15, "i": "6", "type": "Widget", "widget": { - "title": "Outbound Load (calls / min)" - }, - "graph": { - "type": "Line", - "step": false, - "smooth": false, - "showSymbol": true, - "showXAxis": true, - "showYAxis": true - }, - "expressions": [ - "service_instance_cpm" - ] - }, - { - "x": 8, - "y": 45, - "w": 8, - "h": 15, - "i": "7", - "type": "Widget", - "widget": { - "title": "Outbound Avg Response Time (ms)" - }, - "graph": { - "type": "Line", - "step": false, - "smooth": false, - "showSymbol": true, - "showXAxis": true, - "showYAxis": true - }, - "expressions": [ - "service_instance_resp_time" - ] - }, - { - "x": 16, - "y": 45, - "w": 8, - "h": 15, - "i": "8", - "type": "Widget", - "widget": { - "title": "Outbound Success Rate (%)" + "title": "Request Duration Percentile (ms)", + "tips": "P50 / P75 / P90 / P95 / P99 for this release" }, "graph": { "type": "Line", @@ -217,7 +167,12 @@ "showYAxis": true }, "expressions": [ - "service_instance_sla/100" + "relabels(meter_wechat_mp_instance_request_duration_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='P50,P75,P90,P95,P99')" + ], + "metricConfig": [ + { + "label": "P50, P75, P90, P95, P99" + } ] } ] diff --git a/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-root.json b/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-root.json index 6938213e8ec8..ecffb7874641 100644 --- a/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-root.json +++ b/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-root.json @@ -19,18 +19,22 @@ "showGroup": true }, "expressions": [ + "avg(meter_wechat_mp_request_cpm)", "avg(meter_wechat_mp_app_launch_duration)", "avg(meter_wechat_mp_first_render_duration)", - "avg(meter_wechat_mp_request_duration_percentile{p='95'})", - "avg(service_cpm)" + "avg(meter_wechat_mp_request_duration_percentile{p='95'})" ], "subExpressions": [ + "meter_wechat_mp_request_cpm", "meter_wechat_mp_app_launch_duration", "meter_wechat_mp_first_render_duration", - "meter_wechat_mp_request_duration_percentile{p='95'}", - "service_cpm" + "meter_wechat_mp_request_duration_percentile{p='95'}" ], "metricConfig": [ + { + "label": "Request Load", + "unit": "calls / min" + }, { "label": "Launch Duration", "unit": "ms" @@ -42,10 +46,6 @@ { "label": "Request P95", "unit": "ms" - }, - { - "label": "Outbound Load", - "unit": "calls / min" } ] }, diff --git a/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-service.json b/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-service.json index bb17dd548cf0..c0aa6ef93a31 100644 --- a/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-service.json +++ b/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-service.json @@ -132,13 +132,13 @@ { "x": 0, "y": 30, - "w": 24, + "w": 12, "h": 15, "i": "5", "type": "Widget", "widget": { - "title": "Request Duration Percentile (ms)", - "tips": "miniprogram.request.duration histogram — P50 / P75 / P90 / P95 / P99" + "title": "Request Load (calls / min)", + "tips": "Outbound HTTP request count derived from miniprogram.request.duration histogram _count family" }, "graph": { "type": "Line", @@ -149,70 +149,38 @@ "showYAxis": true }, "expressions": [ - "relabels(meter_wechat_mp_request_duration_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='P50,P75,P90,P95,P99')" - ], - "metricConfig": [ - { - "label": "P50, P75, P90, P95, P99" - } + "meter_wechat_mp_request_cpm" ] }, { - "x": 0, - "y": 45, - "w": 8, + "x": 12, + "y": 30, + "w": 12, "h": 15, "i": "6", "type": "Widget", "widget": { - "title": "Outbound Load (calls / min)", - "tips": "Outbound HTTP requests observed by the mini program, derived from native trace segments" + "title": "Error Count", + "tips": "Counted from LAL-processed error logs (js / promise / ajax / pageNotFound), aggregated per service" }, "graph": { - "type": "Line", - "step": false, - "smooth": false, - "showSymbol": true, - "showXAxis": true, - "showYAxis": true + "type": "Bar", + "showBackground": true }, "expressions": [ - "service_cpm" + "meter_wechat_mp_error_count" ] }, { - "x": 8, + "x": 0, "y": 45, - "w": 8, + "w": 24, "h": 15, "i": "7", "type": "Widget", "widget": { - "title": "Outbound Avg Response Time (ms)", - "tips": "Client-observed average latency of outbound HTTP requests" - }, - "graph": { - "type": "Line", - "step": false, - "smooth": false, - "showSymbol": true, - "showXAxis": true, - "showYAxis": true - }, - "expressions": [ - "service_resp_time" - ] - }, - { - "x": 16, - "y": 45, - "w": 8, - "h": 15, - "i": "8", - "type": "Widget", - "widget": { - "title": "Outbound Success Rate (%)", - "tips": "Percentage of outbound HTTP requests with status code < 400" + "title": "Request Duration Percentile (ms)", + "tips": "miniprogram.request.duration histogram — P50 / P75 / P90 / P95 / P99" }, "graph": { "type": "Line", @@ -223,26 +191,12 @@ "showYAxis": true }, "expressions": [ - "service_sla/100" - ] - }, - { - "x": 0, - "y": 60, - "w": 24, - "h": 15, - "i": "9", - "type": "Widget", - "widget": { - "title": "Error Count", - "tips": "Counted from LAL-processed error logs, aggregated per service" - }, - "graph": { - "type": "Bar", - "showBackground": true - }, - "expressions": [ - "meter_wechat_mp_error_count" + "relabels(meter_wechat_mp_request_duration_percentile{p='50,75,90,95,99'},p='50,75,90,95,99',percentile='P50,P75,P90,P95,P99')" + ], + "metricConfig": [ + { + "label": "P50, P75, P90, P95, P99" + } ] } ] @@ -267,18 +221,22 @@ "fontSize": 12 }, "expressions": [ + "avg(meter_wechat_mp_instance_request_cpm)", "avg(meter_wechat_mp_instance_app_launch_duration)", "avg(meter_wechat_mp_instance_first_render_duration)", - "avg(meter_wechat_mp_instance_request_duration_percentile{p='95'})", - "avg(service_instance_cpm)" + "avg(meter_wechat_mp_instance_request_duration_percentile{p='95'})" ], "subExpressions": [ + "meter_wechat_mp_instance_request_cpm", "meter_wechat_mp_instance_app_launch_duration", "meter_wechat_mp_instance_first_render_duration", - "meter_wechat_mp_instance_request_duration_percentile{p='95'}", - "service_instance_cpm" + "meter_wechat_mp_instance_request_duration_percentile{p='95'}" ], "metricConfig": [ + { + "label": "Request Load", + "unit": "calls / min" + }, { "label": "Launch Duration", "unit": "ms" @@ -290,10 +248,6 @@ { "label": "Request P95", "unit": "ms" - }, - { - "label": "Outbound Load", - "unit": "calls / min" } ] } @@ -311,7 +265,7 @@ "type": "Widget", "widget": { "title": "Pages", - "tips": "In-app pages (miniprogram.page.path). Click a row to drill into per-page perf + errors." + "tips": "In-app pages (miniprogram.page.path). Click a row to drill into per-page perf." }, "graph": { "type": "EndpointList", @@ -321,16 +275,20 @@ "showYAxis": false }, "expressions": [ + "avg(meter_wechat_mp_endpoint_request_cpm)", "avg(meter_wechat_mp_endpoint_first_render_duration)", - "avg(meter_wechat_mp_endpoint_request_duration_percentile{p='95'})", - "avg(endpoint_cpm)" + "avg(meter_wechat_mp_endpoint_request_duration_percentile{p='95'})" ], "subExpressions": [ + "meter_wechat_mp_endpoint_request_cpm", "meter_wechat_mp_endpoint_first_render_duration", - "meter_wechat_mp_endpoint_request_duration_percentile{p='95'}", - "endpoint_cpm" + "meter_wechat_mp_endpoint_request_duration_percentile{p='95'}" ], "metricConfig": [ + { + "label": "Request Load", + "unit": "calls / min" + }, { "label": "First Render", "unit": "ms" @@ -338,10 +296,6 @@ { "label": "Request P95", "unit": "ms" - }, - { - "label": "Outbound Load", - "unit": "calls / min" } ] } From 8ad2b518a1633f4d15270c14fad3e5a155a9010d Mon Sep 17 00:00:00 2001 From: Wu Sheng Date: Mon, 20 Apr 2026 23:15:12 +0800 Subject: [PATCH 07/14] new-monitoring-feature skill: codify lessons from SWIP-12 review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates reflect the direction established while reviewing the mini-program PR: - New 'Working posture' section at the top: * Traces are re-used as-is for most new monitoring features. OTLP / Zipkin span-metrics analysis already exists and is only extended for genuinely new semantic-convention attributes (e.g. gen_ai.*, mcp.* drove GenAISpanListener). No CommonAnalysisListener edits unless OAP core proto introduces a new SpanLayer enum value. * If the work is drifting larger than this skill suggests (new listener, new proto/receiver, componentId-based layer overrides), stop and confirm before continuing. * Compile / checkstyle / license-eye are necessary but not sufficient — the new feature must be verified end-to-end locally against a running OAP before pushing to CI. - §2 header reframed: the common case is 'don't touch trace analysis'. The anti-pattern of componentId -> layer mapping on exit-only client-side segments is called out explicitly, with browser / iOS / mini-program listed as reference layers that mirror the right pattern (MAL + LAL produce the Service entity; trace pipeline unchanged). - §3.1 OAL caveat: 'free' core metrics (service_cpm, service_resp_time, endpoint_cpm, ...) only populate for layers with inbound traffic (parseEntry fires). Client-side / edge layers must wire dashboards to MAL metrics instead. - §5.1 dashboard section: collaborate with the developer on layout + metric selection, then manually set up the feature against a live OAP to confirm every widget renders non-empty before shipping. - §9 traps table: two new entries for the client-side trace-analysis anti-pattern and for dashboards wired to OAL metrics that never populate. --- .../skills/new-monitoring-feature/SKILL.md | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/.claude/skills/new-monitoring-feature/SKILL.md b/.claude/skills/new-monitoring-feature/SKILL.md index ef87ca7ef8ce..96c0bcd3c8a9 100644 --- a/.claude/skills/new-monitoring-feature/SKILL.md +++ b/.claude/skills/new-monitoring-feature/SKILL.md @@ -11,6 +11,14 @@ There is one skill per narrow concern. This one is the wiring map. --- +## Working posture + +- **Default scope for a new monitoring feature is narrow.** For most new monitoring targets, traces are **re-used as-is** — you do not write or modify a trace analyzer. Metrics and log extensions via OTLP (MAL + LAL) are the common shape. OTLP / Zipkin span-based metrics analysis (`SpanListener`) already exists and only needs a new implementation if your feature introduces genuinely new semantic-convention attributes to extract (e.g. `gen_ai.*`, `mcp.*` for the GenAI layer). Do **not** touch `CommonAnalysisListener` / `RPCAnalysisListener` / `SegmentAnalysisListener` unless the OAP core protocol gains a new `SpanLayer` enum value — details in §2. +- **If you find yourself editing more than this skill suggests, stop and confirm.** Scope creep on trace analysis, span-layer mapping, receiver handlers, or protobuf/proto contracts is a strong signal the framing is wrong. Ask the developer before committing that direction. +- **Verify every new feature locally before pushing to CI.** Compile + checkstyle + license-eye are minimums, but they only tell you the code builds — they do **not** tell you the new MAL rule compiles its closures at startup, the LAL layer:auto dispatch works, the OTLP payload shape matches the MAL labels, the UI template renders against real data, or the layer shows up in `swctl service ly `. Run the new e2e case end-to-end locally against a live OAP (see [run-e2e skill](../run-e2e/SKILL.md)) and fire every verify step with `swctl` by hand at least once. Pushing to CI without a local green run wastes shared CI cycles and slow-loops review. + +--- + ## 0. Register the `Layer` — the feature's entry point A `Layer` is how OAP slices services / instances / endpoints by data source. **Every new feature needs a new `Layer` enum value.** The UI, storage partitioning, menu navigation, and OAL aggregation all key off it. @@ -63,6 +71,36 @@ All five DSLs (OAL, MAL, LAL, Hierarchy + SpanListener SPI) compile via ANTLR4 + SkyWalking OAP ingests traces through two distinct entry points. Pick based on source. +> **Before writing any trace extension — ask "do I actually need one?"** +> +> For **most new layers you do not touch trace analysis at all.** Segments (native or +> OTLP) flow through the existing pipeline, `RPCAnalysisListener.parseExit` produces +> the outbound `ServiceRelation` edges, the backend-side agent produces the inbound +> metrics via its own `parseEntry`, and the new-layer Service / Instance / Endpoint +> entities come from **MAL + LAL**, not from trace analysis. This is how browser +> (`Layer.BROWSER`), iOS (`Layer.IOS`), mobile mini-programs +> (`Layer.WECHAT_MINI_PROGRAM` / `Layer.ALIPAY_MINI_PROGRAM`), and most +> scraped-metric layers work. +> +> Extend trace analysis **only** when the feature genuinely needs per-span +> interpretation that the existing pipeline cannot express: +> +> - **A new `SpanLayer`** — OAP core protobuf has a new `SpanLayer` enum value that +> callers of `identifyServiceLayer` / `identifyRemoteServiceLayer` must dispatch on. +> That means touching `CommonAnalysisListener` / `RPCAnalysisListener`. +> - **A genuinely new signal class not covered by existing listeners** — e.g. the GenAI +> work added `GenAISpanListener` for LLM / MCP instrumentation because OTLP spans +> carry semantic-convention attributes (`gen_ai.*`, `mcp.*`) that no prior listener +> extracted. That is rare. +> +> **Anti-pattern we've been bitten by:** using a componentId mapping in +> `CommonAnalysisListener` to assign a client-side layer to exit-only segments. This +> does not produce inbound metrics anyway (no entry spans → no +> `service_cpm` / `endpoint_cpm` from OAL), and it is orthogonal to the Service entity +> layer (which MAL / LAL sets). Mirror the browser pattern instead: ship `Layer` + +> `component-libraries.yml` + MAL + LAL + dashboards, and leave the listener chain +> alone. + ### 2.1 SkyWalking native segments — `AnalysisListener` **Source protocol**: `apm-protocol/apm-network/src/main/proto/language-agent/Tracing.proto` (`TraceSegmentObject`). @@ -112,7 +150,9 @@ Return `SpanListenerResult.builder().shouldPersist(false).build()` to prevent a ### 3.1 OAL (for entities already produced by core sources) -`oap-server/server-starter/src/main/resources/oal/core.oal` already defines the standard `service_cpm`, `service_resp_time`, `service_sla`, `endpoint_cpm`, etc. — keyed off the `Service`, `ServiceInstance`, `Endpoint` source scopes. If your new layer just emits those entities with the right `Layer`, you get all the core metrics for free. +`oap-server/server-starter/src/main/resources/oal/core.oal` already defines the standard `service_cpm`, `service_resp_time`, `service_sla`, `endpoint_cpm`, etc. — keyed off the `Service`, `ServiceInstance`, `Endpoint` source scopes. If your new layer emits those entities with the right `Layer`, you get the core metrics for free. + +**Who emits those entities matters.** `service_cpm` / `service_resp_time` / `service_sla` / `endpoint_cpm` / `endpoint_resp_time` come from `RPCAnalysisListener.parseEntry` → `callingIn.toService()` / `toEndpoint()`. That only fires on **inbound entry spans**. Client-side / edge layers (browser, iOS, mini-programs) only emit exit spans, so these OAL metrics do **not** populate under those layers — their Service entities are produced by MAL / LAL, and their dashboards use MAL metrics (`meter__*`) for load / latency, not `service_cpm` / `service_resp_time`. Don't wire dashboards to OAL-derived metrics that the layer never produces. Additional OAL rules go in the same file. OAL grammar: `oap-server/oal-grammar/src/main/antlr4/OALParser.g4`. Syntax and examples: [`docs/en/concepts-and-designs/oal.md`](../../../docs/en/concepts-and-designs/oal.md). @@ -169,6 +209,8 @@ JSON files under `oap-server/server-starter/src/main/resources/ui-initialized-te - `-endpoint.json` — per-endpoint dashboard. - Menu link in `oap-server/server-starter/src/main/resources/ui-initialized-templates/menu.yaml`. +> **Collaborate with the developer on dashboard design.** Dashboard layout and metric selection are product decisions, not derivable from the MAL rules alone. Before authoring the JSON, check in with the developer on: which metric goes on which panel, which percentiles / aggregations to show, what's on the root list vs. per-service vs. per-instance vs. per-endpoint pages, what stays in the Overview tab vs. a sub-tab, widget sizes / ordering, and whether Trace / Log tabs belong at all (depends on whether the feature emits traces and logs). Then **manually set up the feature against a live OAP** — emit a few real OTLP payloads, open each dashboard in the UI, confirm every widget renders non-empty, and walk the drill-down from root → service → instance → endpoint. Only after that eyes-on pass is the dashboard ready to ship. + Details that always bite: - **Tab chrome eats ~4 rows.** Set outer `Tab.h = content_max_y + 4` or you get an inner scrollbar on top of the page scrollbar. @@ -250,6 +292,8 @@ Run in order; each has a dedicated skill: | Trap | Symptom | Fix | |---|---|---| | Layer folder named with hyphens | `ui-initialized-templates/my-layer/*.json` on disk but dashboard empty | Folder must be `Layer.name().toLowerCase()` (underscores). `wechat_mini_program/`, not `wechat-mini-program/` | +| Extending `CommonAnalysisListener` for a client-side layer | Added componentId → layer mapping "to get topology for free"; then `service_cpm` etc. don't populate anyway | Client-side (exit-only) layers don't need trace-analysis changes. Ship `Layer` + `component-libraries.yml` + MAL + LAL. See §2 header for when extending trace analysis is actually justified | +| Dashboards wired to OAL-derived metrics on a client-side layer | Charts stay empty — `service_cpm` / `service_resp_time` / `endpoint_cpm` never populate | Those come from inbound `parseEntry`, which mini-program / browser / iOS don't emit. Use MAL metrics (`meter__*`) instead | | Missing layer-root template | Menu item lands on empty "no dashboard" view | `Layer.vue:41-44` requires a template with `isRoot: true`; ship a `-root.json` (precedent: `ios/ios-root.json`) | | New layer not selectable in UI / swctl | `service ly ` returns empty | Add the enum in `Layer.java` with a fresh id; see §0 | | Stale dist in image | `make docker.oap` succeeds but behavior is old | See [`package` skill](../package/SKILL.md); rebuild dist first, then image | From 63843a27585389b7f74c414117ca100a28cbdc89 Mon Sep 17 00:00:00 2001 From: Wu Sheng Date: Mon, 20 Apr 2026 23:41:05 +0800 Subject: [PATCH 08/14] SWIP-12 fix: split multi-doc log-mal-rule into per-platform files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI caught: MALFilterExecutionTest failed with 'expected a single document in the stream' on log-mal-rules/miniprogram.yaml at the '---' separator. Rules.loadRules() uses Yaml.loadAs(r, Rule.class) — single-document only. Multi-doc YAML would also fail at OAP startup, not just in tests. Split into two single-doc files: - log-mal-rules/miniprogram-wechat.yaml - log-mal-rules/miniprogram-alipay.yaml Update application.yml malFiles default, storage e2e config-dump expected, and both miniprogram e2e docker-compose SW_LOG_MAL_FILES env values. MALFilterExecutionTest + MALExpressionExecutionTest + LALScriptExecutionTest all green locally (1302 MAL, 134 LAL). --- .../src/main/resources/application.yml | 2 +- ...niprogram.yaml => miniprogram-alipay.yaml} | 14 +++-------- .../log-mal-rules/miniprogram-wechat.yaml | 25 +++++++++++++++++++ .../miniprogram/alipay/docker-compose.yml | 2 +- .../miniprogram/wechat/docker-compose.yml | 2 +- .../cases/storage/expected/config-dump.yml | 2 +- 6 files changed, 32 insertions(+), 15 deletions(-) rename oap-server/server-starter/src/main/resources/log-mal-rules/{miniprogram.yaml => miniprogram-alipay.yaml} (65%) create mode 100644 oap-server/server-starter/src/main/resources/log-mal-rules/miniprogram-wechat.yaml diff --git a/oap-server/server-starter/src/main/resources/application.yml b/oap-server/server-starter/src/main/resources/application.yml index 3feb093ad925..cbc604befc64 100644 --- a/oap-server/server-starter/src/main/resources/application.yml +++ b/oap-server/server-starter/src/main/resources/application.yml @@ -238,7 +238,7 @@ log-analyzer: selector: ${SW_LOG_ANALYZER:default} default: lalFiles: ${SW_LOG_LAL_FILES:envoy-als,mesh-dp,mysql-slowsql,pgsql-slowsql,redis-slowsql,k8s-service,nginx,envoy-ai-gateway,miniprogram,default} - malFiles: ${SW_LOG_MAL_FILES:"nginx,miniprogram"} + malFiles: ${SW_LOG_MAL_FILES:"nginx,miniprogram-wechat,miniprogram-alipay"} event-analyzer: selector: ${SW_EVENT_ANALYZER:default} diff --git a/oap-server/server-starter/src/main/resources/log-mal-rules/miniprogram.yaml b/oap-server/server-starter/src/main/resources/log-mal-rules/miniprogram-alipay.yaml similarity index 65% rename from oap-server/server-starter/src/main/resources/log-mal-rules/miniprogram.yaml rename to oap-server/server-starter/src/main/resources/log-mal-rules/miniprogram-alipay.yaml index 40196031851d..3f5aff52bdb7 100644 --- a/oap-server/server-starter/src/main/resources/log-mal-rules/miniprogram.yaml +++ b/oap-server/server-starter/src/main/resources/log-mal-rules/miniprogram-alipay.yaml @@ -13,19 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Log-MAL rules for WeChat / Alipay Mini Program error counters. +# Log-MAL rule for Alipay Mini Program error counts. # Consumes `miniprogram_error_count` samples emitted by the LAL rule -# (lal/miniprogram.yaml, metrics{} block), one per error log, and -# aggregates per-platform into `meter_wechat_mp_error_count` / -# `meter_alipay_mp_error_count`. The top-level filter gates each doc -# to the correct platform so the two rules do not double-count. +# (lal/miniprogram.yaml metrics{} block), filtered to miniprogram_platform +# == 'alipay', aggregated under meter_alipay_mp_error_count. -metricPrefix: meter_wechat_mp -filter: "{ tags -> tags.miniprogram_platform == 'wechat' }" -metricsRules: - - name: error_count - exp: miniprogram_error_count.sum(['service_name', 'exception_type']).downsampling(SUM).service(['service_name'], Layer.WECHAT_MINI_PROGRAM) ---- metricPrefix: meter_alipay_mp filter: "{ tags -> tags.miniprogram_platform == 'alipay' }" metricsRules: diff --git a/oap-server/server-starter/src/main/resources/log-mal-rules/miniprogram-wechat.yaml b/oap-server/server-starter/src/main/resources/log-mal-rules/miniprogram-wechat.yaml new file mode 100644 index 000000000000..bc3ae3bdef65 --- /dev/null +++ b/oap-server/server-starter/src/main/resources/log-mal-rules/miniprogram-wechat.yaml @@ -0,0 +1,25 @@ +# 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. + +# Log-MAL rule for WeChat Mini Program error counts. +# Consumes `miniprogram_error_count` samples emitted by the LAL rule +# (lal/miniprogram.yaml metrics{} block), filtered to miniprogram_platform +# == 'wechat', aggregated under meter_wechat_mp_error_count. + +metricPrefix: meter_wechat_mp +filter: "{ tags -> tags.miniprogram_platform == 'wechat' }" +metricsRules: + - name: error_count + exp: miniprogram_error_count.sum(['service_name', 'exception_type']).downsampling(SUM).service(['service_name'], Layer.WECHAT_MINI_PROGRAM) diff --git a/test/e2e-v2/cases/miniprogram/alipay/docker-compose.yml b/test/e2e-v2/cases/miniprogram/alipay/docker-compose.yml index 670b5807f6bc..c4dcd1e74853 100644 --- a/test/e2e-v2/cases/miniprogram/alipay/docker-compose.yml +++ b/test/e2e-v2/cases/miniprogram/alipay/docker-compose.yml @@ -25,7 +25,7 @@ services: SW_OTEL_RECEIVER_ENABLED_HANDLERS: "otlp-traces,otlp-metrics,otlp-logs" SW_OTEL_RECEIVER_ENABLED_OTEL_METRICS_RULES: "miniprogram/alipay-mini-program,miniprogram/alipay-mini-program-instance" SW_LOG_LAL_FILES: "miniprogram,default" - SW_LOG_MAL_FILES: "miniprogram" + SW_LOG_MAL_FILES: "miniprogram-alipay" SW_LOG_ANALYZER: default banyandb: diff --git a/test/e2e-v2/cases/miniprogram/wechat/docker-compose.yml b/test/e2e-v2/cases/miniprogram/wechat/docker-compose.yml index 1cacb4ef6067..da415aad0611 100644 --- a/test/e2e-v2/cases/miniprogram/wechat/docker-compose.yml +++ b/test/e2e-v2/cases/miniprogram/wechat/docker-compose.yml @@ -25,7 +25,7 @@ services: SW_OTEL_RECEIVER_ENABLED_HANDLERS: "otlp-traces,otlp-metrics,otlp-logs" SW_OTEL_RECEIVER_ENABLED_OTEL_METRICS_RULES: "miniprogram/wechat-mini-program,miniprogram/wechat-mini-program-instance" SW_LOG_LAL_FILES: "miniprogram,default" - SW_LOG_MAL_FILES: "miniprogram" + SW_LOG_MAL_FILES: "miniprogram-wechat" SW_LOG_ANALYZER: default banyandb: diff --git a/test/e2e-v2/cases/storage/expected/config-dump.yml b/test/e2e-v2/cases/storage/expected/config-dump.yml index 16bb839cec28..9306de872486 100644 --- a/test/e2e-v2/cases/storage/expected/config-dump.yml +++ b/test/e2e-v2/cases/storage/expected/config-dump.yml @@ -143,7 +143,7 @@ storage.mysql.properties.dataSource.useServerPrepStmts=true core.default.gRPCThreadPoolSize=-1 status-query.default.keywords4MaskingSecretsOfConfig=user,password,token,accessKey,secretKey,authentication storage.mysql.maxSizeOfBatchSql=2000 -log-analyzer.default.malFiles=nginx,miniprogram +log-analyzer.default.malFiles=nginx,miniprogram-wechat,miniprogram-alipay telemetry.prometheus.port=1234 core.default.recordDataTTL=3 core.default.maxHttpUrisNumberPerService=3000 From 80115c20e8760145217e22edaf109b5be522fd0b Mon Sep 17 00:00:00 2001 From: Wu Sheng Date: Tue, 21 Apr 2026 00:40:50 +0800 Subject: [PATCH 09/14] SWIP-12: e2e verified locally against SkyAPM sim images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Local end-to-end verification against ghcr.io/skyapm/mini-program-monitor/ sim-{wechat,alipay}:v0.4.0 caught three issues the CI would have caught one-by-one: 1) LAL '#' comments don't parse — the grammar only accepts '//' (and '/* */' blocks). Replaced the 5-line '#' comment in lal/miniprogram.yaml with '//'. 2) LAL can't reference a def var as a bare value (only via method chain like platform?.toString). 'tag 'platform': platform' and 'labels ... miniprogram_platform: platform' would hit 'no matching codegen path' at OAP startup. Rewrote the rule to call sourceAttribute('miniprogram.platform') inline in the three value-access sites, keeping the conditionals working (conditions go through a different codegen path and DO handle bare def refs). 3) meter_{wechat,alipay}_mp_error_count is labeled by exception_type so returns multiple series, not one — switched those verify cases to the metrics-has-value-label.yml expected shape. E2E workflow revised to use the published sim images (MODE=timed 60s, SCENARIO=demo, SERVICE_INSTANCE=sim, COLLECTOR_URL + TRACE_COLLECTOR_URL pointed at oap:12800) instead of hand-crafted curl payloads. Sim drives realistic OTLP logs + metrics + native trace segments against OAP. Both cases verified green locally: 8/8 for wechat, 8/8 for alipay. --- .../src/main/resources/lal/miniprogram.yaml | 28 ++++----- .../miniprogram/alipay/docker-compose.yml | 16 +++++ test/e2e-v2/cases/miniprogram/alipay/e2e.yaml | 47 +++++---------- .../miniprogram/alipay/expected/logs.yml | 10 ++-- .../expected/metrics-has-value-label.yml | 34 +++++++++++ .../miniprogram/alipay/expected/service.yml | 6 +- .../miniprogram/wechat/docker-compose.yml | 17 ++++++ test/e2e-v2/cases/miniprogram/wechat/e2e.yaml | 59 ++++++++----------- .../miniprogram/wechat/expected/logs.yml | 10 ++-- .../expected/metrics-has-value-label.yml | 34 +++++++++++ .../miniprogram/wechat/expected/service.yml | 6 +- 11 files changed, 169 insertions(+), 98 deletions(-) create mode 100644 test/e2e-v2/cases/miniprogram/alipay/expected/metrics-has-value-label.yml create mode 100644 test/e2e-v2/cases/miniprogram/wechat/expected/metrics-has-value-label.yml diff --git a/oap-server/server-starter/src/main/resources/lal/miniprogram.yaml b/oap-server/server-starter/src/main/resources/lal/miniprogram.yaml index 5a55667c9215..6cf1cb987a19 100644 --- a/oap-server/server-starter/src/main/resources/lal/miniprogram.yaml +++ b/oap-server/server-starter/src/main/resources/lal/miniprogram.yaml @@ -16,17 +16,17 @@ # WeChat / Alipay Mini Program error-log processing. # One rule covers both runtimes — layer is decided script-side from the # miniprogram.platform resource attribute, using the SWIP-11 layer:auto -# mechanism. Also emits a `miniprogram_error_count` counter sample for -# every error log, picked up by log-mal-rules/miniprogram.yaml and -# aggregated into per-platform `error_count` metrics. +# mechanism. Also emits a miniprogram_error_count counter sample for every +# error log, picked up by log-mal-rules/miniprogram-{wechat,alipay}.yaml +# which aggregate into per-platform error_count metrics. rules: - name: miniprogram-errors layer: auto dsl: | filter { - def platform = sourceAttribute("miniprogram.platform") - if (platform != "wechat" && platform != "alipay") { + if (sourceAttribute("miniprogram.platform") != "wechat" + && sourceAttribute("miniprogram.platform") != "alipay") { abort {} } if (tag("exception.type") == null) { @@ -34,21 +34,21 @@ rules: } extractor { - if (platform == "wechat") { + if (sourceAttribute("miniprogram.platform") == "wechat") { layer "WECHAT_MINI_PROGRAM" - } else if (platform == "alipay") { + } else if (sourceAttribute("miniprogram.platform") == "alipay") { layer "ALIPAY_MINI_PROGRAM" } - # Instance source matches what OTLP metrics use, so logs aggregate under - # the same OAP instance entity when operators follow the recommended - # serviceInstance == serviceVersion pattern (SDK >= v0.4.0). If absent, - # sourceAttribute() returns null/empty -> TrafficSinkListener skips the - # instance traffic, matching MAL's empty-dim behavior. + // Instance source matches what OTLP metrics use, so logs aggregate under + // the same OAP instance entity when operators follow the recommended + // serviceInstance == serviceVersion pattern (SDK >= v0.4.0). If absent, + // sourceAttribute() returns null/empty -> TrafficSinkListener skips the + // instance traffic, matching MAL's empty-dim behavior. instance sourceAttribute("service.instance.id") endpoint tag("miniprogram.page.path") - tag 'platform': platform + tag 'platform': sourceAttribute("miniprogram.platform") tag 'exception.type': tag("exception.type") tag 'exception.stacktrace': tag("exception.stacktrace") tag 'http.method': tag("http.request.method") @@ -57,7 +57,7 @@ rules: metrics { timestamp log.timestamp as Long - labels service_name: log.service, exception_type: tag("exception.type"), miniprogram_platform: platform + labels service_name: log.service, exception_type: tag("exception.type"), miniprogram_platform: sourceAttribute("miniprogram.platform") name "miniprogram_error_count" value 1 } diff --git a/test/e2e-v2/cases/miniprogram/alipay/docker-compose.yml b/test/e2e-v2/cases/miniprogram/alipay/docker-compose.yml index c4dcd1e74853..a84ca258593a 100644 --- a/test/e2e-v2/cases/miniprogram/alipay/docker-compose.yml +++ b/test/e2e-v2/cases/miniprogram/alipay/docker-compose.yml @@ -35,5 +35,21 @@ services: ports: - 17912 + # Upstream SkyAPM mini-program-monitor simulator (shipped from + # https://github.com/SkyAPM/mini-program-monitor). + sim-alipay: + image: ghcr.io/skyapm/mini-program-monitor/sim-alipay:v0.4.0 + depends_on: + oap: + condition: service_healthy + environment: + MODE: timed + DURATION_MS: 60000 + SCENARIO: demo + COLLECTOR_URL: http://oap:12800 + TRACE_COLLECTOR_URL: http://oap:12800 + SERVICE_INSTANCE: sim + networks: [e2e] + networks: e2e: diff --git a/test/e2e-v2/cases/miniprogram/alipay/e2e.yaml b/test/e2e-v2/cases/miniprogram/alipay/e2e.yaml index 4f1235c1b6dd..649256919a54 100644 --- a/test/e2e-v2/cases/miniprogram/alipay/e2e.yaml +++ b/test/e2e-v2/cases/miniprogram/alipay/e2e.yaml @@ -13,11 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Alipay Mini Program e2e test — mirrors the WeChat case with -# miniprogram.platform=alipay so the platform-gated filter on the -# MAL / LAL rules routes to the ALIPAY_MINI_PROGRAM layer. WeChat-only -# metrics (route / script / package_load / first_paint.time) are not -# emitted and not verified. +# Alipay Mini Program e2e test — drives the published +# ghcr.io/skyapm/mini-program-monitor/sim-alipay:v0.4.0 image in MODE=timed +# against OAP. Alipay emits a strict subset of metrics relative to WeChat +# (no route / script / package_load / first_paint) so this case verifies +# only the metrics Alipay actually produces. setup: env: compose @@ -31,44 +31,25 @@ setup: command: bash test/e2e-v2/script/prepare/setup-e2e-shell/install.sh yq - name: install swctl command: bash test/e2e-v2/script/prepare/setup-e2e-shell/install.sh swctl - - name: send Alipay Mini Program JS error log (OTLP) - command: | - TS_NS=$(date +%s)000000000 - set -e - curl -sS -f --retry 30 --retry-delay 5 --retry-connrefused --retry-all-errors --max-time 10 \ - -X POST -H 'Content-Type: application/json' \ - http://${oap_host}:${oap_12800}/v1/logs \ - -d "{\"resourceLogs\":[{\"resource\":{\"attributes\":[{\"key\":\"service.name\",\"value\":{\"stringValue\":\"alipay-mp-demo\"}},{\"key\":\"service.version\",\"value\":{\"stringValue\":\"v1.2.0\"}},{\"key\":\"service.instance.id\",\"value\":{\"stringValue\":\"v1.2.0\"}},{\"key\":\"miniprogram.platform\",\"value\":{\"stringValue\":\"alipay\"}}]},\"scopeLogs\":[{\"scope\":{\"name\":\"mini-program-monitor\"},\"logRecords\":[{\"timeUnixNano\":\"$TS_NS\",\"severityNumber\":17,\"severityText\":\"ERROR\",\"body\":{\"stringValue\":\"TypeError: Cannot read property 'foo' of undefined\"},\"attributes\":[{\"key\":\"exception.type\",\"value\":{\"stringValue\":\"js\"}},{\"key\":\"exception.stacktrace\",\"value\":{\"stringValue\":\"at pages/index/index.js:42\"}},{\"key\":\"miniprogram.page.path\",\"value\":{\"stringValue\":\"pages/index/index\"}}]}]}]}]}" - -trigger: - action: http - interval: 3s - times: 10 - url: http://${oap_host}:${oap_12800}/v1/metrics - method: POST - headers: - Content-Type: application/json - body: '{"resourceMetrics":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"alipay-mp-demo"}},{"key":"service.version","value":{"stringValue":"v1.2.0"}},{"key":"service.instance.id","value":{"stringValue":"v1.2.0"}},{"key":"miniprogram.platform","value":{"stringValue":"alipay"}}]},"scopeMetrics":[{"scope":{"name":"mini-program-monitor"},"metrics":[{"name":"miniprogram.app_launch.duration","unit":"ms","gauge":{"dataPoints":[{"timeUnixNano":"0","asDouble":220.0,"attributes":[{"key":"miniprogram.page.path","value":{"stringValue":"pages/index/index"}}]}]}},{"name":"miniprogram.first_render.duration","unit":"ms","gauge":{"dataPoints":[{"timeUnixNano":"0","asDouble":110.0,"attributes":[{"key":"miniprogram.page.path","value":{"stringValue":"pages/index/index"}}]}]}}]}]}]}' verify: retry: count: 20 interval: 10s cases: - # Service registered under ALIPAY_MINI_PROGRAM layer - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql service ly ALIPAY_MINI_PROGRAM expected: expected/service.yml - # MAL service-scoped metric populated - - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_alipay_mp_app_launch_duration --service-name=alipay-mp-demo + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_alipay_mp_app_launch_duration --service-name=mini-program-sim-alipay expected: expected/metrics-has-value.yml - - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_alipay_mp_first_render_duration --service-name=alipay-mp-demo + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_alipay_mp_first_render_duration --service-name=mini-program-sim-alipay expected: expected/metrics-has-value.yml - # Log-MAL derived error count non-zero on the error-log traffic - - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_alipay_mp_error_count --service-name=alipay-mp-demo + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_alipay_mp_request_cpm --service-name=mini-program-sim-alipay expected: expected/metrics-has-value.yml - # Instance metric populated (serviceInstance == serviceVersion == v1.2.0 per recommended pattern) - - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_alipay_mp_instance_app_launch_duration --service-name=alipay-mp-demo --instance-name=v1.2.0 + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_alipay_mp_request_duration_percentile --service-name=mini-program-sim-alipay + expected: expected/metrics-has-value-label.yml + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_alipay_mp_error_count --service-name=mini-program-sim-alipay + expected: expected/metrics-has-value-label.yml + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_alipay_mp_instance_app_launch_duration --service-name=mini-program-sim-alipay --instance-name=sim expected: expected/metrics-has-value.yml - # Error log persisted via LAL (miniprogram rule, layer:auto -> ALIPAY_MINI_PROGRAM) - - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql logs list --service-name=alipay-mp-demo + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql logs list --service-name=mini-program-sim-alipay expected: expected/logs.yml diff --git a/test/e2e-v2/cases/miniprogram/alipay/expected/logs.yml b/test/e2e-v2/cases/miniprogram/alipay/expected/logs.yml index 6d65a8e4a9fc..a9d6f42e148c 100644 --- a/test/e2e-v2/cases/miniprogram/alipay/expected/logs.yml +++ b/test/e2e-v2/cases/miniprogram/alipay/expected/logs.yml @@ -17,11 +17,11 @@ errorreason: null debuggingtrace: null logs: {{- contains .logs }} - - servicename: alipay-mp-demo + - servicename: mini-program-sim-alipay serviceid: {{ notEmpty .serviceid }} - serviceinstancename: "v1.2.0" + serviceinstancename: "sim" serviceinstanceid: {{ notEmpty .serviceinstanceid }} - endpointname: "pages/index/index" + endpointname: {{ notEmpty .endpointname }} endpointid: {{ notEmpty .endpointid }} traceid: null timestamp: {{ gt .timestamp 0 }} @@ -32,8 +32,6 @@ logs: - key: platform value: alipay - key: exception.type - value: js - - key: exception.stacktrace - value: "at pages/index/index.js:42" + value: {{ notEmpty .value }} {{- end }} {{- end }} diff --git a/test/e2e-v2/cases/miniprogram/alipay/expected/metrics-has-value-label.yml b/test/e2e-v2/cases/miniprogram/alipay/expected/metrics-has-value-label.yml new file mode 100644 index 000000000000..f2abc7d1b7af --- /dev/null +++ b/test/e2e-v2/cases/miniprogram/alipay/expected/metrics-has-value-label.yml @@ -0,0 +1,34 @@ +# 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. + +debuggingtrace: null +type: TIME_SERIES_VALUES +results: + {{- contains .results }} + - metric: + labels: + {{- contains .metric.labels }} + - key: {{ notEmpty .key }} + value: {{ notEmpty .value }} + {{- end }} + values: + {{- contains .values }} + - id: {{ notEmpty .id }} + value: {{ notEmpty .value }} + traceid: null + owner: null + {{- end}} + {{- end}} +error: null diff --git a/test/e2e-v2/cases/miniprogram/alipay/expected/service.yml b/test/e2e-v2/cases/miniprogram/alipay/expected/service.yml index 7fbfda52f6ba..e28661d113ae 100644 --- a/test/e2e-v2/cases/miniprogram/alipay/expected/service.yml +++ b/test/e2e-v2/cases/miniprogram/alipay/expected/service.yml @@ -14,10 +14,10 @@ # limitations under the License. {{- contains . }} -- id: {{ b64enc "alipay-mp-demo" }}.1 - name: alipay-mp-demo +- id: {{ b64enc "mini-program-sim-alipay" }}.1 + name: mini-program-sim-alipay group: "" - shortname: alipay-mp-demo + shortname: mini-program-sim-alipay layers: - ALIPAY_MINI_PROGRAM normal: true diff --git a/test/e2e-v2/cases/miniprogram/wechat/docker-compose.yml b/test/e2e-v2/cases/miniprogram/wechat/docker-compose.yml index da415aad0611..2ba9b634b619 100644 --- a/test/e2e-v2/cases/miniprogram/wechat/docker-compose.yml +++ b/test/e2e-v2/cases/miniprogram/wechat/docker-compose.yml @@ -35,5 +35,22 @@ services: ports: - 17912 + # Upstream SkyAPM mini-program-monitor simulator (shipped from + # https://github.com/SkyAPM/mini-program-monitor). Drives realistic + # OTLP logs + metrics + SW native trace segments against OAP. + sim-wechat: + image: ghcr.io/skyapm/mini-program-monitor/sim-wechat:v0.4.0 + depends_on: + oap: + condition: service_healthy + environment: + MODE: timed + DURATION_MS: 60000 + SCENARIO: demo + COLLECTOR_URL: http://oap:12800 + TRACE_COLLECTOR_URL: http://oap:12800 + SERVICE_INSTANCE: sim + networks: [e2e] + networks: e2e: diff --git a/test/e2e-v2/cases/miniprogram/wechat/e2e.yaml b/test/e2e-v2/cases/miniprogram/wechat/e2e.yaml index 80b1de089608..f832f28da1ab 100644 --- a/test/e2e-v2/cases/miniprogram/wechat/e2e.yaml +++ b/test/e2e-v2/cases/miniprogram/wechat/e2e.yaml @@ -13,11 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -# WeChat Mini Program e2e test — drives OTLP metrics + OTLP error log -# payloads via curl (iOS-style, no external sim image dependency) and -# verifies service / metric / log observable under the WECHAT_MINI_PROGRAM -# layer. Native-segment handling is covered by the layer-mapping unit -# tests in RPCAnalysisListenerTest / EndpointDepFromCrossThreadAnalysisListenerTest. +# WeChat Mini Program e2e test — drives the published +# ghcr.io/skyapm/mini-program-monitor/sim-wechat:v0.4.0 image in MODE=timed +# against OAP wired with the miniprogram MAL / LAL / log-MAL rules. The sim +# emits realistic OTLP logs + metrics (perf gauges + request-duration +# histogram + errors) plus SW native trace segments (/v3/segments) with +# componentId=10002 for 60 s, then exits cleanly. setup: env: compose @@ -31,44 +32,36 @@ setup: command: bash test/e2e-v2/script/prepare/setup-e2e-shell/install.sh yq - name: install swctl command: bash test/e2e-v2/script/prepare/setup-e2e-shell/install.sh swctl - - name: send WeChat Mini Program JS error log (OTLP) - command: | - TS_NS=$(date +%s)000000000 - set -e - curl -sS -f --retry 30 --retry-delay 5 --retry-connrefused --retry-all-errors --max-time 10 \ - -X POST -H 'Content-Type: application/json' \ - http://${oap_host}:${oap_12800}/v1/logs \ - -d "{\"resourceLogs\":[{\"resource\":{\"attributes\":[{\"key\":\"service.name\",\"value\":{\"stringValue\":\"wechat-mp-demo\"}},{\"key\":\"service.version\",\"value\":{\"stringValue\":\"v1.2.0\"}},{\"key\":\"service.instance.id\",\"value\":{\"stringValue\":\"v1.2.0\"}},{\"key\":\"miniprogram.platform\",\"value\":{\"stringValue\":\"wechat\"}}]},\"scopeLogs\":[{\"scope\":{\"name\":\"mini-program-monitor\"},\"logRecords\":[{\"timeUnixNano\":\"$TS_NS\",\"severityNumber\":17,\"severityText\":\"ERROR\",\"body\":{\"stringValue\":\"TypeError: Cannot read property 'foo' of undefined\"},\"attributes\":[{\"key\":\"exception.type\",\"value\":{\"stringValue\":\"js\"}},{\"key\":\"exception.stacktrace\",\"value\":{\"stringValue\":\"at pages/index/index.js:42\"}},{\"key\":\"miniprogram.page.path\",\"value\":{\"stringValue\":\"pages/index/index\"}}]}]}]}]}" - -trigger: - action: http - interval: 3s - times: 10 - url: http://${oap_host}:${oap_12800}/v1/metrics - method: POST - headers: - Content-Type: application/json - body: '{"resourceMetrics":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"wechat-mp-demo"}},{"key":"service.version","value":{"stringValue":"v1.2.0"}},{"key":"service.instance.id","value":{"stringValue":"v1.2.0"}},{"key":"miniprogram.platform","value":{"stringValue":"wechat"}}]},"scopeMetrics":[{"scope":{"name":"mini-program-monitor"},"metrics":[{"name":"miniprogram.app_launch.duration","unit":"ms","gauge":{"dataPoints":[{"timeUnixNano":"0","asDouble":180.0,"attributes":[{"key":"miniprogram.page.path","value":{"stringValue":"pages/index/index"}}]}]}},{"name":"miniprogram.first_render.duration","unit":"ms","gauge":{"dataPoints":[{"timeUnixNano":"0","asDouble":95.0,"attributes":[{"key":"miniprogram.page.path","value":{"stringValue":"pages/index/index"}}]}]}}]}]}]}' verify: retry: count: 20 interval: 10s cases: - # Service registered under WECHAT_MINI_PROGRAM layer + # Service registered under WECHAT_MINI_PROGRAM layer (from MAL + LAL, + # both keyed off miniprogram.platform=wechat). - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql service ly WECHAT_MINI_PROGRAM expected: expected/service.yml - # MAL service-scoped metric populated - - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_wechat_mp_app_launch_duration --service-name=wechat-mp-demo + # MAL service-scoped perf gauges + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_wechat_mp_app_launch_duration --service-name=mini-program-sim-wechat expected: expected/metrics-has-value.yml - - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_wechat_mp_first_render_duration --service-name=wechat-mp-demo + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_wechat_mp_first_render_duration --service-name=mini-program-sim-wechat expected: expected/metrics-has-value.yml - # Log-MAL derived error count non-zero on the error-log traffic - - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_wechat_mp_error_count --service-name=wechat-mp-demo + # Request-count derived from the histogram _count family (delta counter + # aggregated per minute). + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_wechat_mp_request_cpm --service-name=mini-program-sim-wechat expected: expected/metrics-has-value.yml - # Instance metric populated (serviceInstance == serviceVersion == v1.2.0 per recommended pattern) - - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_wechat_mp_instance_app_launch_duration --service-name=wechat-mp-demo --instance-name=v1.2.0 + # Request duration percentile via the bucket family (MILLISECONDS unit + # keeps le labels as-is — no 1000x rescale). + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_wechat_mp_request_duration_percentile --service-name=mini-program-sim-wechat + expected: expected/metrics-has-value-label.yml + # Log-MAL derived error count — demo scenario fires all four error + # surfaces (js / promise / ajax / pageNotFound). + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_wechat_mp_error_count --service-name=mini-program-sim-wechat + expected: expected/metrics-has-value-label.yml + # Instance metric populated (SERVICE_INSTANCE=sim per recommended pattern) + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_wechat_mp_instance_app_launch_duration --service-name=mini-program-sim-wechat --instance-name=sim expected: expected/metrics-has-value.yml - # Error log persisted via LAL (miniprogram rule, layer:auto -> WECHAT_MINI_PROGRAM) - - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql logs list --service-name=wechat-mp-demo + # Error log persisted via LAL (layer:auto -> WECHAT_MINI_PROGRAM) + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql logs list --service-name=mini-program-sim-wechat expected: expected/logs.yml diff --git a/test/e2e-v2/cases/miniprogram/wechat/expected/logs.yml b/test/e2e-v2/cases/miniprogram/wechat/expected/logs.yml index c353a7bf2591..df96d994bf5b 100644 --- a/test/e2e-v2/cases/miniprogram/wechat/expected/logs.yml +++ b/test/e2e-v2/cases/miniprogram/wechat/expected/logs.yml @@ -17,11 +17,11 @@ errorreason: null debuggingtrace: null logs: {{- contains .logs }} - - servicename: wechat-mp-demo + - servicename: mini-program-sim-wechat serviceid: {{ notEmpty .serviceid }} - serviceinstancename: "v1.2.0" + serviceinstancename: "sim" serviceinstanceid: {{ notEmpty .serviceinstanceid }} - endpointname: "pages/index/index" + endpointname: {{ notEmpty .endpointname }} endpointid: {{ notEmpty .endpointid }} traceid: null timestamp: {{ gt .timestamp 0 }} @@ -32,8 +32,6 @@ logs: - key: platform value: wechat - key: exception.type - value: js - - key: exception.stacktrace - value: "at pages/index/index.js:42" + value: {{ notEmpty .value }} {{- end }} {{- end }} diff --git a/test/e2e-v2/cases/miniprogram/wechat/expected/metrics-has-value-label.yml b/test/e2e-v2/cases/miniprogram/wechat/expected/metrics-has-value-label.yml new file mode 100644 index 000000000000..f2abc7d1b7af --- /dev/null +++ b/test/e2e-v2/cases/miniprogram/wechat/expected/metrics-has-value-label.yml @@ -0,0 +1,34 @@ +# 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. + +debuggingtrace: null +type: TIME_SERIES_VALUES +results: + {{- contains .results }} + - metric: + labels: + {{- contains .metric.labels }} + - key: {{ notEmpty .key }} + value: {{ notEmpty .value }} + {{- end }} + values: + {{- contains .values }} + - id: {{ notEmpty .id }} + value: {{ notEmpty .value }} + traceid: null + owner: null + {{- end}} + {{- end}} +error: null diff --git a/test/e2e-v2/cases/miniprogram/wechat/expected/service.yml b/test/e2e-v2/cases/miniprogram/wechat/expected/service.yml index c0c68074391f..119593d13e6e 100644 --- a/test/e2e-v2/cases/miniprogram/wechat/expected/service.yml +++ b/test/e2e-v2/cases/miniprogram/wechat/expected/service.yml @@ -14,10 +14,10 @@ # limitations under the License. {{- contains . }} -- id: {{ b64enc "wechat-mp-demo" }}.1 - name: wechat-mp-demo +- id: {{ b64enc "mini-program-sim-wechat" }}.1 + name: mini-program-sim-wechat group: "" - shortname: wechat-mp-demo + shortname: mini-program-sim-wechat layers: - WECHAT_MINI_PROGRAM normal: true From 3c434ed5a82406d25b082e9df48cc1e09505aa0c Mon Sep 17 00:00:00 2001 From: Wu Sheng Date: Tue, 21 Apr 2026 04:14:25 +0800 Subject: [PATCH 10/14] SWIP-12 e2e: simplify logs expected for sim demo scenario variety MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI log: demo scenario fires 4+ distinct error surfaces (js / promise / ajax / pageNotFound) producing a heterogeneous logs list. My prior expected asserted two tag patterns (platform + exception.type) inside the inner `contains` block; CI's go-cmp diff showed rendered-expected as [pattern_rendered_for_A0, nil, nil, nil] which failed the length check against actual [A0, A1, A2, A3, ...]. Minimised expected: assert service / instance / endpoint ids exist, timestamp > 0, content non-empty, and at least one tag with platform=wechat (resp. alipay) — enough to prove LAL dispatched to the right layer from the right platform attribute. Drop exception.type tag assertion; the earlier error_count metric case already proves that channel. --- .../miniprogram/alipay/expected/logs.yml | 34 +++++++++---------- .../miniprogram/wechat/expected/logs.yml | 34 +++++++++---------- 2 files changed, 32 insertions(+), 36 deletions(-) diff --git a/test/e2e-v2/cases/miniprogram/alipay/expected/logs.yml b/test/e2e-v2/cases/miniprogram/alipay/expected/logs.yml index a9d6f42e148c..65bf22830b46 100644 --- a/test/e2e-v2/cases/miniprogram/alipay/expected/logs.yml +++ b/test/e2e-v2/cases/miniprogram/alipay/expected/logs.yml @@ -16,22 +16,20 @@ errorreason: null debuggingtrace: null logs: - {{- contains .logs }} - - servicename: mini-program-sim-alipay - serviceid: {{ notEmpty .serviceid }} - serviceinstancename: "sim" - serviceinstanceid: {{ notEmpty .serviceinstanceid }} - endpointname: {{ notEmpty .endpointname }} - endpointid: {{ notEmpty .endpointid }} - traceid: null - timestamp: {{ gt .timestamp 0 }} - contenttype: TEXT - content: {{ notEmpty .content }} - tags: - {{- contains .tags }} - - key: platform - value: alipay - - key: exception.type - value: {{ notEmpty .value }} - {{- end }} +{{- contains .logs }} +- servicename: mini-program-sim-alipay + serviceid: {{ b64enc "mini-program-sim-alipay" }}.1 + serviceinstancename: sim + serviceinstanceid: {{ b64enc "mini-program-sim-alipay" }}.1_{{ b64enc "sim" }} + endpointname: {{ notEmpty .endpointname }} + endpointid: {{ notEmpty .endpointid }} + traceid: null + timestamp: {{ gt .timestamp 0 }} + contenttype: TEXT + content: {{ notEmpty .content }} + tags: + {{- contains .tags }} + - key: platform + value: alipay {{- end }} +{{- end }} diff --git a/test/e2e-v2/cases/miniprogram/wechat/expected/logs.yml b/test/e2e-v2/cases/miniprogram/wechat/expected/logs.yml index df96d994bf5b..93425304b0a2 100644 --- a/test/e2e-v2/cases/miniprogram/wechat/expected/logs.yml +++ b/test/e2e-v2/cases/miniprogram/wechat/expected/logs.yml @@ -16,22 +16,20 @@ errorreason: null debuggingtrace: null logs: - {{- contains .logs }} - - servicename: mini-program-sim-wechat - serviceid: {{ notEmpty .serviceid }} - serviceinstancename: "sim" - serviceinstanceid: {{ notEmpty .serviceinstanceid }} - endpointname: {{ notEmpty .endpointname }} - endpointid: {{ notEmpty .endpointid }} - traceid: null - timestamp: {{ gt .timestamp 0 }} - contenttype: TEXT - content: {{ notEmpty .content }} - tags: - {{- contains .tags }} - - key: platform - value: wechat - - key: exception.type - value: {{ notEmpty .value }} - {{- end }} +{{- contains .logs }} +- servicename: mini-program-sim-wechat + serviceid: {{ b64enc "mini-program-sim-wechat" }}.1 + serviceinstancename: sim + serviceinstanceid: {{ b64enc "mini-program-sim-wechat" }}.1_{{ b64enc "sim" }} + endpointname: {{ notEmpty .endpointname }} + endpointid: {{ notEmpty .endpointid }} + traceid: null + timestamp: {{ gt .timestamp 0 }} + contenttype: TEXT + content: {{ notEmpty .content }} + tags: + {{- contains .tags }} + - key: platform + value: wechat {{- end }} +{{- end }} From b65bea689fff9fed44e2b1b1b6f7ff9e7661e8bc Mon Sep 17 00:00:00 2001 From: Wu Sheng Date: Tue, 21 Apr 2026 08:04:37 +0800 Subject: [PATCH 11/14] SWIP-12: fix logs.yml quoting + LAL empty-tag guard; consolidate e2e traps in run-e2e skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI caught: WeChat logs case failed with diff showing `- nil` at every position in expected vs real log maps in actual. Root cause: sim-generated log content like 'POST https://api.example.com/cart failed: 500' contains colons; my expected used `content: {{ notEmpty .content }}` unquoted, so after template rendering the YAML became invalid (`failed:` parsed as a nested key) and every log entry marshalled to nil. Fix: wrap in single quotes — `content: '{{ notEmpty .content }}'`. `snakeyaml` preserves `:` inside single-quoted scalars. Also addressing Copilot review comment (the only one still relevant on the post-unwind branch): `if (tag('exception.type') == null)` in `lal/miniprogram.yaml` never fires because LAL's `tag()` returns empty string for missing tags, not null. Non-error logs matching the platform filter would have passed through and emitted `miniprogram_error_count` with an empty `exception_type` label. Guard both: `tag(...) == null || tag(...) == ""`. Skill refactor: - Moved e2e-specific traps (YAML colon quoting, nested contains fragility, `timeUnixNano: "0"`, setup-curl swallowing failure, published-sim-image preference, swctl flag mismatch, image-pull cache miss) from new-monitoring-feature to run-e2e §9 — that's where they belong. new-monitoring-feature §7 now references run-e2e §6–§9 and keeps only the DSL-authoring / layer-mapping traps that are specific to adding a new feature. - Added LAL `tag() == null` trap to new-monitoring-feature §9 (that one is a DSL semantic, not an e2e issue). Local e2e: WeChat 8/8, Alipay 8/8 with both fixes applied. --- .../skills/new-monitoring-feature/SKILL.md | 11 +++++++++- .claude/skills/run-e2e/SKILL.md | 21 +++++++++++++++++++ .../src/main/resources/lal/miniprogram.yaml | 4 +++- .../miniprogram/alipay/expected/logs.yml | 2 +- .../miniprogram/wechat/expected/logs.yml | 2 +- 5 files changed, 36 insertions(+), 4 deletions(-) diff --git a/.claude/skills/new-monitoring-feature/SKILL.md b/.claude/skills/new-monitoring-feature/SKILL.md index 96c0bcd3c8a9..84e65484faa6 100644 --- a/.claude/skills/new-monitoring-feature/SKILL.md +++ b/.claude/skills/new-monitoring-feature/SKILL.md @@ -269,7 +269,7 @@ Feature-specific extras: 3. **OTLP data hygiene**: - `spanId` is exactly 16 hex chars; `traceId` is exactly 32. Protobuf-JSON decodes them as base64; other lengths return HTTP 400. - Setup-step curl loops must fail loudly: `curl -sS -f --retry 30 --retry-delay 5 --retry-connrefused --retry-all-errors --max-time 10 ...` + `set -e`. The pattern `for …; do curl && break || sleep 5; done` silently succeeds when every attempt connection-refused (the final `sleep` returns 0) because OAP takes ~50 s to start in CI. -4. **Verify order matters**. `e2e verify` stops at the first failing case. Fire each verify command manually against a live OAP via `swctl` before committing (stale CLI flags and wrong metric names burn 20 min of CI retries otherwise). +4. **Verify each case by hand before pushing** and use a published simulator image where the upstream SDK has one. See [`run-e2e` skill §6–§9](../run-e2e/SKILL.md) for verify-query triage, expected-file authoring, and the list of expected-file traps (YAML quoting on colons, nested `contains` fragility, `timeUnixNano: "0"`, setup-step curl loops that swallow failure). 5. **Config dump**: when you modify any `application.yml` default (rule list, handler list, ...), also update `test/e2e-v2/cases/storage/expected/config-dump.yml`. --- @@ -294,6 +294,15 @@ Run in order; each has a dedicated skill: | Layer folder named with hyphens | `ui-initialized-templates/my-layer/*.json` on disk but dashboard empty | Folder must be `Layer.name().toLowerCase()` (underscores). `wechat_mini_program/`, not `wechat-mini-program/` | | Extending `CommonAnalysisListener` for a client-side layer | Added componentId → layer mapping "to get topology for free"; then `service_cpm` etc. don't populate anyway | Client-side (exit-only) layers don't need trace-analysis changes. Ship `Layer` + `component-libraries.yml` + MAL + LAL. See §2 header for when extending trace analysis is actually justified | | Dashboards wired to OAL-derived metrics on a client-side layer | Charts stay empty — `service_cpm` / `service_resp_time` / `endpoint_cpm` never populate | Those come from inbound `parseEntry`, which mini-program / browser / iOS don't emit. Use MAL metrics (`meter__*`) instead | +| LAL `#` comment in DSL body | OAP boot fails with `extraneous input 'metrics'` at the `#` line | LAL grammar accepts `//` and `/* */` only. No `#` comments | +| LAL bare def-var value access — `tag 'x': myvar` | OAP boot fails with `Cannot resolve expression '[myvar]'` | `LALValueCodegen` only treats def-vars as valid when there's a method chain (`myvar?.foo`). Inline `sourceAttribute(...)` or `tag(...)` in the three value-access sites instead of storing to a def, or add a no-op chain like `myvar.toString()`. Conditions (`if (myvar == ...)`) go through a different codegen path and DO handle bare def refs | +| MAL rule file with multi-doc YAML (`---` separator) | `MALFilterExecutionTest` fails with `expected a single document`; OAP would also fail at startup via `Rules.loadRules` (calls `Yaml.loadAs`, single-doc) | Split into one file per top-level `expSuffix` / `filter` / `metricPrefix` combo. Reference each file by name in `application.yml` (`lalFiles`, `malFiles`, `enabledOtelMetricsRules`) | +| Histogram bucket bounds in ms with default `.histogram()` call | Percentile values come out 1000× inflated (`P50 = 100000` for a 100 ms request) | MAL's default `defaultHistogramBucketUnit = SECONDS` multiplies `le` by 1000 when converting to stored ms. For SDKs that emit ms bounds directly (mini-program, most in-house JS SDKs), use `.histogram("le", TimeUnit.MILLISECONDS)`. `TimeUnit` is registered in `MALCodegenHelper.ENUM_FQCN` so rule YAML can reference it | +| `.rate()` / `.increase()` on a DELTA counter / histogram `_count` | Compute delta-of-delta — nonsense values | OTLP DELTA means each flush is already the per-flush count. MAL's per-minute aggregator sums directly to requests-per-minute. For CUMULATIVE (Prometheus default), use `.rate("PT1M")` for per-sec or `.increase("PT1M")` for per-min; they are **not** equivalent (differ by 60×) | +| MAL bucket-family name with `_histogram` suffix | Rule compiles, metric stays empty | `PrometheusMetricConverter` emits the bucket family as `` (no suffix), with `_count` and `_sum` as the companion families. Reference the bucket family by its bare name in the rule | +| LAL `tag("key") == null` check on a missing tag | Check never triggers — LAL's `tag()` returns empty string `""` for absent tags, not `null`, so conditional abort is a no-op | Guard on both: `tag("key") == null || tag("key") == ""`. Same for `sourceAttribute("key")` | + +E2E-side authoring traps (log-content YAML quoting, nested `contains` fragility, `timeUnixNano: "0"`, setup-step curl swallowing failure) live in the [`run-e2e` skill §9](../run-e2e/SKILL.md). | Missing layer-root template | Menu item lands on empty "no dashboard" view | `Layer.vue:41-44` requires a template with `isRoot: true`; ship a `-root.json` (precedent: `ios/ios-root.json`) | | New layer not selectable in UI / swctl | `service ly ` returns empty | Add the enum in `Layer.java` with a fresh id; see §0 | | Stale dist in image | `make docker.oap` succeeds but behavior is old | See [`package` skill](../package/SKILL.md); rebuild dist first, then image | diff --git a/.claude/skills/run-e2e/SKILL.md b/.claude/skills/run-e2e/SKILL.md index ecf3b2427548..8cefd815963d 100644 --- a/.claude/skills/run-e2e/SKILL.md +++ b/.claude/skills/run-e2e/SKILL.md @@ -197,6 +197,27 @@ For a new verify case, the workflow is: 3. Write the expected file. If the response is a list, wrap the items in `{{- contains . }} ... {{- end }}` so ordering and extra actual items don't fail the match. 4. Re-run `e2e verify` alone (the containers are still up from the previous run); iterate on the expected file without rebuilding. +### 9. Expected-file authoring traps + +These burn CI cycles and pass locally. Each was learned the hard way. + +**Unquoted `content: {{ notEmpty .content }}` with `:` inside the value.** Sim-generated or real log content routinely includes colons (`POST https://api.example.com/cart failed: 500`, `HTTP/1.1 500: Internal Server Error`). Without quoting, the template renders to invalid YAML (snakeyaml parses `failed:` as a nested key) and the *whole log entry* marshals to `nil`. Symptom: diff shows `- nil` at *every* position in the expected logs list vs real maps in actual. Fix: wrap in single quotes — `content: '{{ notEmpty .content }}'`. Single-quoted YAML preserves `:` in the scalar; only fails on embedded `'`. Double quotes also work unless the content has `"`. + +**Nested `contains` with multiple per-element pattern assertions against a varied stream.** The template renders the block body once per actual element; when the outer block body has *multiple* inner `contains` patterns asserting specific tag key/value pairs, and only *some* actuals satisfy all the inner patterns, `go-cmp` with `contains` can end up comparing `[rendered_for_A0, nil, nil, ...]` vs `[A0, A1, A2, ...]` and fail despite `contains` being permissive on extras. Specifically: outer `contains .logs` with a single log pattern + inner `contains .tags` asserting two distinct key/value pairs. On a simulator emitting heterogeneous errors (js + promise + ajax + pageNotFound), only a subset satisfy the inner assertion. Passes locally with 1–2 logs, fails in CI with 6+. +- Keep the outer `contains` body lenient: field-shape checks (`notEmpty`, `gt`), one discriminator tag that *every* element in the stream carries. +- Cover per-category assertions via separate labeled-metric verify cases, not inside the nested template. +- Rule of thumb: "at least one log exists with the right layer routing" inside the logs expected; per-category coverage via `meter_*_count{label=X}` verify cases. + +**Hand-crafted OTLP curl payloads drift from real SDK output.** When the upstream SDK ships a published simulator image (mini-program-monitor's `sim-wechat` / `sim-alipay`, browser-client-js sim, etc.), prefer driving the e2e with that image in `MODE=timed` with a bounded `DURATION_MS` over hand-rolling the OTLP JSON. Hand-crafted payloads miss real-world shape issues: delta-vs-cumulative temporality, label-cardinality surprises, stacktrace formatting variance, attribute key names that changed between SDK versions. Pin to a released tag (`v0.4.0`), not `:latest` or HEAD SHA — reproducibility. + +**`timeUnixNano: "0"` in an OTLP metric datapoint.** The receiver propagates this into MAL's bucket computation and the metric lands in the 1970 time bucket — `swctl metrics exec` over the "last N minutes" window won't find it. Either use `$(date +%s)000000000` at setup time or omit the field if the receiver accepts "now" as default. + +**Setup-step curl loop with `|| sleep` pattern.** The shell line `for ... do curl && break || sleep 5; done` exits 0 when every attempt connection-refused because the final `sleep 5` returns 0. OAP takes ~50 s to start in CI, so all attempts fail before OAP is ready, and the setup step silently succeeds with zero traffic ingested. Fix: `curl -sS -f --retry 30 --retry-delay 5 --retry-connrefused --retry-all-errors --max-time 10 ...` + `set -e` at step top. + +**`swctl` flag rejected.** If a verify case uses a flag the pinned `swctl` commit doesn't support (`service ls --layer` vs `service ly`), the whole case fails 20× before CI gives up. Fire each verify query by hand once before pushing (step 6 above). + +**Published image cache miss in CI.** `docker compose pull` sometimes hits rate limits or unreachable registries; the test spins until timeout with "dependency failed to start". Look at the CI log for `Error response from daemon: pull access denied` or `manifest unknown`. If you see that, pin a different image tag that's definitely published (check `docker manifest inspect ` locally), not a floating one. + ## Common test cases | Shorthand | Path | diff --git a/oap-server/server-starter/src/main/resources/lal/miniprogram.yaml b/oap-server/server-starter/src/main/resources/lal/miniprogram.yaml index 6cf1cb987a19..7e17095d4189 100644 --- a/oap-server/server-starter/src/main/resources/lal/miniprogram.yaml +++ b/oap-server/server-starter/src/main/resources/lal/miniprogram.yaml @@ -29,7 +29,9 @@ rules: && sourceAttribute("miniprogram.platform") != "alipay") { abort {} } - if (tag("exception.type") == null) { + // LAL tag() returns "" (not null) for a missing tag, so guard on + // both to short-circuit non-error logs cleanly. + if (tag("exception.type") == null || tag("exception.type") == "") { abort {} } diff --git a/test/e2e-v2/cases/miniprogram/alipay/expected/logs.yml b/test/e2e-v2/cases/miniprogram/alipay/expected/logs.yml index 65bf22830b46..4eb3d0357ad2 100644 --- a/test/e2e-v2/cases/miniprogram/alipay/expected/logs.yml +++ b/test/e2e-v2/cases/miniprogram/alipay/expected/logs.yml @@ -26,7 +26,7 @@ logs: traceid: null timestamp: {{ gt .timestamp 0 }} contenttype: TEXT - content: {{ notEmpty .content }} + content: '{{ notEmpty .content }}' tags: {{- contains .tags }} - key: platform diff --git a/test/e2e-v2/cases/miniprogram/wechat/expected/logs.yml b/test/e2e-v2/cases/miniprogram/wechat/expected/logs.yml index 93425304b0a2..94464ed39a75 100644 --- a/test/e2e-v2/cases/miniprogram/wechat/expected/logs.yml +++ b/test/e2e-v2/cases/miniprogram/wechat/expected/logs.yml @@ -26,7 +26,7 @@ logs: traceid: null timestamp: {{ gt .timestamp 0 }} contenttype: TEXT - content: {{ notEmpty .content }} + content: '{{ notEmpty .content }}' tags: {{- contains .tags }} - key: platform From 3f9701ea4c0c637e0c9d4f159d684ff3b6745bd8 Mon Sep 17 00:00:00 2001 From: Wu Sheng Date: Tue, 21 Apr 2026 10:23:12 +0800 Subject: [PATCH 12/14] SWIP-12 MAL: drop expSuffix in mixed-scope files, chain .service()/.endpoint() explicitly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Endpoint rules produced metric rows but no endpoint_traffic entries: 'swctl endpoint ls' returned []; the Endpoint sub-tab in the WeChat / Alipay service dashboards stayed empty. 'swctl metrics exec' could still hit the row if you guessed the endpoint name, which is misleading. Root cause: MetricConvert.formatExp line 132 wraps each rule expression as '(exp).'. With 'expSuffix: service([...], Layer.X)' at the top of the file and '.endpoint([...], ..., Layer.X)' in the endpoint-* rules, the final compiled expression becomes '(...avg(...).endpoint(...)).service([...], Layer.X)' — the trailing .service(...) overrides the scope back to Service, and no EndpointTraffic source ever fires (Analyzer.java:358 skips when getEndpointName() is empty for a Service entity). Fix: leave expSuffix empty and chain .service(...) / .endpoint(...) explicitly on each rule in the mixed-scope files (the APISIX pattern). The instance-only files (wechat-mini-program-instance.yaml, alipay-mini-program-instance.yaml) keep 'expSuffix: instance(...)' because every rule there is same-scope. Verified locally: endpoint ls now returns pages/detail/index, pages/profile/index, pages/index/index, pages/cart/index for mini-program-sim-wechat. new-monitoring-feature §9 trap table: add this case. --- .../skills/new-monitoring-feature/SKILL.md | 1 + .../miniprogram/alipay-mini-program.yaml | 30 ++++++++-------- .../miniprogram/wechat-mini-program.yaml | 35 +++++++++++-------- 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/.claude/skills/new-monitoring-feature/SKILL.md b/.claude/skills/new-monitoring-feature/SKILL.md index 84e65484faa6..cb0a12d2a2d2 100644 --- a/.claude/skills/new-monitoring-feature/SKILL.md +++ b/.claude/skills/new-monitoring-feature/SKILL.md @@ -301,6 +301,7 @@ Run in order; each has a dedicated skill: | `.rate()` / `.increase()` on a DELTA counter / histogram `_count` | Compute delta-of-delta — nonsense values | OTLP DELTA means each flush is already the per-flush count. MAL's per-minute aggregator sums directly to requests-per-minute. For CUMULATIVE (Prometheus default), use `.rate("PT1M")` for per-sec or `.increase("PT1M")` for per-min; they are **not** equivalent (differ by 60×) | | MAL bucket-family name with `_histogram` suffix | Rule compiles, metric stays empty | `PrometheusMetricConverter` emits the bucket family as `` (no suffix), with `_count` and `_sum` as the companion families. Reference the bucket family by its bare name in the rule | | LAL `tag("key") == null` check on a missing tag | Check never triggers — LAL's `tag()` returns empty string `""` for absent tags, not `null`, so conditional abort is a no-op | Guard on both: `tag("key") == null || tag("key") == ""`. Same for `sourceAttribute("key")` | +| MAL rule file with `expSuffix: service(...)` mixing service-scoped and endpoint-scoped rules | Endpoint rules' metrics appear under the service entity (no endpoint_traffic rows created); `swctl endpoint ls` returns empty. `MetricConvert.formatExp:132` appends `(exp).expSuffix` to every rule, so `.endpoint(...)` in the rule's expression is followed by `.service(...)` and the service scope wins. | Follow the APISIX pattern — leave `expSuffix:` empty and explicitly chain `.service(...)` / `.endpoint(...)` on each rule. If every rule in a file is same-scope (e.g. all instance-scoped in `*-instance.yaml`), `expSuffix` is still fine | E2E-side authoring traps (log-content YAML quoting, nested `contains` fragility, `timeUnixNano: "0"`, setup-step curl swallowing failure) live in the [`run-e2e` skill §9](../run-e2e/SKILL.md). | Missing layer-root template | Menu item lands on empty "no dashboard" view | `Layer.vue:41-44` requires a template with `isRoot: true`; ship a `-root.json` (precedent: `ios/ios-root.json`) | diff --git a/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/alipay-mini-program.yaml b/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/alipay-mini-program.yaml index c56957f2e5c1..37baa022304f 100644 --- a/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/alipay-mini-program.yaml +++ b/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/alipay-mini-program.yaml @@ -13,33 +13,33 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Alipay Mini Program service-level metrics from the mini-program-monitor SDK. -# Alipay's base library does not expose the PerformanceObserver events WeChat -# does (route/script/packageLoad/first_paint), so those metrics are absent here -# — the SDK falls back to lifecycle hooks for app_launch / first_render, which -# are approximations rather than renderer-authoritative timings. -# Aggregated across all app versions (instances). +# Alipay Mini Program service-level + endpoint-level metrics. Alipay's +# base library doesn't expose PerformanceObserver entries (route / script / +# packageLoad / first_paint), so only app_launch / first_render are +# emitted (from lifecycle hooks — approximations, not renderer timings). +# +# expSuffix is empty and each rule explicitly chains .service(...) or +# .endpoint(...) — a non-empty expSuffix would append .service(...) and +# override the endpoint scope on endpoint-* rules (APISIX pattern). -expSuffix: service(['service_name'], Layer.ALIPAY_MINI_PROGRAM) +expSuffix: metricPrefix: meter_alipay_mp filter: "{ tags -> tags.miniprogram_platform == 'alipay' }" metricsRules: - # Perf (service-wide averages) + # --- Service-scoped --- - name: app_launch_duration - exp: miniprogram_app_launch_duration.avg(['service_name']) + exp: miniprogram_app_launch_duration.avg(['service_name']).service(['service_name'], Layer.ALIPAY_MINI_PROGRAM) - name: first_render_duration - exp: miniprogram_first_render_duration.avg(['service_name']) + exp: miniprogram_first_render_duration.avg(['service_name']).service(['service_name'], Layer.ALIPAY_MINI_PROGRAM) - # Request load (calls per minute) — delta counter. - name: request_cpm - exp: miniprogram_request_duration_count.sum(['service_name']) + exp: miniprogram_request_duration_count.sum(['service_name']).service(['service_name'], Layer.ALIPAY_MINI_PROGRAM) - # Request latency percentile — bucket `le` labels already in ms. - name: request_duration_percentile - exp: miniprogram_request_duration.sum(['service_name', 'le']).histogram("le", TimeUnit.MILLISECONDS).histogram_percentile([50,75,90,95,99]) + exp: miniprogram_request_duration.sum(['service_name', 'le']).histogram("le", TimeUnit.MILLISECONDS).histogram_percentile([50,75,90,95,99]).service(['service_name'], Layer.ALIPAY_MINI_PROGRAM) - # Per-page (endpoint-scoped) variants — chained .endpoint(...) overrides the expSuffix. + # --- Per-page (endpoint-scoped) variants --- - name: endpoint_app_launch_duration exp: miniprogram_app_launch_duration.avg(['service_name', 'miniprogram_page_path']).endpoint(['service_name'], ['miniprogram_page_path'], Layer.ALIPAY_MINI_PROGRAM) - name: endpoint_first_render_duration diff --git a/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/wechat-mini-program.yaml b/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/wechat-mini-program.yaml index 0b6ef27b1e45..aee204a49447 100644 --- a/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/wechat-mini-program.yaml +++ b/oap-server/server-starter/src/main/resources/otel-rules/miniprogram/wechat-mini-program.yaml @@ -13,41 +13,46 @@ # See the License for the specific language governing permissions and # limitations under the License. -# WeChat Mini Program service-level metrics from the mini-program-monitor SDK. -# Filter gates the rule to traffic carrying miniprogram.platform == "wechat" so -# the WeChat and Alipay rules do not double-count when both runtimes report to -# the same OAP. Metrics named `meter_wechat_mp_*` power the WeChat dashboards. -# Aggregated across all app versions (instances). +# WeChat Mini Program service-level + endpoint-level metrics from the +# mini-program-monitor SDK. Filter gates the rule to traffic carrying +# miniprogram.platform == "wechat" so the WeChat and Alipay rules do not +# double-count. +# +# expSuffix is intentionally empty — each rule explicitly chains +# .service(...) or .endpoint(...) so service-scoped and endpoint-scoped +# rules coexist in one file (APISIX pattern). A non-empty expSuffix +# would append .service(...) at the tail and override the endpoint +# scope on the endpoint-* rules. -expSuffix: service(['service_name'], Layer.WECHAT_MINI_PROGRAM) +expSuffix: metricPrefix: meter_wechat_mp filter: "{ tags -> tags.miniprogram_platform == 'wechat' }" metricsRules: - # Perf (service-wide averages) + # --- Service-scoped (fleet-aggregated across versions) --- - name: app_launch_duration - exp: miniprogram_app_launch_duration.avg(['service_name']) + exp: miniprogram_app_launch_duration.avg(['service_name']).service(['service_name'], Layer.WECHAT_MINI_PROGRAM) - name: first_render_duration - exp: miniprogram_first_render_duration.avg(['service_name']) + exp: miniprogram_first_render_duration.avg(['service_name']).service(['service_name'], Layer.WECHAT_MINI_PROGRAM) # first_paint.time is an epoch-ms timestamp (not a duration) — not aggregated. - name: route_duration - exp: miniprogram_route_duration.avg(['service_name']) + exp: miniprogram_route_duration.avg(['service_name']).service(['service_name'], Layer.WECHAT_MINI_PROGRAM) - name: script_duration - exp: miniprogram_script_duration.avg(['service_name']) + exp: miniprogram_script_duration.avg(['service_name']).service(['service_name'], Layer.WECHAT_MINI_PROGRAM) - name: package_load_duration - exp: miniprogram_package_load_duration.avg(['service_name']) + exp: miniprogram_package_load_duration.avg(['service_name']).service(['service_name'], Layer.WECHAT_MINI_PROGRAM) # Request load (calls per minute) — delta counter, MAL's 1-minute bucketing # sums per-flush counts into requests-per-minute directly. - name: request_cpm - exp: miniprogram_request_duration_count.sum(['service_name']) + exp: miniprogram_request_duration_count.sum(['service_name']).service(['service_name'], Layer.WECHAT_MINI_PROGRAM) # Request latency percentile — bucket `le` labels already in ms, MILLISECONDS # unit stops MAL's default SECONDS → MS rescale (× 1000). - name: request_duration_percentile - exp: miniprogram_request_duration.sum(['service_name', 'le']).histogram("le", TimeUnit.MILLISECONDS).histogram_percentile([50,75,90,95,99]) + exp: miniprogram_request_duration.sum(['service_name', 'le']).histogram("le", TimeUnit.MILLISECONDS).histogram_percentile([50,75,90,95,99]).service(['service_name'], Layer.WECHAT_MINI_PROGRAM) - # Per-page (endpoint-scoped) variants — chained .endpoint(...) overrides the expSuffix. + # --- Per-page (endpoint-scoped) variants --- - name: endpoint_app_launch_duration exp: miniprogram_app_launch_duration.avg(['service_name', 'miniprogram_page_path']).endpoint(['service_name'], ['miniprogram_page_path'], Layer.WECHAT_MINI_PROGRAM) - name: endpoint_first_render_duration From 5313df7598caaf16d26814307eddda1ca60770e7 Mon Sep 17 00:00:00 2001 From: Wu Sheng Date: Tue, 21 Apr 2026 12:34:47 +0800 Subject: [PATCH 13/14] SWIP-12 dashboards: bump root text banner h=2 -> h=3 for multi-line description The banner description spans ~260 chars (SDK name + signal list) and wraps to 2 lines at normal browser widths; h=2 clipped the second line. Both WeChat and Alipay root dashboards bumped to h=3, ServiceList widget shifted from y=2 to y=3 to preserve the 48-row total. --- .../alipay_mini_program/alipay_mini_program-root.json | 6 +++--- .../wechat_mini_program/wechat_mini_program-root.json | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-root.json b/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-root.json index d9946679e4c6..7382e4eb28e4 100644 --- a/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-root.json +++ b/oap-server/server-starter/src/main/resources/ui-initialized-templates/alipay_mini_program/alipay_mini_program-root.json @@ -5,9 +5,9 @@ "children": [ { "x": 0, - "y": 2, + "y": 3, "w": 24, - "h": 46, + "h": 45, "i": "0", "type": "Widget", "graph": { @@ -53,7 +53,7 @@ "x": 0, "y": 0, "w": 24, - "h": 2, + "h": 3, "i": "100", "type": "Text", "graph": { diff --git a/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-root.json b/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-root.json index ecffb7874641..e47e7039eaf1 100644 --- a/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-root.json +++ b/oap-server/server-starter/src/main/resources/ui-initialized-templates/wechat_mini_program/wechat_mini_program-root.json @@ -5,9 +5,9 @@ "children": [ { "x": 0, - "y": 2, + "y": 3, "w": 24, - "h": 46, + "h": 45, "i": "0", "type": "Widget", "graph": { @@ -53,7 +53,7 @@ "x": 0, "y": 0, "w": 24, - "h": 2, + "h": 3, "i": "100", "type": "Text", "graph": { From 4d5fded8f2a03cdde9fce9ca12b264343919da69 Mon Sep 17 00:00:00 2001 From: Wu Sheng Date: Tue, 21 Apr 2026 13:28:29 +0800 Subject: [PATCH 14/14] SWIP-12 e2e: bump sim images to v0.4.1; cover route/script/package_load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SkyAPM mini-program-monitor v0.4.1 (commit e795c188) adds route, script, loadPackage PerformanceObserver entries to the WeChat sim's firePerfEntries(). The SDK's OAP-side contract (metric names, labels, histogram temporality / bounds) didn't change — only the sim is more faithful to real WeChat runtime now. Bump both WeChat and Alipay docker-compose image tags to v0.4.1. Add three verify cases on the WeChat e2e to lock in the newly-emitted perf gauges: - meter_wechat_mp_route_duration - meter_wechat_mp_script_duration - meter_wechat_mp_package_load_duration Alipay's PerformanceObserver surface is still absent by platform so its case stays at the app_launch / first_render subset. Local run: WeChat 11/11, Alipay 8/8. --- .../cases/miniprogram/alipay/docker-compose.yml | 2 +- test/e2e-v2/cases/miniprogram/alipay/e2e.yaml | 2 +- .../cases/miniprogram/wechat/docker-compose.yml | 2 +- test/e2e-v2/cases/miniprogram/wechat/e2e.yaml | 11 ++++++++++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/test/e2e-v2/cases/miniprogram/alipay/docker-compose.yml b/test/e2e-v2/cases/miniprogram/alipay/docker-compose.yml index a84ca258593a..8ebcc64165f9 100644 --- a/test/e2e-v2/cases/miniprogram/alipay/docker-compose.yml +++ b/test/e2e-v2/cases/miniprogram/alipay/docker-compose.yml @@ -38,7 +38,7 @@ services: # Upstream SkyAPM mini-program-monitor simulator (shipped from # https://github.com/SkyAPM/mini-program-monitor). sim-alipay: - image: ghcr.io/skyapm/mini-program-monitor/sim-alipay:v0.4.0 + image: ghcr.io/skyapm/mini-program-monitor/sim-alipay:v0.4.1 depends_on: oap: condition: service_healthy diff --git a/test/e2e-v2/cases/miniprogram/alipay/e2e.yaml b/test/e2e-v2/cases/miniprogram/alipay/e2e.yaml index 649256919a54..afc5ccac6ebc 100644 --- a/test/e2e-v2/cases/miniprogram/alipay/e2e.yaml +++ b/test/e2e-v2/cases/miniprogram/alipay/e2e.yaml @@ -14,7 +14,7 @@ # limitations under the License. # Alipay Mini Program e2e test — drives the published -# ghcr.io/skyapm/mini-program-monitor/sim-alipay:v0.4.0 image in MODE=timed +# ghcr.io/skyapm/mini-program-monitor/sim-alipay:v0.4.1 image in MODE=timed # against OAP. Alipay emits a strict subset of metrics relative to WeChat # (no route / script / package_load / first_paint) so this case verifies # only the metrics Alipay actually produces. diff --git a/test/e2e-v2/cases/miniprogram/wechat/docker-compose.yml b/test/e2e-v2/cases/miniprogram/wechat/docker-compose.yml index 2ba9b634b619..98012d252607 100644 --- a/test/e2e-v2/cases/miniprogram/wechat/docker-compose.yml +++ b/test/e2e-v2/cases/miniprogram/wechat/docker-compose.yml @@ -39,7 +39,7 @@ services: # https://github.com/SkyAPM/mini-program-monitor). Drives realistic # OTLP logs + metrics + SW native trace segments against OAP. sim-wechat: - image: ghcr.io/skyapm/mini-program-monitor/sim-wechat:v0.4.0 + image: ghcr.io/skyapm/mini-program-monitor/sim-wechat:v0.4.1 depends_on: oap: condition: service_healthy diff --git a/test/e2e-v2/cases/miniprogram/wechat/e2e.yaml b/test/e2e-v2/cases/miniprogram/wechat/e2e.yaml index f832f28da1ab..559b0d4da685 100644 --- a/test/e2e-v2/cases/miniprogram/wechat/e2e.yaml +++ b/test/e2e-v2/cases/miniprogram/wechat/e2e.yaml @@ -14,7 +14,7 @@ # limitations under the License. # WeChat Mini Program e2e test — drives the published -# ghcr.io/skyapm/mini-program-monitor/sim-wechat:v0.4.0 image in MODE=timed +# ghcr.io/skyapm/mini-program-monitor/sim-wechat:v0.4.1 image in MODE=timed # against OAP wired with the miniprogram MAL / LAL / log-MAL rules. The sim # emits realistic OTLP logs + metrics (perf gauges + request-duration # histogram + errors) plus SW native trace segments (/v3/segments) with @@ -47,6 +47,15 @@ verify: expected: expected/metrics-has-value.yml - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_wechat_mp_first_render_duration --service-name=mini-program-sim-wechat expected: expected/metrics-has-value.yml + # WeChat-only perf gauges (SDK ≥ v0.4.1 fires PerformanceObserver + # entries for route / script / loadPackage; emitted by sim-wechat as + # well from v0.4.1 onward). + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_wechat_mp_route_duration --service-name=mini-program-sim-wechat + expected: expected/metrics-has-value.yml + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_wechat_mp_script_duration --service-name=mini-program-sim-wechat + expected: expected/metrics-has-value.yml + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_wechat_mp_package_load_duration --service-name=mini-program-sim-wechat + expected: expected/metrics-has-value.yml # Request-count derived from the histogram _count family (delta counter # aggregated per minute). - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression=meter_wechat_mp_request_cpm --service-name=mini-program-sim-wechat