数据模型与 Scope 体系

这一篇解决什么
前面把 Source/Metrics/Record 当抽象概念用,这一篇挖到底:Source 全家族 56 个子类逐个列字段、Scope 全表 98 个 scopeId 常量、Metrics 全家族 18 个聚合函数的 combine/calculate、DataTable 多值指标的哈希存储格式。读完这篇,OAL 里 from(Service.latency).longAvg() 这行字背后的每个零件都落到实处。


三层结构总览

数据模型分三层,靠两套注解驱动——@ScopeDeclaration 标 Source,@MetricsFunction 标 Metrics——AnnotationScan 启动时扫包注册。

图1

每个概念的真实职责:

  • Source 是从 Receiver 进入 OAP 的原始事实——一次请求的延迟、一次 GC 的耗时。还没被统计过。
  • Scope 是给每个 Source 分配的整数 ID + 名字。OAL 里 from(Service).longAvg(),编译器靠 Scope 名找到对应的 Source 类。它本质是 Source 的类型标签,但用整数 ID 而不是类名,是为了在 OAL 编译产物和运行时分发里走最快的等值匹配路径。
  • Catalog 比 Scope 粗。告警和 UI 渲染需要知道"这个指标挂在哪个拓扑节点上"——Service?Endpoint?Instance?Catalog 就是这个归属标记,一个 Catalog 下挂多个 Scope。
  • Metrics 是 Source 聚合后的时间窗口统计值。每个子类的契约就两个方法:combine() 累加、calculate() 终算。
  • DataTable 是多值指标容器。普通 Metrics 一个时间窗口只存一个数;DataTable 用一个 HashMap<String,Long> 同时存多个值,典型场景是一次算出 P50/P75/P90/P95/P99 五个分位数。

Source 全家族

Source 是什么
Source 是 OAP 分析管线的事实之源,每个子类对应一类可观测数据(一个服务、一个实例、一次 JVM GC、一次 DB 访问……),通过 @ScopeDeclaration 注册后被 OAL 引用。

为什么 Source 要分这么多子类

56 个 Source 子类不是过度设计,而是可观测域本身的形状决定的。一条请求进来,你想看服务级延迟(Service)、实例级延迟(ServiceInstance)、端点级延迟(Endpoint)、服务间调用关系(ServiceRelation)、JVM 内部状态、数据库访问、缓存访问、消息队列、eBPF 网络层、K8s 网格层……这些数据的字段集互不重叠:JVM GC 有 phase/time/count,数据库访问有 databaseTypeId/statement,缓存访问有 cacheTypeId/operation。硬塞一个 God Source 既丢类型安全,又让 OAL 编译器无法按字段名精准映射。

拆成 56 个子类的代价是注册成本,但被 AnnotationScan 启动期一次性扫描消化,运行期零开销。收益是 OAL 脚本里 from(Service.latency) 这类写法能被静态校验——latency 字段确实存在于 Service 类上,编译期就锁定。这就是 Source 多子类的根本理由:字段集的差异是天然的分类边界,用类型系统固化它

ISource 接口的三个钩子

定义在 oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/ISource.java:

钩子 作用 行号
int scope() 返回 scopeId :31。编译期 OAL 用它匹配;运行期分发器用它找正确的 SourceDispatcher
long getTimeBucket() / setTimeBucket() 时间桶 :33-35。所有 Source 都带时间维度,用 yyyMMddHHmm 形式的长整型表示一个时间窗口
String getEntityId() 实体唯一标识 :37。决定这条 Source 聚合到哪一行;同一 entityId 的 Source 合并到同一个 Metrics 行
default void prepare() 分发前预处理 :42。默认空实现,子类用来"用名字反算 ID"
String toJson() 手写 JSON 调试负载 :55手写而非反射序列化,用于 dsl-debug 捕获

prepare() 的典型用法

prepare()SourceDispatcher.dispatch() 之前调用(ISource.java:40 的注释明确点出这个时序),核心作用是把"好读的 name"转成"好关联的 ID":

  • Endpoint.prepare()(Endpoint.java:131):serviceId = IDManager.ServiceID.buildId(serviceName, serviceLayer.isNormal())
  • ServiceRelation.prepare()(ServiceRelation.java:110):同时算 sourceServiceIddestServiceId
  • GenAIModelAccess.prepare()(GenAIModelAccess.java:68):算 serviceId(用 VIRTUAL_GENAI 层)+ entityId

ServiceDatabaseAccessMQAccess 这类"自己就是顶层实体"的 Source 没有 prepare(),而是把 ID 计算放在 getEntityId() 里做懒加载。区别在于:prepare 是分发前一次性补字段(适合一条 Source 关联多个实体的场景,如 ServiceRelation 要算两端),getEntityId 是用到才算(适合只算自身 ID 的场景)。

getEntityId() 的三种风格

风格 代表 实现
懒加载 + 缓存 ServiceServiceInstanceServiceRelation if (entityId == null) entityId = IDManager.xxx.buildId(...)
直接返回 ServiceInstanceJVMGC 等 JVM/CLR 类 return String.valueOf(id),用 Agent 上报的 instance id
固定空串 DatabaseSlowStatement return Const.EMPTY_STRING,每条慢 SQL 都是独立行,不按实体聚合

第三种是刻意为之:慢 SQL 不该被聚合,每条都该能在 UI 单独查到,所以 entityId 给空串,让每条 Source 落成独立行。

Source 子类全表(按类别分组)

下面覆盖 oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/ 下全部带 @ScopeDeclaration 的类。scopeId 常量来自 DefaultScopeDefine.java

A. 拓扑核心类(Service / Instance / Endpoint 及其 Relation)

类名 scopeId 常量 catalog 关键字段
Service SERVICE 1 SERVICE name, layer, latency, status, httpResponseStatusCode, rpcStatusCode, type, tags, attr0~attr5
ServiceInstance SERVICE_INSTANCE 2 SERVICE_INSTANCE serviceId, name, serviceName, serviceLayer, latency, status, sideCar
Endpoint ENDPOINT 3 ENDPOINT name, serviceId, serviceName, serviceLayer, latency, status, attr0~attr5
ServiceRelation SERVICE_RELATION 4 SERVICE_RELATION sourceServiceId/Name/Layer, destServiceId/Name/Layer, endpoint, componentId, latency, detectPoint
ServiceInstanceRelation SERVICE_INSTANCE_RELATION 5 SERVICE_INSTANCE_RELATION source/dest 的 serviceId+instanceId+name+layer, endpoint, componentId, latency, detectPoint, tlsMode
EndpointRelation ENDPOINT_RELATION 6 ENDPOINT_RELATION endpoint, serviceId/Name, childEndpoint, childServiceId/Name, componentId, rpcLatency, detectPoint

拓扑三件套 × 两层
这 6 个是 SkyWalking 最经典的"拓扑三件套 × 两层":每个维度(Service/Instance/Endpoint)既有自身指标,也有 Relation 描述调用关系。@ScopeDefaultColumn.VirtualColumnDefinition(fieldName="entityId", columnName="entity_id", isID=true) 是这 6 个的统一主键声明——一个 entityId 列既是聚合 key 也是存储主键,Source 的多态主键设计在这里收口。

B. JVM 监控类(全部 catalog = SERVICE_INSTANCE)

类名 scopeId 常量 关键字段 位置
ServiceInstanceJVMCPU SERVICE_INSTANCE_JVM_CPU 8 id, name, serviceName, serviceId, usePercent(double) ServiceInstanceJVMCPU.java:28
ServiceInstanceJVMMemory SERVICE_INSTANCE_JVM_MEMORY 9 id, name, serviceId, heapStatus, init, max, used, committed ServiceInstanceJVMMemory.java:28
ServiceInstanceJVMMemoryPool SERVICE_INSTANCE_JVM_MEMORY_POOL 10 id, name, serviceId, poolType(MemoryPoolType), init, max, used, committed ServiceInstanceJVMMemoryPool.java:28
ServiceInstanceJVMGC SERVICE_INSTANCE_JVM_GC 11 id, name, serviceId, phase(GCPhase), time, count ServiceInstanceJVMGC.java:28
ServiceInstanceJVMThread SERVICE_INSTANCE_JVM_THREAD 33 id, name, serviceId, liveCount, daemonCount, peakCount, runnable/blocked/waiting/timedWaiting 各状态线程数 ServiceInstanceJVMThread.java:28
ServiceInstanceJVMClass SERVICE_INSTANCE_JVM_CLASS 44 已加载/未加载类数 ServiceInstanceJVMClass.java:28

JVM 类的共同模式
getEntityId() 直接返回 Agent 上报的 String.valueOf(id),prepare() 不存在或仅设 serviceId,字段都是长整型数值。注意 scopeId 跳过了 7。

C. CLR(.NET)监控类(全部 catalog = SERVICE_INSTANCE)

类名 scopeId 常量 关键字段 位置
ServiceInstanceCLRCPU SERVICE_INSTANCE_CLR_CPU 19 usePercent ServiceInstanceCLRCPU.java:31
ServiceInstanceCLRGC SERVICE_INSTANCE_CLR_GC 20 gen0CollectCount, gen1CollectCount, gen2CollectCount, heapMemory ServiceInstanceCLRGC.java:31
ServiceInstanceCLRThread SERVICE_INSTANCE_CLR_THREAD 21 线程相关统计 ServiceInstanceCLRThread.java:31

D. 数据库 / 缓存 / 消息队列 访问类

类名 scopeId 常量 catalog 关键字段
DatabaseAccess DATABASE_ACCESS 17 SERVICE name, databaseTypeId, latency, status
DatabaseSlowStatement DATABASE_SLOW_STATEMENT 18 SERVICE id, databaseServiceId, statement, latency, traceId, timestamp;getEntityId() 返回空串
ServiceDatabaseSlowStatement SERVICE_DATABASE_SLOW_STATEMENT 91 SERVICE 服务维度的慢 SQL
CacheAccess CACHE_ACCESS 55 SERVICE name, cacheTypeId, latency, status, operation(VirtualCacheOperation)
CacheSlowAccess CACHE_SLOW_ACCESS 56 SERVICE 慢缓存访问
MQAccess MESSAGE_QUEUE_ACCESS 63 SERVICE name, typeId, transmissionLatency, status, operation(MQOperation)
MQEndpointAccess MESSAGE_QUEUE_ENDPOINT_ACCESS 64 ENDPOINT MQ 的 endpoint 级访问

catalog 归属
DatabaseAccess/CacheAccess/MQAccess 的 catalog 都是 SERVICE,在告警/UI 里归到"服务"维度;MQEndpointAccess 归到 ENDPOINT 维度。这决定了 UI 拓扑图上它们挂哪个节点。

E. GenAI 类(AI 模型观测,10.x 引入)

类名 scopeId 常量 catalog 关键字段
GenAIProviderAccess GEN_AI_PROVIDER_ACCESS 96 SERVICE name, inputTokens, outputTokens, totalEstimatedCost, latency, status
GenAIModelAccess GEN_AI_MODEL_ACCESS 97 SERVICE_INSTANCE serviceName, serviceId, modelName, inputTokens, outputTokens, totalEstimatedCost, timeToFirstToken, latency, status

GenAI cost 单位
GenAIMetrics.java:24 是普通 POJO(@Data,无 @ScopeDeclaration),不是 Source,用作中间数据载体。注释说明 cost 单位是 1e-6 货币(micro-USD)——避免浮点累加误差,所有 cost 用 long 存,展示时再除以 1e6。

F. eBPF / Cilium / Envoy 类

Cilium 类全部继承抽象基类 CiliumMetrics(CiliumMetrics.java:24),后者预定义了 verdict(forwarded/dropped)、type(tcp/http/dns/kafka)、direction(ingress/egress)、dropReason 以及内嵌的 HTTPMetrics/KafkaMetrics/DNSMetrics 子结构。抽象基类把 6 个 Cilium Source 的公共字段抽出来,避免 6 份重复声明。

类名 scopeId 常量 catalog 位置
CiliumService CILIUM_SERVICE 78 SERVICE CiliumService.java:30
CiliumServiceInstance CILIUM_SERVICE_INSTANCE 79 SERVICE_INSTANCE CiliumServiceInstance.java:30
CiliumServiceRelation CILIUM_SERVICE_RELATION 80 SERVICE_RELATION CiliumServiceRelation.java:30
CiliumServiceInstanceRelation CILIUM_SERVICE_INSTANCE_RELATION 81 SERVICE_INSTANCE_RELATION CiliumServiceInstanceRelation.java:30
CiliumEndpoint CILIUM_ENDPOINT 82 ENDPOINT CiliumEndpoint.java:30
CiliumEndpointRelation CILIUM_ENDPOINT_REALATION 83 ENDPOINT_RELATION CiliumEndpointRelation.java:30(注:常量名源码即拼成 REALATION)
EnvoyInstanceMetric ENVOY_INSTANCE_METRIC 22 SERVICE_INSTANCE EnvoyInstanceMetric.java:33

eBPF Profiling 数据类(非观测指标,是 profiling 明细):

类名 scopeId 常量 位置
EBPFProfilingData EBPF_PROFILING_DATA 48 EBPFProfilingData.java:29
EBPFProcessProfilingSchedule EBPF_PROFILING_SCHEDULE 47 EBPFProcessProfilingSchedule.java:31
JFRProfilingData JFR_PROFILING_DATA 84 JFRProfilingData.java:30
PprofProfilingData PPROF_PROFILING_DATA 93 PprofProfilingData.java:28

G. K8s 类(rover 采集的 mesh/网络层数据)

K8s 类全部继承抽象基类 K8SMetrics(K8SMetrics.java:24),后者预定义了 connect/accept/close/write/read 五种网络操作,每种再分 L2/L3/L4 三层 + Protocol(HTTP) 嵌套结构。

类名 scopeId 常量 catalog 位置
K8SService K8S_SERVICE 72 SERVICE K8SService.java:34
K8SServiceInstance K8S_SERVICE_INSTANCE 73 SERVICE_INSTANCE K8SServiceInstance.java:30
K8SServiceRelation K8S_SERVICE_RELATION 74 SERVICE_RELATION K8SServiceRelation.java:34
K8SServiceInstanceRelation K8S_SERVICE_INSTANCE_RELATION 75 SERVICE_INSTANCE_RELATION K8SServiceInstanceRelation.java:30
K8SEndpoint K8S_ENDPOINT 76 ENDPOINT K8SEndpoint.java:30

K8s 和 Cilium 复用拓扑 catalog
K8s 和 Cilium 复用了同一套拓扑 catalog(SERVICE/SERVICE_INSTANCE/…),能在 UI 上和普通 Service 拓扑混排。两者都带 attr0~attr5 透传属性。复用 catalog 是刻意的——网络层和 mesh 层最终都要在服务拓扑图上呈现,共用 catalog 让 UI 不必为每层单独写渲染逻辑。

H. Process 类(进程级,eBPF 之上)

类名 scopeId 常量 catalog 关键字段 位置
Process PROCESS 45 PROCESS instanceId, serviceId, name, serviceName, instanceName, agentId, detectType, labels, profilingSupportStatus Process.java:33
ProcessRelation PROCESS_RELATION 54 PROCESS_RELATION 进程间调用关系 ProcessRelation.java:30

I. Log / Event / Segment 等明细类

类名 scopeId 常量 说明 位置
Segment SEGMENT 12 链路分段原始数据:segmentId, traceId, serviceId, serviceInstanceId, endpointId, startTime, latency, isError, dataBinary, tags Segment.java:32
Log LOG 41 继承 AbstractLog:uniqueId, timestamp, serviceId, serviceInstanceId, endpointId, traceId, traceSegmentId, spanId, contentType, content, error Log.java:23

这些是明细 Record
SegmentLogDatabaseSlowStatement 是明细/Record 性质的 Source,@ScopeDeclaration 没有 catalog(告警不基于它们),它们走 LAL 脚本或硬编码 dispatcher 转成 Record 落库。

J. 元数据 / 配置类(无 catalog,不参与指标告警)

类名 scopeId 常量 作用
ServiceMeta SERVICE_META 29 只含 name + layer,用于服务元数据更新
ServiceInstanceUpdate SERVICE_INSTANCE_UPDATE 30 实例元数据更新
EndpointMeta ENDPOINT_META 42 端点元数据
ServiceLabel SERVICE_LABEL 49 给服务打标签:serviceId, serviceName, label
TagAutocomplete TAG_AUTOCOMPLETE 50 tag 自动补全索引

K. TCP / 非标拓扑类

类名 scopeId 常量 catalog
TCPService TCP_SERVICE 57 SERVICE
TCPServiceInstance TCP_SERVICE_INSTANCE 58 SERVICE_INSTANCE
TCPServiceRelation TCP_SERVICE_RELATION 59 SERVICE_RELATION
TCPServiceInstanceRelation TCP_SERVICE_INSTANCE_RELATION 60 SERVICE_INSTANCE_RELATION
TCPServiceInstanceUpdate TCP_SERVICE_INSTANCE_UPDATE 61 (无)

范围说明
上面 A-K 覆盖 server-core/source/ 下全部 56 个带 @ScopeDeclaration 的类。其余 scopeId(BROWSER_、ZIPKIN_、PROFILE_、UI_、ALARM、EVENT、SPAN_ATTACHED_EVENT、CONTINUOUS_PROFILING_POLICY、RUNTIME_RULE、ALARM_RECOVERY、PPROF_TASK、ASYNC_PROFILER_* 等)的 Source 类位于 server-core 其它子包(manual/generic/browser/zipkin/profile 等),不在 source/ 目录内,注册机制完全相同(都靠 AnnotationScan@ScopeDeclaration)。

SourceDecorator 机制

Decorator 是什么
Decorator 是 OAL 在 Source 进入流处理前补字段的扩展点,典型用途是把 Layer 名写到 attr0,让生成的指标带一个可查询的属性列。

接口 oap-server/server-core/.../analysis/ISourceDecorator.java:27:

public interface ISourceDecorator<SOURCE extends ISource> {
    int getSourceScope();      // 声明装饰哪个 scope 的 Source
    void decorate(SOURCE source);  // 补字段,如 set attr0
}

SourceDecoratorManager(analysis/SourceDecoratorManager.java:31)维护静态 DECORATOR_MAP,key 是 decorator 类的 getSimpleName()addIfAsSourceDecorator() 通过反射拿到泛型参数 <SOURCE>,校验它是 ISource 子类后实例化注册。

全仓库目前只有 3 个 ISourceDecorator 实现:

Decorator 装饰的 Source 行为 位置
ServiceDecorator Service setAttr0(layer.name()) source/ServiceDecorator.java:23
EndpointDecorator Endpoint setAttr0(serviceLayer.name()) source/EndpointDecorator.java:23
K8SServiceDecorator K8SService 设置 attr0(layer 相关) source/K8SServiceDecorator.java

调用方式是 Source 上的 decorate(String decoratorName) 方法(见 Service.java:124Endpoint.java:125),按类名从 DECORATOR_MAP 取出对应 decorator 执行。

ScopeDefaultColumn.DefinedByField 注解的 isAttribute=true(ScopeDefaultColumn.java:77,since 10.2.0)正是为这些 attr 字段标记"这是 decorator 写的属性列",用于查询条件。


Scope 体系完整

为什么 Scope 用整数 ID 分区

scopeId 是整数而不是字符串,不是省内存的小技巧,是 OAP 性能链路上的一个关键决策。Source 进入流处理后,SourceDispatcher 要按 scope 把 Source 路由到正确的处理器——这是一个每条数据都要走的热路径。用整数做 Map<Integer,?> 的 key,hash 和等值比较都是 O(1) 的原生操作;若用字符串 scope 名,每次路由都要 String.equals 走字符比较,在高 TPS 下不可忽略。

更关键的是 OAL 编译产物:from(Service).longAvg() 编译后,Service 这个名字被换成它的 scopeId 常量 1,后续所有匹配都是 int 等值。这让 OAL 的运行时和 Source 分发器共享同一套整数契约,字符串只在启动期注册阶段出现一次。

DefaultScopeDefine.java:36-40 的注释把分区规则写死了:

All metrics IDs in [0, 10,000) are reserved in Apache SkyWalking. If you want to extend the scope, recommend to start with 10,000.

0~9999 是官方保留区,第三方扩展从 10000 开始。整数空间足够大,官方用前 100 个,留 9900 个给扩展,且 addNewScope() 会做冲突校验(DefaultScopeDefine.java:217-231):id 重复抛 UnexpectedException,id 为负抛异常,name 重复抛异常。这套校验保证注册表的唯一性,不靠人工纪律。

DefaultScopeDefine 全部 scopeId 常量

文件:oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/DefaultScopeDefine.java。下表按文件出现顺序列出(@since/@Deprecated 直接来自源码):

常量名 备注 行号
UNKNOWN 0 since 9.0.0 :45
ALL 0 @Deprecated from 9.0.0 :50
SERVICE 1 :51
SERVICE_INSTANCE 2 :52
ENDPOINT 3 :53
SERVICE_RELATION 4 :54
SERVICE_INSTANCE_RELATION 5 :55
ENDPOINT_RELATION 6 :56
SERVICE_INSTANCE_JVM_CPU 8 注意:7 跳过 :57
SERVICE_INSTANCE_JVM_MEMORY 9 :58
SERVICE_INSTANCE_JVM_MEMORY_POOL 10 :59
SERVICE_INSTANCE_JVM_GC 11 :60
SEGMENT 12 :61
ALARM 13 :62
DATABASE_ACCESS 17 :63
DATABASE_SLOW_STATEMENT 18 :64
SERVICE_INSTANCE_CLR_CPU 19 :65
SERVICE_INSTANCE_CLR_GC 20 :66
SERVICE_INSTANCE_CLR_THREAD 21 :67
ENVOY_INSTANCE_METRIC 22 :68
ZIPKIN_SPAN 23 :69
JAEGER_SPAN 24 @Deprecated :71
HTTP_ACCESS_LOG 25 @Deprecated :73
PROFILE_TASK 26 :74
PROFILE_TASK_LOG 27 :75
PROFILE_TASK_SEGMENT_SNAPSHOT 28 :76
SERVICE_META 29 :77
SERVICE_INSTANCE_UPDATE 30 :78
NETWORK_ADDRESS_ALIAS 31 :79
UI_TEMPLATE 32 :80
SERVICE_INSTANCE_JVM_THREAD 33 :81
BROWSER_ERROR_LOG 34 browser 段开始 :84
BROWSER_APP_PERF 35 :85
BROWSER_APP_PAGE_PERF 36 :86
BROWSER_APP_SINGLE_VERSION_PERF 37 :87
BROWSER_APP_TRAFFIC 38 :88
BROWSER_APP_SINGLE_VERSION_TRAFFIC 39 :89
BROWSER_APP_PAGE_TRAFFIC 40 :90
LOG 41 :92
ENDPOINT_META 42 :93
EVENT 43 :95
SERVICE_INSTANCE_JVM_CLASS 44 :97
PROCESS 45 :99
EBPF_PROFILING_TASK 46 :100
EBPF_PROFILING_SCHEDULE 47 :101
EBPF_PROFILING_DATA 48 :102
SERVICE_LABEL 49 :103
TAG_AUTOCOMPLETE 50 :104
ZIPKIN_SERVICE 51 :105
ZIPKIN_SERVICE_SPAN 52 :106
ZIPKIN_SERVICE_RELATION 53 :107
PROCESS_RELATION 54 :108
CACHE_ACCESS 55 :109
CACHE_SLOW_ACCESS 56 :110
TCP_SERVICE 57 :112
TCP_SERVICE_INSTANCE 58 :113
TCP_SERVICE_RELATION 59 :114
TCP_SERVICE_INSTANCE_RELATION 60 :115
TCP_SERVICE_INSTANCE_UPDATE 61 :116
SAMPLED_SLOW_TRACE 62 :117
MESSAGE_QUEUE_ACCESS 63 :119
MESSAGE_QUEUE_ENDPOINT_ACCESS 64 :120
SPAN_ATTACHED_EVENT 65 :122
SAMPLED_STATUS_4XX_TRACE 66 :123
SAMPLED_STATUS_5XX_TRACE 67 :124
CONTINUOUS_PROFILING_POLICY 68 :126
UI_MENU 69 :128
SERVICE_HIERARCHY_RELATION 70 服务层级关系 :130
INSTANCE_HIERARCHY_RELATION 71 实例层级关系 :131
K8S_SERVICE 72 :133
K8S_SERVICE_INSTANCE 73 :134
K8S_SERVICE_RELATION 74 :135
K8S_SERVICE_INSTANCE_RELATION 75 :136
K8S_ENDPOINT 76 :137
CILIUM_SERVICE 78 注意:77 跳过 :139
CILIUM_SERVICE_INSTANCE 79 :140
CILIUM_SERVICE_RELATION 80 :141
CILIUM_SERVICE_INSTANCE_RELATION 81 :142
CILIUM_ENDPOINT 82 :143
CILIUM_ENDPOINT_REALATION 83 源码常量名即此拼写 :144
JFR_PROFILING_DATA 84 :146
ASYNC_PROFILER_TASK 85 :147
ASYNC_PROFILER_TASK_LOG 86 :148
BROWSER_APP_WEB_VITALS_PAGE_PERF 87 :150
BROWSER_APP_RESOURCE_PERF 88 :151
BROWSER_APP_WEB_INTERACTION_PAGE_PERF 89 :152
SW_SPAN_ATTACHED_EVENT 90 :153
SERVICE_DATABASE_SLOW_STATEMENT 91 :154
PPROF_TASK 92 :155
PPROF_PROFILING_DATA 93 :156
PPROF_TASK_LOG 94 :157
ALARM_RECOVERY 95 :158
GEN_AI_PROVIDER_ACCESS 96 :159
GEN_AI_MODEL_ACCESS 97 :160
RUNTIME_RULE 98 :161

跳过的 ID
7、14、15、16、77 在源码中无对应常量。这些是历史废弃或预留——addNewScope() 的冲突校验只挡重复,不挡空缺,所以跳号不会报错,只是 ID 空间留白。

ScopeDeclaration 注解

定义在 source/ScopeDeclaration.java:43:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ScopeDeclaration {
    int id();              // 对应 DefaultScopeDefine 里的常量值
    String name();        // scope 名字,如 "Service",OAL 脚本里用这个名字
    String catalog() default "";  // 顶层 catalog 名,空串表示不参与告警
}

catalog 字段(:53-55)注释明确:当 scope 不是 metric,或其生成的 metrics 不支持告警时,留空。这就是 Segment/Log/元数据类没有 catalog 的注解层依据。

Catalog 机制

Catalog 是什么
Catalog 是告警/UI 的维度归属标签。告警规则需要知道一个指标挂在哪个拓扑节点上才能在 UI 渲染——inServiceCatalog(scopeId) 返回 true 就说明这条指标能在服务拓扑图上显示。

DefaultScopeDefine.java:166-182 定义了 8 个 catalog 名字常量和 8 个对应的 Map<Integer,Boolean> catalog 表。注意是 8 张独立的 Map 而不是一张大 Map——查 catalog 时只需 SERVICE_CATALOG.containsKey(scopeId),O(1),不必遍历。

catalog 名常量 字符串值 含义 典型成员 scope
SERVICE_CATALOG_NAME “SERVICE” 服务维度 Service(1), DatabaseAccess(17), CacheAccess(55), MQAccess(63), K8SService(72), CiliumService(78), TCPService(57), GenAIProviderAccess(96)
SERVICE_INSTANCE_CATALOG_NAME “SERVICE_INSTANCE” 实例维度 ServiceInstance(2), 所有 JVM/CLR 类, Envoy(22), K8SServiceInstance(73), CiliumServiceInstance(79), GenAIModelAccess(97)
ENDPOINT_CATALOG_NAME “ENDPOINT” 端点维度 Endpoint(3), MQEndpointAccess(64), K8SEndpoint(76), CiliumEndpoint(82)
SERVICE_RELATION_CATALOG_NAME “SERVICE_RELATION” 服务关系 ServiceRelation(4), K8SServiceRelation(74), CiliumServiceRelation(80), TCPServiceRelation(59)
SERVICE_INSTANCE_RELATION_CATALOG_NAME “SERVICE_INSTANCE_RELATION” 实例关系 ServiceInstanceRelation(5), K8s/Cilium/TCP 对应关系
ENDPOINT_RELATION_CATALOG_NAME “ENDPOINT_RELATION” 端点关系 EndpointRelation(6), CiliumEndpointRelation(83)
PROCESS_CATALOG_NAME “PROCESS” 进程 Process(45)
PROCESS_RELATION_CATALOG_NAME “PROCESS_RELATION” 进程关系 ProcessRelation(54)

查询 API:catalogOf(int scope)(:422)返回该 scope 所属 catalog 名,找不到返回 “ALL”;inServiceCatalog/inServiceInstanceCatalog/inEndpointCatalog/...(:342-414)判断 scopeId 是否属于某 catalog。

AnnotationScan 如何扫描注册

文件:oap-server/server-core/.../annotation/AnnotationScan.java

图2

机制(AnnotationScan.java:53-69):

  1. ClassPath.from(classLoader).getTopLevelClassesRecursive("org.apache.skywalking")——递归扫描 org.apache.skywalking 包下所有顶层类(:55)。用 ClassPath API 而不是自写文件遍历,是因为 SkyWalking 可能打成 fat jar 或被 OSGi 容器加载,ClassPath API 能正确处理这些部署形态。
  2. 对每个类,遍历已注册的 AnnotationListener,若类上有该 listener 关心的注解则加入缓存(:59-62)。
  3. 全部扫完后,按类名排序,逐个回调 listener.notify(aClass)(:66-68,排序见 :89)。排序这一步不是洁癖——DefaultScopeDefine 的注册会写静态 Map,排序保证相同 scopeId 的冲突抛错信息在不同机器上可复现,便于排查。

DefaultScopeDefine.Listener(:194-207)就是这样一个 listener:annotation() 返回 ScopeDeclaration.class,notify()addNewScope(declaration, originalClass)

addNewScope()(:215-297)做四件事:

  1. 冲突校验 + 把 id→namename→id 存入 ID_2_NAME/NAME_2_ID(:233-234),建立双向查找表。
  2. 扫描类上的 @ScopeDefaultColumn.VirtualColumnDefinition(类级注解,定义主键列)和字段上的 @ScopeDefaultColumn.DefinedByField,收集成 ScopeDefaultColumn 列表存入 SCOPE_COLUMNS(:236-268)。这一步把 Source 的"默认列"传递给后续生成的 Metrics 实体——Metrics 继承 Source 的列,这样查询时能按 Source 的字段过滤。
  3. 根据 catalog() 把 scopeId 归入对应 catalog 表(:270-296)。
  4. 字段上若标了 @ScopeDefaultColumn.BanyanDB(shardingKeyIdx=N),记录分片键索引(:252-254),供 BanyanDB 存储层做分片。

ScopeDefaultColumn(source/ScopeDefaultColumn.java:34)承载这些列定义,其中 requireDynamicActive=true 表示只有当 core/activeExtraModelColumns=true 时才真正加到生成的指标列里(:62-64),用于按需开启冗余列以省存储。


Metrics 子类家族

Metrics 是什么
Metrics 是对 Source 聚合后的时间窗口统计值,每个子类封装一种聚合算法(求和/平均/分位数/直方图……),通过 @MetricsFunction 注解暴露给 OAL,核心契约是 combine() 累加 + calculate() 终算。

为什么 combine 和 calculate 要分两步

这是 SkyWalking 流处理模型的核心设计,不是工程洁癖。一条 Source 进来时,你不知道它是不是这个时间窗口的最后一条——窗口可能还没结束,实例可能还在不断上报。所以 combine() 只做可合并的中间态累加:LongAvg 存 summationcount 而不是 value,因为 summationcount 在多实例合并时各自相加结果正确,而 value = avg1 + avg2 是错的。

calculate() 在窗口结束或降采样时才调,这时所有中间态都齐了,做一次除法产出最终值。这个分工让跨实例合并变得自然:实例 A 的 (sum=100, count=10) 和实例 B 的 (sum=200, count=20) 合并成 (sum=300, count=30),最后 calculate 得到 value=10。如果一开始就存 value,A 是 10、B 是 10,合并后还是 10,但真实平均是 10——这次碰巧对,但只要 count 不同就错。

不是所有 Metrics 都需要两步:CountMetricsSumMetricsMaxLongMetricscalculate() 是空操作,因为它们的中间态就是终态。但契约统一保留两步,让流处理框架不用关心具体子类。

Metrics 基类契约

文件:oap-server/server-core/.../analysis/metrics/Metrics.javaMetrics 继承 StreamData 并实现 StorageData, ToJson(:41)。核心抽象方法:

方法 作用 行号
boolean combine(Metrics metrics) 合并同类型 metrics;返回 true 表示继续处理,false 表示丢弃 :68
void calculate() 时间窗口结束时计算最终值 :73
Metrics toHour() / toDay() 降采样到小时/天 :80,87
StorageID id0() 生成存储唯一 ID(基类 id() 做缓存) :167

时间相关:

  • timeBucket 字段(:49)+ lastUpdateTimestamp(:59):用于 session cache 过期判断 isExpired(timestamp, expiredThreshold)(:103)。
  • toTimeBucketInHour()(:107):分钟桶 /100 转小时桶;toTimeBucketInDay()(:115):分钟桶 /10000 或小时桶 /100
  • getDurationInMinute()(:128):分钟桶返回 1,小时桶 60,天桶 1440——这是 CPMMetrics 算"每分钟次数"的依据。

OAL 注解体系(Metrics 如何被 OAL 引用)

每个 Metrics 子类用 @MetricsFunction(functionName="xxx") 声明 OAL 函数名(如 count/longAvg/percentile2)。combine 方法上还用一组参数注解告诉 OAL 编译器"这个参数从哪来":

注解 含义
@Entrance 标记这是 OAL 入口 combine 方法
@SourceFrom 参数来自 Source 的某字段
@ConstOne 参数是常量 1(每来一条 Source 计 1)
@Arg 参数是 OAL 调用者传的参数
@Expression 参数是 OAL 表达式(boolean)
@DefaultValue 给 @Arg 一个默认值

例如 LongAvgMetrics.combine(@SourceFrom long summation, @ConstOne long count)(LongAvgMetrics.java:57)表示"summation 取自 Source 的 latency,count 每次计 1"。OAL 编译器读这些注解,把 from(Service.latency).longAvg() 翻译成"取 Source.latency 字段传给 summation,count 固定传 1"。

全部 Metrics 子类(18 个函数)

类名 OAL 函数名 聚合语义 combine 算法 calculate 算法 值类型 位置
CountMetrics count 计数 value += count(@ConstOne) 空操作 long CountMetrics.java:32
SumMetrics sum 求和 value += count(@SourceFrom) 空操作 long SumMetrics.java:32
LongAvgMetrics longAvg 长整型平均 summation += s; count += 1 value = summation / count long LongAvgMetrics.java:33
DoubleAvgMetrics doubleAvg 浮点平均 summation += s; count += 1 value = summation / count double DoubleAvgMetrics.java:33
PercentMetrics percent 百分比 match 命中则 match++; total++ percentage = match*10000/total int PercentMetrics.java:32
RateMetrics rate 比率 两个 @Expression boolean 分别累加 numerator/denominator percentage = numerator*10000/denominator int RateMetrics.java:31
CPMMetrics cpm 每分钟调用数 total += count value = total / getDurationInMinute() long CPMMetrics.java:32
ApdexMetrics apdex Apdex 满意度 按 t(阈值)/4t 分档累加 sNum/tNum/totalNum value = (sNum*10000 + tNum*10000/2) / totalNum int ApdexMetrics.java:41
MaxLongMetrics max 长整型最大值 if (count > value) value = count 空操作 long MaxLongMetrics.java:32
MinLongMetrics min 长整型最小值 if (count < value) value = count 空操作 long MinLongMetrics.java
MaxDoubleMetrics maxDouble 浮点最大值 同 max 空操作 double MaxDoubleMetrics.java
MinDoubleMetrics minDouble 浮点最小值 同 min,初值 Double.MAX_VALUE 空操作 double MinDoubleMetrics.java:32
PercentileMetrics percentile 多分位数(旧) dataset.valueAccumulation(value/precision, 1) 排序后按 roof 取 P50/75/90/95/99 int[] (MultiIntValuesHolder) PercentileMetrics.java:44(@Deprecated)
PercentileMetrics2 percentile2 多分位数(新) 同上 同上,但结果存为 {p=50},v1\\\|{p=75},v2... 的 DataTable DataTable (LabeledValueHolder) PercentileMetrics2.java:43
HistogramMetrics histogram 直方图/热力图 dataset.valueAccumulation(index*step, 1) 空操作(无单值) 无(用 dataset) HistogramMetrics.java:39
LabelCountMetrics labelCount 按标签计数 dataset.valueAccumulation(label, count, maxLabelCount) 把 dataset 转 {n=label},count 的 value DataTable DataTable LabelCountMetrics.java:33
LabelAvgMetrics labelAvg 按标签平均 summation.valueAccumulation(label,count); count.valueAccumulation(label,1) value = {n=label} : summation/count DataTable LabelAvgMetrics.java:34

另有辅助类型:LongValueHolder/DoubleValueHolder/IntValueHolder(单值接口)、MultiIntValuesHolder(多 int 值)、LabeledValueHolder(返回 DataTable)、DataLabel(标签 map)。

三个典型 combine/calculate 实现详解

1) LongAvgMetrics —— 最经典的"先存中间态、终算时做除法"

// LongAvgMetrics.java:57-72
@Entrance
public final void combine(@SourceFrom long summation, @ConstOne long count) {
    this.summation += summation;   // 累加被求和字段
    this.count += count;          // 累加计数
}
public final boolean combine(Metrics metrics) {  // 跨实例合并
    LongAvgMetrics m = (LongAvgMetrics) metrics;
    combine(m.summation, m.count);
    return true;
}
public final void calculate() {  // 窗口终算
    this.value = this.summation / this.count;
}

summationcount 标了 @Column(storageOnly=true)(:41,46,不建索引只存值),value@Column(dataType=COMMON_VALUE)(:52,可查询的最终值)。combine 阶段只累加中间态,calculate 阶段才做除法——这样多实例合并时先把 sum 和 count 各自加起来,最后再除,结果正确。

2) PercentileMetrics2 —— 用 DataTable 算多分位数

// PercentileMetrics2.java:81-128
@Entrance
public final void combine(@SourceFrom int value, @Arg int precision) {
    String index = String.valueOf(value / precision);   // 把 latency 按 precision 分桶
    dataset.valueAccumulation(index, 1L);                 // 该桶计数 +1
}
public boolean combine(Metrics metrics) {
    this.dataset.append(percentileMetrics.dataset);       // DataTable 直接合并
    return true;
}
public final void calculate() {
    long total = dataset.sumOfValues();
    int[] roofs = {Math.round(total*50/100), ...99};      // 每个 rank 的累计计数阈值
    // 按 key(桶下标)升序累加,达到某 rank 的 roof 就记录该分位数值
    percentileValues.put(label.toString(), Long.parseLong(key) * precision);
}

RANKS = {50,75,90,95,99}(:48),一次 combine 同时算 5 个分位数。算法本质是桶计数 + 累加定位:每条 latency 除以 precision 落到某个桶,calculate 时把所有桶按 key 升序累加,累加值首次达到 total * p/100 的那个桶,其桶下标乘 precision 就是该分位数的近似值。precision 越小越精确,但桶数越多。

结果用 DataLabel{p=50} 作 key 存入 percentileValues 这个 DataTable,存储格式为 {p=50},value1|{p=75},value2|...(注释 :37-39)。

3) ApdexMetrics —— 带"档位"的满意度评分

// ApdexMetrics.java:75-100
@Entrance
public final void combine(@SourceFrom int value, @Arg String name, @Arg boolean status) {
    int t = DICT.lookup(name).intValue();   // 从 ConfigurationDictionary 取阈值 T
    int t4 = t * 4;
    totalNum++;
    if (!status || value > t4) return;       // 失败或 >4T 不计入满意
    if (value > t) tNum++;                   // (T, 4T] 是 Tolerating
    else sNum++;                              // [0, T] 是 Satisfied
}
public void calculate() {
    value = (int) ((sNum * 10000 + tNum * 10000 / 2) / totalNum);
}

Satisfied 权重 1.0,Tolerating 权重 0.5。结果乘 10000(万分级)避免浮点。DICT 是静态 ConfigurationDictionary,阈值通过 @Arg name 从配置查(:75-76)——不同服务可以有不同 T 阈值。

@MetricsExtension 注解

文件:oap-server/server-core/.../analysis/MetricsExtension.java:32

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME)
public @interface MetricsExtension {
    boolean supportDownSampling();   // 是否支持降采样(小时/天)
    boolean supportUpdate();         // 是否支持更新(而非只追加)
    boolean timeRelativeID() default false;  // ID 是否含时间戳
}

三个字段含义

  • supportDownSampling:true 表示该 metrics 能被 toHour()/toDay() 降采样。某些指标(如 max)降采样有意义,某些(如 percentile 的中间 dataset)降采样需特殊处��。
  • supportUpdate:true 表示可被更新(适合"当前值类"指标如 max/min),false 表示纯追加。
  • timeRelativeID:true 则实体 ID 带 timeBucket 前缀(如 202211081200-serviceId),典型用于 metadata 级 metrics 如 ServiceTraffic

DataTable 多值指标

DataTable 是什么
DataTable 是一个 HashMap<String,Long> 的包装,让一个 Metrics 实体在一个时间窗口内同时存多个值,是 percentile(多分位数)、histogram(多 bucket)、labelAvg/labelCount(多标签)的多值载体。

文件:oap-server/server-core/.../analysis/metrics/DataTable.java:41

public class DataTable implements StorageDataComplexObject<DataTable> {
    private HashMap<String, Long> data;
}

DataTable vs 单值 Metrics

维度 单值 Metrics(CountMetrics/LongAvgMetrics…) 多值 DataTable(Percentile2/Histogram/Label*)
一个时间窗口存 1 个最终值(value 列) N 个值(一个 HashMap)
combine 中间态 几个 long 字段(summation/count) 一个 DataTable(dataset)
calculate 做除法/比较,产出单值 遍历 dataset 产出另一个 DataTable
存储列标注 @Column(dataType=COMMON_VALUE) @Column(dataType=LABELED_VALUE/HISTOGRAM, multiIntValues=true)
OAL 返回类型 LongValueHolder/IntValueHolder LabeledValueHolder/MultiIntValuesHolder

多值指标怎么存:哈希结构 → 字符串

DataTable 不是直接把 HashMap 存库,而是序列化成字符串。原因在于底层存储(BanyanDB/ElasticSearch)的列式模型不接受 HashMap 类型,只能存标量或字符串。toStorageData()(DataTable.java:134)把整个 HashMap 压成一个字符串:

key1=value1,key2=value2,key3=value3

其中 ,Const.ARRAY_SPLIT,=Const.KEY_VALUE_SPLIT(DataTable.java:140,142)。反序列化 toObject()(:148)按 , 切分再按最后一个 = 切分 key/value。

当 key 本身是 DataLabel(如 {p=50})时,key 里也带 =,所以反序列化按最后一个 = 切,而不是第一个。存储形如 {p=50}=120|{p=75}=200|...(|Const.ARRAY_PARSER_SPLIT)。这正是 PercentileMetrics2 注释(:37-39)描述的格式。

这个设计的取舍:用字符串换通用性——任何后端都能存字符串,不必为每种多值结构定制列类型。代价是查询时要做一次解析,但多值指标本来就不是高频过滤字段,可接受。

combine 语义

DataTable 提供多种合并方式(DataTable.java:163-215):

方法 语义 行号
append(DataTable, maxDataSize) 同 key 值相加;key 不存在则新增(受 maxDataSize 限制) :171
copyFrom(source) 等价于 append :163
valueAccumulation(key, value, maxDataSize) 单 key 累加;超容量则丢弃新 key :79
setMaxValue(DataTable) 同 key 取较大 :187
setMinValue(DataTable) 同 key 取较小 :202
sumOfValues() 所有 value 求和(Percentile 算 total 用) :95
sortedKeys/sortedValues(Comparator) 按 key 排序取 keys/values(Percentile 排序找分位用) :106-115

典型 combine 流程(以 PercentileMetrics2 为例):

  1. 每来一条 Source:dataset.valueAccumulation(value/precision, 1L)——把延迟按精度分桶,桶计数 +1;
  2. 跨实例合并:this.dataset.append(other.dataset)——两个 DataTable 同桶累加;
  3. calculate:sumOfValues() 得总数 → 按 key 排序 → 累加到每个 rank 的 roof = total*p/100 时记录该分位值 → 写入 percentileValues 这个新 DataTable。

maxDataSize 参数用于 LabelCount/LabelAvg 这类"标签可能爆炸"的场景,限制最多保留多少个 label(默认 50,见 @DefaultValue("50"))。没有这个限制,一个高基数标签会把单条 Metrics 的 HashMap 撑爆内存。

在 OAL 里的使用

OAL 函数 对应类 DataTable 角色
percentile2 PercentileMetrics2 dataset(中间桶计数)+ percentileValues(结果 5 个分位值,key={p=50}等)
percentile(旧) PercentileMetrics 同上但结果用 int[](MultiIntValuesHolder),已废弃
histogram HistogramMetrics dataset(每个 bucket 一项,key=0/100/200...,value=计数),无单值
labelCount LabelCountMetrics dataset(中间按 label 计数)+ value(结果 {n=label},count)
labelAvg LabelAvgMetrics summation+count 两个 DataTable + value 结果 DataTable

DataLabel(metrics/DataLabel.java:28)是 DataTable 的 key 之一:它继承 LinkedHashMap<String,String>,toString() 生成 {k1=v1,k2=v2} 格式(:72-91)。预定义两个 label 名:

  • GENERAL_LABEL_NAME = "_"(DataLabel.java:29):无标签时的通用 key;
  • PERCENTILE_LABEL_NAME = "p"(:30):分位数标签 key。

buildLabelIndex()(DataTable.java:217)会把 DataTable 的 key 拆成倒排索引(label→keys),供按标签查询使用。

存储列类型标注

DataTable 字段在 @Column 上用特殊 dataType,告诉存储层这是多值结构:

  • Column.ValueDataType.LABELED_VALUE:带标签的多值(Percentile2、LabelCount、LabelAvg 的 value 列);
  • Column.ValueDataType.HISTOGRAM:直方图(HistogramMetrics 的 dataset,HistogramMetrics.java:45);
  • Column.ValueDataType.COMMON_VALUE:普通单值。

PercentileMetrics 旧版还用 multiIntValues=true(PercentileMetrics.java:59)表示多 int 值,新版改用 LABELED_VALUE + DataTable。


数据流总览(Source → Dispatcher → Metrics → Storage)

把上面三节串起来的完整数据流:

图3

  1. Receiver 收到上游数据 → 构造某个 Source 子类(如 Service)→ 调 SourceReceiver.receive();
  2. Source.prepare() 执行(补 ID 字段);
  3. SourceDecorator 执行(补 attr0~attr5 属性列);
  4. SourceDispatcher.dispatch(source)(接口在 analysis/SourceDispatcher.java:32)——分发器有两种:硬编码的(在 manual 包内)和 OAL 运行时按模板生成的(注释 :24-28)。它把 Source 字段映射成 Metrics 实例并触发 combine;
  5. Metrics 进入 MetricsStreamProcessor 的 session cache,同 entityId + timeBucket 的会调 Metrics.combine();
  6. 时间窗口结束/降采样时调 Metrics.calculate(),产出最终 valueDataTable;
  7. @MetricsExtension(supportDownSampling/supportUpdate) 决定是否降采样、是否 update;
  8. 通过 Metrics.id0() 生成 StorageID(注释 Metrics.java:162-167,BanyanDB 用 @BanyanDB.SeriesID 替代)落库。

关键文件速查

子系统 关键类 路径
Source 基类 ISource / Source oap-server/server-core/.../source/ISource.javaSource.java
Source 全家族 56 个子类 oap-server/server-core/.../source/*.java
Scope 注册 DefaultScopeDefine oap-server/server-core/.../source/DefaultScopeDefine.java
ScopeDeclaration 同目录 ScopeDeclaration.java
ScopeDefaultColumn 同目录 ScopeDefaultColumn.java
注解扫描 AnnotationScan oap-server/server-core/.../annotation/AnnotationScan.java
SourceDecorator ISourceDecorator / SourceDecoratorManager oap-server/server-core/.../analysis/ISourceDecorator.javaSourceDecoratorManager.java
Metrics 基类 Metrics oap-server/server-core/.../analysis/metrics/Metrics.java
Metrics 子类 18 个函数类 oap-server/server-core/.../analysis/metrics/*.java
MetricsExtension oap-server/server-core/.../analysis/MetricsExtension.java
DataTable DataTable / DataLabel oap-server/server-core/.../analysis/metrics/DataTable.javaDataLabel.java
Dispatcher SourceDispatcher oap-server/server-core/.../analysis/SourceDispatcher.java

未���认事项

  • source/ 目录的 Scope 类(BROWSER_、ZIPKIN_、PROFILE_*、ALARM、EVENT 等)的 @ScopeDeclaration 类位于 server-core 其它子包(manual/generic/browser/zipkin/profile 等),注册机制与 source/ 下的类完全相同,本篇未逐一展开其字段。
  • scopeId 7/14/15/16/77 在 DefaultScopeDefine 中无对应常量(被跳过),推测为历史废弃保留,源码无注释说明,未确认具体用途。

接下来

数据模型清楚了,这些数据到底怎么从 Agent 传到 OAP?协议层每条消息长什么样?详见 09-协议层与Agent采集。

心智模型
把数据模型想成工厂的数据字典:Source 是原材料规格书(56 种原料,每种贴一个 Scope 标签,归到 8 个 Catalog 大类),Metrics 是加工配方表(18 种配方,每种 combine 算中间态、calculate 出成品),DataTable 是多格包装盒(一个盒子能同时装 P50/P75/P90/P95/P99 五个值)。OAL 脚本就是工艺单,从某类原材料(Source)取某字段,用某配方(Metrics 函数)加工,产出成品(value 或 DataTable)。