From 9026baed2e7fa80e3a13bab368e692090e7e8246 Mon Sep 17 00:00:00 2001 From: Dmitry Filimonov Date: Fri, 23 Feb 2024 10:36:46 -0800 Subject: [PATCH] Introduces Profiling Data Model v2 (open-telemetry/oteps#239) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is second version of the Profiling Data Model OTEP. After [we've gotten feedback from the greater OTel community](https://github.com/open-telemetry/oteps/pull/237) we went back to the drawing board and came up with a new version of the data model. The main difference between the two versions is that the new version is more similar to the original pprof format, which makes it easier to understand and implement. It also has better performance characteristics. We've also incorporated a lot of the feedback we've gotten on the first PR into this OTEP. Some minor details about the data model are still being discussed and will be flushed out in the future OTEPs. We intend to finalize these details after doing experiments with early versions of working client + collector + backend implementations and getting feedback from the community. The goal of this OTEP is to provide a solid foundation for these experiments. So far we've done a number of things to validate it: * we've written a new profiles proto described in this OTEP * we've documented decisions made along the way in a [decision log](https://github.com/open-telemetry/opentelemetry-proto-profile/blob/main/opentelemetry/proto/profiles/v1/decision-log.md) * we've done benchmarking to refine the data representation (see Benchmarking section in a [collector PR](https://github.com/petethepig/opentelemetry-collector/pull/1)) * diff between original pprof and the new proto: [link](https://github.com/open-telemetry/opentelemetry-proto-profile/compare/2cf711b3cfcc1edd4e46f9b82d19d016d6d0aa2a...petethepig:opentelemetry-proto:pprof-experiments#diff-9cb689ea05ecfd2edffc39869eca3282a3f2f45a8e1aa21624b452fa5362d1d2) We're seeking feedback and hoping to get this approved. --- For (a lot) more details, see: * [OTel Profiling SIG Meeting Notes](https://docs.google.com/document/d/19UqPPPlGE83N37MhS93uRlxsP1_wGxQ33Qv6CDHaEp0/edit) --------- Co-authored-by: Juraci Paixão Kröhling Co-authored-by: Christos Kalkanis Co-authored-by: Felix Geisendörfer Co-authored-by: Reiley Yang --- oteps/profiles/0239-profiles-data-model.md | 1604 +++++++++++++++++ .../images/otep0239/profiles-data-model.png | Bin 0 -> 75855 bytes 2 files changed, 1604 insertions(+) create mode 100644 oteps/profiles/0239-profiles-data-model.md create mode 100644 oteps/profiles/images/otep0239/profiles-data-model.png diff --git a/oteps/profiles/0239-profiles-data-model.md b/oteps/profiles/0239-profiles-data-model.md new file mode 100644 index 00000000000..396e848175b --- /dev/null +++ b/oteps/profiles/0239-profiles-data-model.md @@ -0,0 +1,1604 @@ +# Profiles Data Format + +Introduces Data Model for Profiles signal to OpenTelemetry. + + +* [Motivation](#motivation) +* [Design Notes](#design-notes) + * [Design Goals](#design-goals) +* [Data Model](#data-model) + * [Relationships Diagram](#relationships-diagram) + * [Relationships With Other Signals](#relationships-with-other-signals) + * [From Profiles to Other Signals](#from-profiles-to-other-signals) + * [From Other Signals to Profiles](#from-other-signals-to-profiles) + * [Compatibility With Original pprof](#compatibility-with-original-pprof) + * [Proto Definition](#proto-definition) + * [Message Descriptions](#message-descriptions) + * [Message `ProfilesData`](#message-profilesdata) + * [Message `ResourceProfiles`](#message-resourceprofiles) + * [Message `ScopeProfiles`](#message-scopeprofiles) + * [Message `ProfileContainer`](#message-profilecontainer) + * [Message `Profile`](#message-profile) + * [Message `ValueType`](#message-valuetype) + * [Message `Sample`](#message-sample) + * [Message `AttributeUnit`](#message-attributeunit) + * [Message `Link`](#message-link) + * [Message `Location`](#message-location) + * [Message `Line`](#message-line) + * [Message `Mapping`](#message-mapping) + * [Message `Function`](#message-function) + * [Example Payloads](#example-payloads) + * [Simple Example](#simple-example) + * [Notable Differences Compared to Other Signals](#notable-differences-compared-to-other-signals) + * [Relationships Between Messages](#relationships-between-messages) + * [Relationship Between Samples and Locations](#relationship-between-samples-and-locations) +* [Trade-Offs and Mitigations](#trade-offs-and-mitigations) +* [Prior Art and Alternatives](#prior-art-and-alternatives) + * [Other Popular Formats](#other-popular-formats) + * [Folded Stacks](#folded-stacks) + * [Chromium's Trace Event Format](#chromiums-trace-event-format) + * [Linux perf.data](#linux-perfdata) + * [Java Flight Recorder (JFR)](#java-flight-recorder-jfr) + * [Alternative Representations](#alternative-representations) + * [Benchmarking](#benchmarking) + * ["average" Profile](#average-profile) + * ["average" Profile With Timestamps Added to Each Sample](#average-profile-with-timestamps-added-to-each-sample) + * ["ruby" Profile With Very Deep Stacktraces](#ruby-profile-with-very-deep-stacktraces) + * ["large" Profile](#large-profile) + * [Conclusions](#conclusions) + * [Semantic Conventions](#semantic-conventions) + * [Attributes](#attributes) + * [Profile Types](#profile-types) + * [Profile Units](#profile-units) + * [Decision Log](#decision-log) +* [Open Questions](#open-questions) + * [Units in Attributes](#units-in-attributes) + * [Timestamps](#timestamps) + * [Repetition of Attribute Keys](#repetition-of-attribute-keys) + * [Locations Optimization](#locations-optimization) +* [Future Possibilities](#future-possibilities) + + +## Motivation + +This is a proposal of a data model and semantic conventions that allow to represent profiles coming from a variety of different applications or systems. Existing profiling formats can be unambiguously mapped to this data model. Reverse mapping from this data model is also possible to the extent that the target profiling format has equivalent capabilities. + +The purpose of the data model is to have a common understanding of what a profile is, what data needs to be recorded, transferred, stored and interpreted by a profiling system. + +## Design Notes + +### Design Goals + +These goals are based on the vision set out in [Profiling Vision OTEP](./0212-profiling-vision.md): + +* Make profiling compatible with other signals. +* Standardize profiling data model for industry-wide sharing and reuse. +* Profilers must be implementable with low overhead and conforming to OpenTelemetry-wide runtime overhead/intrusiveness and wire data size requirements. + +The last point is particularly important in the context of profiling. Profilers generate large amounts of data, and users of profiling technology are very sensitive to the overhead that profiling introduces. In the past high overhead has been a blocker for wider adoption of continuous profiling and was one of the reasons why profiling was not used in production environments. Therefore, it is important to make sure that the overhead of handling the profiling data on the client side as well as in intermediaries (e.g collector) is minimal. + +## Data Model + +This section describes various protobuf messages that are used to represent profiles data. + +### Relationships Diagram + +The following diagram shows the relationships between the messages. Relationships between messages are represented by either embedding one message in another (red arrows), or by referencing a message by index in a lookup table (blue arrows). More on that in [Relationships Between Messages](#relationships-between-messages) section below. + +In addition to that, relationship between `samples` and `locations` is further optimized for better performance. More on that in [Relationship Between Samples and Locations](#relationship-between-samples-and-locations) section below. + +![diagram of data relationships](./images/otep0239/profiles-data-model.png) + +### Relationships With Other Signals + +There are two types of relationships between profiles and other signals: + +* from other signals to profiles (e.g from log records, exemplars or trace spans) +* from profiles to other signals + +#### From Profiles to Other Signals + +[Link](#message-link) is a message that is used to represent connections between profile [Samples](#message-sample) and trace spans. It uses `trace_id` and `span_id` as identifiers. + +For other signals, such as logs or metrics, because other signals use the same way of linking between such signals and traces (`trace_id` and `span_id`), it is possible to correlate profiles with other signals using this same information. + +#### From Other Signals to Profiles + +Other signals can use `profile_id` to reference a profile. For example, a log record can reference a profile that was collected at the time when the log record was generated by using `profile_id` as one of the attributes. This allows to correlate logs with profiles. + +Additionally, `trace_id`, `span_id` can be used to reference groups of [Samples](#message-sample) (but not individual [Samples](#message-sample)) in a Profile, since [Samples](#message-sample) are linked to traces with these same identifiers using [Links](#message-link). + +The exact details of such linking are out of scope for this OTEP. It is expected that the exact details will be defined in Profiles part of [opentelemetry-specification](https://github.com/open-telemetry/opentelemetry-specification). + +### Compatibility With Original pprof + +The proposed data model is backward compatible with original pprof in a sense that a pprof file generated by existing software can be parsed using the new proto. All fields in the original pprof are preserved, so that original pprof files can still be parsed using the new proto, and no data is lost. + +It is not forward compatible, meaning that a pprof file generated by the new proto cannot be parsed by existing software. This is mainly due to the sharing of the call stacks between samples + new format for labels (more on these differences below). + +### Proto Definition + +Proto definition is based on [pprof format](https://github.com/google/pprof/blob/main/proto/profile.proto). + +In the landscape of performance profiling tools, pprof's data format stands as a clear industry standard. Its evolution and enduring relevance are a reflection of its effectiveness in addressing diverse and complex performance profiling needs. Major technology firms and open-source projects alike routinely employ pprof, underscoring its universal applicability and reliability. + +According to the [data from Profilerpedia](https://docs.google.com/spreadsheets/d/1UM-WFQhNf4GcyXmluSUGnMbOenvN-TqP2HQC9-Y50Lc/edit?usp=sharing), pprof is one of the most widely used formats. Compared to other formats it has the highest number of profilers, UIs, formats it can be converted to and from. + +The original pprof data model underwent enhancements to more effectively manage profiling data within the scope of OpenTelemetry, and certain upgrades were implemented to overcome a few of the original format's constraints. + +Here's a [link to a diff between original pprof and modified pprof](https://github.com/open-telemetry/opentelemetry-proto-profile/compare/2cf711b3cfcc1edd4e46f9b82d19d016d6d0aa2a...petethepig:opentelemetry-proto:pprof-experiments#diff-9cb689ea05ecfd2edffc39869eca3282a3f2f45a8e1aa21624b452fa5362d1d2) and here's a list of main differences between pprof and OTLP profiles: + +* Sharing of the call stacks between samples. +* Sharing of labels (now called attributes) between samples. +* Reuse of OpenTelemetry conventions and message types. +* Semantic conventions for linking to other signals via `trace_id`s and `span_id`s. +* First-class timestamp support. +* Expanded metadata attach points (Sample / Location / Mapping). + +Below you will find the proto for the new Profiles signal. It is split into two parts: the first part is the OpenTelemetry specific part, and the second part is the modified pprof proto. Intention here is to make it easier to compare modified pprof proto to the original pprof proto. + +OpenTelemetry specific part: + + +```proto +syntax = "proto3"; + +package opentelemetry.proto.profiles.v1; + +import "opentelemetry/proto/common/v1/common.proto"; +import "opentelemetry/proto/resource/v1/resource.proto"; + +import "opentelemetry/proto/profiles/v1/alternatives/pprofextended/pprofextended.proto"; + +option csharp_namespace = "OpenTelemetry.Proto.Profiles.V1"; +option java_multiple_files = true; +option java_package = "io.opentelemetry.proto.profiles.v1"; +option java_outer_classname = "ProfilesProto"; +option go_package = "go.opentelemetry.io/proto/otlp/profiles/v1"; + +// Relationships Diagram +// +// ┌──────────────────┐ LEGEND +// │ ProfilesData │ +// └──────────────────┘ ─────▶ embedded +// │ +// │ 1-n ─────▷ referenced by index +// ▼ +// ┌──────────────────┐ +// │ ResourceProfiles │ +// └──────────────────┘ +// │ +// │ 1-n +// ▼ +// ┌──────────────────┐ +// │ ScopeProfiles │ +// └──────────────────┘ +// │ +// │ 1-n +// ▼ +// ┌──────────────────┐ +// │ ProfileContainer │ +// └──────────────────┘ +// │ +// │ 1-1 +// ▼ +// ┌──────────────────┐ +// │ Profile │ +// └──────────────────┘ +// │ 1-n +// │ 1-n ┌───────────────────────────────────────┐ +// ▼ │ ▽ +// ┌──────────────────┐ 1-n ┌──────────────┐ ┌──────────┐ +// │ Sample │ ──────▷ │ KeyValue │ │ Link │ +// └──────────────────┘ └──────────────┘ └──────────┘ +// │ 1-n △ △ +// │ 1-n ┌─────────────────┘ │ 1-n +// ▽ │ │ +// ┌──────────────────┐ n-1 ┌──────────────┐ +// │ Location │ ──────▷ │ Mapping │ +// └──────────────────┘ └──────────────┘ +// │ +// │ 1-n +// ▼ +// ┌──────────────────┐ +// │ Line │ +// └──────────────────┘ +// │ +// │ 1-1 +// ▽ +// ┌──────────────────┐ +// │ Function │ +// └──────────────────┘ +// + +// 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. +message ProfilesData { + // An array of ResourceProfiles. + // For data coming from a single resource this array will typically contain + // one element. Intermediary nodes that receive data from multiple origins + // typically batch the data before forwarding further and in that case this + // array will contain multiple elements. + repeated ResourceProfiles resource_profiles = 1; +} + +// A collection of ScopeProfiles from a Resource. +message ResourceProfiles { + reserved 1000; + + // The resource for the profiles 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 ScopeProfiles that originate from a resource. + repeated ScopeProfiles scope_profiles = 2; + + // 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. + string schema_url = 3; +} + +// A collection of Profiles produced by an InstrumentationScope. +message 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). + opentelemetry.proto.common.v1.InstrumentationScope scope = 1; + + // A list of ProfileContainers that originate from an instrumentation scope. + repeated ProfileContainer profiles = 2; + + // This schema_url applies to all profiles and profile events in the "profiles" field. + string schema_url = 3; +} + +// A ProfileContainer represents a single profile. It wraps pprof profile with OpenTelemetry specific metadata. +message ProfileContainer { + // 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. + bytes profile_id = 1; + + // start_time_unix_nano is the start time of the profile. + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. + // + // This field is semantically required and it is expected that end_time >= start_time. + fixed64 start_time_unix_nano = 2; + + // end_time_unix_nano is the end time of the profile. + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. + // + // This field is semantically required and it is expected that end_time >= start_time. + fixed64 end_time_unix_nano = 3; + + // attributes 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: + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/common/README.md#attribute + // 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 = 4; + + // 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. + uint32 dropped_attributes_count = 5; + + // Specifies format of the original payload. Common values are defined in semantic conventions. [required if original_payload is present] + string original_payload_format = 6; + + // 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 `profile` MUST be present and contain the same profiling information. + bytes original_payload = 7; + + // This is a reference to a pprof profile. Required, even when original_payload is present. + opentelemetry.proto.profiles.v1.alternatives.pprofextended.Profile profile = 8; +} +``` + + + +Modified pprof: + + + +```proto +// Profile is a common stacktrace profile format. +// +// Measurements represented with this format should follow the +// following conventions: +// +// - Consumers should treat unset optional fields as if they had been +// set with their default value. +// +// - When possible, measurements should be stored in "unsampled" form +// that is most useful to humans. There should be enough +// information present to determine the original sampled values. +// +// - On-disk, the serialized proto must be gzip-compressed. +// +// - The profile is represented as a set of samples, where each sample +// references a sequence of locations, and where each location belongs +// to a mapping. +// - There is a N->1 relationship from sample.location_id entries to +// locations. For every sample.location_id entry there must be a +// unique Location with that index. +// - There is an optional N->1 relationship from locations to +// mappings. For every nonzero Location.mapping_id there must be a +// unique Mapping with that index. + +syntax = "proto3"; + +package opentelemetry.proto.profiles.v1.alternatives.pprofextended; + +import "opentelemetry/proto/common/v1/common.proto"; + +option csharp_namespace = "OpenTelemetry.Proto.Profiles.V1.Alternatives.PprofExtended"; +option go_package = "go.opentelemetry.io/proto/otlp/profiles/v1/alternatives/pprofextended"; + +// Represents a complete profile, including sample types, samples, +// mappings to binaries, locations, functions, string table, and additional metadata. +message 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". + repeated ValueType sample_type = 1; + // The set of samples recorded in this profile. + repeated Sample sample = 2; + // Mapping from address ranges to the image/binary/library mapped + // into that address range. mapping[0] will be the main binary. + repeated Mapping mapping = 3; + // Locations referenced by samples via location_indices. + repeated Location location = 4; + // Array of locations referenced by samples. + repeated int64 location_indices = 15; + // Functions referenced by locations. + repeated Function function = 5; + // Lookup table for attributes. + repeated opentelemetry.proto.common.v1.KeyValue attribute_table = 16; + // Represents a mapping between Attribute Keys and Units. + repeated AttributeUnit attribute_units = 17; + // Lookup table for links. + repeated Link link_table = 18; + // A common table for strings referenced by various messages. + // string_table[0] must always be "". + repeated string string_table = 6; + // frames with Function.function_name fully matching the following + // regexp will be dropped from the samples, along with their successors. + int64 drop_frames = 7; // Index into string table. + // frames with Function.function_name fully matching the following + // regexp will be kept, even if it matches drop_frames. + int64 keep_frames = 8; // Index into string table. + + // The following fields are informational, do not affect + // interpretation of results. + + // Time of collection (UTC) represented as nanoseconds past the epoch. + int64 time_nanos = 9; + // Duration of the profile, if a duration makes sense. + int64 duration_nanos = 10; + // The kind of events between sampled occurrences. + // e.g [ "cpu","cycles" ] or [ "heap","bytes" ] + ValueType period_type = 11; + // The number of events between sampled occurrences. + int64 period = 12; + // 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. + repeated int64 comment = 13; // Indices into string table. + // Index into the string table of the type of the preferred sample + // value. If unset, clients should default to the last sample value. + int64 default_sample_type = 14; +} + +// Represents a mapping between Attribute Keys and Units. +message AttributeUnit { + // Index into string table. + int64 attribute_key = 1; + // Index into string table. + int64 unit = 2; +} + +// 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. +message Link { + // A unique identifier of a trace that this linked span is part of. The ID is a + // 16-byte array. + bytes trace_id = 1; + + // A unique identifier for the linked span. The ID is an 8-byte array. + bytes span_id = 2; +} + +// Specifies the method of aggregating metric values, either DELTA (change since last report) +// or CUMULATIVE (total since a fixed start time). +enum AggregationTemporality { + /* UNSPECIFIED is the default AggregationTemporality, it MUST not be used. */ + AGGREGATION_TEMPORALITY_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. */ + AGGREGATION_TEMPORALITY_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_0+1 with a value of 1. + + Note: Even though, when reporting changes since last report time, using + CUMULATIVE is valid, it is not recommended. */ + AGGREGATION_TEMPORALITY_CUMULATIVE = 2; +} + +// ValueType describes the type and units of a value, with an optional aggregation temporality. +message ValueType { + int64 type = 1; // Index into string table. + int64 unit = 2; // Index into string table. + + AggregationTemporality aggregation_temporality = 3; +} + +// 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. +message Sample { + // The indices recorded here correspond to locations in Profile.location. + // The leaf is at location_index[0]. [deprecated, superseded by locations_start_index / locations_length] + repeated uint64 location_index = 1; + // locations_start_index along with locations_length refers to to a slice of locations in Profile.location. + // Supersedes location_index. + uint64 locations_start_index = 7; + // locations_length along with locations_start_index refers to a slice of locations in Profile.location. + // Supersedes location_index. + uint64 locations_length = 8; + // A 128bit id that uniquely identifies this stacktrace, globally. Index into string table. [optional] + uint32 stacktrace_id_index = 9; + // 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. + repeated int64 value = 2; + // label includes additional context for this sample. It can include + // things like a thread id, allocation size, etc. + // + // NOTE: While possible, having multiple values for the same label key is + // strongly discouraged and should never be used. Most tools (e.g. pprof) do + // not have good (or any) support for multi-value labels. And an even more + // discouraged case is having a string label and a numeric label of the same + // name on a sample. Again, possible to express, but should not be used. + // [deprecated, superseded by attributes] + repeated Label label = 3; + // References to attributes in Profile.attribute_table. [optional] + repeated uint64 attributes = 10; + + // Reference to link in Profile.link_table. [optional] + uint64 link = 12; + + // Timestamps associated with Sample represented in ms. These timestamps are expected + // to fall within the Profile's time range. [optional] + repeated uint64 timestamps = 13; +} + +// Provides additional context for a sample, +// such as thread ID or allocation size, with optional units. [deprecated] +message Label { + int64 key = 1; // Index into string table + + // At most one of the following must be present + int64 str = 2; // Index into string table + int64 num = 3; + + // Should only be present when num is present. + // Specifies the units of num. + // Use arbitrary string (for example, "requests") as a custom count unit. + // If no unit is specified, consumer may apply heuristic to deduce the unit. + // Consumers may also interpret units like "bytes" and "kilobytes" as memory + // units and units like "seconds" and "nanoseconds" as time units, + // and apply appropriate unit conversions to these. + int64 num_unit = 4; // Index into string table +} + +// Indicates the semantics of the build_id field. +enum BuildIdKind { + // Linker-generated build ID, stored in the ELF binary notes. + BUILD_ID_LINKER = 0; + // Build ID based on the content hash of the binary. Currently no particular + // hashing approach is standardized, so a given producer needs to define it + // themselves and thus unlike BUILD_ID_LINKER this kind of hash is producer-specific. + // We may choose to provide a standardized stable hash recommendation later. + BUILD_ID_BINARY_HASH = 1; +} + +// Describes the mapping of a binary in memory, including its address range, +// file offset, and metadata like build ID +message Mapping { + // Unique nonzero id for the mapping. [deprecated] + uint64 id = 1; + // Address at which the binary (or DLL) is loaded into memory. + uint64 memory_start = 2; + // The limit of the address range occupied by this mapping. + uint64 memory_limit = 3; + // Offset in the binary that corresponds to the first mapped address. + uint64 file_offset = 4; + // 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]". + int64 filename = 5; // Index into string table + // A string that uniquely identifies a particular program version + // with high probability. E.g., for binaries generated by GNU tools, + // it could be the contents of the .note.gnu.build-id field. + int64 build_id = 6; // Index into string table + // Specifies the kind of build id. See BuildIdKind enum for more details [optional] + BuildIdKind build_id_kind = 11; + // References to attributes in Profile.attribute_table. [optional] + repeated uint64 attributes = 12; + // The following fields indicate the resolution of symbolic info. + bool has_functions = 7; + bool has_filenames = 8; + bool has_line_numbers = 9; + bool has_inline_frames = 10; +} + +// Describes function and line table debug information. +message Location { + // Unique nonzero id for the location. A profile could use + // instruction addresses or any integer sequence as ids. [deprecated] + uint64 id = 1; + // The index of the corresponding profile.Mapping for this location. + // It can be unset if the mapping is unknown or not applicable for + // this profile type. + uint64 mapping_index = 2; + // 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. + uint64 address = 3; + // 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" + repeated Line line = 4; + // 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. + bool is_folded = 5; + + // Type of frame (e.g. kernel, native, python, hotspot, php). Index into string table. + uint32 type_index = 6; + + // References to attributes in Profile.attribute_table. [optional] + repeated uint64 attributes = 7; +} + +// Details a specific line in a source code, linked to a function. +message Line { + // The index of the corresponding profile.Function for this line. + uint64 function_index = 1; + // Line number in source code. + int64 line = 2; + // Column number in source code. + int64 column = 3; +} + +// Describes a function, including its human-readable name, system name, +// source file, and starting line number in the source. +message Function { + // Unique nonzero id for the function. [deprecated] + uint64 id = 1; + // Name of the function, in human-readable form if available. + int64 name = 2; // Index into string table + // Name of the function, as identified by the system. + // For instance, it can be a C++ mangled name. + int64 system_name = 3; // Index into string table + // Source file containing the function. + int64 filename = 4; // Index into string table + // Line number in source file. + int64 start_line = 5; +} +``` + + + +### Message Descriptions + +These are detailed descriptions of protobuf messages that are used to represent profiling data. + + +#### Message `ProfilesData` + +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. + +#### Message `ResourceProfiles` + +A collection of ScopeProfiles from a Resource. + +
+Field Descriptions + +##### Field `resource` + +The resource for the profiles in this message. +If this field is not set then no resource info is known. + +##### Field `scope_profiles` + +A list of ScopeProfiles that originate from a resource. + +##### Field `schema_url` + +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. +
+ +#### Message `ScopeProfiles` + +A collection of Profiles produced by an InstrumentationScope. + +
+Field Descriptions + +##### Field `scope` + +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). + +##### Field `profiles` + +A list of ProfileContainers that originate from an instrumentation scope. + +##### Field `schema_url` + +This schema_url applies to all profiles and profile events in the "profiles" field. +
+ +#### Message `ProfileContainer` + +A ProfileContainer represents a single profile. It wraps pprof profile with OpenTelemetry specific metadata. + +
+Field Descriptions + +##### Field `profile_id` + +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. + +##### Field `start_time_unix_nano` + +start_time_unix_nano is the start time of the profile. +Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. +This field is semantically required and it is expected that end_time >= start_time. + +##### Field `end_time_unix_nano` + +end_time_unix_nano is the end time of the profile. +Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. +This field is semantically required and it is expected that end_time >= start_time. + +##### Field `attributes` + +attributes is a collection of key/value pairs. Note, global attributes +like server name can be set using the resource API. + +##### Field `dropped_attributes_count` + +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. + +##### Field `original_payload_format` + +Specifies format of the original payload. Common values are defined in semantic conventions. [required if original_payload is present] + +##### Field `original_payload` + +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 `profile` MUST be present and contain the same profiling information. + +##### Field `profile` + +This is a reference to a pprof profile. Required, even when original_payload is present. +
+ +#### Message `Profile` + +Profile is a common stacktrace profile format. +Measurements represented with this format should follow the +following conventions: + +- Consumers should treat unset optional fields as if they had been + set with their default value. +- When possible, measurements should be stored in "unsampled" form + that is most useful to humans. There should be enough + information present to determine the original sampled values. +- On-disk, the serialized proto must be gzip-compressed. +- The profile is represented as a set of samples, where each sample + references a sequence of locations, and where each location belongs + to a mapping. +- There is a N->1 relationship from sample.location_id entries to + locations. For every sample.location_id entry there must be a + unique Location with that index. +- There is an optional N->1 relationship from locations to + mappings. For every nonzero Location.mapping_id there must be a + unique Mapping with that index. + +Represents a complete profile, including sample types, samples, +mappings to binaries, locations, functions, string table, and additional metadata. + +
+Field Descriptions + +##### Field `sample_type` + +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". + +##### Field `sample` + +The set of samples recorded in this profile. + +##### Field `mapping` + +Mapping from address ranges to the image/binary/library mapped +into that address range. mapping[0] will be the main binary. + +##### Field `location` + +Locations referenced by samples via location_indices. + +##### Field `location_indices` + +Array of locations referenced by samples. + +##### Field `function` + +Functions referenced by locations. + +##### Field `attribute_table` + +Lookup table for attributes. + +##### Field `attribute_units` + +Represents a mapping between Attribute Keys and Units. + +##### Field `link_table` + +Lookup table for links. + +##### Field `string_table` + +A common table for strings referenced by various messages. +string_table[0] must always be "". + +##### Field `drop_frames` + +frames with Function.function_name fully matching the following +regexp will be dropped from the samples, along with their successors. + +##### Field `keep_frames` + +Index into string table. +frames with Function.function_name fully matching the following +regexp will be kept, even if it matches drop_frames. + +##### Field `time_nanos` + +Index into string table. +The following fields are informational, do not affect +interpretation of results. +Time of collection (UTC) represented as nanoseconds past the epoch. + +##### Field `duration_nanos` + +Duration of the profile, if a duration makes sense. + +##### Field `period_type` + +The kind of events between sampled occurrences. +e.g [ "cpu","cycles" ] or [ "heap","bytes" ] + +##### Field `period` + +The number of events between sampled occurrences. + +##### Field `comment` + +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. + +##### Field `default_sample_type` + +Indices into string table. +Index into the string table of the type of the preferred sample +value. If unset, clients should default to the last sample value. +
+ +#### Message `ValueType` + +ValueType describes the type and units of a value, with an optional aggregation temporality. + +
+Field Descriptions + +##### Field `unit` + +Index into string table. + +##### Field `aggregation_temporality` + +Index into string table. +
+ +#### Message `Sample` + +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. + +
+Field Descriptions + +##### Field `location_index` + +The indices recorded here correspond to locations in Profile.location. +The leaf is at location_index[0]. [deprecated, superseded by locations_start_index / locations_length] + +##### Field `locations_start_index` + +locations_start_index along with locations_length refers to to a slice of locations in Profile.location. +Supersedes location_index. + +##### Field `locations_length` + +locations_length along with locations_start_index refers to a slice of locations in Profile.location. +Supersedes location_index. + +##### Field `stacktrace_id_index` + +A 128bit id that uniquely identifies this stacktrace, globally. Index into string table. [optional] + +##### Field `value` + +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. + +##### Field `label` + +label includes additional context for this sample. It can include +things like a thread id, allocation size, etc. +NOTE: While possible, having multiple values for the same label key is +strongly discouraged and should never be used. Most tools (e.g. pprof) do +not have good (or any) support for multi-value labels. And an even more +discouraged case is having a string label and a numeric label of the same +name on a sample. Again, possible to express, but should not be used. +[deprecated, superseded by attributes] + +##### Field `attributes` + +References to attributes in Profile.attribute_table. [optional] + +##### Field `link` + +Reference to link in Profile.link_table. [optional] + +##### Field `timestamps` + +Timestamps associated with Sample represented in ms. These timestamps are expected +to fall within the Profile's time range. [optional] +
+ +#### Message `AttributeUnit` + +Represents a mapping between Attribute Keys and Units. + +
+Field Descriptions + +##### Field `attribute_key` + +Index into string table. + +##### Field `unit` + +Index into string table. +
+ +#### Message `Link` + +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. + +
+Field Descriptions + +##### Field `trace_id` + +A unique identifier of a trace that this linked span is part of. The ID is a +16-byte array. + +##### Field `span_id` + +A unique identifier for the linked span. The ID is an 8-byte array. +
+ +#### Message `Location` + +Describes function and line table debug information. + +
+Field Descriptions + +##### Field `id` + +Unique nonzero id for the location. A profile could use +instruction addresses or any integer sequence as ids. [deprecated] + +##### Field `mapping_index` + +The index of the corresponding profile.Mapping for this location. +It can be unset if the mapping is unknown or not applicable for +this profile type. + +##### Field `address` + +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. + +##### Field `line` + +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" + +##### Field `is_folded` + +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. + +##### Field `type_index` + +Type of frame (e.g. kernel, native, python, hotspot, php). Index into string table. + +##### Field `attributes` + +References to attributes in Profile.attribute_table. [optional] +
+ +#### Message `Line` + +Details a specific line in a source code, linked to a function. + +
+Field Descriptions + +##### Field `function_index` + +The index of the corresponding profile.Function for this line. + +##### Field `line` + +Line number in source code. + +##### Field `column` + +Column number in source code. +
+ +#### Message `Mapping` + +Describes the mapping of a binary in memory, including its address range, +file offset, and metadata like build ID + +
+Field Descriptions + +##### Field `id` + +Unique nonzero id for the mapping. [deprecated] + +##### Field `memory_start` + +Address at which the binary (or DLL) is loaded into memory. + +##### Field `memory_limit` + +The limit of the address range occupied by this mapping. + +##### Field `file_offset` + +Offset in the binary that corresponds to the first mapped address. + +##### Field `filename` + +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]". + +##### Field `build_id` + +Index into string table +A string that uniquely identifies a particular program version +with high probability. E.g., for binaries generated by GNU tools, +it could be the contents of the .note.gnu.build-id field. + +##### Field `build_id_kind` + +Index into string table +Specifies the kind of build id. See BuildIdKind enum for more details [optional] + +##### Field `attributes` + +References to attributes in Profile.attribute_table. [optional] + +##### Field `has_functions` + +The following fields indicate the resolution of symbolic info. +
+ +#### Message `Function` + +Describes a function, including its human-readable name, system name, +source file, and starting line number in the source. + +
+Field Descriptions + +##### Field `id` + +Unique nonzero id for the function. [deprecated] + +##### Field `name` + +Name of the function, in human-readable form if available. + +##### Field `system_name` + +Index into string table +Name of the function, as identified by the system. +For instance, it can be a C++ mangled name. + +##### Field `filename` + +Index into string table +Source file containing the function. + +##### Field `start_line` + +Index into string table +Line number in source file. +
+ + + +### Example Payloads + +#### Simple Example + +Considering the following example presented in a modified folded format: + +``` +foo;bar;baz 100 region=us,trace_id=0x01020304010203040102030401020304,span_id=0x9999999999999999 1687841528000000 +foo;bar 200 region=us +``` + +It represents 2 samples: + +* one for stacktrace `foo;bar;baz` with value `100`, attributes `region=us`, linked to trace `0x01020304010203040102030401020304` and span `0x9999999999999999`, and timestamp `1687841528000000` +* one for stacktrace `foo;bar` with value `200`, attributes `region=us`, no link, no timestamp + +The resulting profile in OTLP format would look like this (converted to YAML format for legibility): + +```yaml +resource_profiles: + - resource: + attributes: null + schema_url: todo + scope_profiles: + - profiles: + - profile_id: 0x0102030405060708090a0b0c0d0e0f10 + start_time_unix_nano: 1687841520000000 + end_time_unix_nano: 1687841530000000 + profile: + sample_type: + type: 4 + unit: 5 + aggregation_temporality: 1 + attribute_table: + - key: trace_id + value: + Value: + bytes_value: 0x01020304010203040102030401020304 + - key: span_id + value: + Value: + bytes_value: 0x9999999999999999 + - key: region + value: + Value: + string_value: us + function: + - name: 1 + - name: 2 + - name: 3 + location: + - line: + - function_index: 0 + - line: + - function_index: 1 + - line: + - function_index: 2 + location_indices: + - 0 + - 1 + - 2 + sample: + - locations_start_index: 0 + locations_length: 3 + timestamps: + - 1687841528000000 + value: + - 100 + - locations_start_index: 0 + locations_length: 2 + value: + - 200 + string_table: + - "" + - foo + - bar + - baz + - cpu + - samples +``` + +### Notable Differences Compared to Other Signals + +Due to the increased performance requirements associated with profiles signal, here are some notable differences between profiles signal and other signals. + +#### Relationships Between Messages + +There are two main ways relationships between messages are represented: + +* by embedding a message into another message (standard protobuf way) +* by referencing a message by index (similar to how it's done in pprof) + +Profiling signal is different from most other ones in that we use the referencing technique a lot to represent relationships between messages where there is a lot of duplication happening. This allows to reduce the size of the resulting protobuf payload and the number of objects that need to be allocated to parse such payload. + +This example illustrates the conceptual difference between the two approaches. Note that this example is simplified for clarity and provided in YAML format for legibility: + +```yaml +# denormalized +samples: +- stacktrace: + - foo + - bar + value: 100 + attribute_set: + endpoint: "/v1/users" +- stacktrace: + - foo + - bar + - baz + value: 200 + attribute_set: + endpoint: "/v1/users" + +# normalized +attribute_sets: +- endpoint: "/v1/users" +samples: +- stacktrace: + - foo + - bar + value: 100 + attribute_set_index: 0 +- stacktrace: + - foo + - bar + - baz + value: 200 + attribute_set_index: 0 + +``` + +Explanation: because multiple samples have the same attributes, we can store them in a separate table and reference them by index. This reduces the size of the resulting protobuf payload and the number of objects that need to be allocated to parse such payload. + +Benchmarking shows that this approach is significantly more efficient in terms of CPU utilization, memory consumption and size of the resulting protobuf payload. See [Prior art and alternatives](#prior-art-and-alternatives) for more details. + +#### Relationship Between Samples and Locations + +Relationship between Samples and Locations is using the referencing technique described above. However, there's an additional optimization technique used to reduce the size of the resulting protobuf payload and the number of objects that need to be allocated to parse such payload. The technique is based on the fact that many samples share the same locations. + +Considering the following example presented in a folded format: + +``` +foo;bar;baz 100 +abc;def 200 +foo;bar 300 +``` + +It represents 3 samples: + +* one for stacktrace `foo;bar;baz` with value `100`. +* one for stacktrace `abc;def` with value `200`. +* one for stacktrace `foo;bar` with value `300`. Note that 2 of the locations are shared with the first sample. + +By storing locations in a separate table and referring to start_index+length in that table, we can reduce the size of the resulting protobuf payload and the number of objects that need to be allocated to parse such payload. With this approach we can also take advantage of the fact that many samples share the same locations. Below is a representation of the resulting protobuf payload (in YAML format for legibility): + +```yaml +sample: + - locations_start_index: 0 + locations_length: 3 + value: + - 100 + - locations_start_index: 2 + locations_length: 2 + value: + - 200 + - locations_start_index: 0 + locations_length: 2 + value: + - 300 +location_indices: + - 0 # foo + - 1 # bar + - 2 # baz + - 4 # abc + - 5 # def +location: + - line: + - function_index: 0 # foo + - line: + - function_index: 1 # bar + - line: + - function_index: 2 # baz + - line: + - function_index: 3 # abc + - line: + - function_index: 4 # def +function: + - name: 1 # foo + - name: 2 # bar + - name: 3 # baz + - name: 4 # abc + - name: 5 # def +string_table: + - "" + - foo + - bar + - baz + - abc + - def +``` + +Benchmarking shows that this approach is significantly more efficient in terms of CPU utilization, memory consumption and size of the resulting protobuf payload. See [Prior art and alternatives](#prior-art-and-alternatives) for more details. + +## Trade-Offs and Mitigations + +The biggest trade-off was made between the performance characteristics of the format and it's simplicity. The emphasis was made on the performance characteristics, which resulted in a cognitively more complex format. + +Authors feel like the complexity is justified for the following reasons: + +* as presented in [Design Goals](#design-goals) section, the performance characteristics of the format are very important for the profiling signal +* the format is not intended to be used directly by the end users, but rather by the developers of profiling systems that are used to and are expected to be able to handle the complexity. It is not more complex than other existing formats + +Alternative formats that are simpler to understand were considered, but they were not as efficient in terms of CPU utilization, memory consumption and size of the resulting protobuf payload. See [next chapter, Prior art and alternatives](#prior-art-and-alternatives) for more details. + +## Prior Art and Alternatives + +This section describes other existing popular formats and alternative representations that were considered in the process of designing this data model. + +### Other Popular Formats + +Many other popular formats were considered as part of the process of designing this format. The popularity was assessed based on [data from Profilerpedia website](https://docs.google.com/spreadsheets/d/1UM-WFQhNf4GcyXmluSUGnMbOenvN-TqP2HQC9-Y50Lc/edit?usp=sharing). This chapter describes the most notable formats that were considered. + +#### Folded Stacks + +The [Folded Stacks representation](https://profilerpedia.markhansen.co.nz/formats/folded-stacks/), which is rooted in a straightforward text-based format, presents its own set of limitations. The main one is its inefficiency in handling large datasets, as it can struggle with both the complexity and the volume of data. The format's definition also reveals shortcomings, such as the absence of standardized, machine-readable attributes, resulting in varying interpretations and implementations by different profiling tools and analysis software. + +#### Chromium's Trace Event Format + +The [Chromium Trace Event Format](https://profilerpedia.markhansen.co.nz/formats/trace-event-format/), which employs JSON as its foundation, exhibits certain limitations. Notably, it does not excel in terms of payload size efficiency and lacks robust support for aggregation. Additionally, its specification demonstrates weaknesses, as it lacks machine-readable attributes, leading to distinct interpretations by various implementations, such as Perfetto and Catapult. + +#### Linux perf.data + +The [Linux perf.data format](https://profilerpedia.markhansen.co.nz/formats/linux-perf-data/), primarily aimed at low-level data collection, offers insights at a granularity that may not be suitable for high-level analysis. As it contains events generated by Performance Monitoring Units (PMUs) along with metadata, many of its fields find relevance primarily in data collected at the kernel level. + +#### Java Flight Recorder (JFR) + +[Java Flight Recorder](https://profilerpedia.markhansen.co.nz/formats/jfr/) (JFR) may not be the ideal choice for profiling applications outside the Java ecosystem. Its specialization in Java profiling limits its applicability in environments that rely on other programming languages, rendering it unsuitable for non-Java applications. + +### Alternative Representations + +In the process of refining the data model, multiple alternative representations were considered, including: + +* `pprof` representation is data in original pprof format. +* `denormalized` representation, where all messages are embedded and no references by index are used. This is the simplest representation, but it is also the least efficient (by a huge margin) in terms of CPU utilization, memory consumption and size of the resulting protobuf payload. +* `normalized` representation, where messages that repeat often are stored in separate tables and are referenced by indices. See [this chapter](#relationships-between-messages) for more details. This technique reduces the size of the resulting protobuf payload and the number of objects that need to be allocated to parse such payload. +* `arrays` representation, which is based on `normalized` representation, but uses arrays of integers instead of arrays of structures to represent messages. It further reduces the number of allocations, and the size of the resulting protobuf payload. +* `pprofextended` is a modified `pprof` representation. It is the one presented in this OTEP. + +You can find exact proto definitions for each one [here](https://github.com/open-telemetry/opentelemetry-proto-profile/commit/622c1658673283102a9429109185615bfcfaa78e#diff-a21ad1b0e4735fa9b5085cf46abe16f6c13d1710fd255b15b28adb2493a129bfR1). + +These alternative representations helped us narrow down various techniques for representing profiling data. It was found that all of the representations above are less performant compared to the data model presented in this OTEP. More on that in the next section, [Benchmarking](#benchmarking). + +### Benchmarking + +The benchmarking was done using Go benchmarking tools. +As part of the process of designing the data model we ran many benchmarks. All benchmarks follow the same 3 step process: + +* Get a profile in pprof format +* Convert it into a profile in some other format, serialize it to bytes, gzip the result +* Measure key performance indicators + +All benchmarks measured a few key indicators: + +* `bytes` — size of payload after conversion and serialization +* `gzipped_bytes` — size of payload after gzip compression. +* `retained_objects` — number of go runtime objects created and retained after conversion. +* `unique_label_sets` — number of unique label sets in source pprof file +* `bytes_allocated` - number of bytes allocated by go runtime during conversion +* `allocs` — number of allocations by go runtime during conversion + +`gzipped_bytes` is an important metric since this is a cost center for network traffic. + +`bytes`, `retained_objects`, `bytes_allocated`, `allocs` metrics are important because they directly affects memory as well as garbage collection overhead on data producers as well as the intermediaries (such as collector). + +[Benchmarking results](https://docs.google.com/spreadsheets/d/1Q-6MlegV8xLYdz5WD5iPxQU2tsfodX1-CDV1WeGzyQ0/edit#gid=0) spreadsheet shows the most recent benchmarking results as well as history of previous benchmarking runs. [Here](https://github.com/petethepig/opentelemetry-collector/pull/1#:~:text=in%20text%20form-,To%20run%20benchmarks%3A,-Clone%20this%20repo) are instructions on how to run the benchmarks. Here's a rough history of benchmarking our group has done: + +* "23 July 2023", "24 Aug 2023" — show differences between "pprof", "denormalized" and "normalized" representations +* "04 Oct 2023" — introduces "pprofextended" representation, compares it to "pprof" and "arrays". Shoes that "pprofextended" version is overall better than "pprof", but not as a good as "arrays" +* "06 Oct 2023" vs "After Stacktrace removal (Oct 6 2023)" — shows difference between representing stacktraces as separate struct vs a separate array of integers. Shows massive reduction in retained_objects. Demonstrates that after Stacktrace struct removal "pprofextended" representation is better than "arrays" representation +* "Attribute Representations (Oct 13 2023)" — focuses on differences between different attribute representations. Shows that having a lookup table for attributes is optimal compared to other representations + +Below you can see the benchmarking results for most notable example profiles: + +#### "average" Profile + +The source for this benchmark is a single 10 second pprof profile collected from a simple go program. It represents a typical profile that is collected from a running application. + +|name|bytes|gzipped_bytes|retained_objects|unique_label_sets|bytes_allocated|allocs| +|---|---|---|---|---|---|---| +|pprof|7,974|3,772|653|1|876,968|824| +|denormalized|83,204|3,844|3,166|1|1,027,424|3,191| +|normalized|7,940|3,397|753|1|906,848|1,815| +|arrays|7,487|3,276|586|1|922,948|2,391| +|pprofextended|7,695|3,347|654|1|899,400|779| + +#### "average" Profile With Timestamps Added to Each Sample + +The source is the same as in the previous example, but this time there were timestamps added to each sample in the profile. + +|name|bytes|gzipped_bytes|retained_objects|unique_label_sets|bytes_allocated|allocs| +|---|---|---|---|---|---|---| +|pprof|9,516|3,787|968|1|898,696|1,568| +|denormalized|121,396|4,233|4,536|1|1,126,280|4,894| +|normalized|9,277|3,394|900|1|925,904|2,632| +|arrays|8,877|3,309|588|1|946,468|3,306| +|pprofextended|8,863|3,387|806|1|919,904|1,476| + +#### "ruby" Profile With Very Deep Stacktraces + +The source for this test is an aggregated pprof profile collected from a Ruby application that has very deep stacktraces. + +|name|bytes|gzipped_bytes|retained_objects|unique_label_sets|bytes_allocated|allocs| +|---|---|---|---|---|---|---| +|pprof|1,869,549|115,289|19,759|1|14,488,578|42,359| +|denormalized|163,107,501|4,716,428|3,840,093|1|319,473,752|3,844,625| +|normalized|1,931,909|130,565|46,890|1|315,003,144|1,725,508| +|arrays|1,868,982|120,298|23,483|1|314,117,160|1,689,537| +|pprofextended|841,957|94,852|19,759|1|20,719,752|33,410| + +#### "large" Profile + +The source for this test is an aggregated pprof profile collected from a Go application over a long period of time (24 hours). + +|name|bytes|gzipped_bytes|retained_objects|unique_label_sets|bytes_allocated|allocs| +|---|---|---|---|---|---|---| +|pprof|2,874,764|1,110,109|350,659|27|27,230,584|470,033| +|denormalized|87,887,253|6,890,103|2,287,303|27|190,243,856|2,325,604| +|normalized|2,528,337|953,211|333,565|27|46,449,824|1,274,000| +|arrays|2,251,355|999,310|213,018|27|60,971,752|1,904,756| +|pprofextended|2,398,961|872,059|274,140|27|38,874,712|353,083| + +#### Conclusions + +After running many benchmarks and analyzing the results, we came to the following conclusions: + +* `pprof` representation is good but lacks deeper integration with OpenTelemetry standards and could be improved in terms of performance. +* `denormalized` representation is significantly more expensive in terms of CPU utilization, memory consumption and size of the resulting protobuf payload compared to `normalized` representation. It is not suitable for production use. +* `normalized` representation is much better than `denormalized` one +* `arrays` representation is generally better than `normalized` one, but introduces significant changes to the data model and is not as easy to understand +* `pprofextended` (the representation that is used in this OTEP) is the perfect mix of performance and simplicity. It is significantly better than `normalized` representation in terms of CPU utilization, memory consumption and size of the resulting protobuf payload, but it is also more similar to original pprof and easier to understand and implement than `arrays` representation. + +### Semantic Conventions + +We plan to leverage OTEL Semantic Conventions for various attributes and enums such as profile types or units. Here's a non-exhaustive list of semantic conventions that are used in data model. It is expected to be polished and extended in the future. + +#### Profile Types + +Here's a list of possible profile types. It is not exhaustive, and it is expected that more profile types will be added in the future: + +* `cpu` +* `wall` +* `goroutines` +* `alloc_objects` +* `alloc_space` +* `inuse_objects` +* `inuse_space` +* `mutex_contentions` +* `mutex_delay` +* `block_contentions` +* `block_delay` + +#### Profile Units + +Here's a list of possible profile units. It is not exhaustive, and it is expected that more units will be added in the future. [UCUM](https://ucum.org/) will be used as a reference for some of these units: + +* `bytes` +* `samples` +* `ns` +* `ms` +* `count` + +### Decision Log + +There were many other alternatives considered during the design process. See [Decision Log](https://github.com/open-telemetry/opentelemetry-proto-profile/blob/54bba7a86d839b9d29488de8e22d8c567d283e7b/opentelemetry/proto/profiles/v1/decision-log.md#L0-L1) for more information about various decisions that were made during the design process. + +## Open Questions + +Some minor details about the data model are still being discussed and will be flushed out in the future OTEPs. We intend to finalize these details after doing experiments with early versions of working client + collector + backend implementations and getting feedback from the community. The goal of this OTEP is to provide a solid foundation for these experiments and [more](#future-possibilities). + +Here's a list of open questions: + +### Units in Attributes + +Original pprof format allows to specify units for attributes. The current data model supports a similar concept via the use of string table (see `attribute_units` in `Sample` message). It might be a good idea to have the units be specified directly in `KeyValue` message. However, such change would require changes in virtually all signals and it is not clear if it is worth it. We intend to research this question in the future and if it is worth it, we will submit a separate OTEP to make this change. + +### Timestamps + +Although there's support for timestamps in the data model, it is not clear how they should be used and therefore we expect to make changes to this aspect of the data model in the future after we do more experiments. + +### Repetition of Attribute Keys + +Original pprof format allows for efficient encoding of repeating attribute keys. For example, if 3 attributes have the same key (e.g `pid: 1`, `pid: 2`, `pid: 3`), the key is only stored once in the string table. The current data model doesn't support this optimization. We intend to research if this optimization is truly needed and if it is, it will be added to the data model in the future. + +### Locations Optimization + +[Relationship Between Samples And Location](#relationship-between-samples-and-locations) section describes the technique that is used to reduce the size of the resulting protobuf payload and the number of objects that need to be allocated to parse such payload. However, there are concerns that this technique makes the data model more complex, in particular: + +* it requires somewhat custom memory management which means things like array-out-of-bounds can go unnoticed +* makes it harder to implement a `Merge` function for 2 or more profiles + +We intend to research this question in the future while we experiment with early versions of working client + collector + backend implementations. If it is not worth it, we will submit a separate OTEP to remove this change. + +## Future Possibilities + +This OTEP enables us to start working on various parts of [OTEL Specification](https://github.com/open-telemetry/opentelemetry-specification): + +* Profiles Data Model +* Profiles API +* Profiles SDK + +That in turn would enable us to start working on: + +* Profiles support in [OTEL Collector](https://github.com/open-telemetry/opentelemetry-collector) +* Client SDK implementations in various languages (e.g Go and Java) diff --git a/oteps/profiles/images/otep0239/profiles-data-model.png b/oteps/profiles/images/otep0239/profiles-data-model.png new file mode 100644 index 0000000000000000000000000000000000000000..3c9ac90a2bf03b5880269ce9fab66d89b7a8a2dd GIT binary patch literal 75855 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 J={a-#e*^Cdt+fCE literal 0 HcmV?d00001