Skip to main content

libdd_profiling_protobuf/
location.rs

1// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{Record, Value, WireType, NO_OPT_ZERO, OPT_ZERO};
5use std::io::{self, Write};
6
7/// Describes function and line table debug information. This only supports a
8/// single Line, whereas protobuf supports zero or more. The `is_folding`
9/// field is not omitted for size/CPU reasons.
10#[repr(C)]
11#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
12#[cfg_attr(feature = "bolero", derive(bolero::generator::TypeGenerator))]
13pub struct Location {
14    /// Unique nonzero id for the location. A profile could use instruction
15    /// addresses or any integer sequence as ids.
16    pub id: Record<u64, 1, NO_OPT_ZERO>,
17    /// The id of the corresponding profile.Mapping for this location.
18    /// It can be unset if the mapping is unknown or not applicable for
19    /// this profile type.
20    pub mapping_id: Record<u64, 2, OPT_ZERO>,
21    /// The instruction address for this location, if available. It should be
22    /// within `Mapping.memory_start..Mapping.memory_limit` for the
23    /// corresponding mapping. A non-leaf address may be in the middle of a
24    /// call instruction. It is up to display tools to find the beginning of
25    /// the instruction if necessary.
26    pub address: Record<u64, 3, OPT_ZERO>,
27    pub line: Record<Line, 4, OPT_ZERO>,
28}
29
30/// Represents function and line number information. Omits column.
31#[repr(C)]
32#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
33#[cfg_attr(feature = "bolero", derive(bolero::generator::TypeGenerator))]
34pub struct Line {
35    /// The id of the corresponding profile.Function for this line.
36    pub function_id: Record<u64, 1, OPT_ZERO>,
37    /// Line number in source code.
38    pub lineno: Record<i64, 2, OPT_ZERO>,
39}
40
41/// # Safety
42/// The Default implementation will return all zero-representations.
43unsafe impl Value for Line {
44    const WIRE_TYPE: WireType = WireType::LengthDelimited;
45
46    fn proto_len(&self) -> u64 {
47        self.function_id.proto_len() + self.lineno.proto_len()
48    }
49
50    fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
51        self.function_id.encode(writer)?;
52        self.lineno.encode(writer)
53    }
54}
55
56#[cfg(feature = "prost_impls")]
57impl From<Line> for crate::prost_impls::Line {
58    fn from(line: Line) -> Self {
59        // If the prost file is regenerated, this may pick up new members,
60        // such as column.
61        #[allow(clippy::needless_update)]
62        Self {
63            function_id: line.function_id.value,
64            line: line.lineno.value,
65            ..Self::default()
66        }
67    }
68}
69
70/// # Safety
71/// The Default implementation will return all zero-representations.
72unsafe impl Value for Location {
73    const WIRE_TYPE: WireType = WireType::LengthDelimited;
74
75    fn proto_len(&self) -> u64 {
76        self.id.proto_len()
77            + self.mapping_id.proto_len()
78            + self.address.proto_len()
79            + self.line.proto_len()
80    }
81
82    fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
83        self.id.encode(writer)?;
84        self.mapping_id.encode(writer)?;
85        self.address.encode(writer)?;
86        self.line.encode(writer)
87    }
88}
89
90#[cfg(feature = "prost_impls")]
91impl From<&Location> for crate::prost_impls::Location {
92    fn from(location: &Location) -> Self {
93        Self {
94            id: location.id.value,
95            mapping_id: location.mapping_id.value,
96            address: location.address.value,
97            lines: if location.line == Default::default() {
98                Vec::new()
99            } else {
100                vec![crate::prost_impls::Line::from(location.line.value)]
101            },
102            is_folded: false,
103        }
104    }
105}
106
107#[cfg(feature = "prost_impls")]
108impl From<Location> for crate::prost_impls::Location {
109    fn from(location: Location) -> Self {
110        Self::from(&location)
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    use crate::prost_impls;
118    use prost::Message;
119
120    #[track_caller]
121    fn test(location: &Location) {
122        let mut buffer = Vec::new();
123        let prost_location = prost_impls::Location::from(location);
124
125        location.encode(&mut buffer).unwrap();
126        let roundtrip = prost_impls::Location::decode(buffer.as_slice()).unwrap();
127        assert_eq!(prost_location, roundtrip);
128
129        // This doesn't need to strictly be true, but it currently it is
130        // true and makes testing easier.
131        let mut buffer2 = Vec::new();
132        prost_location.encode(&mut buffer2).unwrap();
133        let roundtrip2 = prost_impls::Location::decode(buffer2.as_slice()).unwrap();
134        assert_eq!(roundtrip, roundtrip2);
135    }
136
137    #[test]
138    fn basic() {
139        let location = Location {
140            id: Record::default(),
141            mapping_id: Record::default(),
142            address: Record::default(),
143            line: Record::from(Line {
144                function_id: Record::from(1),
145                lineno: Record::default(),
146            }),
147        };
148        test(&location);
149    }
150
151    #[test]
152    fn roundtrip() {
153        bolero::check!().with_type::<Location>().for_each(test);
154    }
155}