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 #ifndef GRPC_PYTHON_OPENCENSUS_H
16 #define GRPC_PYTHON_OPENCENSUS_H
17
18 #include <stddef.h>
19 #include <stdint.h>
20
21 #include <algorithm>
22 #include <atomic>
23 #include <string>
24 #include <vector>
25
26 #include "absl/strings/string_view.h"
27 #include "absl/strings/strip.h"
28 #include "absl/time/clock.h"
29 #include "absl/time/time.h"
30 #include "constants.h"
31 #include "sampler.h"
32
33 #include <grpc/slice.h>
34
35 #include "src/core/lib/channel/channel_stack.h"
36
37 namespace grpc_observability {
38
39 namespace {
40 std::atomic<bool> g_python_census_stats_enabled(false);
41 std::atomic<bool> g_python_census_tracing_enabled(false);
42 } // namespace
43
44 // Enables/Disables Python census stats/tracing. It's only safe to do at the
45 // start of a program, before any channels/servers are built.
46 void EnablePythonCensusStats(bool enable);
47 void EnablePythonCensusTracing(bool enable);
48 // Gets the current status of Python OpenCensus stats/tracing
49 bool PythonCensusStatsEnabled();
50 bool PythonCensusTracingEnabled();
51
52 static constexpr size_t kTraceIdSize = 16;
53 static constexpr size_t kSpanIdSize = 8;
54
55 constexpr uint8_t kVersionId = 0;
56 constexpr uint8_t kTraceIdField = 0;
57 constexpr uint8_t kSpanIdField = 1;
58 constexpr uint8_t kTraceOptionsField = 2;
59
60 constexpr int kVersionLen = 1;
61 constexpr int kTraceIdLen = 16;
62 constexpr int kSpanIdLen = 8;
63 constexpr int kTraceOptionsLen = 1;
64
65 constexpr int kVersionOfs = 0;
66 constexpr int kTraceIdOfs = 1;
67 constexpr int kSpanIdOfs = kTraceIdOfs + 1 + kTraceIdLen;
68 constexpr int kTraceOptionsOfs = kSpanIdOfs + 1 + kSpanIdLen;
69
70 static constexpr size_t kSizeTraceID = 16;
71 static constexpr size_t kSizeSpanID = 8;
72 static constexpr size_t kSizeTraceOptions = 1;
73
74 // The length of the grpc-trace-bin value:
75 // 1 (version)
76 // + 1 (trace_id field)
77 // + 16 (length of trace_id)
78 // + 1 (span_id field)
79 // + 8 (span_id length)
80 // + 1 (trace_options field)
81 // + 1 (trace_options length)
82 // ----
83 // 29
84 constexpr int kGrpcTraceBinHeaderLen =
85 kVersionLen + 1 + kTraceIdLen + 1 + kSpanIdLen + 1 + kTraceOptionsLen;
86
87 struct Tag {
88 std::string key;
89 std::string value;
90 };
91
92 struct Label {
LabelLabel93 Label() {}
LabelLabel94 Label(std::string k, std::string v) : key(k), value(v) {}
95 std::string key;
96 std::string value;
97 };
98
99 union MeasurementValue {
100 double value_double;
101 int64_t value_int;
102 };
103
104 struct Measurement {
105 MetricsName name;
106 MeasurementType type;
107 MeasurementValue value;
108 };
109
110 struct Annotation {
111 std::string time_stamp;
112 std::string description;
113 };
114
115 struct SpanCensusData {
116 std::string name;
117 std::string start_time;
118 std::string end_time;
119 std::string trace_id;
120 std::string span_id;
121 std::string parent_span_id;
122 std::string status;
123 std::vector<Label> span_labels;
124 std::vector<Annotation> span_annotations;
125 int64_t child_span_count;
126 bool should_sample;
127 };
128
129 // SpanContext is associated with span to help manage the current context of a
130 // span. It's created when creating a new Span and will be destroyed together
131 // with associated Span.
132 class SpanContext final {
133 public:
SpanContext()134 SpanContext() : is_valid_(false) {}
135
SpanContext(const std::string & trace_id,const std::string & span_id,bool should_sample)136 SpanContext(const std::string& trace_id, const std::string& span_id,
137 bool should_sample)
138 : trace_id_(trace_id),
139 span_id_(span_id),
140 should_sample_(should_sample),
141 is_valid_(true) {}
142
143 // Returns the TraceId associated with this SpanContext.
TraceId()144 std::string TraceId() const { return trace_id_; }
145
146 // Returns the SpanId associated with this SpanContext.
SpanId()147 std::string SpanId() const { return span_id_; }
148
IsSampled()149 bool IsSampled() const { return should_sample_; }
150
IsValid()151 bool IsValid() const { return is_valid_; }
152
153 private:
154 std::string trace_id_;
155 std::string span_id_;
156 bool should_sample_;
157 bool is_valid_;
158 };
159
160 // Span is associated with PythonCensusContext to help manage tracing related
161 // data. It's created by calling StartSpan and will be destroyed together with
162 // associated PythonCensusContext.
163 class Span final {
164 public:
Span(const std::string & name,const std::string & parent_span_id,absl::Time start_time,const SpanContext & context)165 explicit Span(const std::string& name, const std::string& parent_span_id,
166 absl::Time start_time, const SpanContext& context)
167 : name_(name),
168 parent_span_id_(parent_span_id),
169 start_time_(start_time),
170 context_(context) {}
171
End()172 void End() { end_time_ = absl::Now(); }
173
IncreaseChildSpanCount()174 void IncreaseChildSpanCount() { ++child_span_count_; }
175
176 static Span StartSpan(absl::string_view name, const Span* parent);
177
178 static Span StartSpan(absl::string_view name,
179 const SpanContext& parent_context);
180
181 static Span StartSpan(absl::string_view name, absl::string_view trace_id);
182
BlankSpan()183 static Span BlankSpan() { return StartSpan("", ""); }
184
Context()185 const SpanContext& Context() const { return context_; }
186
187 void SetStatus(absl::string_view status);
188
189 void AddAttribute(absl::string_view key, absl::string_view value);
190
191 void AddAnnotation(absl::string_view description);
192
193 SpanCensusData ToCensusData() const;
194
195 private:
ShouldSample(const std::string & trace_id)196 static bool ShouldSample(const std::string& trace_id) {
197 return ProbabilitySampler::Get().ShouldSample(trace_id);
198 }
199
200 std::string name_;
201 std::string parent_span_id_;
202 absl::Time start_time_;
203 absl::Time end_time_;
204 std::string status_;
205 std::vector<Label> span_labels_;
206 std::vector<Annotation> span_annotations_;
207 SpanContext context_;
208 uint64_t child_span_count_ = 0;
209 };
210
211 // PythonCensusContext is associated with each clientCallTrcer,
212 // clientCallAttemptTracer and ServerCallTracer to help manage the span,
213 // spanContext and labels for each tracer. Craete a new PythonCensusContext will
214 // always reasult in creating a new span (and a new SpanContext for that span).
215 // It's created during callTraceer initialization and will be destroyed after
216 // the destruction of each callTracer.
217 class PythonCensusContext {
218 public:
PythonCensusContext()219 PythonCensusContext() : span_(Span::BlankSpan()), labels_({}) {}
220
PythonCensusContext(absl::string_view name)221 explicit PythonCensusContext(absl::string_view name)
222 : span_(Span::StartSpan(name, nullptr)), labels_({}) {}
223
PythonCensusContext(absl::string_view name,absl::string_view trace_id)224 PythonCensusContext(absl::string_view name, absl::string_view trace_id)
225 : span_(Span::StartSpan(name, trace_id)), labels_({}) {}
226
PythonCensusContext(absl::string_view name,const SpanContext & parent_context)227 PythonCensusContext(absl::string_view name, const SpanContext& parent_context)
228 : span_(Span::StartSpan(name, parent_context)), labels_({}) {}
229
PythonCensusContext(absl::string_view name,const Span * parent,const std::vector<Label> & labels)230 PythonCensusContext(absl::string_view name, const Span* parent,
231 const std::vector<Label>& labels)
232 : span_(Span::StartSpan(name, parent)), labels_(labels) {}
233
234 // For attempt Spans only
PythonCensusContext(absl::string_view name,const Span * parent)235 PythonCensusContext(absl::string_view name, const Span* parent)
236 : span_(Span::StartSpan(name, parent)), labels_({}) {}
237
GetSpan()238 Span& GetSpan() { return span_; }
Labels()239 std::vector<Label>& Labels() { return labels_; } // Only used for metrics
GetSpanContext()240 const SpanContext& GetSpanContext() const { return span_.Context(); }
241
AddSpanAttribute(absl::string_view key,absl::string_view attribute)242 void AddSpanAttribute(absl::string_view key, absl::string_view attribute) {
243 span_.AddAttribute(key, attribute);
244 }
245
AddSpanAnnotation(absl::string_view description)246 void AddSpanAnnotation(absl::string_view description) {
247 span_.AddAnnotation(description);
248 }
249
IncreaseChildSpanCount()250 void IncreaseChildSpanCount() { span_.IncreaseChildSpanCount(); }
251
EndSpan()252 void EndSpan() { GetSpan().End(); }
253
254 private:
255 grpc_observability::Span span_;
256 std::vector<Label> labels_;
257 };
258
259 // Creates a new client context that is by default a new root context.
260 // If the current context is the default context then the newly created
261 // span automatically becomes a root span. This should only be called with a
262 // blank CensusContext as it overwrites it.
263 void GenerateClientContext(absl::string_view method, absl::string_view trace_id,
264 absl::string_view parent_span_id,
265 PythonCensusContext* context);
266
267 // Deserialize the incoming SpanContext and generate a new server context based
268 // on that. This new span will never be a root span. This should only be called
269 // with a blank CensusContext as it overwrites it.
270 void GenerateServerContext(absl::string_view header, absl::string_view method,
271 PythonCensusContext* context);
272
GetMethod(const char * method)273 inline absl::string_view GetMethod(const char* method) {
274 if (std::string(method).empty()) {
275 return "";
276 }
277 // Check for leading '/' and trim it if present.
278 return absl::StripPrefix(absl::string_view(method), "/");
279 }
280
GetTarget(const char * target)281 inline absl::string_view GetTarget(const char* target) {
282 return absl::string_view(target);
283 }
284
285 // Fills a pre-allocated buffer with the value for the grpc-trace-bin header.
286 // The buffer must be at least kGrpcTraceBinHeaderLen bytes long.
287 void ToGrpcTraceBinHeader(const PythonCensusContext& ctx, uint8_t* out);
288
289 // Parses the value of the binary grpc-trace-bin header, returning a
290 // SpanContext. If parsing fails, IsValid will be false.
291 //
292 // Example value, hex encoded:
293 // 00 (version)
294 // 00 (trace_id field)
295 // 12345678901234567890123456789012 (trace_id)
296 // 01 (span_id field)
297 // 0000000000003039 (span_id)
298 // 02 (trace_options field)
299 // 01 (options: enabled)
300 //
301 // See also:
302 // https://github.com/census-instrumentation/opencensus-specs/blob/master/encodings/BinaryEncoding.md
303 SpanContext FromGrpcTraceBinHeader(absl::string_view header);
304
305 // Serializes the outgoing trace context. tracing_buf must be
306 // opencensus::trace::propagation::kGrpcTraceBinHeaderLen bytes long.
307 size_t TraceContextSerialize(const PythonCensusContext& context,
308 char* tracing_buf, size_t tracing_buf_size);
309
310 // Serializes the outgoing stats context. Field IDs are 1 byte followed by
311 // field data. A 1 byte version ID is always encoded first. Tags are directly
312 // serialized into the given grpc_slice.
313 size_t StatsContextSerialize(size_t max_tags_len, grpc_slice* tags);
314
315 // Deserialize incoming server stats. Returns the number of bytes deserialized.
316 size_t ServerStatsDeserialize(const char* buf, size_t buf_size,
317 uint64_t* server_elapsed_time);
318
319 // Serialize outgoing server stats. Returns the number of bytes serialized.
320 size_t ServerStatsSerialize(uint64_t server_elapsed_time, char* buf,
321 size_t buf_size);
322
323 // Returns the incoming data size from the grpc call final info.
324 uint64_t GetIncomingDataSize(const grpc_call_final_info* final_info);
325
326 // Returns the outgoing data size from the grpc call final info.
327 uint64_t GetOutgoingDataSize(const grpc_call_final_info* final_info);
328
329 } // namespace grpc_observability
330
331 #endif // GRPC_PYTHON_OPENCENSUS_H
332