

# OpenTelemetry 1.0.0 格式的 CloudWatch 指标流输出
<a name="CloudWatch-metric-streams-formats-opentelemetry-100"></a>

**注意**  
如果选择 OpenTelemetry 1.0.0 格式，指标属性会被编码为一个 `KeyValue` 对象列表，而不是 0.7.0 格式中使用的 `StringKeyValue` 类型。作为消费端，这是 0.7.0 和 1.0.0 格式之间的唯一重大变化。从 0.7.0 原型文件生成的解析器无法解析以 1.0.0 格式编码的指标属性。反之亦然，从 1.0.0 原型文件生成的解析器无法解析以 0.7.0 格式编码的指标属性。

OpenTelemetry 是工具、API 和软件开发工具包的集合。您可以使用它来测量、生成、收集及导出遥测数据（指标、日志和跟踪）以进行分析。OpenTelemetry 属于云原生计算基金会。有关更多信息，请参阅 [OpenTelemetry](https://opentelemetry.io/)。

有关完整 OpenTelemetry 1.0.0 规范的信息，请参阅 [Release version 1.0.0](https://github.com/open-telemetry/opentelemetry-proto/releases/tag/v1.0.0)。

一条 Kinesis 记录可以包含一个或多个 `ExportMetricsServiceRequest` OpenTelemetry 数据结构。每个数据结构都以一个带有指示记录长度（字节）的 `UnsignedVarInt32` 的标头开头。每个 `ExportMetricsServiceRequest` 可同时包含来自多个指标的数据。

以下是 `ExportMetricsServiceRequest` OpenTelemetry 数据结构形式的消息的字符串表示。OpenTelemetry 会序列化 Google Protocol Buffers 二进制协议，此协议人类不可读。

```
resource_metrics {
  resource {
    attributes {
      key: "cloud.provider"
      value {
        string_value: "aws"
      }
    }
    attributes {
      key: "cloud.account.id"
      value {
        string_value: "123456789012"
      }
    }
    attributes {
      key: "cloud.region"
      value {
        string_value: "us-east-1"
      }
    }
    attributes {
      key: "aws.exporter.arn"
      value {
        string_value: "arn:aws:cloudwatch:us-east-1:123456789012:metric-stream/MyMetricStream"
      }
    }
  }
  scope_metrics {
    metrics {
      name: "amazonaws.com/AWS/DynamoDB/ConsumedReadCapacityUnits"
      unit: "NoneTranslated"
      summary {
        data_points {
          start_time_unix_nano: 60000000000
          time_unix_nano: 120000000000
          count: 1
          sum: 1.0
          quantile_values {
            value: 1.0
          }
          quantile_values {
            quantile: 0.95
            value: 1.0
          }
          quantile_values {
            quantile: 0.99
            value: 1.0
          }
          quantile_values {
            quantile: 1.0
            value: 1.0
          }
          attributes {
            key: "Namespace"
            value {
              string_value: "AWS/DynamoDB"
            }
          }
          attributes {
            key: "MetricName"
            value {
              string_value: "ConsumedReadCapacityUnits"
            }
          }
          attributes {
            key: "Dimensions"
            value {
              kvlist_value {
                values {
                  key: "TableName"
                  value {
                    string_value: "MyTable"
                  }
                }
              }
            }
          }
        }
        data_points {
          start_time_unix_nano: 70000000000
          time_unix_nano: 130000000000
          count: 2
          sum: 5.0
          quantile_values {
            value: 2.0
          }
          quantile_values {
            quantile: 1.0
            value: 3.0
          }
          attributes {
            key: "Namespace"
            value {
              string_value: "AWS/DynamoDB"
            }
          }
          attributes {
            key: "MetricName"
            value {
              string_value: "ConsumedReadCapacityUnits"
            }
          }
          attributes {
            key: "Dimensions"
            value {
              kvlist_value {
                values {
                  key: "TableName"
                  value {
                    string_value: "MyTable"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
```

**用于序列化 OpenTelemetry 指标数据的顶级对象**

`ExportMetricsServiceRequest` 是用于序列化 OpenTelemetry 导出器负载的顶级包装器。它包含一个或多个 `ResourceMetrics`。

```
message ExportMetricsServiceRequest {
  // An array of ResourceMetrics.
  // For data coming from a single resource this array will typically contain one
  // element. Intermediary nodes (such as OpenTelemetry Collector) that receive
  // data from multiple origins typically batch the data before forwarding further and
  // in that case this array will contain multiple elements.
  repeated opentelemetry.proto.metrics.v1.ResourceMetrics resource_metrics = 1;
}
```

`ResourceMetrics` 是表示 MetricData 对象的顶级对象。

```
// A collection of ScopeMetrics from a Resource.
message ResourceMetrics {
  reserved 1000;

  // The resource for the metrics in this message.
  // If this field is not set then no resource info is known.
  opentelemetry.proto.resource.v1.Resource resource = 1;

  // A list of metrics that originate from a resource.
  repeated ScopeMetrics scope_metrics = 2;

  // This schema_url applies to the data in the "resource" field. It does not apply
  // to the data in the "scope_metrics" field which have their own schema_url field.
  string schema_url = 3;
}
```

**资源对象**

`Resource` 对象是一个值对对象，其中包含有关生成指标的资源的一些信息。对于由 AWS 创建的指标，数据结构包含与指标相关的资源的 Amazon Resource Name (ARN)，例如 EC2 实例或 S3 存储桶。

`Resource` 对象包含名为 `attributes` 的属性，其会存储键值对列表。
+ `cloud.account.id` 包含账户 ID
+ `cloud.region` 包含区域
+ `aws.exporter.arn` 包含指标流 ARN
+ `cloud.provider` 始终为 `aws`。

```
// Resource information.
message Resource {
  // Set of attributes that describe the resource.
  // Attribute keys MUST be unique (it is not allowed to have more than one
  // attribute with the same key).
  repeated opentelemetry.proto.common.v1.KeyValue attributes = 1;

  // dropped_attributes_count is the number of dropped attributes. If the value is 0, then
  // no attributes were dropped.
  uint32 dropped_attributes_count = 2;
}
```

**ScopeMetrics 对象**

`scope` 字段将不会填充。我们仅填充正在导出的指标字段。

```
// A collection of Metrics produced by an Scope.
message ScopeMetrics {
  // The instrumentation scope information for the metrics in this message.
  // Semantically when InstrumentationScope isn't set, it is equivalent with
  // an empty instrumentation scope name (unknown).
  opentelemetry.proto.common.v1.InstrumentationScope scope = 1;

  // A list of metrics that originate from an instrumentation library.
  repeated Metric metrics = 2;

  // This schema_url applies to all metrics in the "metrics" field.
  string schema_url = 3;
}
```

**指标对象**

指标对象包含一些元数据和一个 `Summary` 数据字段，该字段包含一个 `SummaryDataPoint` 列表。

对于指标流，元数据如下所示：
+ `name` 将为 `amazonaws.com/metric_namespace/metric_name`
+ `description` 将为空
+ `unit` 将通过将指标基准单位映射为计量单位统一代码的变体（区分大小写）来填充。有关更多信息，请参阅 [在 CloudWatch 中转换为 OpenTelemetry 1.0.0 格式](CloudWatch-metric-streams-formats-opentelemetry-translation-100.md) 和[计量单位统一代码](https://ucum.org/ucum.html)。
+ `type` 将为 `SUMMARY`

```
message Metric {
  reserved 4, 6, 8;

  // name of the metric, including its DNS name prefix. It must be unique.
  string name = 1;

  // description of the metric, which can be used in documentation.
  string description = 2;

  // unit in which the metric value is reported. Follows the format
  // described by http://unitsofmeasure.org/ucum.html.
  string unit = 3;

  // Data determines the aggregation type (if any) of the metric, what is the
  // reported value type for the data points, as well as the relatationship to
  // the time interval over which they are reported.
  oneof data {
    Gauge gauge = 5;
    Sum sum = 7;
    Histogram histogram = 9;
    ExponentialHistogram exponential_histogram = 10;
    Summary summary = 11;
  }
}

message Summary {
  repeated SummaryDataPoint data_points = 1;
}
```

**SummaryDataPoint 对象**

SummaryDataPoint 对象包含 DoubleSummary 指标时间序列中单个数据点的值。

```
// SummaryDataPoint is a single data point in a timeseries that describes the
// time-varying values of a Summary metric.
message SummaryDataPoint {
  reserved 1;

  // The set of key/value pairs that uniquely identify the timeseries from
  // where this point belongs. The list may be empty (may contain 0 elements).
  // Attribute keys MUST be unique (it is not allowed to have more than one
  // attribute with the same key).
  repeated opentelemetry.proto.common.v1.KeyValue attributes = 7;

  // StartTimeUnixNano is optional but strongly encouraged, see the
  // the detailed comments above Metric.
  //
  // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January
  // 1970.
  fixed64 start_time_unix_nano = 2;

  // TimeUnixNano is required, see the detailed comments above Metric.
  //
  // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January
  // 1970.
  fixed64 time_unix_nano = 3;

  // count is the number of values in the population. Must be non-negative.
  fixed64 count = 4;

  // sum of the values in the population. If count is zero then this field
  // must be zero.
  //
  // Note: Sum should only be filled out when measuring non-negative discrete
  // events, and is assumed to be monotonic over the values of these events.
  // Negative events *can* be recorded, but sum should not be filled out when
  // doing so.  This is specifically to enforce compatibility w/ OpenMetrics,
  // see: https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#summary
  double sum = 5;

  // Represents the value at a given quantile of a distribution.
  //
  // To record Min and Max values following conventions are used:
  // - The 1.0 quantile is equivalent to the maximum value observed.
  // - The 0.0 quantile is equivalent to the minimum value observed.
  //
  // See the following issue for more context:
  // https://github.com/open-telemetry/opentelemetry-proto/issues/125
  message ValueAtQuantile {
    // The quantile of a distribution. Must be in the interval
    // [0.0, 1.0].
    double quantile = 1;

    // The value at the given quantile of a distribution.
    //
    // Quantile values must NOT be negative.
    double value = 2;
  }

  // (Optional) list of values at different quantiles of the distribution calculated
  // from the current snapshot. The quantiles must be strictly increasing.
  repeated ValueAtQuantile quantile_values = 6;

  // Flags that apply to this specific data point.  See DataPointFlags
  // for the available flags and their meaning.
  uint32 flags = 8;
}
```

有关更多信息，请参阅 [在 CloudWatch 中转换为 OpenTelemetry 1.0.0 格式](CloudWatch-metric-streams-formats-opentelemetry-translation-100.md)。

# 在 CloudWatch 中转换为 OpenTelemetry 1.0.0 格式
<a name="CloudWatch-metric-streams-formats-opentelemetry-translation-100"></a>

CloudWatch 会执行一些转换，将 CloudWatch 数据转换为 OpenTelemetry 格式。

**转换命名空间、指标名称和维度**

这些属性是在映射中编码的键值对。
+ 一个属性的键是 `Namespace`，其值是指标的命名空间
+ 一个属性的键是 `MetricName`，其值是指标的名称
+ 一个键值对的键是 `Dimensions`，其值是一个键值对嵌套列表。此列表中的每个键值对都映射到一个 CloudWatch 指标维度，其中键值对的键是维度的名称，其值是维度的值。

**转换平均值、总和、样本计数、最小值和最大值**

摘要数据点使 CloudWatch 能够使用一个数据点导出所有这些统计数据。
+ `startTimeUnixNano` 包含 CloudWatch `startTime`
+ `timeUnixNano` 包含 CloudWatch `endTime`
+ `sum` 包含总和统计数据。
+ `count` 包含样本数统计数据。
+ `quantile_values` 包含两个 `valueAtQuantile.value` 对象：
  + `valueAtQuantile.quantile = 0.0` 与 `valueAtQuantile.value = Min value`
  + `valueAtQuantile.quantile = 0.99` 与 `valueAtQuantile.value = p99 value`
  + `valueAtQuantile.quantile = 0.999` 与 `valueAtQuantile.value = p99.9 value`
  + `valueAtQuantile.quantile = 1.0` 与 `valueAtQuantile.value = Max value`

使用该指标流的资源可以按**总和/样本数**来计算平均值统计数据。

**转换单位**

CloudWatch 单位映射到计量单位统一代码区分大小写的变体，如下表所示。有关更多信息，请参阅[计量单位统一代码](https://ucum.org/ucum.html)。


| CloudWatch | OpenTelemetry | 
| --- | --- | 
|  秒 |  s | 
|  秒 |  s | 
|  微秒 |  us | 
|  毫秒 |  ms | 
|  字节 |  By | 
|  千字节 |  kBy | 
|  兆字节 |  MBy | 
|  千兆字节 |  GBy | 
|  千吉字节 |  TBy | 
|  Bits |  bit | 
|  千位 |  kbit | 
|  兆位 |  MBit | 
|  千兆位 |  GBit | 
|  太位 |  Tbit | 
|  百分比 |  % | 
|  计数 |  \$1Count\$1 | 
|  无 |  1 | 

由斜线组合而成的单位通过同时对斜线前后两个单位应用 OpenTelemetry 转换来进行映射。例如，字节/秒映射为 By/s。

# 如何解析 OpenTelemetry 1.0.0 消息
<a name="CloudWatch-metric-streams-formats-opentelemetry-parse-100"></a>

本节提供了有助于您开始解析 OpenTelemetry 1.0.0 的信息。

首先，您应获得特定于语言的绑定，这样您才能够以首选语言解析 OpenTelemetry 1.0.0 消息。

**获取特定于语言的绑定**
+ 根据您的首选语言选择以下步骤。
  + 若要使用 Java，请将以下 Maven 依赖项添加到您的 Java 项目中：[OpenTelemetry Java >> 0.14.1](https://mvnrepository.com/artifact/io.opentelemetry/opentelemetry-proto/0.14.1)。
  + 若要使用任何其他语言，请按照下列步骤操作：

    1. 检查[生成类](https://developers.google.com/protocol-buffers/docs/proto3#generating)中的列表，确保您的语言受支持。

    1. 按照[下载协议缓冲区](https://developers.google.com/protocol-buffers/docs/downloads)中的步骤安装 Protobuf 编译器。

    1. 在 [Release version 1.0.0](https://github.com/open-telemetry/opentelemetry-proto/releases/tag/v1.0.0) 中下载 OpenTelemetry 1.0.0 ProtoBuf 定义。

    1. 确认您位于下载的 OpenTelemetry 1.0.0 ProtoBuf 定义的根文件夹中。创建一个 `src` 文件夹，然后运行命令以生成特定于语言的绑定。有关更多信息，请参阅[生成类](https://developers.google.com/protocol-buffers/docs/proto3#generating)。

       以下示例展示了如何生成 Javascript 绑定。

       ```
       protoc --proto_path=./ --js_out=import_style=commonjs,binary:src \
       opentelemetry/proto/common/v1/common.proto \
       opentelemetry/proto/resource/v1/resource.proto \
       opentelemetry/proto/metrics/v1/metrics.proto \
       opentelemetry/proto/collector/metrics/v1/metrics_service.proto
       ```

以下部分包括使用特定于语言的绑定的示例，您可以使用前面的说明来构建这些绑定。

**Java**

```
package com.example;

import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

public class MyOpenTelemetryParser {

    public List<ExportMetricsServiceRequest> parse(InputStream inputStream) throws IOException {
        List<ExportMetricsServiceRequest> result = new ArrayList<>();

        ExportMetricsServiceRequest request;
        /* A Kinesis record can contain multiple `ExportMetricsServiceRequest`
           records, each of them starting with a header with an
           UnsignedVarInt32 indicating the record length in bytes:
            ------ --------------------------- ------ -----------------------
           |UINT32|ExportMetricsServiceRequest|UINT32|ExportMetricsService...
            ------ --------------------------- ------ -----------------------
         */
        while ((request = ExportMetricsServiceRequest.parseDelimitedFrom(inputStream)) != null) {
            // Do whatever we want with the parsed message
            result.add(request);
        }

        return result;
    }
}
```

**Javascript**

本示例假定具有所生成的绑定的根文件夹为 `./`

函数 `parseRecord` 的数据参数可以是以下类型之一：
+ `Uint8Array`，此类型最佳
+ `Buffer`，在节点下最佳
+ `Array.number`，8 位整数

```
const pb = require('google-protobuf')
const pbMetrics =
    require('./opentelemetry/proto/collector/metrics/v1/metrics_service_pb')

function parseRecord(data) {
    const result = []

    // Loop until we've read all the data from the buffer
    while (data.length) {
        /* A Kinesis record can contain multiple `ExportMetricsServiceRequest`
           records, each of them starting with a header with an
           UnsignedVarInt32 indicating the record length in bytes:
            ------ --------------------------- ------ -----------------------
           |UINT32|ExportMetricsServiceRequest|UINT32|ExportMetricsService...
            ------ --------------------------- ------ -----------------------
         */
        const reader = new pb.BinaryReader(data)
        const messageLength = reader.decoder_.readUnsignedVarint32()
        const messageFrom = reader.decoder_.cursor_
        const messageTo = messageFrom + messageLength

        // Extract the current `ExportMetricsServiceRequest` message to parse
        const message = data.subarray(messageFrom, messageTo)

        // Parse the current message using the ProtoBuf library
        const parsed =
            pbMetrics.ExportMetricsServiceRequest.deserializeBinary(message)

        // Do whatever we want with the parsed message
        result.push(parsed.toObject())

        // Shrink the remaining buffer, removing the already parsed data
        data = data.subarray(messageTo)
    }

    return result
}
```

**Python**

您必须自行读取 `var-int` 分隔符或使用内部方法 `_VarintBytes(size)` 和 `_DecodeVarint32(buffer, position)`。它们会返回刚好位于缓冲区中大小字节之后的位置。读取端构建一个新的缓冲区，该缓冲区仅限于读取消息的字节。

```
size = my_metric.ByteSize()
f.write(_VarintBytes(size))
f.write(my_metric.SerializeToString())
msg_len, new_pos = _DecodeVarint32(buf, 0)
msg_buf = buf[new_pos:new_pos+msg_len]
request = metrics_service_pb.ExportMetricsServiceRequest()
request.ParseFromString(msg_buf)
```

**Go**

使用 `Buffer.DecodeMessage()`。

**C\$1**

使用 `CodedInputStream`。此类可以读取分隔了大小的消息。

**C\$1\$1**

`google/protobuf/util/delimited_message_util.h` 中描述的函数可以读取分隔了大小的消息。

**其他语言**

有关使用其他语言，请参阅[下载协议缓冲区](https://developers.google.com/protocol-buffers/docs/downloads)。

在实现解析器时，请注意，一条 Kinesis 记录可以包含多个 `ExportMetricsServiceRequest` 协议缓冲区消息，每个消息都以具有指示记录长度（字节）的 `UnsignedVarInt32` 的标头开头。