You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by "wu-sheng (via GitHub)" <gi...@apache.org> on 2026/04/18 00:00:38 UTC
[PR] Add iOS/iPadOS app monitoring via OpenTelemetry Swift SDK (SWIP-11) [skywalking]
wu-sheng opened a new pull request, #13828:
URL: https://github.com/apache/skywalking/pull/13828
### Add iOS/iPadOS app monitoring via OpenTelemetry Swift SDK (SWIP-11)
- [x] If this is non-trivial feature, paste the links/URLs to the design doc. [SWIP-11](docs/en/swip/SWIP-11.md).
- [x] Update the documentation to include this new feature. Setup guide at [`docs/en/setup/backend/backend-ios-monitoring.md`](docs/en/setup/backend/backend-ios-monitoring.md); SWIP-11 design doc revised.
- [x] Tests(including UT, IT, E2E) are added to verify the new feature. New `oap-cases/ios-metrickit` LAL unit tests and `test/e2e-v2/cases/ios/` e2e covering HTTP OAL, MetricKit MAL, and LAL log persistence.
- [x] If it's UI related, attach the screenshots below.
Introduces the `IOS` layer with two `SpanListener`s:
- **`IOSHTTPSpanListener`** — outbound HTTP (NSURLSession) client metrics via OAL (`service_cpm`, `endpoint_cpm`, etc.). Supports all three OTel Swift URLSessionInstrumentation semconv modes (`.old` / `.stable` / `.httpDup`) via stable-then-legacy attribute fallback (`server.address` → `net.peer.name`, `http.request.method` → `http.method`, `http.response.status_code` → `http.status_code`).
- **`IOSMetricKitSpanListener`** — daily MetricKit stats (app launch time, hang time, CPU/GPU/memory, network transfer, exit counts split by **foreground/background**, OOM kills). Histogram percentiles use a finite 30 s overflow ceiling and emit **per-bucket counts** with `defaultHistogramBucketUnit(MILLISECONDS)` so MAL's percentile math produces correct values.
Ships LAL rule for MetricKit crash/hang diagnostic logs, MAL rules for service- and instance-level aggregation, iOS Root/Service/Instance/Endpoint dashboards, a Mobile menu entry, user-setup docs, SWIP-11 design doc, and an e2e test registered in the CI workflow.
**Bug fixes wrapped in:**
- LAL `layer: auto` mode dropped logs after the extractor set the layer — codegen now propagates `layer "..."` assignments to `LogMetadata.layer` so `FilterSpec.doSink()` sees the script-decided value.
**UI submodule bump** (`apache/skywalking-booster-ui@a6be0e0`):
- Mobile menu icon + i18n labels (en/es/zh) for the iOS layer.
- Fix metric label rendering in multi-expression dashboard widgets.
### Screenshots
<!-- Drag the 4 screenshots from ~/Downloads/FireShot/ here -->
- [ ] If this pull request closes/resolves/fixes an existing issue, replace the issue number.
- [x] Update the [`CHANGES` log](https://github.com/apache/skywalking/blob/master/docs/en/changes/changes.md).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@skywalking.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add iOS/iPadOS app monitoring via OpenTelemetry Swift SDK (SWIP-11) [skywalking]
Posted by "wu-sheng (via GitHub)" <gi...@apache.org>.
wu-sheng commented on code in PR #13828:
URL: https://github.com/apache/skywalking/pull/13828#discussion_r3105114091
##########
oap-server/analyzer/ios-analyzer/src/main/java/org/apache/skywalking/oap/analyzer/ios/listener/IOSHTTPSpanListener.java:
##########
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.analyzer.ios.listener;
+
+import java.util.Collections;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+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.NamingControl;
+import org.apache.skywalking.oap.server.core.source.Endpoint;
+import org.apache.skywalking.oap.server.core.source.RequestType;
+import org.apache.skywalking.oap.server.core.source.Service;
+import org.apache.skywalking.oap.server.core.source.ServiceInstance;
+import org.apache.skywalking.oap.server.core.source.ServiceMeta;
+import org.apache.skywalking.oap.server.core.source.SourceReceiver;
+import org.apache.skywalking.oap.server.core.trace.OTLPSpanReader;
+import org.apache.skywalking.oap.server.core.trace.SpanListener;
+import org.apache.skywalking.oap.server.core.trace.SpanListenerResult;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+
+/**
+ * Extracts per-request HTTP traffic metrics from iOS URLSession spans and emits
+ * OAL sources ({@link Service}, {@link ServiceInstance}, {@link Endpoint}).
+ *
+ * <p>Detection: resource attribute {@code os.name} is {@code iOS} or {@code iPadOS},
+ * the instrumentation scope is {@code NSURLSession}, the span kind is
+ * {@code CLIENT}, and the span has {@code http.method} plus
+ * {@code net.peer.name} (domain name).
+ *
+ * <p>These keys match the current default output of the OpenTelemetry Swift
+ * {@code URLSessionInstrumentation}. Its default HTTP semantic convention is
+ * {@code .old}, which emits {@code net.peer.name} and {@code http.status_code}
+ * rather than the stable {@code server.address} and
+ * {@code http.response.status_code} keys.
+ *
+ * <p>Endpoint name uses the domain only ({@code net.peer.name}) to keep cardinality
+ * manageable on the client side.
+ *
+ * <p>The existing {@code core.oal} rules automatically produce traffic metrics
+ * ({@code service_cpm}, {@code service_resp_time}, {@code endpoint_cpm}, etc.)
+ * from these sources, all under {@link Layer#IOS}.
+ */
+@Slf4j
+public class IOSHTTPSpanListener implements SpanListener {
+ private SourceReceiver sourceReceiver;
+ private NamingControl namingControl;
+
+ @Override
+ public String[] requiredModules() {
+ return new String[] {CoreModule.NAME};
+ }
+
+ @Override
+ public void init(final ModuleManager moduleManager) {
+ sourceReceiver = moduleManager.find(CoreModule.NAME)
+ .provider()
+ .getService(SourceReceiver.class);
+ namingControl = moduleManager.find(CoreModule.NAME)
+ .provider()
+ .getService(NamingControl.class);
+ }
+
+ @Override
+ public SpanListenerResult onOTLPSpan(final OTLPSpanReader span,
+ final Map<String, String> resourceAttributes,
+ final String scopeName,
+ final String scopeVersion) {
+ final String osName = resourceAttributes.get("os.name");
+ if (!"iOS".equals(osName) && !"iPadOS".equals(osName)) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ // OpenTelemetry Swift URLSessionInstrumentation supports three HTTP
+ // semantic-convention modes: `.old` (legacy: net.peer.name / http.method /
+ // http.status_code), `.stable` (server.address / http.request.method /
+ // http.response.status_code), and `.httpDup` (both). We prefer the stable
+ // keys and fall back to legacy so all three modes work.
+ if (!"NSURLSession".equals(scopeName) || !"SPAN_KIND_CLIENT".equals(span.spanKind())) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ final String httpMethod = attr(span, "http.request.method", "http.method");
+ if (httpMethod == null || httpMethod.isEmpty()) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ final String domain = attr(span, "server.address", "net.peer.name");
+ if (domain == null || domain.isEmpty()) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ final String rawServiceName = resourceAttributes.getOrDefault("service.name", "");
+ if (rawServiceName.isEmpty()) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ final String serviceName = namingControl.formatServiceName(rawServiceName);
+ final String instanceName = resourceAttributes.getOrDefault("service.version", "");
Review Comment:
Addressed in b192a3944d predecessor commit a6660573c6: pipe `service.version` through `namingControl.formatInstanceName()` before using it as the instance name, so length/format constraints are respected for both `Service` and `ServiceInstance` sources.
##########
oap-server/analyzer/ios-analyzer/src/main/java/org/apache/skywalking/oap/analyzer/ios/listener/IOSMetricKitSpanListener.java:
##########
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.analyzer.ios.listener;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.oap.meter.analyzer.v2.dsl.Sample;
+import org.apache.skywalking.oap.meter.analyzer.v2.dsl.SampleFamily;
+import org.apache.skywalking.oap.meter.analyzer.v2.dsl.SampleFamilyBuilder;
+import org.apache.skywalking.oap.server.core.trace.OTLPSpanReader;
+import org.apache.skywalking.oap.server.core.trace.SpanListener;
+import org.apache.skywalking.oap.server.core.trace.SpanListenerResult;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+import org.apache.skywalking.oap.server.receiver.otel.OtelMetricReceiverModule;
+import org.apache.skywalking.oap.server.receiver.otel.otlp.OpenTelemetryMetricRequestProcessor;
+
+/**
+ * Extracts Apple MetricKit daily statistics from OTLP spans and converts them
+ * to MAL metrics. MetricKit spans are NOT stored as traces (24-hour duration).
+ *
+ * <p>Detection: {@code scopeName == "MetricKit"} AND {@code spanName == "MXMetricPayload"}.
+ * These values come from the current OpenTelemetry Swift MetricKit
+ * instrumentation, which builds the tracer/logger with scope name
+ * {@code "MetricKit"} and reports the main aggregated metrics span as
+ * {@code "MXMetricPayload"}.
+ *
+ * <p>The listener converts span attributes into labeled {@link SampleFamily} samples
+ * and pushes them into the shared MAL pipeline via
+ * {@link OpenTelemetryMetricRequestProcessor#toMeter}.
+ * MAL rules are loaded by the otel-receiver module via {@code enabledOtelMetricsRules}.
+ */
+@Slf4j
+public class IOSMetricKitSpanListener implements SpanListener {
+ /**
+ * Histogram bucket boundaries for app launch time (ms).
+ * Covers common ranges from fast launch to slow cold start. The last bucket (30 s)
+ * acts as a finite overflow sentinel: observations above it are catastrophic and
+ * should remain visible on the chart rather than landing in a {@code +Inf} bucket
+ * (which would surface as {@code Long.MAX_VALUE} in percentile queries).
+ */
+ private static final double[] APP_LAUNCH_BUCKETS =
+ {400, 600, 800, 1000, 1500, 2000, 3000, 5000, 10000, 30000};
+
+ /**
+ * Histogram bucket boundaries for hang time (ms).
+ * Covers common ranges from minor UI jank to severe hangs. MetricKit hard-caps
+ * hang observation near 30 s; that value is used as the finite overflow sentinel
+ * (see {@link #APP_LAUNCH_BUCKETS} for rationale).
+ */
+ private static final double[] HANG_TIME_BUCKETS =
+ {250, 400, 600, 1000, 1500, 2000, 3000, 5000, 10000, 30000};
+
+ private OpenTelemetryMetricRequestProcessor metricProcessor;
+
+ @Override
+ public String[] requiredModules() {
+ return new String[] {OtelMetricReceiverModule.NAME};
+ }
+
+ @Override
+ public void init(final ModuleManager moduleManager) {
+ metricProcessor = moduleManager.find(OtelMetricReceiverModule.NAME)
+ .provider()
+ .getService(OpenTelemetryMetricRequestProcessor.class);
+ }
Review Comment:
Addressed in a6660573c6: added `CoreModule.NAME` to `requiredModules()`, wired `NamingControl` in `init()`, and formatted the `service_name` / `service_instance_id` labels via `namingControl.formatServiceName()` / `formatInstanceName()` before emitting MAL samples.
##########
oap-server/analyzer/ios-analyzer/src/main/java/org/apache/skywalking/oap/analyzer/ios/listener/IOSMetricKitSpanListener.java:
##########
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.analyzer.ios.listener;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.oap.meter.analyzer.v2.dsl.Sample;
+import org.apache.skywalking.oap.meter.analyzer.v2.dsl.SampleFamily;
+import org.apache.skywalking.oap.meter.analyzer.v2.dsl.SampleFamilyBuilder;
+import org.apache.skywalking.oap.server.core.trace.OTLPSpanReader;
+import org.apache.skywalking.oap.server.core.trace.SpanListener;
+import org.apache.skywalking.oap.server.core.trace.SpanListenerResult;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+import org.apache.skywalking.oap.server.receiver.otel.OtelMetricReceiverModule;
+import org.apache.skywalking.oap.server.receiver.otel.otlp.OpenTelemetryMetricRequestProcessor;
+
+/**
+ * Extracts Apple MetricKit daily statistics from OTLP spans and converts them
+ * to MAL metrics. MetricKit spans are NOT stored as traces (24-hour duration).
+ *
+ * <p>Detection: {@code scopeName == "MetricKit"} AND {@code spanName == "MXMetricPayload"}.
+ * These values come from the current OpenTelemetry Swift MetricKit
+ * instrumentation, which builds the tracer/logger with scope name
+ * {@code "MetricKit"} and reports the main aggregated metrics span as
+ * {@code "MXMetricPayload"}.
+ *
+ * <p>The listener converts span attributes into labeled {@link SampleFamily} samples
+ * and pushes them into the shared MAL pipeline via
+ * {@link OpenTelemetryMetricRequestProcessor#toMeter}.
+ * MAL rules are loaded by the otel-receiver module via {@code enabledOtelMetricsRules}.
+ */
+@Slf4j
+public class IOSMetricKitSpanListener implements SpanListener {
+ /**
+ * Histogram bucket boundaries for app launch time (ms).
+ * Covers common ranges from fast launch to slow cold start. The last bucket (30 s)
+ * acts as a finite overflow sentinel: observations above it are catastrophic and
+ * should remain visible on the chart rather than landing in a {@code +Inf} bucket
+ * (which would surface as {@code Long.MAX_VALUE} in percentile queries).
+ */
+ private static final double[] APP_LAUNCH_BUCKETS =
+ {400, 600, 800, 1000, 1500, 2000, 3000, 5000, 10000, 30000};
+
+ /**
+ * Histogram bucket boundaries for hang time (ms).
+ * Covers common ranges from minor UI jank to severe hangs. MetricKit hard-caps
+ * hang observation near 30 s; that value is used as the finite overflow sentinel
+ * (see {@link #APP_LAUNCH_BUCKETS} for rationale).
+ */
+ private static final double[] HANG_TIME_BUCKETS =
+ {250, 400, 600, 1000, 1500, 2000, 3000, 5000, 10000, 30000};
+
+ private OpenTelemetryMetricRequestProcessor metricProcessor;
+
+ @Override
+ public String[] requiredModules() {
+ return new String[] {OtelMetricReceiverModule.NAME};
+ }
+
+ @Override
+ public void init(final ModuleManager moduleManager) {
+ metricProcessor = moduleManager.find(OtelMetricReceiverModule.NAME)
+ .provider()
+ .getService(OpenTelemetryMetricRequestProcessor.class);
+ }
+
+ @Override
+ public SpanListenerResult onOTLPSpan(final OTLPSpanReader span,
+ final Map<String, String> resourceAttributes,
+ final String scopeName,
+ final String scopeVersion) {
+ final String osName = resourceAttributes.get("os.name");
+ if (!"iOS".equals(osName) && !"iPadOS".equals(osName)) {
+ return SpanListenerResult.CONTINUE;
+ }
+ // OpenTelemetry Swift MetricKit instrumentation emits the aggregated
+ // daily metrics span under scope "MetricKit" with span name
+ // "MXMetricPayload".
+ if (!"MetricKit".equals(scopeName) || !"MXMetricPayload".equals(span.spanName())) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ final String serviceName = resourceAttributes.getOrDefault("service.name", "");
+ final String serviceVersion = resourceAttributes.getOrDefault("service.version", "");
+ final String deviceModel = getDeviceModel(span, resourceAttributes);
+ final String osVersion = getOsVersion(span, resourceAttributes);
+ final long timestamp = span.endTimeNanos() > 0
+ ? span.endTimeNanos() / 1_000_000 : System.currentTimeMillis();
+
+ final ImmutableMap<String, String> labels = ImmutableMap.of(
+ "service_name", serviceName,
+ "service_instance_id", serviceVersion,
+ "device_model", deviceModel,
+ "os_version", osVersion
+ );
Review Comment:
Addressed in a6660573c6: early `return SpanListenerResult.CONTINUE` when `service.name` is blank (same guard as `IOSHTTPSpanListener`), and when `service.version` is blank the instance id falls back to the literal `"unknown"` instead of an empty string.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@skywalking.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add iOS/iPadOS app monitoring via OpenTelemetry Swift SDK (SWIP-11) [skywalking]
Posted by "wu-sheng (via GitHub)" <gi...@apache.org>.
wu-sheng merged PR #13828:
URL: https://github.com/apache/skywalking/pull/13828
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@skywalking.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add iOS/iPadOS app monitoring via OpenTelemetry Swift SDK (SWIP-11) [skywalking]
Posted by "wu-sheng (via GitHub)" <gi...@apache.org>.
wu-sheng commented on code in PR #13828:
URL: https://github.com/apache/skywalking/pull/13828#discussion_r3105130527
##########
oap-server/analyzer/ios-analyzer/src/main/java/org/apache/skywalking/oap/analyzer/ios/listener/IOSHTTPSpanListener.java:
##########
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.analyzer.ios.listener;
+
+import java.util.Collections;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+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.NamingControl;
+import org.apache.skywalking.oap.server.core.source.Endpoint;
+import org.apache.skywalking.oap.server.core.source.RequestType;
+import org.apache.skywalking.oap.server.core.source.Service;
+import org.apache.skywalking.oap.server.core.source.ServiceInstance;
+import org.apache.skywalking.oap.server.core.source.ServiceMeta;
+import org.apache.skywalking.oap.server.core.source.SourceReceiver;
+import org.apache.skywalking.oap.server.core.trace.OTLPSpanReader;
+import org.apache.skywalking.oap.server.core.trace.SpanListener;
+import org.apache.skywalking.oap.server.core.trace.SpanListenerResult;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+
+/**
+ * Extracts per-request HTTP traffic metrics from iOS URLSession spans and emits
+ * OAL sources ({@link Service}, {@link ServiceInstance}, {@link Endpoint}).
+ *
+ * <p>Detection: resource attribute {@code os.name} is {@code iOS} or {@code iPadOS},
+ * the instrumentation scope is {@code NSURLSession}, the span kind is
+ * {@code CLIENT}, and the span has {@code http.method} plus
+ * {@code net.peer.name} (domain name).
+ *
+ * <p>These keys match the current default output of the OpenTelemetry Swift
+ * {@code URLSessionInstrumentation}. Its default HTTP semantic convention is
+ * {@code .old}, which emits {@code net.peer.name} and {@code http.status_code}
+ * rather than the stable {@code server.address} and
+ * {@code http.response.status_code} keys.
+ *
+ * <p>Endpoint name uses the domain only ({@code net.peer.name}) to keep cardinality
+ * manageable on the client side.
+ *
+ * <p>The existing {@code core.oal} rules automatically produce traffic metrics
+ * ({@code service_cpm}, {@code service_resp_time}, {@code endpoint_cpm}, etc.)
+ * from these sources, all under {@link Layer#IOS}.
+ */
+@Slf4j
+public class IOSHTTPSpanListener implements SpanListener {
+ private SourceReceiver sourceReceiver;
+ private NamingControl namingControl;
+
+ @Override
+ public String[] requiredModules() {
+ return new String[] {CoreModule.NAME};
+ }
+
+ @Override
+ public void init(final ModuleManager moduleManager) {
+ sourceReceiver = moduleManager.find(CoreModule.NAME)
+ .provider()
+ .getService(SourceReceiver.class);
+ namingControl = moduleManager.find(CoreModule.NAME)
+ .provider()
+ .getService(NamingControl.class);
+ }
+
+ @Override
+ public SpanListenerResult onOTLPSpan(final OTLPSpanReader span,
+ final Map<String, String> resourceAttributes,
+ final String scopeName,
+ final String scopeVersion) {
+ final String osName = resourceAttributes.get("os.name");
+ if (!"iOS".equals(osName) && !"iPadOS".equals(osName)) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ // OpenTelemetry Swift URLSessionInstrumentation supports three HTTP
+ // semantic-convention modes: `.old` (legacy: net.peer.name / http.method /
+ // http.status_code), `.stable` (server.address / http.request.method /
+ // http.response.status_code), and `.httpDup` (both). We prefer the stable
+ // keys and fall back to legacy so all three modes work.
+ if (!"NSURLSession".equals(scopeName) || !"SPAN_KIND_CLIENT".equals(span.spanKind())) {
+ return SpanListenerResult.CONTINUE;
Review Comment:
Addressed in 2507b69e1c by updating the `OTLPSpanReader.spanKind()` Javadoc to match the actual contract (`SPAN_KIND_CLIENT` — the OTLP proto enum name returned by `Enum.name()`). The listener keeps matching a single stable value; a span carries exactly one kind at runtime, so there's only one spelling to check. The Javadoc was stale.
##########
oap-server/analyzer/ios-analyzer/src/main/java/org/apache/skywalking/oap/analyzer/ios/listener/IOSHTTPSpanListener.java:
##########
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.analyzer.ios.listener;
+
+import java.util.Collections;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+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.NamingControl;
+import org.apache.skywalking.oap.server.core.source.Endpoint;
+import org.apache.skywalking.oap.server.core.source.RequestType;
+import org.apache.skywalking.oap.server.core.source.Service;
+import org.apache.skywalking.oap.server.core.source.ServiceInstance;
+import org.apache.skywalking.oap.server.core.source.ServiceMeta;
+import org.apache.skywalking.oap.server.core.source.SourceReceiver;
+import org.apache.skywalking.oap.server.core.trace.OTLPSpanReader;
+import org.apache.skywalking.oap.server.core.trace.SpanListener;
+import org.apache.skywalking.oap.server.core.trace.SpanListenerResult;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+
+/**
+ * Extracts per-request HTTP traffic metrics from iOS URLSession spans and emits
+ * OAL sources ({@link Service}, {@link ServiceInstance}, {@link Endpoint}).
+ *
+ * <p>Detection: resource attribute {@code os.name} is {@code iOS} or {@code iPadOS},
+ * the instrumentation scope is {@code NSURLSession}, the span kind is
+ * {@code CLIENT}, and the span has {@code http.method} plus
+ * {@code net.peer.name} (domain name).
+ *
+ * <p>These keys match the current default output of the OpenTelemetry Swift
+ * {@code URLSessionInstrumentation}. Its default HTTP semantic convention is
+ * {@code .old}, which emits {@code net.peer.name} and {@code http.status_code}
+ * rather than the stable {@code server.address} and
+ * {@code http.response.status_code} keys.
+ *
+ * <p>Endpoint name uses the domain only ({@code net.peer.name}) to keep cardinality
+ * manageable on the client side.
+ *
+ * <p>The existing {@code core.oal} rules automatically produce traffic metrics
+ * ({@code service_cpm}, {@code service_resp_time}, {@code endpoint_cpm}, etc.)
+ * from these sources, all under {@link Layer#IOS}.
+ */
+@Slf4j
+public class IOSHTTPSpanListener implements SpanListener {
+ private SourceReceiver sourceReceiver;
+ private NamingControl namingControl;
+
+ @Override
+ public String[] requiredModules() {
+ return new String[] {CoreModule.NAME};
+ }
+
+ @Override
+ public void init(final ModuleManager moduleManager) {
+ sourceReceiver = moduleManager.find(CoreModule.NAME)
+ .provider()
+ .getService(SourceReceiver.class);
+ namingControl = moduleManager.find(CoreModule.NAME)
+ .provider()
+ .getService(NamingControl.class);
+ }
+
+ @Override
+ public SpanListenerResult onOTLPSpan(final OTLPSpanReader span,
+ final Map<String, String> resourceAttributes,
+ final String scopeName,
+ final String scopeVersion) {
+ final String osName = resourceAttributes.get("os.name");
+ if (!"iOS".equals(osName) && !"iPadOS".equals(osName)) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ // OpenTelemetry Swift URLSessionInstrumentation supports three HTTP
+ // semantic-convention modes: `.old` (legacy: net.peer.name / http.method /
+ // http.status_code), `.stable` (server.address / http.request.method /
+ // http.response.status_code), and `.httpDup` (both). We prefer the stable
+ // keys and fall back to legacy so all three modes work.
+ if (!"NSURLSession".equals(scopeName) || !"SPAN_KIND_CLIENT".equals(span.spanKind())) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ final String httpMethod = attr(span, "http.request.method", "http.method");
+ if (httpMethod == null || httpMethod.isEmpty()) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ final String domain = attr(span, "server.address", "net.peer.name");
+ if (domain == null || domain.isEmpty()) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ final String rawServiceName = resourceAttributes.getOrDefault("service.name", "");
+ if (rawServiceName.isEmpty()) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ final String serviceName = namingControl.formatServiceName(rawServiceName);
+ final String rawInstanceName = resourceAttributes.getOrDefault("service.version", "");
+ final String instanceName = namingControl.formatInstanceName(rawInstanceName);
+ final String endpointName = namingControl.formatEndpointName(serviceName, domain);
+ final long now = System.currentTimeMillis();
+ final long endTimeMs = span.endTimeNanos() > 0 ? span.endTimeNanos() / 1_000_000 : now;
+ final long startTimeMs = span.startTimeNanos() > 0 ? span.startTimeNanos() / 1_000_000 : endTimeMs;
+ final long timeBucket = TimeBucket.getMinuteTimeBucket(endTimeMs);
+ final int latency = (int) (endTimeMs - startTimeMs);
Review Comment:
Addressed in 2507b69e1c: `latency` is now `rawLatency < 0 ? 0 : (int) Math.min(rawLatency, Integer.MAX_VALUE)`. Guards both clock-skew (end < start) and the int-overflow case for pathologically long-running spans.
##########
oap-server/server-receiver-plugin/otel-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/otel/otlp/OpenTelemetryMetricRequestProcessor.java:
##########
@@ -149,6 +149,18 @@ public void processMetricsRequest(final ExportMetricsServiceRequest requests) {
}
}
+ /**
+ * Push pre-built sample families into the MAL pipeline.
+ * Used by SpanListeners (e.g., IOSMetricKitSpanListener) that extract
+ * metrics from OTLP spans and need to feed them through the same
+ * MAL converters configured via enabledOtelMetricsRules.
+ */
+ public void toMeter(final ImmutableMap<String, SampleFamily> sampleFamilies) {
+ if (converters != null) {
+ converters.forEach(convert -> convert.toMeter(sampleFamilies));
+ }
+ }
Review Comment:
Addressed in 2507b69e1c: `converters` now initializes to an empty `ArrayList` at field declaration, so `processMetricsRequest`'s unconditional `converters.forEach(...)` is a safe no-op when no MAL rules are enabled (or when `Rules.loadRules` returned none). The now-redundant null guard in `toMeter` was removed.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@skywalking.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add iOS/iPadOS app monitoring via OpenTelemetry Swift SDK (SWIP-11) [skywalking]
Posted by "Copilot (via GitHub)" <gi...@apache.org>.
Copilot commented on code in PR #13828:
URL: https://github.com/apache/skywalking/pull/13828#discussion_r3105124342
##########
oap-server/analyzer/ios-analyzer/src/main/java/org/apache/skywalking/oap/analyzer/ios/listener/IOSHTTPSpanListener.java:
##########
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.analyzer.ios.listener;
+
+import java.util.Collections;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+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.NamingControl;
+import org.apache.skywalking.oap.server.core.source.Endpoint;
+import org.apache.skywalking.oap.server.core.source.RequestType;
+import org.apache.skywalking.oap.server.core.source.Service;
+import org.apache.skywalking.oap.server.core.source.ServiceInstance;
+import org.apache.skywalking.oap.server.core.source.ServiceMeta;
+import org.apache.skywalking.oap.server.core.source.SourceReceiver;
+import org.apache.skywalking.oap.server.core.trace.OTLPSpanReader;
+import org.apache.skywalking.oap.server.core.trace.SpanListener;
+import org.apache.skywalking.oap.server.core.trace.SpanListenerResult;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+
+/**
+ * Extracts per-request HTTP traffic metrics from iOS URLSession spans and emits
+ * OAL sources ({@link Service}, {@link ServiceInstance}, {@link Endpoint}).
+ *
+ * <p>Detection: resource attribute {@code os.name} is {@code iOS} or {@code iPadOS},
+ * the instrumentation scope is {@code NSURLSession}, the span kind is
+ * {@code CLIENT}, and the span has {@code http.method} plus
+ * {@code net.peer.name} (domain name).
+ *
+ * <p>These keys match the current default output of the OpenTelemetry Swift
+ * {@code URLSessionInstrumentation}. Its default HTTP semantic convention is
+ * {@code .old}, which emits {@code net.peer.name} and {@code http.status_code}
+ * rather than the stable {@code server.address} and
+ * {@code http.response.status_code} keys.
+ *
+ * <p>Endpoint name uses the domain only ({@code net.peer.name}) to keep cardinality
+ * manageable on the client side.
+ *
+ * <p>The existing {@code core.oal} rules automatically produce traffic metrics
+ * ({@code service_cpm}, {@code service_resp_time}, {@code endpoint_cpm}, etc.)
+ * from these sources, all under {@link Layer#IOS}.
+ */
+@Slf4j
+public class IOSHTTPSpanListener implements SpanListener {
+ private SourceReceiver sourceReceiver;
+ private NamingControl namingControl;
+
+ @Override
+ public String[] requiredModules() {
+ return new String[] {CoreModule.NAME};
+ }
+
+ @Override
+ public void init(final ModuleManager moduleManager) {
+ sourceReceiver = moduleManager.find(CoreModule.NAME)
+ .provider()
+ .getService(SourceReceiver.class);
+ namingControl = moduleManager.find(CoreModule.NAME)
+ .provider()
+ .getService(NamingControl.class);
+ }
+
+ @Override
+ public SpanListenerResult onOTLPSpan(final OTLPSpanReader span,
+ final Map<String, String> resourceAttributes,
+ final String scopeName,
+ final String scopeVersion) {
+ final String osName = resourceAttributes.get("os.name");
+ if (!"iOS".equals(osName) && !"iPadOS".equals(osName)) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ // OpenTelemetry Swift URLSessionInstrumentation supports three HTTP
+ // semantic-convention modes: `.old` (legacy: net.peer.name / http.method /
+ // http.status_code), `.stable` (server.address / http.request.method /
+ // http.response.status_code), and `.httpDup` (both). We prefer the stable
+ // keys and fall back to legacy so all three modes work.
+ if (!"NSURLSession".equals(scopeName) || !"SPAN_KIND_CLIENT".equals(span.spanKind())) {
+ return SpanListenerResult.CONTINUE;
Review Comment:
`OTLPSpanReader#spanKind()` currently returns proto enum names (see `OTLPSpanReaderImpl`: `SPAN_KIND_CLIENT`), but the `OTLPSpanReader` interface Javadoc says it returns values like `"CLIENT"`. To avoid silently skipping spans if the implementation is aligned with the Javadoc (or if a different reader implementation is introduced), consider accepting both representations here (e.g., `SPAN_KIND_CLIENT` and `CLIENT`, or a suffix check).
##########
oap-server/analyzer/ios-analyzer/src/main/java/org/apache/skywalking/oap/analyzer/ios/listener/IOSHTTPSpanListener.java:
##########
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.analyzer.ios.listener;
+
+import java.util.Collections;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+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.NamingControl;
+import org.apache.skywalking.oap.server.core.source.Endpoint;
+import org.apache.skywalking.oap.server.core.source.RequestType;
+import org.apache.skywalking.oap.server.core.source.Service;
+import org.apache.skywalking.oap.server.core.source.ServiceInstance;
+import org.apache.skywalking.oap.server.core.source.ServiceMeta;
+import org.apache.skywalking.oap.server.core.source.SourceReceiver;
+import org.apache.skywalking.oap.server.core.trace.OTLPSpanReader;
+import org.apache.skywalking.oap.server.core.trace.SpanListener;
+import org.apache.skywalking.oap.server.core.trace.SpanListenerResult;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+
+/**
+ * Extracts per-request HTTP traffic metrics from iOS URLSession spans and emits
+ * OAL sources ({@link Service}, {@link ServiceInstance}, {@link Endpoint}).
+ *
+ * <p>Detection: resource attribute {@code os.name} is {@code iOS} or {@code iPadOS},
+ * the instrumentation scope is {@code NSURLSession}, the span kind is
+ * {@code CLIENT}, and the span has {@code http.method} plus
+ * {@code net.peer.name} (domain name).
+ *
+ * <p>These keys match the current default output of the OpenTelemetry Swift
+ * {@code URLSessionInstrumentation}. Its default HTTP semantic convention is
+ * {@code .old}, which emits {@code net.peer.name} and {@code http.status_code}
+ * rather than the stable {@code server.address} and
+ * {@code http.response.status_code} keys.
+ *
+ * <p>Endpoint name uses the domain only ({@code net.peer.name}) to keep cardinality
+ * manageable on the client side.
+ *
+ * <p>The existing {@code core.oal} rules automatically produce traffic metrics
+ * ({@code service_cpm}, {@code service_resp_time}, {@code endpoint_cpm}, etc.)
+ * from these sources, all under {@link Layer#IOS}.
+ */
+@Slf4j
+public class IOSHTTPSpanListener implements SpanListener {
+ private SourceReceiver sourceReceiver;
+ private NamingControl namingControl;
+
+ @Override
+ public String[] requiredModules() {
+ return new String[] {CoreModule.NAME};
+ }
+
+ @Override
+ public void init(final ModuleManager moduleManager) {
+ sourceReceiver = moduleManager.find(CoreModule.NAME)
+ .provider()
+ .getService(SourceReceiver.class);
+ namingControl = moduleManager.find(CoreModule.NAME)
+ .provider()
+ .getService(NamingControl.class);
+ }
+
+ @Override
+ public SpanListenerResult onOTLPSpan(final OTLPSpanReader span,
+ final Map<String, String> resourceAttributes,
+ final String scopeName,
+ final String scopeVersion) {
+ final String osName = resourceAttributes.get("os.name");
+ if (!"iOS".equals(osName) && !"iPadOS".equals(osName)) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ // OpenTelemetry Swift URLSessionInstrumentation supports three HTTP
+ // semantic-convention modes: `.old` (legacy: net.peer.name / http.method /
+ // http.status_code), `.stable` (server.address / http.request.method /
+ // http.response.status_code), and `.httpDup` (both). We prefer the stable
+ // keys and fall back to legacy so all three modes work.
+ if (!"NSURLSession".equals(scopeName) || !"SPAN_KIND_CLIENT".equals(span.spanKind())) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ final String httpMethod = attr(span, "http.request.method", "http.method");
+ if (httpMethod == null || httpMethod.isEmpty()) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ final String domain = attr(span, "server.address", "net.peer.name");
+ if (domain == null || domain.isEmpty()) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ final String rawServiceName = resourceAttributes.getOrDefault("service.name", "");
+ if (rawServiceName.isEmpty()) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ final String serviceName = namingControl.formatServiceName(rawServiceName);
+ final String rawInstanceName = resourceAttributes.getOrDefault("service.version", "");
+ final String instanceName = namingControl.formatInstanceName(rawInstanceName);
+ final String endpointName = namingControl.formatEndpointName(serviceName, domain);
+ final long now = System.currentTimeMillis();
+ final long endTimeMs = span.endTimeNanos() > 0 ? span.endTimeNanos() / 1_000_000 : now;
+ final long startTimeMs = span.startTimeNanos() > 0 ? span.startTimeNanos() / 1_000_000 : endTimeMs;
+ final long timeBucket = TimeBucket.getMinuteTimeBucket(endTimeMs);
+ final int latency = (int) (endTimeMs - startTimeMs);
Review Comment:
`latency` is computed as `(int) (endTimeMs - startTimeMs)`. If an instrumented span has `startTimeUnixNano > endTimeUnixNano` (clock skew / bad instrumentation), this becomes negative and will feed invalid values into OAL latency metrics. Consider clamping to `>= 0` (and potentially keeping it as a `long` until assigning to the source) to prevent negative/overflowed latency.
##########
oap-server/server-receiver-plugin/otel-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/otel/otlp/OpenTelemetryMetricRequestProcessor.java:
##########
@@ -149,6 +149,18 @@ public void processMetricsRequest(final ExportMetricsServiceRequest requests) {
}
}
+ /**
+ * Push pre-built sample families into the MAL pipeline.
+ * Used by SpanListeners (e.g., IOSMetricKitSpanListener) that extract
+ * metrics from OTLP spans and need to feed them through the same
+ * MAL converters configured via enabledOtelMetricsRules.
+ */
+ public void toMeter(final ImmutableMap<String, SampleFamily> sampleFamilies) {
+ if (converters != null) {
+ converters.forEach(convert -> convert.toMeter(sampleFamilies));
+ }
+ }
Review Comment:
`toMeter(...)` treats `converters == null` as “no enabled rules” and becomes a no-op, but `processMetricsRequest(...)` still unconditionally calls `converters.forEach(...)` and will NPE if `enabledOtelMetricsRules` is empty or rule-loading failed (since `start()` returns early with `converters` left null). Consider normalizing this by initializing `converters` to an empty list (or adding the same null-guard in `processMetricsRequest`) so OTLP metrics ingestion fails gracefully when no rules are enabled.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@skywalking.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add iOS/iPadOS app monitoring via OpenTelemetry Swift SDK (SWIP-11) [skywalking]
Posted by "Copilot (via GitHub)" <gi...@apache.org>.
Copilot commented on code in PR #13828:
URL: https://github.com/apache/skywalking/pull/13828#discussion_r3104069665
##########
oap-server/analyzer/ios-analyzer/src/main/java/org/apache/skywalking/oap/analyzer/ios/listener/IOSHTTPSpanListener.java:
##########
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.analyzer.ios.listener;
+
+import java.util.Collections;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+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.NamingControl;
+import org.apache.skywalking.oap.server.core.source.Endpoint;
+import org.apache.skywalking.oap.server.core.source.RequestType;
+import org.apache.skywalking.oap.server.core.source.Service;
+import org.apache.skywalking.oap.server.core.source.ServiceInstance;
+import org.apache.skywalking.oap.server.core.source.ServiceMeta;
+import org.apache.skywalking.oap.server.core.source.SourceReceiver;
+import org.apache.skywalking.oap.server.core.trace.OTLPSpanReader;
+import org.apache.skywalking.oap.server.core.trace.SpanListener;
+import org.apache.skywalking.oap.server.core.trace.SpanListenerResult;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+
+/**
+ * Extracts per-request HTTP traffic metrics from iOS URLSession spans and emits
+ * OAL sources ({@link Service}, {@link ServiceInstance}, {@link Endpoint}).
+ *
+ * <p>Detection: resource attribute {@code os.name} is {@code iOS} or {@code iPadOS},
+ * the instrumentation scope is {@code NSURLSession}, the span kind is
+ * {@code CLIENT}, and the span has {@code http.method} plus
+ * {@code net.peer.name} (domain name).
+ *
+ * <p>These keys match the current default output of the OpenTelemetry Swift
+ * {@code URLSessionInstrumentation}. Its default HTTP semantic convention is
+ * {@code .old}, which emits {@code net.peer.name} and {@code http.status_code}
+ * rather than the stable {@code server.address} and
+ * {@code http.response.status_code} keys.
+ *
+ * <p>Endpoint name uses the domain only ({@code net.peer.name}) to keep cardinality
+ * manageable on the client side.
+ *
+ * <p>The existing {@code core.oal} rules automatically produce traffic metrics
+ * ({@code service_cpm}, {@code service_resp_time}, {@code endpoint_cpm}, etc.)
+ * from these sources, all under {@link Layer#IOS}.
+ */
+@Slf4j
+public class IOSHTTPSpanListener implements SpanListener {
+ private SourceReceiver sourceReceiver;
+ private NamingControl namingControl;
+
+ @Override
+ public String[] requiredModules() {
+ return new String[] {CoreModule.NAME};
+ }
+
+ @Override
+ public void init(final ModuleManager moduleManager) {
+ sourceReceiver = moduleManager.find(CoreModule.NAME)
+ .provider()
+ .getService(SourceReceiver.class);
+ namingControl = moduleManager.find(CoreModule.NAME)
+ .provider()
+ .getService(NamingControl.class);
+ }
+
+ @Override
+ public SpanListenerResult onOTLPSpan(final OTLPSpanReader span,
+ final Map<String, String> resourceAttributes,
+ final String scopeName,
+ final String scopeVersion) {
+ final String osName = resourceAttributes.get("os.name");
+ if (!"iOS".equals(osName) && !"iPadOS".equals(osName)) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ // OpenTelemetry Swift URLSessionInstrumentation supports three HTTP
+ // semantic-convention modes: `.old` (legacy: net.peer.name / http.method /
+ // http.status_code), `.stable` (server.address / http.request.method /
+ // http.response.status_code), and `.httpDup` (both). We prefer the stable
+ // keys and fall back to legacy so all three modes work.
+ if (!"NSURLSession".equals(scopeName) || !"SPAN_KIND_CLIENT".equals(span.spanKind())) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ final String httpMethod = attr(span, "http.request.method", "http.method");
+ if (httpMethod == null || httpMethod.isEmpty()) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ final String domain = attr(span, "server.address", "net.peer.name");
+ if (domain == null || domain.isEmpty()) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ final String rawServiceName = resourceAttributes.getOrDefault("service.name", "");
+ if (rawServiceName.isEmpty()) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ final String serviceName = namingControl.formatServiceName(rawServiceName);
+ final String instanceName = resourceAttributes.getOrDefault("service.version", "");
Review Comment:
`service.version` is used as the service instance name without applying `NamingControl.formatInstanceName(...)`. If `service.version` is long (build metadata, Git SHA, etc.), this can exceed instance-name limits and cause inconsistent instance identity across the UI/storage. Consider formatting `instanceName` (and using the formatted value consistently for `Service` and `ServiceInstance`).
##########
oap-server/analyzer/ios-analyzer/src/main/java/org/apache/skywalking/oap/analyzer/ios/listener/IOSMetricKitSpanListener.java:
##########
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.analyzer.ios.listener;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.oap.meter.analyzer.v2.dsl.Sample;
+import org.apache.skywalking.oap.meter.analyzer.v2.dsl.SampleFamily;
+import org.apache.skywalking.oap.meter.analyzer.v2.dsl.SampleFamilyBuilder;
+import org.apache.skywalking.oap.server.core.trace.OTLPSpanReader;
+import org.apache.skywalking.oap.server.core.trace.SpanListener;
+import org.apache.skywalking.oap.server.core.trace.SpanListenerResult;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+import org.apache.skywalking.oap.server.receiver.otel.OtelMetricReceiverModule;
+import org.apache.skywalking.oap.server.receiver.otel.otlp.OpenTelemetryMetricRequestProcessor;
+
+/**
+ * Extracts Apple MetricKit daily statistics from OTLP spans and converts them
+ * to MAL metrics. MetricKit spans are NOT stored as traces (24-hour duration).
+ *
+ * <p>Detection: {@code scopeName == "MetricKit"} AND {@code spanName == "MXMetricPayload"}.
+ * These values come from the current OpenTelemetry Swift MetricKit
+ * instrumentation, which builds the tracer/logger with scope name
+ * {@code "MetricKit"} and reports the main aggregated metrics span as
+ * {@code "MXMetricPayload"}.
+ *
+ * <p>The listener converts span attributes into labeled {@link SampleFamily} samples
+ * and pushes them into the shared MAL pipeline via
+ * {@link OpenTelemetryMetricRequestProcessor#toMeter}.
+ * MAL rules are loaded by the otel-receiver module via {@code enabledOtelMetricsRules}.
+ */
+@Slf4j
+public class IOSMetricKitSpanListener implements SpanListener {
+ /**
+ * Histogram bucket boundaries for app launch time (ms).
+ * Covers common ranges from fast launch to slow cold start. The last bucket (30 s)
+ * acts as a finite overflow sentinel: observations above it are catastrophic and
+ * should remain visible on the chart rather than landing in a {@code +Inf} bucket
+ * (which would surface as {@code Long.MAX_VALUE} in percentile queries).
+ */
+ private static final double[] APP_LAUNCH_BUCKETS =
+ {400, 600, 800, 1000, 1500, 2000, 3000, 5000, 10000, 30000};
+
+ /**
+ * Histogram bucket boundaries for hang time (ms).
+ * Covers common ranges from minor UI jank to severe hangs. MetricKit hard-caps
+ * hang observation near 30 s; that value is used as the finite overflow sentinel
+ * (see {@link #APP_LAUNCH_BUCKETS} for rationale).
+ */
+ private static final double[] HANG_TIME_BUCKETS =
+ {250, 400, 600, 1000, 1500, 2000, 3000, 5000, 10000, 30000};
+
+ private OpenTelemetryMetricRequestProcessor metricProcessor;
+
+ @Override
+ public String[] requiredModules() {
+ return new String[] {OtelMetricReceiverModule.NAME};
+ }
+
+ @Override
+ public void init(final ModuleManager moduleManager) {
+ metricProcessor = moduleManager.find(OtelMetricReceiverModule.NAME)
+ .provider()
+ .getService(OpenTelemetryMetricRequestProcessor.class);
+ }
+
+ @Override
+ public SpanListenerResult onOTLPSpan(final OTLPSpanReader span,
+ final Map<String, String> resourceAttributes,
+ final String scopeName,
+ final String scopeVersion) {
+ final String osName = resourceAttributes.get("os.name");
+ if (!"iOS".equals(osName) && !"iPadOS".equals(osName)) {
+ return SpanListenerResult.CONTINUE;
+ }
+ // OpenTelemetry Swift MetricKit instrumentation emits the aggregated
+ // daily metrics span under scope "MetricKit" with span name
+ // "MXMetricPayload".
+ if (!"MetricKit".equals(scopeName) || !"MXMetricPayload".equals(span.spanName())) {
+ return SpanListenerResult.CONTINUE;
+ }
+
+ final String serviceName = resourceAttributes.getOrDefault("service.name", "");
+ final String serviceVersion = resourceAttributes.getOrDefault("service.version", "");
+ final String deviceModel = getDeviceModel(span, resourceAttributes);
+ final String osVersion = getOsVersion(span, resourceAttributes);
+ final long timestamp = span.endTimeNanos() > 0
+ ? span.endTimeNanos() / 1_000_000 : System.currentTimeMillis();
+
+ final ImmutableMap<String, String> labels = ImmutableMap.of(
+ "service_name", serviceName,
+ "service_instance_id", serviceVersion,
+ "device_model", deviceModel,
+ "os_version", osVersion
+ );
Review Comment:
`service.name` is defaulted to an empty string; if the resource attribute is missing, this will emit metrics that can create a service/instance with an empty name via MAL `expSuffix`, which is likely invalid and may pollute inventories. Consider returning `CONTINUE` when `service.name` is missing/blank (similar to `IOSHTTPSpanListener`) and optionally defaulting `service.version` to a sentinel like `unknown` rather than an empty instance id.
##########
oap-server/analyzer/ios-analyzer/src/main/java/org/apache/skywalking/oap/analyzer/ios/listener/IOSMetricKitSpanListener.java:
##########
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.analyzer.ios.listener;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.oap.meter.analyzer.v2.dsl.Sample;
+import org.apache.skywalking.oap.meter.analyzer.v2.dsl.SampleFamily;
+import org.apache.skywalking.oap.meter.analyzer.v2.dsl.SampleFamilyBuilder;
+import org.apache.skywalking.oap.server.core.trace.OTLPSpanReader;
+import org.apache.skywalking.oap.server.core.trace.SpanListener;
+import org.apache.skywalking.oap.server.core.trace.SpanListenerResult;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+import org.apache.skywalking.oap.server.receiver.otel.OtelMetricReceiverModule;
+import org.apache.skywalking.oap.server.receiver.otel.otlp.OpenTelemetryMetricRequestProcessor;
+
+/**
+ * Extracts Apple MetricKit daily statistics from OTLP spans and converts them
+ * to MAL metrics. MetricKit spans are NOT stored as traces (24-hour duration).
+ *
+ * <p>Detection: {@code scopeName == "MetricKit"} AND {@code spanName == "MXMetricPayload"}.
+ * These values come from the current OpenTelemetry Swift MetricKit
+ * instrumentation, which builds the tracer/logger with scope name
+ * {@code "MetricKit"} and reports the main aggregated metrics span as
+ * {@code "MXMetricPayload"}.
+ *
+ * <p>The listener converts span attributes into labeled {@link SampleFamily} samples
+ * and pushes them into the shared MAL pipeline via
+ * {@link OpenTelemetryMetricRequestProcessor#toMeter}.
+ * MAL rules are loaded by the otel-receiver module via {@code enabledOtelMetricsRules}.
+ */
+@Slf4j
+public class IOSMetricKitSpanListener implements SpanListener {
+ /**
+ * Histogram bucket boundaries for app launch time (ms).
+ * Covers common ranges from fast launch to slow cold start. The last bucket (30 s)
+ * acts as a finite overflow sentinel: observations above it are catastrophic and
+ * should remain visible on the chart rather than landing in a {@code +Inf} bucket
+ * (which would surface as {@code Long.MAX_VALUE} in percentile queries).
+ */
+ private static final double[] APP_LAUNCH_BUCKETS =
+ {400, 600, 800, 1000, 1500, 2000, 3000, 5000, 10000, 30000};
+
+ /**
+ * Histogram bucket boundaries for hang time (ms).
+ * Covers common ranges from minor UI jank to severe hangs. MetricKit hard-caps
+ * hang observation near 30 s; that value is used as the finite overflow sentinel
+ * (see {@link #APP_LAUNCH_BUCKETS} for rationale).
+ */
+ private static final double[] HANG_TIME_BUCKETS =
+ {250, 400, 600, 1000, 1500, 2000, 3000, 5000, 10000, 30000};
+
+ private OpenTelemetryMetricRequestProcessor metricProcessor;
+
+ @Override
+ public String[] requiredModules() {
+ return new String[] {OtelMetricReceiverModule.NAME};
+ }
+
+ @Override
+ public void init(final ModuleManager moduleManager) {
+ metricProcessor = moduleManager.find(OtelMetricReceiverModule.NAME)
+ .provider()
+ .getService(OpenTelemetryMetricRequestProcessor.class);
+ }
Review Comment:
This listener builds MAL entities from `service.name`/`service.version` but doesn't apply `NamingControl` (service/instance length limits, grouping conventions). To avoid storage failures or inconsistent entity IDs, consider requiring `CoreModule` as well and formatting service/instance names via `NamingControl` before putting them into labels.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@skywalking.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org