diff --git a/examples/metrics-advanced/src/main.rs b/examples/metrics-advanced/src/main.rs index 287ec2ca3b..104e75b501 100644 --- a/examples/metrics-advanced/src/main.rs +++ b/examples/metrics-advanced/src/main.rs @@ -7,7 +7,7 @@ use std::error::Error; fn init_meter_provider() -> opentelemetry_sdk::metrics::SdkMeterProvider { // for example 1 let my_view_rename_and_unit = |i: &Instrument| { - if i.name == "my_histogram" { + if i.name() == "my_histogram" { Some( Stream::builder() .with_name("my_histogram_renamed") @@ -22,7 +22,7 @@ fn init_meter_provider() -> opentelemetry_sdk::metrics::SdkMeterProvider { // for example 2 let my_view_change_cardinality = |i: &Instrument| { - if i.name == "my_second_histogram" { + if i.name() == "my_second_histogram" { // Note: If Stream is invalid, build() will return an error. By // calling `.ok()`, any such error is ignored and treated as if the // view does not match the instrument. If this is not the desired diff --git a/opentelemetry-proto/CHANGELOG.md b/opentelemetry-proto/CHANGELOG.md index 3c97586aed..e53c832369 100644 --- a/opentelemetry-proto/CHANGELOG.md +++ b/opentelemetry-proto/CHANGELOG.md @@ -2,7 +2,6 @@ ## vNext -- **Feature**: Added Rust code generation for profiles protos. [#2979](https://github.com/open-telemetry/opentelemetry-rust/pull/2979) - Update `tonic` dependency version to 0.13 - - Update proto definitions to v1.6.0. diff --git a/opentelemetry-proto/Cargo.toml b/opentelemetry-proto/Cargo.toml index bc6bce3dbf..d4ee2ffc74 100644 --- a/opentelemetry-proto/Cargo.toml +++ b/opentelemetry-proto/Cargo.toml @@ -43,7 +43,6 @@ trace = ["opentelemetry/trace", "opentelemetry_sdk/trace"] metrics = ["opentelemetry/metrics", "opentelemetry_sdk/metrics"] logs = ["opentelemetry/logs", "opentelemetry_sdk/logs"] zpages = ["trace"] -profiles = [] testing = ["opentelemetry/testing"] # add ons diff --git a/opentelemetry-proto/src/proto.rs b/opentelemetry-proto/src/proto.rs index a21f6cef70..13a28ce2a4 100644 --- a/opentelemetry-proto/src/proto.rs +++ b/opentelemetry-proto/src/proto.rs @@ -6,7 +6,7 @@ pub(crate) mod serializers { use crate::tonic::common::v1::any_value::{self, Value}; use crate::tonic::common::v1::AnyValue; use serde::de::{self, MapAccess, Visitor}; - use serde::ser::{SerializeMap, SerializeSeq, SerializeStruct}; + use serde::ser::{SerializeMap, SerializeStruct}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::fmt; @@ -174,30 +174,6 @@ pub(crate) mod serializers { s.parse::().map_err(de::Error::custom) } - pub fn serialize_vec_u64_to_string(value: &[u64], serializer: S) -> Result - where - S: Serializer, - { - let s = value.iter() - .map(|v| v.to_string()) - .collect::>(); - let mut sq = serializer.serialize_seq(Some(s.len()))?; - for v in value { - sq.serialize_element(&v.to_string())?; - } - sq.end() - } - - pub fn deserialize_vec_string_to_vec_u64<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - let s: Vec = Deserialize::deserialize(deserializer)?; - s.into_iter() - .map(|v| v.parse::().map_err(de::Error::custom)) - .collect() - } - pub fn serialize_i64_to_string(value: &i64, serializer: S) -> Result where S: Serializer, @@ -290,13 +266,5 @@ pub mod tonic { pub mod v1; } - /// Generated types used in zpages. - #[cfg(feature = "profiles")] - #[path = ""] - pub mod profiles { - #[path = "opentelemetry.proto.profiles.v1development.rs"] - pub mod v1; - } - pub use crate::transform::common::tonic::Attributes; } diff --git a/opentelemetry-proto/src/proto/tonic/opentelemetry.proto.profiles.v1development.rs b/opentelemetry-proto/src/proto/tonic/opentelemetry.proto.profiles.v1development.rs deleted file mode 100644 index 5851887f11..0000000000 --- a/opentelemetry-proto/src/proto/tonic/opentelemetry.proto.profiles.v1development.rs +++ /dev/null @@ -1,487 +0,0 @@ -// This file is @generated by prost-build. -/// ProfilesData represents the profiles data that can be stored in persistent storage, -/// OR can be embedded by other protocols that transfer OTLP profiles data but do not -/// implement the OTLP protocol. -/// -/// The main difference between this message and collector protocol is that -/// in this message there will not be any "control" or "metadata" specific to -/// OTLP protocol. -/// -/// When new fields are added into this message, the OTLP request MUST be updated -/// as well. -#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))] -#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ProfilesData { - /// An array of ResourceProfiles. - /// For data coming from an SDK profiler, this array will typically contain one - /// element. Host-level profilers will usually create one ResourceProfile per - /// container, as well as one additional ResourceProfile grouping all samples - /// from non-containerized processes. - /// Other resource groupings are possible as well and clarified via - /// Resource.attributes and semantic conventions. - #[prost(message, repeated, tag = "1")] - pub resource_profiles: ::prost::alloc::vec::Vec, - /// Mappings from address ranges to the image/binary/library mapped - /// into that address range referenced by locations via Location.mapping_index. - #[prost(message, repeated, tag = "2")] - pub mapping_table: ::prost::alloc::vec::Vec, - /// Locations referenced by samples via Profile.location_indices. - #[prost(message, repeated, tag = "3")] - pub location_table: ::prost::alloc::vec::Vec, - /// Functions referenced by locations via Line.function_index. - #[prost(message, repeated, tag = "4")] - pub function_table: ::prost::alloc::vec::Vec, - /// Links referenced by samples via Sample.link_index. - #[prost(message, repeated, tag = "5")] - pub link_table: ::prost::alloc::vec::Vec, - /// A common table for strings referenced by various messages. - /// string_table\[0\] must always be "". - #[prost(string, repeated, tag = "6")] - pub string_table: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - /// A common table for attributes referenced by various messages. - #[prost(message, repeated, tag = "7")] - pub attribute_table: ::prost::alloc::vec::Vec, - /// Represents a mapping between Attribute Keys and Units. - #[prost(message, repeated, tag = "8")] - pub attribute_units: ::prost::alloc::vec::Vec, -} -/// A collection of ScopeProfiles from a Resource. -#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))] -#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ResourceProfiles { - /// The resource for the profiles in this message. - /// If this field is not set then no resource info is known. - #[prost(message, optional, tag = "1")] - pub resource: ::core::option::Option, - /// A list of ScopeProfiles that originate from a resource. - #[prost(message, repeated, tag = "2")] - pub scope_profiles: ::prost::alloc::vec::Vec, - /// The Schema URL, if known. This is the identifier of the Schema that the resource data - /// is recorded in. Notably, the last part of the URL path is the version number of the - /// schema: http\[s\]://server\[:port\]/path/. To learn more about Schema URL see - /// - /// This schema_url applies to the data in the "resource" field. It does not apply - /// to the data in the "scope_profiles" field which have their own schema_url field. - #[prost(string, tag = "3")] - pub schema_url: ::prost::alloc::string::String, -} -/// A collection of Profiles produced by an InstrumentationScope. -#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))] -#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ScopeProfiles { - /// The instrumentation scope information for the profiles in this message. - /// Semantically when InstrumentationScope isn't set, it is equivalent with - /// an empty instrumentation scope name (unknown). - #[prost(message, optional, tag = "1")] - pub scope: ::core::option::Option, - /// A list of Profiles that originate from an instrumentation scope. - #[prost(message, repeated, tag = "2")] - pub profiles: ::prost::alloc::vec::Vec, - /// The Schema URL, if known. This is the identifier of the Schema that the profile data - /// is recorded in. Notably, the last part of the URL path is the version number of the - /// schema: http\[s\]://server\[:port\]/path/. To learn more about Schema URL see - /// - /// This schema_url applies to all profiles in the "profiles" field. - #[prost(string, tag = "3")] - pub schema_url: ::prost::alloc::string::String, -} -/// Represents a complete profile, including sample types, samples, -/// mappings to binaries, locations, functions, string table, and additional metadata. -/// It modifies and annotates pprof Profile with OpenTelemetry specific fields. -/// -/// Note that whilst fields in this message retain the name and field id from pprof in most cases -/// for ease of understanding data migration, it is not intended that pprof:Profile and -/// OpenTelemetry:Profile encoding be wire compatible. -#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))] -#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Profile { - /// A description of the samples associated with each Sample.value. - /// For a cpu profile this might be: - /// \[["cpu","nanoseconds"]\] or \[["wall","seconds"]\] or \[["syscall","count"]\] - /// For a heap profile, this might be: - /// \[["allocations","count"\], \["space","bytes"]\], - /// If one of the values represents the number of events represented - /// by the sample, by convention it should be at index 0 and use - /// sample_type.unit == "count". - #[prost(message, repeated, tag = "1")] - pub sample_type: ::prost::alloc::vec::Vec, - /// The set of samples recorded in this profile. - #[prost(message, repeated, tag = "2")] - pub sample: ::prost::alloc::vec::Vec, - /// References to locations in ProfilesData.location_table. - #[prost(int32, repeated, tag = "3")] - pub location_indices: ::prost::alloc::vec::Vec, - /// Time of collection (UTC) represented as nanoseconds past the epoch. - #[prost(int64, tag = "4")] - #[cfg_attr( - feature = "with-serde", - serde( - serialize_with = "crate::proto::serializers::serialize_i64_to_string", - deserialize_with = "crate::proto::serializers::deserialize_string_to_i64" - ) - )] - pub time_nanos: i64, - /// Duration of the profile, if a duration makes sense. - #[prost(int64, tag = "5")] - pub duration_nanos: i64, - /// The kind of events between sampled occurrences. - /// e.g \[ "cpu","cycles" \] or \[ "heap","bytes" \] - #[prost(message, optional, tag = "6")] - pub period_type: ::core::option::Option, - /// The number of events between sampled occurrences. - #[prost(int64, tag = "7")] - pub period: i64, - /// Free-form text associated with the profile. The text is displayed as is - /// to the user by the tools that read profiles (e.g. by pprof). This field - /// should not be used to store any machine-readable information, it is only - /// for human-friendly content. The profile must stay functional if this field - /// is cleaned. - /// - /// Indices into ProfilesData.string_table. - #[prost(int32, repeated, tag = "8")] - pub comment_strindices: ::prost::alloc::vec::Vec, - /// Index into the sample_type array to the default sample type. - #[prost(int32, tag = "9")] - pub default_sample_type_index: i32, - /// A globally unique identifier for a profile. The ID is a 16-byte array. An ID with - /// all zeroes is considered invalid. - /// - /// This field is required. - #[prost(bytes = "vec", tag = "10")] - #[cfg_attr( - feature = "with-serde", - serde( - serialize_with = "crate::proto::serializers::serialize_to_hex_string", - deserialize_with = "crate::proto::serializers::deserialize_from_hex_string" - ) - )] - pub profile_id: ::prost::alloc::vec::Vec, - /// dropped_attributes_count is the number of attributes that were discarded. Attributes - /// can be discarded because their keys are too long or because there are too many - /// attributes. If this value is 0, then no attributes were dropped. - #[prost(uint32, tag = "11")] - pub dropped_attributes_count: u32, - /// Specifies format of the original payload. Common values are defined in semantic conventions. \[required if original_payload is present\] - #[prost(string, tag = "12")] - pub original_payload_format: ::prost::alloc::string::String, - /// Original payload can be stored in this field. This can be useful for users who want to get the original payload. - /// Formats such as JFR are highly extensible and can contain more information than what is defined in this spec. - /// Inclusion of original payload should be configurable by the user. Default behavior should be to not include the original payload. - /// If the original payload is in pprof format, it SHOULD not be included in this field. - /// The field is optional, however if it is present then equivalent converted data should be populated in other fields - /// of this message as far as is practicable. - #[prost(bytes = "vec", tag = "13")] - pub original_payload: ::prost::alloc::vec::Vec, - /// References to attributes in attribute_table. \[optional\] - /// It is a collection of key/value pairs. Note, global attributes - /// like server name can be set using the resource API. Examples of attributes: - /// - /// "/http/user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36" - /// "/http/server_latency": 300 - /// "abc.com/myattribute": true - /// "abc.com/score": 10.239 - /// - /// The OpenTelemetry API specification further restricts the allowed value types: - /// - /// Attribute keys MUST be unique (it is not allowed to have more than one - /// attribute with the same key). - #[prost(int32, repeated, tag = "14")] - pub attribute_indices: ::prost::alloc::vec::Vec, -} -/// Represents a mapping between Attribute Keys and Units. -#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))] -#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))] -#[derive(Clone, Copy, PartialEq, ::prost::Message)] -pub struct AttributeUnit { - /// Index into string table. - #[prost(int32, tag = "1")] - pub attribute_key_strindex: i32, - /// Index into string table. - #[prost(int32, tag = "2")] - pub unit_strindex: i32, -} -/// A pointer from a profile Sample to a trace Span. -/// Connects a profile sample to a trace span, identified by unique trace and span IDs. -#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))] -#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Link { - /// A unique identifier of a trace that this linked span is part of. The ID is a - /// 16-byte array. - #[prost(bytes = "vec", tag = "1")] - pub trace_id: ::prost::alloc::vec::Vec, - /// A unique identifier for the linked span. The ID is an 8-byte array. - #[prost(bytes = "vec", tag = "2")] - pub span_id: ::prost::alloc::vec::Vec, -} -/// ValueType describes the type and units of a value, with an optional aggregation temporality. -#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))] -#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))] -#[derive(Clone, Copy, PartialEq, ::prost::Message)] -pub struct ValueType { - /// Index into ProfilesData.string_table. - #[prost(int32, tag = "1")] - pub type_strindex: i32, - /// Index into ProfilesData.string_table. - #[prost(int32, tag = "2")] - pub unit_strindex: i32, - #[prost(enumeration = "AggregationTemporality", tag = "3")] - pub aggregation_temporality: i32, -} -/// Each Sample records values encountered in some program -/// context. The program context is typically a stack trace, perhaps -/// augmented with auxiliary information like the thread-id, some -/// indicator of a higher level request being handled etc. -#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))] -#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Sample { - /// locations_start_index along with locations_length refers to to a slice of locations in Profile.location_indices. - #[prost(int32, tag = "1")] - pub locations_start_index: i32, - /// locations_length along with locations_start_index refers to a slice of locations in Profile.location_indices. - /// Supersedes location_index. - #[prost(int32, tag = "2")] - pub locations_length: i32, - /// The type and unit of each value is defined by the corresponding - /// entry in Profile.sample_type. All samples must have the same - /// number of values, the same as the length of Profile.sample_type. - /// When aggregating multiple samples into a single sample, the - /// result has a list of values that is the element-wise sum of the - /// lists of the originals. - #[prost(int64, repeated, tag = "3")] - pub value: ::prost::alloc::vec::Vec, - /// References to attributes in ProfilesData.attribute_table. \[optional\] - #[prost(int32, repeated, tag = "4")] - pub attribute_indices: ::prost::alloc::vec::Vec, - /// Reference to link in ProfilesData.link_table. \[optional\] - #[prost(int32, optional, tag = "5")] - pub link_index: ::core::option::Option, - /// Timestamps associated with Sample represented in nanoseconds. These timestamps are expected - /// to fall within the Profile's time range. \[optional\] - #[prost(uint64, repeated, tag = "6")] - #[cfg_attr( - feature = "with-serde", - serde( - serialize_with = "crate::proto::serializers::serialize_vec_u64_to_string", - deserialize_with = "crate::proto::serializers::deserialize_vec_string_to_vec_u64" - ) - )] - pub timestamps_unix_nano: ::prost::alloc::vec::Vec, -} -/// Describes the mapping of a binary in memory, including its address range, -/// file offset, and metadata like build ID -#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))] -#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Mapping { - /// Address at which the binary (or DLL) is loaded into memory. - #[prost(uint64, tag = "1")] - pub memory_start: u64, - /// The limit of the address range occupied by this mapping. - #[prost(uint64, tag = "2")] - pub memory_limit: u64, - /// Offset in the binary that corresponds to the first mapped address. - #[prost(uint64, tag = "3")] - pub file_offset: u64, - /// The object this entry is loaded from. This can be a filename on - /// disk for the main binary and shared libraries, or virtual - /// abstractions like "\[vdso\]". - /// - /// Index into ProfilesData.string_table. - #[prost(int32, tag = "4")] - pub filename_strindex: i32, - /// References to attributes in ProfilesData.attribute_table. \[optional\] - #[prost(int32, repeated, tag = "5")] - pub attribute_indices: ::prost::alloc::vec::Vec, - /// The following fields indicate the resolution of symbolic info. - #[prost(bool, tag = "6")] - pub has_functions: bool, - #[prost(bool, tag = "7")] - pub has_filenames: bool, - #[prost(bool, tag = "8")] - pub has_line_numbers: bool, - #[prost(bool, tag = "9")] - pub has_inline_frames: bool, -} -/// Describes function and line table debug information. -#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))] -#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Location { - /// Reference to mapping in ProfilesData.mapping_table. - /// It can be unset if the mapping is unknown or not applicable for - /// this profile type. - #[prost(int32, optional, tag = "1")] - pub mapping_index: ::core::option::Option, - /// The instruction address for this location, if available. It - /// should be within \[Mapping.memory_start...Mapping.memory_limit\] - /// for the corresponding mapping. A non-leaf address may be in the - /// middle of a call instruction. It is up to display tools to find - /// the beginning of the instruction if necessary. - #[prost(uint64, tag = "2")] - pub address: u64, - /// Multiple line indicates this location has inlined functions, - /// where the last entry represents the caller into which the - /// preceding entries were inlined. - /// - /// E.g., if memcpy() is inlined into printf: - /// line\[0\].function_name == "memcpy" - /// line\[1\].function_name == "printf" - #[prost(message, repeated, tag = "3")] - pub line: ::prost::alloc::vec::Vec, - /// Provides an indication that multiple symbols map to this location's - /// address, for example due to identical code folding by the linker. In that - /// case the line information above represents one of the multiple - /// symbols. This field must be recomputed when the symbolization state of the - /// profile changes. - #[prost(bool, tag = "4")] - pub is_folded: bool, - /// References to attributes in ProfilesData.attribute_table. \[optional\] - #[prost(int32, repeated, tag = "5")] - pub attribute_indices: ::prost::alloc::vec::Vec, -} -/// Details a specific line in a source code, linked to a function. -#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))] -#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))] -#[derive(Clone, Copy, PartialEq, ::prost::Message)] -pub struct Line { - /// Reference to function in ProfilesData.function_table. - #[prost(int32, tag = "1")] - pub function_index: i32, - /// Line number in source code. 0 means unset. - #[prost(int64, tag = "2")] - pub line: i64, - /// Column number in source code. 0 means unset. - #[prost(int64, tag = "3")] - pub column: i64, -} -/// Describes a function, including its human-readable name, system name, -/// source file, and starting line number in the source. -#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))] -#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))] -#[cfg_attr(feature = "with-serde", serde(default))] -#[derive(Clone, Copy, PartialEq, ::prost::Message)] -pub struct Function { - /// Function name. Empty string if not available. - #[prost(int32, tag = "1")] - pub name_strindex: i32, - /// Function name, as identified by the system. For instance, - /// it can be a C++ mangled name. Empty string if not available. - #[prost(int32, tag = "2")] - pub system_name_strindex: i32, - /// Source file containing the function. Empty string if not available. - #[prost(int32, tag = "3")] - pub filename_strindex: i32, - /// Line number in source file. 0 means unset. - #[prost(int64, tag = "4")] - pub start_line: i64, -} -/// Specifies the method of aggregating metric values, either DELTA (change since last report) -/// or CUMULATIVE (total since a fixed start time). -#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))] -#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] -#[repr(i32)] -pub enum AggregationTemporality { - /// UNSPECIFIED is the default AggregationTemporality, it MUST not be used. - Unspecified = 0, - /// * DELTA is an AggregationTemporality for a profiler which reports - /// changes since last report time. Successive metrics contain aggregation of - /// values from continuous and non-overlapping intervals. - /// - /// The values for a DELTA metric are based only on the time interval - /// associated with one measurement cycle. There is no dependency on - /// previous measurements like is the case for CUMULATIVE metrics. - /// - /// For example, consider a system measuring the number of requests that - /// it receives and reports the sum of these requests every second as a - /// DELTA metric: - /// - /// 1. The system starts receiving at time=t_0. - /// 2. A request is received, the system measures 1 request. - /// 3. A request is received, the system measures 1 request. - /// 4. A request is received, the system measures 1 request. - /// 5. The 1 second collection cycle ends. A metric is exported for the - /// number of requests received over the interval of time t_0 to - /// t_0+1 with a value of 3. - /// 6. A request is received, the system measures 1 request. - /// 7. A request is received, the system measures 1 request. - /// 8. The 1 second collection cycle ends. A metric is exported for the - /// number of requests received over the interval of time t_0+1 to - /// t_0+2 with a value of 2. - Delta = 1, - /// * CUMULATIVE is an AggregationTemporality for a profiler which - /// reports changes since a fixed start time. This means that current values - /// of a CUMULATIVE metric depend on all previous measurements since the - /// start time. Because of this, the sender is required to retain this state - /// in some form. If this state is lost or invalidated, the CUMULATIVE metric - /// values MUST be reset and a new fixed start time following the last - /// reported measurement time sent MUST be used. - /// - /// For example, consider a system measuring the number of requests that - /// it receives and reports the sum of these requests every second as a - /// CUMULATIVE metric: - /// - /// 1. The system starts receiving at time=t_0. - /// 2. A request is received, the system measures 1 request. - /// 3. A request is received, the system measures 1 request. - /// 4. A request is received, the system measures 1 request. - /// 5. The 1 second collection cycle ends. A metric is exported for the - /// number of requests received over the interval of time t_0 to - /// t_0+1 with a value of 3. - /// 6. A request is received, the system measures 1 request. - /// 7. A request is received, the system measures 1 request. - /// 8. The 1 second collection cycle ends. A metric is exported for the - /// number of requests received over the interval of time t_0 to - /// t_0+2 with a value of 5. - /// 9. The system experiences a fault and loses state. - /// 10. The system recovers and resumes receiving at time=t_1. - /// 11. A request is received, the system measures 1 request. - /// 12. The 1 second collection cycle ends. A metric is exported for the - /// number of requests received over the interval of time t_1 to - /// t_1+1 with a value of 1. - /// - /// Note: Even though, when reporting changes since last report time, using - /// CUMULATIVE is valid, it is not recommended. - Cumulative = 2, -} -impl AggregationTemporality { - /// String value of the enum field names used in the ProtoBuf definition. - /// - /// The values are not transformed in any way and thus are considered stable - /// (if the ProtoBuf definition does not change) and safe for programmatic use. - pub fn as_str_name(&self) -> &'static str { - match self { - Self::Unspecified => "AGGREGATION_TEMPORALITY_UNSPECIFIED", - Self::Delta => "AGGREGATION_TEMPORALITY_DELTA", - Self::Cumulative => "AGGREGATION_TEMPORALITY_CUMULATIVE", - } - } - /// Creates an enum from field names used in the ProtoBuf definition. - pub fn from_str_name(value: &str) -> ::core::option::Option { - match value { - "AGGREGATION_TEMPORALITY_UNSPECIFIED" => Some(Self::Unspecified), - "AGGREGATION_TEMPORALITY_DELTA" => Some(Self::Delta), - "AGGREGATION_TEMPORALITY_CUMULATIVE" => Some(Self::Cumulative), - _ => None, - } - } -} diff --git a/opentelemetry-proto/src/transform/mod.rs b/opentelemetry-proto/src/transform/mod.rs index f0b7b86d3d..cd80544714 100644 --- a/opentelemetry-proto/src/transform/mod.rs +++ b/opentelemetry-proto/src/transform/mod.rs @@ -11,6 +11,3 @@ pub mod logs; #[cfg(feature = "zpages")] pub mod tracez; - -#[cfg(feature = "profiles")] -pub mod profiles; diff --git a/opentelemetry-proto/src/transform/profiles.rs b/opentelemetry-proto/src/transform/profiles.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/opentelemetry-proto/src/transform/profiles.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/opentelemetry-proto/tests/grpc_build.rs b/opentelemetry-proto/tests/grpc_build.rs index 45d7faf4f9..d09a13cd64 100644 --- a/opentelemetry-proto/tests/grpc_build.rs +++ b/opentelemetry-proto/tests/grpc_build.rs @@ -12,7 +12,6 @@ const TONIC_PROTO_FILES: &[&str] = &[ "src/proto/opentelemetry-proto/opentelemetry/proto/collector/metrics/v1/metrics_service.proto", "src/proto/opentelemetry-proto/opentelemetry/proto/logs/v1/logs.proto", "src/proto/opentelemetry-proto/opentelemetry/proto/collector/logs/v1/logs_service.proto", - "src/proto/opentelemetry-proto/opentelemetry/proto/profiles/v1development/profiles.proto", "src/proto/tracez.proto", ]; const TONIC_INCLUDES: &[&str] = &["src/proto/opentelemetry-proto", "src/proto"]; @@ -67,7 +66,6 @@ fn build_tonic() { "metrics.v1.Summary", "metrics.v1.NumberDataPoint", "metrics.v1.HistogramDataPoint", - "profiles.v1development.Function", ] { builder = builder.type_attribute( path, @@ -89,7 +87,6 @@ fn build_tonic() { "logs.v1.LogRecord.trace_id", "metrics.v1.Exemplar.span_id", "metrics.v1.Exemplar.trace_id", - "profiles.v1development.Profile.profile_id", ] { builder = builder .field_attribute(path, "#[cfg_attr(feature = \"with-serde\", serde(serialize_with = \"crate::proto::serializers::serialize_to_hex_string\", deserialize_with = \"crate::proto::serializers::deserialize_from_hex_string\"))]") @@ -113,14 +110,6 @@ fn build_tonic() { builder = builder .field_attribute(path, "#[cfg_attr(feature = \"with-serde\", serde(serialize_with = \"crate::proto::serializers::serialize_u64_to_string\", deserialize_with = \"crate::proto::serializers::deserialize_string_to_u64\"))]") } - for path in ["profiles.v1development.Profile.time_nanos"] { - builder = builder - .field_attribute(path, "#[cfg_attr(feature = \"with-serde\", serde(serialize_with = \"crate::proto::serializers::serialize_i64_to_string\", deserialize_with = \"crate::proto::serializers::deserialize_string_to_i64\"))]") - } - for path in ["profiles.v1development.Sample.timestamps_unix_nano"] { - builder = builder - .field_attribute(path, "#[cfg_attr(feature = \"with-serde\", serde(serialize_with = \"crate::proto::serializers::serialize_vec_u64_to_string\", deserialize_with = \"crate::proto::serializers::deserialize_vec_string_to_vec_u64\"))]") - } // special serializer and deserializer for value // The Value::value field must be hidden diff --git a/opentelemetry-sdk/CHANGELOG.md b/opentelemetry-sdk/CHANGELOG.md index 1cd4213839..c788e44d30 100644 --- a/opentelemetry-sdk/CHANGELOG.md +++ b/opentelemetry-sdk/CHANGELOG.md @@ -55,6 +55,13 @@ also modified to suppress telemetry before invoking exporters. - Added `Stream::builder()` method that returns a new `StreamBuilder` - `StreamBuilder::build()` returns `Result>` enabling proper validation +<<<<<<< HEAD +- Removed `new_view()` on `View`. Views can be instead added by passing anything + that implements `View` trait to `with_view` method on `MeterProviderBuilder`. + `View` is implemented for `Fn(&Instrument) -> Option`, so this can be + used to add views. +======= +>>>>>>> f04e9ec6 (feat: Use builder pattern for constructing Metric Streams (#2984)) - *Breaking* `Aggregation` enum moved behind feature flag "spec_unstable_metrics_views". This was only required when using Views. diff --git a/opentelemetry-sdk/Cargo.toml b/opentelemetry-sdk/Cargo.toml index 171296e3ef..120acec442 100644 --- a/opentelemetry-sdk/Cargo.toml +++ b/opentelemetry-sdk/Cargo.toml @@ -18,7 +18,6 @@ futures-executor = { workspace = true } futures-util = { workspace = true, features = ["std", "sink", "async-await-macro"] } percent-encoding = { workspace = true, optional = true } rand = { workspace = true, features = ["std", "std_rng", "small_rng", "os_rng", "thread_rng"], optional = true } -glob = { workspace = true, optional = true } serde = { workspace = true, features = ["derive", "rc"], optional = true } serde_json = { workspace = true, optional = true } thiserror = { workspace = true } @@ -45,7 +44,7 @@ trace = ["opentelemetry/trace", "rand", "percent-encoding"] jaeger_remote_sampler = ["trace", "opentelemetry-http", "http", "serde", "serde_json", "url", "experimental_async_runtime"] logs = ["opentelemetry/logs", "serde_json"] spec_unstable_logs_enabled = ["logs", "opentelemetry/spec_unstable_logs_enabled"] -metrics = ["opentelemetry/metrics", "glob"] +metrics = ["opentelemetry/metrics"] testing = ["opentelemetry/testing", "trace", "metrics", "logs", "rt-tokio", "rt-tokio-current-thread", "tokio/macros", "tokio/rt-multi-thread"] experimental_async_runtime = [] rt-tokio = ["tokio", "tokio-stream", "experimental_async_runtime"] diff --git a/opentelemetry-sdk/src/metrics/error.rs b/opentelemetry-sdk/src/metrics/error.rs index 7d7f2bd19b..df09d6b0bf 100644 --- a/opentelemetry-sdk/src/metrics/error.rs +++ b/opentelemetry-sdk/src/metrics/error.rs @@ -3,30 +3,8 @@ use std::sync::PoisonError; use thiserror::Error; /// A specialized `Result` type for metric operations. -#[cfg(feature = "spec_unstable_metrics_views")] -pub type MetricResult = result::Result; -#[cfg(not(feature = "spec_unstable_metrics_views"))] pub(crate) type MetricResult = result::Result; -/// Errors returned by the metrics API. -#[cfg(feature = "spec_unstable_metrics_views")] -#[derive(Error, Debug)] -#[non_exhaustive] -pub enum MetricError { - /// Other errors not covered by specific cases. - #[error("Metrics error: {0}")] - Other(String), - /// Invalid configuration - #[error("Config error {0}")] - Config(String), - /// Invalid instrument configuration such invalid instrument name, invalid instrument description, invalid instrument unit, etc. - /// See [spec](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#general-characteristics) - /// for full list of requirements. - #[error("Invalid instrument configuration: {0}")] - InvalidInstrumentConfiguration(&'static str), -} - -#[cfg(not(feature = "spec_unstable_metrics_views"))] #[derive(Error, Debug)] pub(crate) enum MetricError { /// Other errors not covered by specific cases. diff --git a/opentelemetry-sdk/src/metrics/instrument.rs b/opentelemetry-sdk/src/metrics/instrument.rs index 259460ef67..0b08b55e0b 100644 --- a/opentelemetry-sdk/src/metrics/instrument.rs +++ b/opentelemetry-sdk/src/metrics/instrument.rs @@ -84,92 +84,179 @@ impl InstrumentKind { /// let view = new_view(criteria, mask); /// # drop(view); /// ``` -#[derive(Clone, Default, Debug, PartialEq)] -#[non_exhaustive] +#[derive(Clone, Debug, PartialEq)] pub struct Instrument { /// The human-readable identifier of the instrument. - pub name: Cow<'static, str>, + pub(crate) name: Cow<'static, str>, /// describes the purpose of the instrument. - pub description: Cow<'static, str>, + pub(crate) description: Cow<'static, str>, /// The functional group of the instrument. - pub kind: Option, + pub(crate) kind: InstrumentKind, /// Unit is the unit of measurement recorded by the instrument. - pub unit: Cow<'static, str>, + pub(crate) unit: Cow<'static, str>, /// The instrumentation that created the instrument. - pub scope: InstrumentationScope, + pub(crate) scope: InstrumentationScope, } -#[cfg(feature = "spec_unstable_metrics_views")] impl Instrument { - /// Create a new instrument with default values - pub fn new() -> Self { - Instrument::default() + /// Instrument name. + pub fn name(&self) -> &str { + self.name.as_ref() } - /// Set the instrument name. - pub fn name(mut self, name: impl Into>) -> Self { - self.name = name.into(); - self + /// Instrument kind. + pub fn kind(&self) -> InstrumentKind { + self.kind } - /// Set the instrument description. - pub fn description(mut self, description: impl Into>) -> Self { - self.description = description.into(); - self + /// Instrument unit. + pub fn unit(&self) -> &str { + self.unit.as_ref() } - /// Set the instrument unit. - pub fn unit(mut self, unit: impl Into>) -> Self { - self.unit = unit.into(); + /// Instrument scope. + pub fn scope(&self) -> &InstrumentationScope { + &self.scope + } +} + +/// A builder for creating Stream objects. +/// +/// # Example +/// +/// ``` +/// use opentelemetry_sdk::metrics::{Aggregation, Stream}; +/// use opentelemetry::Key; +/// +/// let stream = Stream::builder() +/// .with_name("my_stream") +/// .with_aggregation(Aggregation::Sum) +/// .with_cardinality_limit(100) +/// .build() +/// .unwrap(); +/// ``` +#[derive(Default, Debug)] +pub struct StreamBuilder { + name: Option>, + description: Option>, + unit: Option>, + aggregation: Option, + allowed_attribute_keys: Option>>, + cardinality_limit: Option, +} + +impl StreamBuilder { + /// Create a new stream builder with default values. + pub(crate) fn new() -> Self { + StreamBuilder::default() + } + + /// Set the stream name. If this is not set, name provide while creating the instrument will be used. + pub fn with_name(mut self, name: impl Into>) -> Self { + self.name = Some(name.into()); self } - /// Set the instrument scope. - pub fn scope(mut self, scope: InstrumentationScope) -> Self { - self.scope = scope; + /// Set the stream description. If this is not set, description provided while creating the instrument will be used. + pub fn with_description(mut self, description: impl Into>) -> Self { + self.description = Some(description.into()); self } - /// empty returns if all fields of i are their default-value. - pub(crate) fn is_empty(&self) -> bool { - self.name.is_empty() - && self.description.is_empty() - && self.kind.is_none() - && self.unit.is_empty() - && self.scope == InstrumentationScope::default() + /// Set the stream unit. If this is not set, unit provided while creating the instrument will be used. + pub fn with_unit(mut self, unit: impl Into>) -> Self { + self.unit = Some(unit.into()); + self } - pub(crate) fn matches(&self, other: &Instrument) -> bool { - self.matches_name(other) - && self.matches_description(other) - && self.matches_kind(other) - && self.matches_unit(other) - && self.matches_scope(other) + #[cfg(feature = "spec_unstable_metrics_views")] + /// Set the stream aggregation. This is used to customize the aggregation. + /// If not set, the default aggregation based on the instrument kind will be used. + pub fn with_aggregation(mut self, aggregation: Aggregation) -> Self { + self.aggregation = Some(aggregation); + self } - pub(crate) fn matches_name(&self, other: &Instrument) -> bool { - self.name.is_empty() || self.name.as_ref() == other.name.as_ref() + #[cfg(feature = "spec_unstable_metrics_views")] + /// Set the stream allowed attribute keys. + /// + /// Any attribute recorded for the stream with a key not in this set will be + /// dropped. If the set is empty, all attributes will be dropped, if `None` all + /// attributes will be kept. + pub fn with_allowed_attribute_keys( + mut self, + attribute_keys: impl IntoIterator, + ) -> Self { + self.allowed_attribute_keys = Some(Arc::new(attribute_keys.into_iter().collect())); + self } - pub(crate) fn matches_description(&self, other: &Instrument) -> bool { - self.description.is_empty() || self.description.as_ref() == other.description.as_ref() + /// Set the stream cardinality limit. If this is not set, the default limit of 2000 will be used. + pub fn with_cardinality_limit(mut self, limit: usize) -> Self { + self.cardinality_limit = Some(limit); + self } - pub(crate) fn matches_kind(&self, other: &Instrument) -> bool { - self.kind.is_none() || self.kind == other.kind + /// Build a new Stream instance using the configuration in this builder. + /// + /// # Returns + /// + /// A Result containing the new Stream instance or an error if the build failed. + pub fn build(self) -> Result> { + // TODO: Add same validation as already done while + // creating instruments. It is better to move validation logic + // to a common helper and call it from both places. + // The current implementations does a basic validation + // only to close the overall API design. + + // if name is provided, it must not be empty + if let Some(name) = &self.name { + if name.is_empty() { + return Err("Stream name must not be empty".into()); + } + } + + // if cardinality limit is provided, it must be greater than 0 + if let Some(limit) = self.cardinality_limit { + if limit == 0 { + return Err("Cardinality limit must be greater than 0".into()); + } + } + + // If the aggregation is set to ExplicitBucketHistogram, validate the bucket boundaries. + if let Some(Aggregation::ExplicitBucketHistogram { boundaries, .. }) = &self.aggregation { + validate_bucket_boundaries(boundaries)?; + } + + Ok(Stream { + name: self.name, + description: self.description, + unit: self.unit, + aggregation: self.aggregation, + allowed_attribute_keys: self.allowed_attribute_keys, + cardinality_limit: self.cardinality_limit, + }) } +} - pub(crate) fn matches_unit(&self, other: &Instrument) -> bool { - self.unit.is_empty() || self.unit.as_ref() == other.unit.as_ref() +fn validate_bucket_boundaries(boundaries: &[f64]) -> Result<(), String> { + // Validate boundaries do not contain f64::NAN, f64::INFINITY, or f64::NEG_INFINITY + for boundary in boundaries { + if boundary.is_nan() || boundary.is_infinite() { + return Err( + "Bucket boundaries must not contain NaN, Infinity, or -Infinity".to_string(), + ); + } } - pub(crate) fn matches_scope(&self, other: &Instrument) -> bool { - (self.scope.name().is_empty() || self.scope.name() == other.scope.name()) - && (self.scope.version().is_none() - || self.scope.version().as_ref() == other.scope.version().as_ref()) - && (self.scope.schema_url().is_none() - || self.scope.schema_url().as_ref() == other.scope.schema_url().as_ref()) + // validate that buckets are sorted and non-duplicate + for i in 1..boundaries.len() { + if boundaries[i] <= boundaries[i - 1] { + return Err("Bucket boundaries must be sorted and non-duplicate".to_string()); + } } + + Ok(()) } /// A builder for creating Stream objects. @@ -331,8 +418,6 @@ fn validate_bucket_boundaries(boundaries: &[f64]) -> Result<(), String> { /// # drop(view); /// ``` #[derive(Default, Debug)] -#[non_exhaustive] -#[allow(unreachable_pub)] pub struct Stream { /// The human-readable identifier of the stream. pub(crate) name: Option>, diff --git a/opentelemetry-sdk/src/metrics/meter.rs b/opentelemetry-sdk/src/metrics/meter.rs index 3950cbce55..0ec960c313 100644 --- a/opentelemetry-sdk/src/metrics/meter.rs +++ b/opentelemetry-sdk/src/metrics/meter.rs @@ -683,7 +683,7 @@ where name, description: description.unwrap_or_default(), unit: unit.unwrap_or_default(), - kind: Some(kind), + kind: kind, scope: self.meter.scope.clone(), }; diff --git a/opentelemetry-sdk/src/metrics/meter_provider.rs b/opentelemetry-sdk/src/metrics/meter_provider.rs index 73cc9c23d1..b46308f201 100644 --- a/opentelemetry-sdk/src/metrics/meter_provider.rs +++ b/opentelemetry-sdk/src/metrics/meter_provider.rs @@ -308,7 +308,11 @@ impl MeterProviderBuilder { /// ``` /// # use opentelemetry_sdk::metrics::{Stream, Instrument}; /// let view_rename = |i: &Instrument| { +<<<<<<< HEAD + /// if i.name() == "my_counter" { +======= /// if i.name == "my_counter" { +>>>>>>> f04e9ec6 (feat: Use builder pattern for constructing Metric Streams (#2984)) /// Some(Stream::builder().with_name("my_counter_renamed").build().expect("Stream should be valid")) /// } else { /// None @@ -324,7 +328,11 @@ impl MeterProviderBuilder { /// ``` /// # use opentelemetry_sdk::metrics::{Stream, Instrument}; /// let view_change_cardinality = |i: &Instrument| { +<<<<<<< HEAD + /// if i.name() == "my_counter" { +======= /// if i.name == "my_counter" { +>>>>>>> f04e9ec6 (feat: Use builder pattern for constructing Metric Streams (#2984)) /// Some( /// Stream::builder() /// .with_cardinality_limit(100).build().expect("Stream should be valid"), @@ -343,7 +351,11 @@ impl MeterProviderBuilder { /// ``` /// # use opentelemetry_sdk::metrics::{Stream, Instrument}; /// let my_view_change_cardinality = |i: &Instrument| { +<<<<<<< HEAD + /// if i.name() == "my_second_histogram" { +======= /// if i.name == "my_second_histogram" { +>>>>>>> f04e9ec6 (feat: Use builder pattern for constructing Metric Streams (#2984)) /// // Note: If Stream is invalid, build() will return `Error` variant. /// // By calling `.ok()`, any such error is ignored and treated as if the view does not match /// // the instrument. diff --git a/opentelemetry-sdk/src/metrics/mod.rs b/opentelemetry-sdk/src/metrics/mod.rs index fc20f88f19..922d7d6835 100644 --- a/opentelemetry-sdk/src/metrics/mod.rs +++ b/opentelemetry-sdk/src/metrics/mod.rs @@ -73,8 +73,7 @@ pub use in_memory_exporter::{InMemoryMetricExporter, InMemoryMetricExporterBuild #[cfg(feature = "spec_unstable_metrics_views")] pub use aggregation::*; -#[cfg(feature = "spec_unstable_metrics_views")] -pub use error::{MetricError, MetricResult}; + #[cfg(feature = "experimental_metrics_custom_reader")] pub use manual_reader::*; pub use meter_provider::*; @@ -84,9 +83,12 @@ pub use pipeline::Pipeline; pub use instrument::{Instrument, InstrumentKind, Stream, StreamBuilder}; +<<<<<<< HEAD +======= #[cfg(feature = "spec_unstable_metrics_views")] pub use view::new_view; +>>>>>>> f04e9ec6 (feat: Use builder pattern for constructing Metric Streams (#2984)) pub use view::View; use std::hash::Hash; diff --git a/opentelemetry-sdk/src/metrics/pipeline.rs b/opentelemetry-sdk/src/metrics/pipeline.rs index e012211ffc..68c48b5387 100644 --- a/opentelemetry-sdk/src/metrics/pipeline.rs +++ b/opentelemetry-sdk/src/metrics/pipeline.rs @@ -259,10 +259,7 @@ where let mut matched = false; let mut measures = vec![]; let mut errs = vec![]; - let kind = match inst.kind { - Some(kind) => kind, - None => return Err(MetricError::Other("instrument must have a kind".into())), - }; + let kind = inst.kind; // The cache will return the same Aggregator instance. Use stream ids to de duplicate. let mut seen = HashSet::new(); diff --git a/opentelemetry-sdk/src/metrics/view.rs b/opentelemetry-sdk/src/metrics/view.rs index ac2e0493d9..41abaea007 100644 --- a/opentelemetry-sdk/src/metrics/view.rs +++ b/opentelemetry-sdk/src/metrics/view.rs @@ -1,13 +1,4 @@ use super::instrument::{Instrument, Stream}; -#[cfg(feature = "spec_unstable_metrics_views")] -use crate::metrics::{MetricError, MetricResult}; -#[cfg(feature = "spec_unstable_metrics_views")] -use glob::Pattern; - -#[cfg(feature = "spec_unstable_metrics_views")] -fn empty_view(_inst: &Instrument) -> Option { - None -} /// Used to customize the metrics that are output by the SDK. /// @@ -67,6 +58,8 @@ impl View for Box { (**self).match_inst(inst) } } +<<<<<<< HEAD +======= #[cfg(feature = "spec_unstable_metrics_views")] /// Creates a [View] that applies the [Stream] mask for all instruments that @@ -251,3 +244,4 @@ mod tests { ); } } +>>>>>>> f04e9ec6 (feat: Use builder pattern for constructing Metric Streams (#2984))