- Mock
- 测试
【免费下载链接】moto
A library that allows you to easily mock out tests based on AWS infrastructure.
导读
本文聚焦开源项目 Moto 中sagemaker-metrics(SageMaker Metrics)服务模块的模拟实现,讲解如何在本地开发与测试环境中通过 Moto 模拟 Amazon SageMaker 实验追踪指标上报(BatchPutMetrics)接口。读完本文,你将掌握该服务的启用方式、batch_put_metrics的请求/响应结构、与 SageMaker Trial Component 共享的底层数据模型,以及如何借助现有测试用例验证模拟行为。
一、服务定位:SageMaker Metrics 在 Moto 中模拟了什么
sagemaker-metrics是 Amazon SageMaker 的一个辅助服务,负责接收实验运行(Trial Component)的训练指标上报,供 SageMaker 实验追踪功能聚合展示。Moto 在 moto/sagemakermetrics/ 目录下实现了该服务的模拟后端,其官方服务文档见 docs/docs/services/sagemaker-metrics.rst。
该文档中.. autoclass:: moto.sagemakermetrics.models.SageMakerMetricsBackend直接引用源码中的后端类作为文档主体,并给出了功能实现清单:
[ ] batch_get_metrics—— 未实现(批量读取指标,暂不支持);[X] batch_put_metrics—— 已实现(批量上报指标)。
也就是说,当前 Moto 对 sagemaker-metrics 的支持集中在“写入”一侧:可以把指标写入对应 SageMaker Trial Component,并能在之后通过 SageMaker 的describe_trial_component读到聚合结果,但暂不提供独立的批量查询 API。
二、模块结构:五个文件的职责划分
sagemaker-metrics模块遵循 Moto 每个 AWS 服务一贯的分层架构,包含五个文件:
| 文件 | 职责 |
|---|---|
| moto/sagemakermetrics/init.py | 导出后端单例sagemakermetrics_backends |
| moto/sagemakermetrics/models.py | 定义SageMakerMetricsBackend,实现业务逻辑 |
| moto/sagemakermetrics/responses.py | 定义SageMakerMetricsResponse,解析 HTTP 请求并序列化响应 |
| moto/sagemakermetrics/urls.py | 注册服务 URL 路由与 dispatch 入口 |
| moto/sagemakermetrics/exceptions.py | 服务相关异常定义 |
路由层 urls.py 定义了模拟服务的接入点:URL 基础为https://metrics.sagemaker.{region}.amazonaws.com,支持两个路径——根路径{0}/$以及操作路径{0}/BatchPutMetrics$,二者都交给SageMakerMetricsResponse.dispatch统一分发。这意味着当你的 boto3 客户端以sagemaker-metrics为服务名发起调用时,Moto 的 URL 拦截机制会把请求路由到该响应处理器。
三、核心 API:batch_put_metrics 的完整调用链
3.1 请求处理入口
响应层 responses.py 中,SageMakerMetricsResponse通过sagemakermetrics_backends[self.current_account][self.region]获取当前账号、当前区域的 backend 实例(这与 Moto 的多账号/多区域隔离机制一致)。batch_put_metrics方法从请求体提取两个参数:
TrialComponentName:目标实验组件的名称;MetricData:指标数据列表。
随后调用后端方法,并将返回的errors以 JSON 字符串返回。请求处理返回的是json.dumps(errors),其中Errors字段默认是空列表,仅在校验失败时填充错误项。
3.2 后端业务逻辑
后端 models.py 中的SageMakerMetricsBackend是核心实现,其batch_put_metrics方法的处理流程如下:
- 校验 Trial Component 是否存在:通过
self.sagemaker_backend.trial_components字典查找;若不存在,则在返回结果中追加{"Code": "VALIDATION_ERROR", "MetricIndex": 0}并提前返回; - 写入或初始化指标:对每条
MetricData,取出Step(步数)与MetricName(指标名)。若指标名在 Trial Component 中尚不存在,则以MetricName、Timestamp(时间戳)和空的Values字典初始化一条新指标; - 按 Step 追加数据点:以
Step为键、原始指标数据为值,更新Values字典。因此同一指标名下的不同 Step 会各自保留数据点,为后续聚合计算提供原始样本。
值得注意的实现细节是:SageMakerMetricsBackend在初始化时直接引用了同区域同账号的 SageMaker backend(sagemaker_backends[account_id][region_name]),这意味着sagemaker-metrics与sagemaker两个服务共享同一个trial_components状态存储——正是这种共享设计,使得通过batch_put_metrics写入的指标可以立即被 SageMaker 的describe_trial_component读取。
3.3 底层指标数据结构
batch_put_metrics依赖 moto/sagemaker/models.py 中定义的类型别名与FakeTrialComponent模型:
METRIC_INFO_TYPE = dict[str, str | int | float | datetime]:单条指标信息;METRIC_STEP_TYPE = dict[int, METRIC_INFO_TYPE]:以 Step 为键的指标数据点集合;FakeTrialComponent.metrics:Trial Component 对象上的指标字典,结构为{"MetricName": {"MetricName": ..., "Timestamp": ..., "Values": {step: metric}}}(见 models.py)。
聚合逻辑在FakeTrialComponent.gen_metrics_response_object()(见 models.py)中完成:它取出每个指标名下全部 Step 的Value,计算Max、Min、Last(最大 Step 对应的值)、Count、Avg(均值)与StdDev(标准差),并将Timestamp转换为带 UTC 时区的datetime。也就是说,Moto 在收到指标后不仅保存原始数据,还会按 SageMaker 的语义实时生成统计聚合字段。
四、实战:用 boto3 上报指标并读取聚合结果
以下示例直接取自仓库测试 tests/test_sagemakermetrics/test_sagemakermetrics.py,展示了完整的“创建 Trial Component → 上报指标 → 读取聚合”闭环:
import datetime import boto3 from moto import mock_aws @mock_aws def test_batch_put_metrics(): trial_component_name = "some-trial-component-name" # 1. 在 SageMaker 中创建实验组件 client_sagemaker = boto3.client("sagemaker", region_name="eu-west-1") client_sagemaker.create_trial_component(TrialComponentName=trial_component_name) # 上报前的指标列表为空 describe_before_metrics = client_sagemaker.describe_trial_component( TrialComponentName=trial_component_name ) assert describe_before_metrics["Metrics"] == [] # 2. 通过 sagemaker-metrics 客户端上报指标 client = boto3.client("sagemaker-metrics", region_name="eu-west-1") given_datetime = datetime.datetime(2024, 4, 21, 0, 0, 0) resp = client.batch_put_metrics( TrialComponentName=trial_component_name, MetricData=[ { "MetricName": "some-metric-name", "Timestamp": given_datetime, "Step": 0, "Value": 123.0, }, ], ) # 3. 校验响应:HTTP 200 且无错误 assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200 assert resp["Errors"] == [] # 4. 通过 describe_trial_component 读取聚合结果 describe_after_metrics = client_sagemaker.describe_trial_component( TrialComponentName=trial_component_name ) metric = describe_after_metrics["Metrics"][0] assert metric["MetricName"] == "some-metric-name" assert ( metric["SourceArn"] == "arn:aws:sagemaker:eu-west-1:123456789012:experiment-trial-component/some-trial-component-name" ) assert metric["TimeStamp"] == given_datetime.replace(tzinfo=datetime.timezone.utc) assert metric["Max"] == metric["Min"] == metric["Last"] == metric["Avg"] == 123.0 assert metric["Count"] == 1 assert metric["StdDev"] == 0.0关键观察点:
batch_put_metrics的MetricData元素包含四个字段:MetricName、Timestamp(datetime 类型)、Step(整数步数)与Value(浮点值);- 成功时响应中的
Errors为空列表; - 上报后指标立即出现在
describe_trial_component的Metrics列表中,SourceArn按arn:aws:sagemaker:{region}:{account_id}:experiment-trial-component/{name}格式生成,统计字段(Max/Min/Last/Count/Avg/StdDev)均与源码中的聚合逻辑一一对应。
五、错误路径:Trial Component 不存在时返回 VALIDATION_ERROR
仓库测试同样覆盖了校验失败的分支(见 test_sagemakermetrics.py):当TrialComponentName指向一个尚未创建的组件时,batch_put_metrics不会抛出异常,而是在响应体Errors数组中返回{"Code": "VALIDATION_ERROR", "MetricIndex": 0}:
@mock_aws def test_batch_put_metrics_should_return_validation_error_if_trial_component_not_found(): trial_component_name = "some-trial-component-name-not-existing" client = boto3.client("sagemaker-metrics", region_name="eu-west-1") resp = client.batch_put_metrics( TrialComponentName=trial_component_name, MetricData=[ { "MetricName": "some-metric-name", "Timestamp": datetime.datetime(2015, 1, 1), "Step": 0, "Value": 123.0, } ], ) assert resp.get("Errors") is not None assert resp["Errors"][0]["Code"] == "VALIDATION_ERROR"这一行为与源码中“先在trial_components中查找,未命中则填充VALIDATION_ERROR并提前返回”的逻辑完全吻合。开发者在编写测试时可以利用该行为断言异常分支,而不必依赖真实 AWS 环境。
六、服务模式验证:独立 Server 与 URL 路由
除了装饰器模式(@mock_aws),Moto 还支持以独立 Server 方式运行服务。仓库中的 tests/test_sagemakermetrics/test_server.py 验证了该服务的 HTTP 路由与响应行为:
import moto.server as server def test_sagemakermetrics_batch_put_metrics(): backend = server.create_backend_app("sagemaker-metrics") test_client = backend.test_client() resp = test_client.put("/BatchPutMetrics") assert resp.status_code == 200 assert "VALIDATION_ERROR" in str(resp.data)该测试说明:即使请求体为空、Trial Component 不存在,对PUT /BatchPutMetrics的请求依然返回 HTTP 200,并在响应体中携带VALIDATION_ERROR——这印证了模拟服务对不合法请求采用“业务级错误码而非 HTTP 错误码”的兼容策略,与真实 SageMaker Metrics 的行为保持一致。
七、注册机制与使用前提
sagemaker-metrics服务通过BackendDict(SageMakerMetricsBackend, "sagemaker-metrics")注册进 Moto 的后端索引(见 models.py),并出现在 moto/backends.py 的服务列表中。使用时无需任何额外安装步骤:只要项目已安装moto,在测试中引入from moto import mock_aws并创建boto3.client("sagemaker-metrics")即可。
需要说明的前提与限制:
batch_get_metrics尚未实现,若代码依赖该接口会得到 NotImplemented 之类的错误,需自行规避或等待后续版本补齐;- 指标数据依赖 SageMaker 的 Trial Component 存在性:必须先通过
sagemaker客户端(或等价的模拟路径)创建对应组件,否则写入会返回VALIDATION_ERROR; - 跨账号/跨区域的状态隔离由 Moto 的
BackendDict保证,测试中应保持创建组件与上报指标使用相同的region_name。
八、小结
moto的 sagemaker-metrics 模块提供了一个轻量但自洽的模拟实现:以SageMakerMetricsBackend为核心,通过共享 SageMaker 后端状态实现了batch_put_metrics的写入与聚合查询闭环,并以VALIDATION_ERROR模拟真实服务的校验语义。对于需要离线测试 SageMaker 实验追踪指标管道的开发者,可参照本文的示例与测试用例,将指标上报流程无缝纳入本地 CI。
- Mock
- 测试
【免费下载链接】moto
A library that allows you to easily mock out tests based on AWS infrastructure.
相关推荐
Moto 的 bedrock-runtime 服务模拟:invoke_model 实现原理与测试实践
Moto 的 bedrock runtime 服务模拟:invoke_model 实现原理与测试实践 本文以 Moto 开源仓库中 docs/docs/serv
Mock测试Moto 中 Amazon Managed Prometheus(amp)服务的模拟实现与实战指南
Moto 中 Amazon Managed Prometheus(amp)服务的模拟实现与实战指南 Amazon Managed Prometheus(AMP,
Mock测试moto 中 emr-containers 服务的模拟实现:虚拟集群与作业运行的完整 Mock 指南
moto 中 emr containers 服务的模拟实现:虚拟集群与作业运行的完整 Mock 指南 在基于 AWS EMR on EKS(即 emr cont
Mock测试
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考