跳转到: 导航, 搜索

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。每个参数对应一个操作。重要的是要注意应用这些操作的顺序。查询过滤始终首先应用。那么周期和分组呢?

由于周期分组基本上是时间范围的分组,当同时请求周期和对其他字段的分组时,存在歧义。可以想象

  1. 首先应用周期分组,然后对其他字段进行分组
  2. 首先对字段进行分组,然后应用周期分组


我们选择实现第一种可能性,即首先执行周期分组。

总而言之,应用顺序是

  1. 查询过滤器
  2. 周期分组
  3. 对其他字段进行分组

存储驱动程序测试以检查分组统计

已解决:https://review.openstack.org/41597 “添加 SQLAlchemy 分组实现”

在 tests/storage/base.py 中创建了一个新的类 StatisticsGroupByTest,其中包含分组统计的存储测试,并拥有自己的测试数据

存储测试检查分组统计信息,用于

  1. 单个字段,“user-id”
  2. 单个字段,“resource-id”
  3. 单个字段,“project-id”
  4. 单个字段,“source”
  5. 单个字段,无效/未知的字段值
  6. 单个 metadata 字段(尚未实现)
  7. 多个字段
  8. 多个 metadata 字段(尚未实现)
  9. 多个混合字段,常规和 metadata(尚未实现)
  10. 单个字段 groupby 与查询过滤器
  11. 单个 metadata 字段 groupby 与查询过滤器(尚未实现)
  12. 多个字段 groupby 与多个查询过滤器
  13. 多个 metadata 字段 groupby 与多个查询过滤器(尚未实现)
  14. 单个字段与周期
  15. 单个 metadata 字段与周期(尚未实现)
  16. 单个字段与查询过滤器和周期
  17. 单个 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 中的计量统计信息,需要考虑四种情况

  1. 没有周期,没有分组
  2. 仅周期
  3. 仅分组
  4. 周期和分组


可以使用略微不同的 map 函数,使用相同的 reduce 和 finalize 函数来实现所有情况。map() 函数通过处理每个文档并发出键值对来工作。每种情况都需要不同的键。

  1. 没有周期,没有分组 --> 键可以是任何东西,只要它是一个常量,例如 'statistics'
  2. 仅周期 --> 键是变量“period_start”
  3. 仅分组 --> 键是变量“groupby”
  4. 周期和分组 --> 键是变量“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 中

实现的测试是分组

  1. 单个字段,“user-id”
  2. 单个字段,“resource-id”
  3. 单个字段,“project-id”
  4. 单个字段,“source” (*)
  5. 单个字段,无效/未知的字段值
  6. 多个字段
  7. 单个字段 groupby 与查询过滤器
  8. 多个字段 groupby 与多个查询过滤器
  9. 单个字段,起始时间戳在所有样本之后
  10. 单个字段,结束时间戳在所有样本之前
  11. 单个字段,起始时间戳
  12. 单个字段,结束时间戳
  13. 单个字段,起始和结束时间戳
  14. 单个字段,起始和结束时间戳和查询过滤器
  15. 单个字段,起始和结束时间戳和周期
  16. 单个字段,起始和结束时间戳、查询过滤器和周期

(*) 由于 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']

参考文献

  1. MongoDB 手册 - 聚合命令比较
  2. MongoDB 手册 - 聚合概念:Map-Reduce
  3. Ceilometer 蓝图“API v2 改进”
  4. MongoDB 手册 - 聚合简介
  5. MongoDB 手册 - mapReduce