【SkyWalking从入门到精通】第42篇:OAL语法全解析:声明式指标计算的优雅之舞
上一篇【第41篇】OAL是什么观察力分析语言的前世今生下一篇【第43篇】OAL实现原理从脚本到字节码的魔法之旅一、开篇一句话定义一个指标OAL的设计哲学是**“一句话一个指标”**。不夸张地说你甚至可以把OAL语法写在一张小纸条上——但它蕴含的能力足以覆盖99%的APM指标计算需求。让我们从最经典的例子开始// 这就是一个完整的OAL指标定义——代码级别 service_resp_time from Service.latency;就这么一行代码它在运行时做的事情包括把每个探针上报的Segment中属于同一个Service的数据聚合计算latency的平均值按1分钟/1小时/1天三个粒度分别存储自动生成可被Dashboard查询的指标现在让我们深入这片少即是多的语法世界。二、OAL文件的完整结构------------------------------------------------------------------ | 一个标准OAL文件的结构 | ------------------------------------------------------------------ | | | 1. 注释// 或 /* */ | | | | 2. import声明引入source类 | | import org.apache.skywalking.oap.server.core.source.*; | | import org.apache.skywalking.oap.server.core.source.Service; | | | | 3. filter定义可选定义过滤表达式 | | filter active_endpoints endpoint.avg 0; | | | | 4. 指标定义核心 | | service_resp_time from Service.latency; | | | | 5. disable语句禁用不需要的指标 | | disable(service_instance_throughput); | | | ------------------------------------------------------------------ 图1OAL文件的标准结构三、指标定义语法核心四元素一个完整的OAL指标定义由四个元素组成指标名称 from 数据源 . 聚合函数 ( 字段 ) ; | | | | | 唯一标识 关键字 数据从哪里来 怎么计算 末尾分号3.1 数据源SourceSource是OAL的食材。SkyWalking内置了以下标准Source类型// 最常用的Source类型Service// 服务级别的数据ServiceInstance// 服务实例级别的数据Endpoint// 端点级别的数据ServiceRelation// 服务间的调用关系EndpointRelation// 端点间的调用关系每个Source都有自己的属性字段Service { String name; // 服务名 int latency; // 响应时间(ms) boolean status; // 是否成功 int httpResponseStatusCode; // ... } Endpoint { String name; // 端点URI int latency; // 响应时间 boolean status; // 是否成功 // ... }3.2 聚合函数Aggregation FunctionsOAL支持的聚合函数覆盖了APM场景的全部需求函数说明典型场景存储开销.avg(field)平均值平均响应时间O(1).sum(field)累加值总调用次数O(1).max(field)最大值P100响应时间O(1).min(field)最小值最快响应O(1).count()计数调用次数O(1).longAvg(field)长整型平均值大数值平均O(1).doubleAvg(field)双精度平均值高精度平均O(1).p99(field)P99分位数99%用户响应O(k).p95(field)P95分位数95%用户响应O(k).p90(field)P90分位数90%用户响应O(k).p75(field)P75分位数75%用户响应O(k).p50(field)P50分位数/中位数典型响应O(k).histogram(field, ...)直方图分桶分布可视化O(n).thermodynamic(..)热力图响应分布O(n).percentile(field, [p1,p2..])自定义分位灵活分位数按需3.3 基本语法示例// 服务级别指标 // 服务调用次数service_cpmfromService.count();// 服务平均响应时间service_resp_timefromService.avg(latency);// 服务99分位响应时间service_p99fromService.p99(latency);// 服务成功率百分比service_slafromService.avg(statustrue?100:0);// 服务调用总数service_sum_callfromService.sum(calls);// 端点级别指标 // 端点调用次数endpoint_cpmfromEndpoint.count();// 端点平均响应时间endpoint_avgfromEndpoint.avg(latency);// 端点成功率endpoint_slafromEndpoint.avg(statustrue?100:0);// 实例级别指标 // 实例调用次数instance_cpmfromServiceInstance.count();// 实例JVM指标如果有instance_jvm_memory_heapfromServiceInstance.avg(heapUsed);instance_jvm_memory_maxfromServiceInstance.avg(heapMax);instance_jvm_cpufromServiceInstance.avg(cpuUsage);// 关系级别指标 // 服务间调用关系指标service_relation_client_cpmfromServiceRelation.count();service_relation_server_cpmfromServiceRelation.count();// 服务间调用延迟service_relation_client_call_slafromServiceRelation.avg(statustrue?100:0);// 端点间调用关系指标endpoint_relation_cpmfromEndpointRelation.count();四、Filter表达式数据的安检门Filter让你只对感兴趣的数据做聚合。它的语法类似于Java的三元表达式但更简洁// 语法格式 指标名 from Source.(filter表达式).聚合函数(字段); // 示例1只统计成功的调用 service_success_cpm from Service.(status true).count(); // 示例2只统计超过3秒的慢调用 slow_service_cpm from Service.(latency 3000).count(); // 示例3只统计特定HTTP状态码的调用 http_500_cpm from Service.(httpResponseStatusCode 500).count(); // 示例4过滤特定端点 user_login_cpm from Endpoint.(name /api/user/login).count(); // 示例5组合过滤条件 error_and_slow from Service.(status false latency 5000).count(); // 示例6使用字符串匹配 api_endpoint_cpm from Endpoint.(name like /api/%).count(); // 示例7排除健康检查端点 business_endpoint_cpm from Endpoint.(name ! /health name ! /metrics).count();Filter支持的操作符比较,!,,,,逻辑,||字符串like,not like算术,-,*,/五、内置OAL文件解读SkyWalking内置了几个OAL文件覆盖了所有默认的APM指标。我们来拆解最重要的service.oal// // 文件名: oal/official_analysis.oal// 位置: oap-server/server-starter/src/main/resources///// 这是SkyWalking最核心的OAL文件// // 服务级别指标 // 1. 服务调用次数每1分钟/小时/天service_cpmfromService.count();// 2. 服务平均响应时间service_resp_timefromService.avg(latency);// 3. 服务成功率statustrue表示成功service_slafromService.avg(statustrue?100:0);// 4. 服务调用次数累加service_sum_callfromService.sum(1);// 5. 服务P99/P95/P90/P75/P50分位响应时间service_percentilefromService.percentile(latency,50,75,90,95,99);// 6. 服务响应时间直方图4个桶: 0-500ms, 500-2000ms, 2000-5000ms, 5000msservice_resp_time_histogramfromService.histogram(latency,500,2000,5000);// 服务实例级别 // 7. 实列的调用次数service_instance_cpmfromServiceInstance.count();// 8. 实例成功率service_instance_slafromServiceInstance.avg(statustrue?100:0);// 9. 实例平均响应时间service_instance_resp_timefromServiceInstance.avg(latency);// 端点级别 // 10. 端点调用次数endpoint_cpmfromEndpoint.count();// 11. 端点平均响应时间endpoint_avgfromEndpoint.avg(latency);// 12. 端点成功率endpoint_slafromEndpoint.avg(statustrue?100:0);// 端点慢调用 // 13. 端点P99分位用于识别慢端点endpoint_percentilefromEndpoint.percentile(latency,50,75,90,95,99);// 关系级别指标 // 14. 服务间客户端调用次数service_relation_client_cpmfromServiceRelation.count();// 15. 服务间服务端调用次数service_relation_server_cpmfromServiceRelation.count();// 16. 服务间成功率service_relation_client_call_slafromServiceRelation.avg(statustrue?100:0);// 17. 端点间调用次数endpoint_relation_cpmfromEndpointRelation.count();// 18. 端点间成功率endpoint_relation_slafromEndpointRelation.avg(statustrue?100:0);// 慢服务和慢端点监测 // 19. 服务超过3秒的调用慢调用监测slow_service_callfromService.(latency3000).count();// 20. 端点超过3秒的调用slow_endpoint_callfromEndpoint.(latency3000).count();六、高级语法特性6.1 disable语句当你不需要某个内置指标时可以禁用它以节省计算和存储资源// 禁用不需要的指标 disable(service_instance_throughput); disable(endpoint_percentile); // 注意disable语句必须在指标被定义之后 // 正确的顺序 endpoint_avg from Endpoint.avg(latency); disable(endpoint_avg); // 放在定义之后6.2 字符串匹配// like 支持 % 通配符 api_calls from Endpoint.(name like /api/%).count(); // not like 排除 non_health_calls from Endpoint.(name not like /health/%).count();6.3 多值聚合// histogram需要指定分桶边界 // 格式histogram(字段, 边界1, 边界2, 边界3, ...) response_histogram from Service.histogram(latency, 100, // 100ms 500, // 500ms 2000, // 2000ms 10000 // 10000ms ); // 10000ms 自动归入最后一个桶 // percentile可以指定任意分位点 custom_percentile from Service.percentile(latency, 50, // P50 90, // P90 95, // P95 99, // P99 99.9 // P999 );七、OAL语法速查卡 | OAL 语法速查卡 | | | | 【基本格式】 | | 指标名 from 数据源.聚合函数(字段); | | 指标名 from 数据源.(过滤).聚合函数(字段); | | | | 【常用聚合函数】 | | .avg(field) 平均值 | | .sum(field) 求和 | | .max(field) 最大值 | | .min(field) 最小值 | | .count() 计数 | | .p50/p75/p90/p95/p99(field) 分位数 | | .percentile(field, 50,90,95,99) 自定义分位 | | .histogram(field, 100,500,2000) 直方图分桶 | | | | 【Filter操作符】 | | ! | | || like not like | | | | 【控制语句】 | | disable(指标名); 禁用指标 | | import 路径; 引入Source类 | | | 图2OAL语法速查卡——贴在显示器旁随时查阅八、实战练习试着写下以下场景的OAL定义Q1统计支付接口/api/payment每分钟的错误次数// 方法1用Filterpayment_error_cpmfromEndpoint.(name/api/paymentstatusfalse).count();// 方法2用成功率反推100-SLA得到错误率payment_slafromEndpoint.(name/api/payment).avg(statustrue?100:0);Q2统计超过5秒的超慢调用数量super_slow_callsfromService.(latency5000).count();Q3按服务统计不同响应时间段的分布直方图service_latency_distributionfromService.histogram(latency,10,// 10ms (极快)50,// 50ms200,// 200ms1000,// 1s3000// 3s);// 3s (慢调用)九、总结OAL的语法设计体现了少即是多的哲学。它只有一种语句格式、十几个聚合函数、几个操作符——但能拼出几乎所有APM指标计算的需求。关键要点数据源Source 聚合函数 Filter 完整的指标定义Filter是可选的安检门用Java语法风格过滤数据分位数函数p50-p99开销高于平均/求和按需使用disable语句可以关闭不需要的内置指标内置OAL文件是学习的最佳范本下一篇我们将深入OAL的编译器实现原理揭开Antlr4语法解析和Java字节码生成的奥秘。上一篇【第41篇】OAL是什么观察力分析语言的前世今生下一篇【第43篇】OAL实现原理从脚本到字节码的魔法之旅