Ceilometer/blueprints/api-group-by
目录
总结
增强 API v2,使其实现新的参数来执行 GROUP BY 操作,以计算计量统计信息。
如果用户请求查询过滤和/或周期分组,这些操作将首先应用,然后其次应用 GROUP BY 操作。
用户故事
我运行了一个实例 6 小时。它在最初的 2 小时作为 m1.tiny flavor 启动,然后在接下来的 4 小时增长到 m1.large flavor。我需要获取这两个持续时间,以便使用不同的费率进行计费。
设计示例
例如,添加
g[]=<field name>
这使用以下方式解决了上述用户故事
/v2/meters/instance/statistics? q[0].field=resource& q[0].op=eq& q[0].value=<my-resource-id>& q[1].field=timestamp& q[1].op=lt& q[1].value=<now>& q[2].field=timestamp& q[2].op=gt& q[2].value=<now - 6 hours>& g[0]=metadata.flavor& period=360
将返回
{[
{ "m1.tiny": { "min": 1, "max": 1, "avg": 1, "sum": 1 },
{ "m1.tiny": { "min": 1, "max": 1, "avg": 1, "sum": 1 } },
{ "m1.large": { "min": 1, "max": 1, "avg": 1, "sum": 1 } },
{ "m1.large": { "min": 1, "max": 1, "avg": 1, "sum": 1 } },
{ "m1.large": { "min": 1, "max": 1, "avg": 1, "sum": 1 } },
{ "m1.large": { "min": 1, "max": 1, "avg": 1, "sum": 1 } },
]}
此外,删除仅将搜索范围缩小到单个资源的 q[0] 请求,可以检索该期间内所有实例的信息
/v2/meters/instance/statistics? q[0].field=timestamp& q[0].op=lt& q[0].value=<now>& q[1].field=timestamp& q[1].op=gt& q[1].value=<now - 6 hours>& g[0]=metadata.flavor& g[1]=resource& period=360
如果有另一个大型实例,将返回
{[
{ "m1.tiny": { "min": 1, "max": 1, "avg": 1, "sum": 1 }, "m1.large": { "min": 1, "max": 1, "avg": 1, "sum": 1 } },
{ "m1.tiny": { "min": 1, "max": 1, "avg": 1, "sum": 1 }, "m1.large": { "min": 1, "max": 1, "avg": 1, "sum": 1 } },
{ "m1.large": { "min": 1, "max": 1, "avg": 1, "sum": 2 } },
{ "m1.large": { "min": 1, "max": 1, "avg": 1, "sum": 2 } },
{ "m1.large": { "min": 1, "max": 1, "avg": 1, "sum": 2 } },
{ "m1.large": { "min": 1, "max": 1, "avg": 1, "sum": 2 } },
]}
Angus 的评论/想法
1) 我假设我们不能按多个字段分组?如果是这样,这应该(不是数组)
groupby=metadata.flavor&
你可以按多个字段分组,请参阅第二个示例 -- jd
2) period 尚未实现。我最好去实现它 ;)
3) 目前我们返回
{[
{ "min": 1,
"max": 1,
"avg": 1,
"sum": 1,
"count": 1,
"duration": 1,
},
]}
为了显示 groupby,我们可以返回以下内容
{[
{ "min": 1,
"max": 1,
"avg": 1,
"sum": 1,
"count": 1,
"duration": 1,
"groupby": "m1.tiny",
},
]}
如果没有 groupby,那就可以是 None。
我同意,但你可能想要 "groupby": [ "m1.tiny" ] 因为你可以按多个值分组。 -- jd
我们可能希望将其映射到字段名称及其值的映射。{'metadata.instance_type': 'm1.tiny'} -- dhellmann
4) 从实现的角度来看 (mongo) 我们有
MAP_STATS = bson.code.Code("""
function () {
- emit('statistics', { min : this.counter_volume,
+ emit(groupby_field, { min : this.counter_volume,
max : this.counter_volume,
qty : this.counter_volume,
count : 1,
timestamp_min : this.timestamp,
timestamp_max : this.timestamp } )
}
""")
如果我们可以将 groupby 字段传递到上面的函数,这将非常容易。我们能动态生成这个 bcode 吗?
我认为你可以 :) -- jd
我们需要小心注入攻击。 -- dhellmann
设计说明
通用评论
Metadata 字段
决定不为 metadata 字段实现分组,并在以后日期进行此操作。
参数应用顺序
计量统计信息可以使用三个参数调用:查询过滤器、周期和 groupby。每个参数对应一个操作。重要的是要注意应用这些操作的顺序。查询过滤始终首先应用。那么周期和分组呢?
由于周期分组基本上是时间范围的分组,当同时请求周期和对其他字段的分组时,存在歧义。可以想象
- 首先应用周期分组,然后对其他字段进行分组
- 首先对字段进行分组,然后应用周期分组
我们选择实现第一种可能性,即首先执行周期分组。
总而言之,应用顺序是
- 查询过滤器
- 周期分组
- 对其他字段进行分组
存储驱动程序测试以检查分组统计
已解决:https://review.openstack.org/41597 “添加 SQLAlchemy 分组实现”
在 tests/storage/base.py 中创建了一个新的类 StatisticsGroupByTest,其中包含分组统计的存储测试,并拥有自己的测试数据
存储测试检查分组统计信息,用于
- 单个字段,“user-id”
- 单个字段,“resource-id”
- 单个字段,“project-id”
- 单个字段,“source”
- 单个字段,无效/未知的字段值
- 单个 metadata 字段(尚未实现)
- 多个字段
- 多个 metadata 字段(尚未实现)
- 多个混合字段,常规和 metadata(尚未实现)
- 单个字段 groupby 与查询过滤器
- 单个 metadata 字段 groupby 与查询过滤器(尚未实现)
- 多个字段 groupby 与多个查询过滤器
- 多个 metadata 字段 groupby 与多个查询过滤器(尚未实现)
- 单个字段与周期
- 单个 metadata 字段与周期(尚未实现)
- 单个字段与查询过滤器和周期
- 单个 metadata 字段与查询过滤器和周期(尚未实现)
测试数据构建为测量值为整数(由样本的“volume”属性指定),并且统计信息中的平均值也是整数。这有助于避免在测试中检查统计属性(例如 min、max、avg)时出现浮点错误。
目前,metadata groupby 测试尚未实现。支持 metadata 字段是一个更复杂的情况,因此我们将其留给未来的工作。测试数据包含 metadata 字段,作为未来 metadata groupby 工作的基础。
groupby 周期测试和测试数据构建为,存在没有样本的周期。对于 groupby 周期测试,统计信息计算为周期 10:11 - 12:11、12:11 - 14:11、14:11 - 16:11 和 16:11 - 18:11。但是,在 12:11 - 14:11 期间没有带有时间戳的样本。重要的是要考虑这种情况,以检查存储驱动程序在周期内没有样本时是否行为正确。
SQL Alchemy 分组实现
已解决:https://review.openstack.org/41597 “添加 SQLAlchemy 分组实现”
决定仅为“user-id”、“resource-id”和“project-id”字段实现分组。不支持“source”和 metadata 字段。事实证明,在 SQL Alchemy 中支持“source”比“user-id”、“resource-id”和“project-id”复杂得多。
MongoDB 驱动程序分组实现
已解决:https://review.openstack.org/43043 “为 MongoDB 驱动程序添加分组统计信息”
为 MongoDB 驱动程序添加分组计量统计信息,对于 groupby 字段是“user-id”、“resource-id”、“project-id”和“source”的组合(如上述“通用设计评论”部分所述,未实现 metadata 字段)
聚合方法设计
摘要:决定继续使用 mapReduce() MongoDB 聚合方法,即使 API 中有其他选项。
MongoDB 聚合命令有三种类型:aggregate()、mapReduce()、group()
MongoDB 手册对这三种类型进行了比较。[1]
显然,MongoDB 现在建议尽可能使用 aggregate()(aka “聚合管道”,MongoDB 版本 2.2 之后的新功能)
“对于大多数聚合操作,聚合管道提供更好的性能和更连贯的接口。但是,map-reduce 操作提供了一些聚合管道目前不可用的灵活性。”[2]
Ceilometer 当前使用 mapReduce() 方法计算计量统计信息,但我们可以设想切换到使用 aggregate() 或 group()。
决定坚持使用 mapReduce(),因为它最灵活。mapReduce() 可以支持非标准聚合运算符(不是 min、max、avg 等的操作),而 aggregate() 则不能。
例如,在“API v2 的改进”蓝图中,[3] 建议进行改进
“提供额外的统计函数(偏差、中位数、方差、分布、斜率等),这些函数可以作为给定数据集集合的多个结果”
诸如偏差和中位数之类的函数不是 MongoDB 聚合管道 aggregate() 中的标准聚合运算符。
group() 聚合命令比 mapReduce() 灵活性较差,性能比 `aggregate` 慢,并且不支持分片集合(即跨多台服务器分布的数据库)
此外,对于 aggregate() 和 group() 方法,结果集必须适合最大的 BSON 文档大小限制(16 MB)。但是
“此外,map-reduce 操作可以具有超过聚合管道 16 兆字节输出限制的输出集。”[4]
Map 函数设计
在 MongoDB 中实现分组统计信息很简单。统计信息使用 mapReduce() 方法计算。mapReduce() 在 Ceilometer 中实现的方式是,mapReduce() 需要一个 map() 函数、一个 reduce() 函数和一个 finalize() 函数。[5]
要计算 MongoDB 中的计量统计信息,需要考虑四种情况
- 没有周期,没有分组
- 仅周期
- 仅分组
- 周期和分组
可以使用略微不同的 map 函数,使用相同的 reduce 和 finalize 函数来实现所有情况。map() 函数通过处理每个文档并发出键值对来工作。每种情况都需要不同的键。
- 没有周期,没有分组 --> 键可以是任何东西,只要它是一个常量,例如 'statistics'
- 仅周期 --> 键是变量“period_start”
- 仅分组 --> 键是变量“groupby”
- 周期和分组 --> 键是变量“period_start”和“groupby”的组合
然后,我们只需要将正确的“groupby”、“period_start”和“period_end”对象的值传递到发出的值中。
尝试通过在 map 函数 MAP_STATS、MAP_STATS_PERIOD、MAP_STATS_GROUPBY、MAP_STATS_PERIOD_GROUPBY 中尽可能多地使用字符串替换来最大程度地减少重复代码
API 测试以检查分组统计
已解决:https://review.openstack.org/44130 “在 API v2 测试中添加分组统计测试”
添加分组统计的 API 测试
API 分组统计测试位于 tests/api/v2/test_statistics_scenarios.py 中的一个新类 StatisticsGroupByTest 中
实现的测试是分组
- 单个字段,“user-id”
- 单个字段,“resource-id”
- 单个字段,“project-id”
- 单个字段,“source” (*)
- 单个字段,无效/未知的字段值
- 多个字段
- 单个字段 groupby 与查询过滤器
- 多个字段 groupby 与多个查询过滤器
- 单个字段,起始时间戳在所有样本之后
- 单个字段,结束时间戳在所有样本之前
- 单个字段,起始时间戳
- 单个字段,结束时间戳
- 单个字段,起始和结束时间戳
- 单个字段,起始和结束时间戳和查询过滤器
- 单个字段,起始和结束时间戳和周期
- 单个字段,起始和结束时间戳、查询过滤器和周期
(*) 由于 SQLAlchemy 目前不支持按 source 分组,因此我们必须将此测试放在其自己的类 TestGroupBySource 中
测试使用与 tests/storage/test_storage_scenarios.py 中 StatisticsGroupByTest 类中的 groupby 存储测试相同的数据和测试用例
目前未实现按 metadata 字段分组,因此没有 metadata 字段的测试。
为 test_query.py 中的新函数 _validate_groupby_fields() 添加测试
ceilometer/api/controllers/v2.py 中添加了一个新函数 _validate_groupby_fields(),因此需要对其进行测试。将测试放在 tests/api/v2/test_query.py 中是合乎逻辑的
测试检查有效字段、无效字段和重复字段。
在 test_compute_duration_by_resource_scenarios.py 中添加 groupby 参数到 stub 中
在 tests/api/v2/test_compute_duration_by_resource_scenarios.py 中,函数 _stub_interval_func() 对 get_meter_statistics() 进行 stub。由于 get_meter_statistics() 函数现在接受 groupby 参数,因此 stub 也应该具有 groupby 参数。
将额外的参数 groupby 添加到函数 get_interval()
修改 get_json() 以接受 groupby 参数
ceilometer/tests/api.py 中的方法 get_json() 模拟 HTTP GET 请求以进行测试。它已被修改为接受 groupby 参数。
API 分组统计实现
已解决:https://review.openstack.org/44130 “在 API v2 测试中添加分组统计测试”
以下添加到 ceilometer/api/controllers/v2.py
将 groupby 属性添加到 Statistics 类
API 具有一个 Statistics 类,该类保存从 meter/meter_name/statistics 请求计算的所有统计信息。该类已更新为包含一个 groupby 属性,用于分组,以便我们知道统计信息与哪个组关联。例如,如果我们请求按 user_id 分组,"groupby" 可能是 {'user_id': 'user-1'},表示这些是所有具有 'user-1' 的样本的统计信息。
将 groupby 参数添加到 API 方法 statistics()
API 具有一个方法 statistics(),该方法在用户提交形式为“meter/meter_name/statistics”的 HTTP GET 请求时调用
此方法已更新,以便可以接受 groupby 参数,例如
/v2/meters/instance/statistics?groupby=user_id&groupby=source
groupby 字段假定为 Unicode 字符串,因此传递给 statistics() 的 groupby 参数是 Unicode 字符串列表。对于上面的示例,groupby 参数将是 ['user_id', 'source']
API 方法 statistics() 然后使用新方法 _validate_groupby_fields() 验证 groupby 字段,如果字段有效,则调用与当前存储驱动程序对应的 get_meter_statistics() 方法,并将这些 groupby 字段传递给它。
方法 _validate_groupby_fields() 验证 groupby 参数并删除重复字段。此方法很有用,因为它会在提供无效字段时抛出错误,即不在集合 ['user_id', 'resource_id', 'project_id', 'source'] 中的字段。请注意,重复字段使用 list(set(groupby_fields)) 删除,这不会保留 groupby 字段的顺序。因此,如果一个请求
/v2/meters/instance/statistics?groupby=user_id&groupby=source
发出,顺序可能会从 ['user_id', 'source'] 切换到 ['source', 'user_id']
参考文献
- ↑ MongoDB 手册 - 聚合命令比较
- ↑ MongoDB 手册 - 聚合概念:Map-Reduce
- ↑ Ceilometer 蓝图“API v2 改进”
- ↑ MongoDB 手册 - 聚合简介
- ↑ MongoDB 手册 - mapReduce