Monitoring System 0.1.0
System resource monitoring with pluggable collectors and alerting
Loading...
Searching...
No Matches
zipkin_proto.h
Go to the documentation of this file.
1#pragma once
2
3// BSD 3-Clause License
4// Copyright (c) 2025
5// See the LICENSE file in the project root for full license information.
6
23#include "protobuf_wire.h"
24
25#include <cstdint>
26#include <string>
27#include <utility>
28#include <vector>
29
30namespace kcenon { namespace monitoring { namespace zipkin_proto {
31
32enum class span_kind : std::int32_t {
33 unspecified = 0,
34 client = 1,
35 server = 2,
36 producer = 3,
37 consumer = 4,
38};
39
41inline span_kind parse_kind(const std::string& value) {
42 std::string upper;
43 upper.reserve(value.size());
44 for (char c : value) {
45 if (c >= 'a' && c <= 'z') upper.push_back(static_cast<char>(c - 'a' + 'A'));
46 else upper.push_back(c);
47 }
48 if (upper == "CLIENT") return span_kind::client;
49 if (upper == "SERVER") return span_kind::server;
50 if (upper == "PRODUCER") return span_kind::producer;
51 if (upper == "CONSUMER") return span_kind::consumer;
53}
54
55struct endpoint {
56 std::string service_name;
57 std::vector<std::uint8_t> ipv4;
58 std::vector<std::uint8_t> ipv6;
59 std::int32_t port{0};
60
61 bool empty() const {
62 return service_name.empty() && ipv4.empty() && ipv6.empty() && port == 0;
63 }
64};
65
66struct annotation {
67 std::uint64_t timestamp{0}; // epoch microseconds
68 std::string value;
69};
70
71struct span {
72 std::vector<std::uint8_t> trace_id; // 8 or 16 bytes
73 std::vector<std::uint8_t> parent_id; // 8 bytes (may be empty)
74 std::vector<std::uint8_t> id; // 8 bytes
76 std::string name;
77 std::uint64_t timestamp{0}; // epoch microseconds
78 std::uint64_t duration{0}; // microseconds
81 std::vector<annotation> annotations;
82 std::vector<std::pair<std::string, std::string>> tags; // proto map<string,string>
83 bool debug{false};
84 bool shared{false};
85};
86
88 std::vector<span> spans;
89};
90
91// ---------------------------------------------------------------------------
92// Encoders
93// ---------------------------------------------------------------------------
94
95inline std::vector<std::uint8_t> encode_endpoint(const endpoint& ep) {
96 std::vector<std::uint8_t> out;
100 protobuf_wire::encode_uint64_field(out, 4, static_cast<std::uint64_t>(ep.port));
101 return out;
102}
103
104inline std::vector<std::uint8_t> encode_annotation(const annotation& ann) {
105 std::vector<std::uint8_t> out;
106 if (ann.timestamp != 0) {
109 }
111 return out;
112}
113
120inline std::vector<std::uint8_t> encode_string_map_entry(const std::string& key,
121 const std::string& value) {
122 std::vector<std::uint8_t> out;
125 return out;
126}
127
128inline std::vector<std::uint8_t> encode_span(const span& s) {
129 std::vector<std::uint8_t> out;
133 protobuf_wire::encode_enum_field(out, 4, static_cast<std::int32_t>(s.kind));
137 if (!s.local_endpoint.empty()) {
138 auto serialized = encode_endpoint(s.local_endpoint);
139 protobuf_wire::encode_message_field(out, 8, serialized);
140 }
141 if (!s.remote_endpoint.empty()) {
142 auto serialized = encode_endpoint(s.remote_endpoint);
143 protobuf_wire::encode_message_field(out, 9, serialized);
144 }
145 for (const auto& ann : s.annotations) {
146 auto serialized = encode_annotation(ann);
147 protobuf_wire::encode_message_field(out, 10, serialized);
148 }
149 for (const auto& [key, value] : s.tags) {
150 auto entry = encode_string_map_entry(key, value);
152 }
155 return out;
156}
157
158inline std::vector<std::uint8_t> encode_list_of_spans(const list_of_spans& list) {
159 std::vector<std::uint8_t> out;
160 for (const auto& s : list.spans) {
161 auto serialized = encode_span(s);
162 protobuf_wire::encode_message_field(out, 1, serialized);
163 }
164 return out;
165}
166
167// ---------------------------------------------------------------------------
168// Decoders (used by round-trip tests)
169// ---------------------------------------------------------------------------
170
171inline bool decode_endpoint(const std::uint8_t* data, std::size_t size,
172 endpoint& out) {
173 protobuf_wire::reader r(data, size);
174 while (!r.eof()) {
175 std::uint32_t field_number;
177 if (!protobuf_wire::decode_tag(r, field_number, wt)) return false;
178 switch (field_number) {
179 case 1:
180 if (wt != protobuf_wire::wire_type::length_delimited) return false;
181 if (!r.read_string(out.service_name)) return false;
182 break;
183 case 2:
184 if (wt != protobuf_wire::wire_type::length_delimited) return false;
185 if (!r.read_bytes(out.ipv4)) return false;
186 break;
187 case 3:
188 if (wt != protobuf_wire::wire_type::length_delimited) return false;
189 if (!r.read_bytes(out.ipv6)) return false;
190 break;
191 case 4: {
192 if (wt != protobuf_wire::wire_type::varint) return false;
193 auto v = r.read_varint();
194 if (!v) return false;
195 out.port = static_cast<std::int32_t>(*v);
196 break;
197 }
198 default:
199 if (!r.skip_field(wt)) return false;
200 }
201 }
202 return true;
203}
204
205inline bool decode_annotation(const std::uint8_t* data, std::size_t size,
206 annotation& out) {
207 protobuf_wire::reader r(data, size);
208 while (!r.eof()) {
209 std::uint32_t field_number;
211 if (!protobuf_wire::decode_tag(r, field_number, wt)) return false;
212 switch (field_number) {
213 case 1: {
214 if (wt != protobuf_wire::wire_type::fixed64) return false;
215 auto v = r.read_fixed64();
216 if (!v) return false;
217 out.timestamp = *v;
218 break;
219 }
220 case 2:
221 if (wt != protobuf_wire::wire_type::length_delimited) return false;
222 if (!r.read_string(out.value)) return false;
223 break;
224 default:
225 if (!r.skip_field(wt)) return false;
226 }
227 }
228 return true;
229}
230
231inline bool decode_map_entry(const std::uint8_t* data, std::size_t size,
232 std::string& key, std::string& value) {
233 protobuf_wire::reader r(data, size);
234 while (!r.eof()) {
235 std::uint32_t field_number;
237 if (!protobuf_wire::decode_tag(r, field_number, wt)) return false;
239 if (!r.skip_field(wt)) return false;
240 continue;
241 }
242 if (field_number == 1) {
243 if (!r.read_string(key)) return false;
244 } else if (field_number == 2) {
245 if (!r.read_string(value)) return false;
246 } else {
247 const std::uint8_t* ptr; std::size_t len;
248 if (!r.read_length_delimited(&ptr, &len)) return false;
249 }
250 }
251 return true;
252}
253
254inline bool decode_span(const std::uint8_t* data, std::size_t size, span& out) {
255 protobuf_wire::reader r(data, size);
256 while (!r.eof()) {
257 std::uint32_t field_number;
259 if (!protobuf_wire::decode_tag(r, field_number, wt)) return false;
260 switch (field_number) {
261 case 1:
262 if (wt != protobuf_wire::wire_type::length_delimited) return false;
263 if (!r.read_bytes(out.trace_id)) return false;
264 break;
265 case 2:
266 if (wt != protobuf_wire::wire_type::length_delimited) return false;
267 if (!r.read_bytes(out.parent_id)) return false;
268 break;
269 case 3:
270 if (wt != protobuf_wire::wire_type::length_delimited) return false;
271 if (!r.read_bytes(out.id)) return false;
272 break;
273 case 4: {
274 if (wt != protobuf_wire::wire_type::varint) return false;
275 auto v = r.read_varint();
276 if (!v) return false;
277 out.kind = static_cast<span_kind>(*v);
278 break;
279 }
280 case 5:
281 if (wt != protobuf_wire::wire_type::length_delimited) return false;
282 if (!r.read_string(out.name)) return false;
283 break;
284 case 6: {
285 if (wt != protobuf_wire::wire_type::fixed64) return false;
286 auto v = r.read_fixed64();
287 if (!v) return false;
288 out.timestamp = *v;
289 break;
290 }
291 case 7: {
292 if (wt != protobuf_wire::wire_type::varint) return false;
293 auto v = r.read_varint();
294 if (!v) return false;
295 out.duration = *v;
296 break;
297 }
298 case 8: {
299 if (wt != protobuf_wire::wire_type::length_delimited) return false;
300 const std::uint8_t* ptr; std::size_t len;
301 if (!r.read_length_delimited(&ptr, &len)) return false;
302 if (!decode_endpoint(ptr, len, out.local_endpoint)) return false;
303 break;
304 }
305 case 9: {
306 if (wt != protobuf_wire::wire_type::length_delimited) return false;
307 const std::uint8_t* ptr; std::size_t len;
308 if (!r.read_length_delimited(&ptr, &len)) return false;
309 if (!decode_endpoint(ptr, len, out.remote_endpoint)) return false;
310 break;
311 }
312 case 10: {
313 if (wt != protobuf_wire::wire_type::length_delimited) return false;
314 const std::uint8_t* ptr; std::size_t len;
315 if (!r.read_length_delimited(&ptr, &len)) return false;
316 annotation ann;
317 if (!decode_annotation(ptr, len, ann)) return false;
318 out.annotations.push_back(std::move(ann));
319 break;
320 }
321 case 11: {
322 if (wt != protobuf_wire::wire_type::length_delimited) return false;
323 const std::uint8_t* ptr; std::size_t len;
324 if (!r.read_length_delimited(&ptr, &len)) return false;
325 std::string key, value;
326 if (!decode_map_entry(ptr, len, key, value)) return false;
327 out.tags.emplace_back(std::move(key), std::move(value));
328 break;
329 }
330 case 12: {
331 if (wt != protobuf_wire::wire_type::varint) return false;
332 auto v = r.read_varint();
333 if (!v) return false;
334 out.debug = (*v != 0);
335 break;
336 }
337 case 13: {
338 if (wt != protobuf_wire::wire_type::varint) return false;
339 auto v = r.read_varint();
340 if (!v) return false;
341 out.shared = (*v != 0);
342 break;
343 }
344 default:
345 if (!r.skip_field(wt)) return false;
346 }
347 }
348 return true;
349}
350
351inline bool decode_list_of_spans(const std::uint8_t* data, std::size_t size,
352 list_of_spans& out) {
353 protobuf_wire::reader r(data, size);
354 while (!r.eof()) {
355 std::uint32_t field_number;
357 if (!protobuf_wire::decode_tag(r, field_number, wt)) return false;
358 if (field_number == 1 && wt == protobuf_wire::wire_type::length_delimited) {
359 const std::uint8_t* ptr; std::size_t len;
360 if (!r.read_length_delimited(&ptr, &len)) return false;
361 span s;
362 if (!decode_span(ptr, len, s)) return false;
363 out.spans.push_back(std::move(s));
364 } else {
365 if (!r.skip_field(wt)) return false;
366 }
367 }
368 return true;
369}
370
371}}} // namespace kcenon::monitoring::zipkin_proto
Minimal protobuf wire reader used for round-trip tests.
std::optional< std::uint64_t > read_fixed64()
bool read_bytes(std::vector< std::uint8_t > &out)
std::optional< std::uint64_t > read_varint()
bool read_length_delimited(const std::uint8_t **out_ptr, std::size_t *out_len)
Read a length-delimited payload. Returns pointer into the underlying buffer and the length....
bool skip_field(wire_type wt)
Skip a field whose wire type is given.
void encode_fixed64_field(std::vector< std::uint8_t > &out, std::uint32_t field_number, std::uint64_t value)
Encode a fixed64 field.
void encode_string_field(std::vector< std::uint8_t > &out, std::uint32_t field_number, const std::string &value)
Encode a string field.
void encode_bool_field(std::vector< std::uint8_t > &out, std::uint32_t field_number, bool value)
Encode a bool field (proto3 skips false).
bool decode_tag(reader &r, std::uint32_t &field_number, wire_type &wt)
Decode a tag into (field_number, wire_type).
void encode_uint64_field(std::vector< std::uint8_t > &out, std::uint32_t field_number, std::uint64_t value)
Encode a uint32 / uint64 / int64 varint field (skips zero).
void encode_enum_field(std::vector< std::uint8_t > &out, std::uint32_t field_number, std::int32_t value)
Encode an enum field (always written when nonzero).
void encode_bytes_field(std::vector< std::uint8_t > &out, std::uint32_t field_number, const std::vector< std::uint8_t > &value)
Encode a bytes field.
void encode_message_field(std::vector< std::uint8_t > &out, std::uint32_t field_number, const std::vector< std::uint8_t > &serialized)
Encode an embedded message field given its pre-serialized bytes.
void encode_tag(std::vector< std::uint8_t > &out, std::uint32_t field_number, wire_type wt)
Encode a tag (field_number << 3 | wire_type) as a varint.
@ varint
int32/int64/uint32/uint64/sint*‍/bool/enum
@ length_delimited
string/bytes/embedded messages/packed repeated
void encode_fixed64(std::vector< std::uint8_t > &out, std::uint64_t value)
Encode a fixed64 little-endian value (used for Zipkin timestamps).
bool decode_span(const std::uint8_t *data, std::size_t size, span &out)
span_kind parse_kind(const std::string &value)
Convert a textual Zipkin kind (e.g. "CLIENT") to its enum value.
std::vector< std::uint8_t > encode_annotation(const annotation &ann)
bool decode_annotation(const std::uint8_t *data, std::size_t size, annotation &out)
bool decode_map_entry(const std::uint8_t *data, std::size_t size, std::string &key, std::string &value)
std::vector< std::uint8_t > encode_list_of_spans(const list_of_spans &list)
std::vector< std::uint8_t > encode_string_map_entry(const std::string &key, const std::string &value)
Encode a single entry of a map<string,string> field.
std::vector< std::uint8_t > encode_endpoint(const endpoint &ep)
std::vector< std::uint8_t > encode_span(const span &s)
bool decode_list_of_spans(const std::uint8_t *data, std::size_t size, list_of_spans &out)
bool decode_endpoint(const std::uint8_t *data, std::size_t size, endpoint &out)
Zero-dependency protobuf wire-format encoder and decoder primitives.
std::vector< std::uint8_t > trace_id
std::vector< std::uint8_t > id
std::vector< std::uint8_t > parent_id
std::vector< std::pair< std::string, std::string > > tags
std::vector< annotation > annotations