Module: Bitfab::Otel

Defined in:
lib/bitfab/otel.rb

Overview

OpenTelemetry delivery for Bitfab spans and trace completions.

Bitfab payloads ride as OTLP span attributes (bitfab.operation + bitfab.payload), so a batch travels to Bitfab as OTLP/JSON without the payload itself changing shape.

Defined Under Namespace

Modules: Encoder Classes: BatchTransport, CarrierMeta, CarrierRef, DeliveryError, DeliveryTrackingExporter, DirectExporter, EncodedSpan, RequestBatch, RequestEnvelope

Constant Summary collapse

OPERATION_ATTRIBUTE =
"bitfab.operation"
PAYLOAD_ATTRIBUTE =
"bitfab.payload"
CARRIER_REF_IVAR =
:@bitfab_carrier_ref
MAX_REQUEST_BYTES_ENV =
"BITFAB_OTEL_MAX_REQUEST_BYTES"
EXPORT_CONCURRENCY_ENV =
"BITFAB_OTEL_EXPORT_CONCURRENCY"
MAX_EXPORT_REQUEST_BYTES =
3_000_000
MAX_DECOMPRESSED_REQUEST_BYTES =
8_000_000
MAX_QUEUE_SIZE =
8_192
DIRECT_MAX_EXPORT_BATCH_SIZE =
512
DIRECT_MAX_REQUEST_BATCH_SIZE =
8
DEFAULT_EXPORT_CONCURRENCY =
32
MAX_EXPORT_CONCURRENCY =
64
SCHEDULE_DELAY_MILLIS =
5_000
EXPORT_TIMEOUT_MILLIS =
30_000
EXPORT_RETRIES =
3
RETRY_BASE_DELAY_MILLIS =
100
RETRY_BACKOFF_CEILING_MILLIS =

Ceiling on the exponential growth of our OWN backoff. It does not bound a wait the server asked for: OTLP says to honor Retry-After, and calls data dropped while throttled the outcome to avoid. What bounds an honored wait is the export budget, since the processor kills a longer export.

5_000
SPAN_SEPARATOR_BYTES =

The comma that joins adjacent spans in the request's span list.

1
SUCCESS =
OpenTelemetry::SDK::Trace::Export::SUCCESS
FAILURE =
OpenTelemetry::SDK::Trace::Export::FAILURE
SPAN_KINDS =
{
  internal: 1,
  server: 2,
  client: 3,
  producer: 4,
  consumer: 5
}.freeze
INVALID_SPAN_ID =
("\0" * 8).b

Class Method Summary collapse

Class Method Details

.create_transport(direct_sender:, on_delivered: nil) ⇒ Object



133
134
135
136
137
138
139
140
# File 'lib/bitfab/otel.rb', line 133

def create_transport(direct_sender:, on_delivered: nil)
  BatchTransport.new(
    direct_sender:,
    on_delivered:,
    export_concurrency: export_concurrency_from_env,
    max_request_bytes: max_request_bytes_from_env
  )
end

.ensure_process_stateObject

Ruby has no after-fork hook a library can register (Python's os.register_at_fork has no equivalent), so every entry point that touches module state checks the pid first. Inherited mutexes need no special handling: CRuby abandons a mutex held by a thread the child did not inherit, so the child sees it unlocked.



177
178
179
180
181
182
# File 'lib/bitfab/otel.rb', line 177

def ensure_process_state
  @state_pid ||= Process.pid
  return if @state_pid == Process.pid

  reset_state_after_fork
end

.export_concurrency_from_envObject



118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/bitfab/otel.rb', line 118

def export_concurrency_from_env
  raw = ENV[EXPORT_CONCURRENCY_ENV]
  return DEFAULT_EXPORT_CONCURRENCY if raw.nil?

  value = Integer(raw, exception: false) || 0
  return value if value.positive? && value <= MAX_EXPORT_CONCURRENCY

  Bitfab.warn_once(
    "otel-export-concurrency-invalid",
    "#{EXPORT_CONCURRENCY_ENV} must be a positive integer no greater than " \
    "#{MAX_EXPORT_CONCURRENCY}; using #{DEFAULT_EXPORT_CONCURRENCY}"
  )
  DEFAULT_EXPORT_CONCURRENCY
end

.flush_transports(timeout = 30.0) ⇒ Object



152
153
154
155
156
157
158
# File 'lib/bitfab/otel.rb', line 152

def flush_transports(timeout = 30.0)
  ensure_process_state
  deadline = monotonic_now + [timeout, 0].max
  current_process_transports.reduce(true) do |succeeded, transport|
    transport.flush([deadline - monotonic_now, 0].max) && succeeded
  end
end

.max_request_bytes_from_envObject



103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/bitfab/otel.rb', line 103

def max_request_bytes_from_env
  raw = ENV[MAX_REQUEST_BYTES_ENV]
  return MAX_EXPORT_REQUEST_BYTES if raw.nil?

  value = Integer(raw, exception: false) || 0
  return value if value.positive? && value <= MAX_EXPORT_REQUEST_BYTES

  Bitfab.warn_once(
    "otel-max-request-bytes-invalid",
    "#{MAX_REQUEST_BYTES_ENV} must be a positive integer no greater than " \
    "#{MAX_EXPORT_REQUEST_BYTES}; using #{MAX_EXPORT_REQUEST_BYTES}"
  )
  MAX_EXPORT_REQUEST_BYTES
end

.monotonic_nowObject



168
169
170
# File 'lib/bitfab/otel.rb', line 168

def monotonic_now
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
end

.register_transport(transport) ⇒ Object



142
143
144
145
# File 'lib/bitfab/otel.rb', line 142

def register_transport(transport)
  ensure_process_state
  live_transports_mutex.synchronize { live_transports << transport }
end

.reset_state_after_forkObject



184
185
186
# File 'lib/bitfab/otel.rb', line 184

def reset_state_after_fork
  @state_pid = Process.pid
end

.shutdown_transports(timeout = 30.0) ⇒ Object



160
161
162
163
164
165
166
# File 'lib/bitfab/otel.rb', line 160

def shutdown_transports(timeout = 30.0)
  ensure_process_state
  deadline = monotonic_now + [timeout, 0].max
  current_process_transports.reduce(true) do |succeeded, transport|
    transport.shutdown([deadline - monotonic_now, 0].max) && succeeded
  end
end

.unregister_transport(transport) ⇒ Object



147
148
149
150
# File 'lib/bitfab/otel.rb', line 147

def unregister_transport(transport)
  ensure_process_state
  live_transports_mutex.synchronize { live_transports.delete(transport) }
end