1 // Copyright 2023 gRPC authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "python_observability_context.h"
16
17 #include <string.h>
18
19 #include <iomanip>
20 #include <iostream>
21 #include <new>
22
23 #include "absl/numeric/int128.h"
24 #include "absl/random/random.h"
25 #include "absl/strings/escaping.h"
26 #include "rpc_encoding.h"
27 #include "src/core/lib/transport/transport.h"
28
29 namespace grpc_observability {
30
EnablePythonCensusStats(bool enable)31 void EnablePythonCensusStats(bool enable) {
32 g_python_census_stats_enabled = enable;
33 }
34
EnablePythonCensusTracing(bool enable)35 void EnablePythonCensusTracing(bool enable) {
36 g_python_census_tracing_enabled = enable;
37 }
38
PythonCensusStatsEnabled()39 bool PythonCensusStatsEnabled() {
40 return g_python_census_stats_enabled.load(std::memory_order_relaxed);
41 }
42
PythonCensusTracingEnabled()43 bool PythonCensusTracingEnabled() {
44 return g_python_census_tracing_enabled.load(std::memory_order_relaxed);
45 }
46
GenerateClientContext(absl::string_view method,absl::string_view trace_id,absl::string_view parent_span_id,PythonCensusContext * context)47 void GenerateClientContext(absl::string_view method, absl::string_view trace_id,
48 absl::string_view parent_span_id,
49 PythonCensusContext* context) {
50 // Destruct the current CensusContext to free the Span memory before
51 // overwriting it below.
52 context->~PythonCensusContext();
53 if (method.empty()) {
54 new (context) PythonCensusContext();
55 return;
56 }
57 if (!parent_span_id.empty()) {
58 // Note that parent_span_id exist also means it was marked as sampled at
59 // Python OC, we'll respect that decision.
60 SpanContext parent_context =
61 SpanContext(std::string(trace_id), std::string(parent_span_id), true);
62 new (context) PythonCensusContext(method, parent_context);
63 return;
64 }
65 // Create span without parent.
66 new (context) PythonCensusContext(method, trace_id);
67 }
68
GenerateServerContext(absl::string_view header,absl::string_view method,PythonCensusContext * context)69 void GenerateServerContext(absl::string_view header, absl::string_view method,
70 PythonCensusContext* context) {
71 // Destruct the current CensusContext to free the Span memory before
72 // overwriting it below.
73 context->~PythonCensusContext();
74 if (method.empty()) {
75 new (context) PythonCensusContext();
76 return;
77 }
78 SpanContext parent_ctx = FromGrpcTraceBinHeader(header);
79 if (parent_ctx.IsValid()) {
80 new (context) PythonCensusContext(method, parent_ctx);
81 } else {
82 new (context) PythonCensusContext(method);
83 }
84 }
85
ToGrpcTraceBinHeader(const PythonCensusContext & ctx,uint8_t * out)86 void ToGrpcTraceBinHeader(const PythonCensusContext& ctx, uint8_t* out) {
87 out[kVersionOfs] = kVersionId;
88 out[kTraceIdOfs] = kTraceIdField;
89 uint8_t trace_options_rep_[kSizeTraceOptions];
90
91 std::string trace_id =
92 absl::HexStringToBytes(absl::string_view(ctx.GetSpanContext().TraceId()));
93 std::string span_id =
94 absl::HexStringToBytes(absl::string_view(ctx.GetSpanContext().SpanId()));
95 trace_options_rep_[0] = ctx.GetSpanContext().IsSampled() ? 1 : 0;
96
97 memcpy(reinterpret_cast<uint8_t*>(&out[kTraceIdOfs + 1]), trace_id.c_str(),
98 kSizeTraceID);
99
100 out[kSpanIdOfs] = kSpanIdField;
101 memcpy(reinterpret_cast<uint8_t*>(&out[kSpanIdOfs + 1]), span_id.c_str(),
102 kSizeSpanID);
103
104 out[kTraceOptionsOfs] = kTraceOptionsField;
105 memcpy(reinterpret_cast<uint8_t*>(&out[kTraceOptionsOfs + 1]),
106 trace_options_rep_, kSizeTraceOptions);
107 }
108
FromGrpcTraceBinHeader(absl::string_view header)109 SpanContext FromGrpcTraceBinHeader(absl::string_view header) {
110 if (header.size() < kGrpcTraceBinHeaderLen ||
111 header[kVersionOfs] != kVersionId ||
112 header[kTraceIdOfs] != kTraceIdField ||
113 header[kSpanIdOfs] != kSpanIdField ||
114 header[kTraceOptionsOfs] != kTraceOptionsField) {
115 return SpanContext(); // Invalid.
116 }
117
118 uint8_t options = header[kTraceOptionsOfs + 1] & 1;
119 constexpr uint8_t kIsSampled = 1;
120
121 uint8_t trace_id_rep_[kTraceIdSize];
122 memcpy(trace_id_rep_,
123 reinterpret_cast<const uint8_t*>(&header[kTraceIdOfs + 1]),
124 kTraceIdSize);
125
126 uint8_t span_id_rep_[kSpanIdSize];
127 memcpy(span_id_rep_,
128 reinterpret_cast<const uint8_t*>(&header[kSpanIdOfs + 1]),
129 kSpanIdSize);
130
131 uint8_t trace_option_rep_[kTraceOptionsLen];
132 memcpy(trace_option_rep_, &options, kTraceOptionsLen);
133
134 SpanContext context(
135 absl::BytesToHexString(absl::string_view(
136 reinterpret_cast<const char*>(trace_id_rep_), kTraceIdSize)),
137 absl::BytesToHexString(absl::string_view(
138 reinterpret_cast<const char*>(span_id_rep_), kSpanIdSize)),
139 trace_option_rep_[0] & kIsSampled);
140 return context;
141 }
142
TraceContextSerialize(const PythonCensusContext & context,char * tracing_buf,size_t tracing_buf_size)143 size_t TraceContextSerialize(const PythonCensusContext& context,
144 char* tracing_buf, size_t tracing_buf_size) {
145 if (tracing_buf_size < kGrpcTraceBinHeaderLen) {
146 return 0;
147 }
148 ToGrpcTraceBinHeader(context, reinterpret_cast<uint8_t*>(tracing_buf));
149 return kGrpcTraceBinHeaderLen;
150 }
151
StatsContextSerialize(size_t,grpc_slice *)152 size_t StatsContextSerialize(size_t /*max_tags_len*/, grpc_slice* /*tags*/) {
153 return 0;
154 }
155
ServerStatsDeserialize(const char * buf,size_t buf_size,uint64_t * server_elapsed_time)156 size_t ServerStatsDeserialize(const char* buf, size_t buf_size,
157 uint64_t* server_elapsed_time) {
158 return RpcServerStatsEncoding::Decode(absl::string_view(buf, buf_size),
159 server_elapsed_time);
160 }
161
ServerStatsSerialize(uint64_t server_elapsed_time,char * buf,size_t buf_size)162 size_t ServerStatsSerialize(uint64_t server_elapsed_time, char* buf,
163 size_t buf_size) {
164 return RpcServerStatsEncoding::Encode(server_elapsed_time, buf, buf_size);
165 }
166
GetIncomingDataSize(const grpc_call_final_info * final_info)167 uint64_t GetIncomingDataSize(const grpc_call_final_info* final_info) {
168 return final_info->stats.transport_stream_stats.incoming.data_bytes;
169 }
170
GetOutgoingDataSize(const grpc_call_final_info * final_info)171 uint64_t GetOutgoingDataSize(const grpc_call_final_info* final_info) {
172 return final_info->stats.transport_stream_stats.outgoing.data_bytes;
173 }
174
175 namespace {
176 // span_id is a 16-character hexadecimal encoded string.
GenerateSpanId()177 std::string GenerateSpanId() {
178 uint64_t span_id = absl::Uniform<uint64_t>(absl::BitGen());
179 std::stringstream hex_string;
180 hex_string << std::setfill('0') << std::setw(16) << std::hex << span_id;
181 return std::string(hex_string.str());
182 }
183
184 // trace_id is a 32-character hexadecimal encoded string
GenerateTraceId()185 std::string GenerateTraceId() {
186 absl::uint128 trace_id = absl::Uniform<absl::uint128>(absl::BitGen());
187 std::stringstream hex_string;
188 hex_string << std::setfill('0') << std::setw(32) << std::hex << trace_id;
189 return std::string(hex_string.str());
190 }
191
192 } // namespace
193
194 //
195 // Span
196 //
197
StartSpan(absl::string_view name,const Span * parent)198 Span Span::StartSpan(absl::string_view name, const Span* parent) {
199 std::string span_id = GenerateSpanId();
200 std::string trace_id;
201 std::string parent_span_id;
202 bool should_sample;
203 auto start_time = absl::Now();
204
205 if (parent != nullptr) {
206 parent_span_id = parent->Context().SpanId();
207 trace_id = parent->Context().TraceId();
208 should_sample = parent->Context().IsSampled();
209 } else {
210 trace_id = GenerateTraceId();
211 should_sample = ShouldSample(trace_id);
212 }
213
214 SpanContext context = SpanContext(trace_id, span_id, should_sample);
215 return Span(std::string(name), parent_span_id, start_time, context);
216 }
217
StartSpan(absl::string_view name,const SpanContext & parent_context)218 Span Span::StartSpan(absl::string_view name,
219 const SpanContext& parent_context) {
220 std::string trace_id = parent_context.TraceId();
221 std::string parent_span_id = parent_context.SpanId();
222 std::string span_id = GenerateSpanId();
223 bool should_sample = parent_context.IsSampled();
224 if (!should_sample) {
225 // Resampling here so that it's possible to collect trace on server side
226 // if client tracing is not enabled.
227 should_sample = ShouldSample(std::string(trace_id));
228 }
229 auto start_time = absl::Now();
230 SpanContext context(trace_id, span_id, should_sample);
231 return Span(std::string(name), parent_span_id, start_time, context);
232 }
233
StartSpan(absl::string_view name,absl::string_view trace_id)234 Span Span::StartSpan(absl::string_view name, absl::string_view trace_id) {
235 std::string span_id = GenerateSpanId();
236 auto start_time = absl::Now();
237 bool should_sample = ShouldSample(std::string(trace_id));
238 SpanContext context(std::string(trace_id), span_id, should_sample);
239 return Span(std::string(name), "", start_time, context);
240 }
241
SetStatus(absl::string_view status)242 void Span::SetStatus(absl::string_view status) {
243 status_ = std::string(status);
244 }
245
AddAttribute(absl::string_view key,absl::string_view value)246 void Span::AddAttribute(absl::string_view key, absl::string_view value) {
247 span_labels_.emplace_back(std::string(key), std::string(value));
248 }
249
AddAnnotation(absl::string_view description)250 void Span::AddAnnotation(absl::string_view description) {
251 // Need a string format which can be converted to Python datetime.datetime
252 // class directly.
253 std::string time_stamp =
254 absl::FormatTime("%Y-%m-%d %H:%M:%E3S", absl::Now(), absl::UTCTimeZone());
255 span_annotations_.emplace_back(
256 Annotation{time_stamp, std::string(description)});
257 }
258
ToCensusData() const259 SpanCensusData Span::ToCensusData() const {
260 SpanCensusData census_data;
261 absl::TimeZone utc = absl::UTCTimeZone();
262 census_data.name = name_;
263 // Need a string format which can be exported to StackDriver directly.
264 // See format details:
265 // https://cloud.google.com/trace/docs/reference/v2/rest/v2/projects.traces/batchWrite
266 census_data.start_time =
267 absl::FormatTime("%Y-%m-%dT%H:%M:%E6SZ", start_time_, utc);
268 census_data.end_time =
269 absl::FormatTime("%Y-%m-%dT%H:%M:%E6SZ", end_time_, utc);
270 census_data.trace_id = Context().TraceId();
271 census_data.span_id = Context().SpanId();
272 census_data.should_sample = Context().IsSampled();
273 census_data.parent_span_id = parent_span_id_;
274 census_data.status = status_;
275 census_data.span_labels = span_labels_;
276 census_data.span_annotations = span_annotations_;
277 census_data.child_span_count = child_span_count_;
278 return census_data;
279 }
280
281 } // namespace grpc_observability
282