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