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"
- 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
132
133
134
135
136
137
138
139
|
# File 'lib/bitfab/otel.rb', line 132
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_state ⇒ Object
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.
194
195
196
197
198
199
|
# File 'lib/bitfab/otel.rb', line 194
def ensure_process_state
@state_pid ||= Process.pid
return if @state_pid == Process.pid
reset_state_after_fork
end
|
.export_concurrency_from_env ⇒ Object
.flush_transports(timeout = 30.0) ⇒ Object
151
152
153
154
155
156
157
|
# File 'lib/bitfab/otel.rb', line 151
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_env ⇒ Object
.monotonic_now ⇒ Object
185
186
187
|
# File 'lib/bitfab/otel.rb', line 185
def monotonic_now
Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
|
.record_carrier_ref(span_id, ref) ⇒ Object
Bounded, because the only thing that removes an entry is the encode
that consumes it: a span the processor drops under backpressure never
reaches encode, and its ref would otherwise sit here for the life of the
process. The queue is the ceiling on how many spans can be waiting at
once, so anything older than that is already unreachable. Evicting a
live one costs an ack, and the barrier falls back to asking the server.
173
174
175
176
177
178
179
|
# File 'lib/bitfab/otel.rb', line 173
def record_carrier_ref(span_id, ref)
ensure_process_state
submission_mutex.synchronize do
carrier_refs[span_id] = ref
carrier_refs.shift while carrier_refs.size > MAX_QUEUE_SIZE
end
end
|
.register_transport(transport) ⇒ Object
141
142
143
144
|
# File 'lib/bitfab/otel.rb', line 141
def register_transport(transport)
ensure_process_state
live_transports_mutex.synchronize { live_transports << transport }
end
|
.reset_state_after_fork ⇒ Object
Carrier refs belong to the parent's run, never the child's.
202
203
204
205
|
# File 'lib/bitfab/otel.rb', line 202
def reset_state_after_fork
@state_pid = Process.pid
@carrier_refs = {}
end
|
.shutdown_transports(timeout = 30.0) ⇒ Object
159
160
161
162
163
164
165
|
# File 'lib/bitfab/otel.rb', line 159
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
|
.take_carrier_ref(span_id) ⇒ Object
181
182
183
|
# File 'lib/bitfab/otel.rb', line 181
def take_carrier_ref(span_id)
submission_mutex.synchronize { carrier_refs.delete(span_id) }
end
|
.unregister_transport(transport) ⇒ Object
146
147
148
149
|
# File 'lib/bitfab/otel.rb', line 146
def unregister_transport(transport)
ensure_process_state
live_transports_mutex.synchronize { live_transports.delete(transport) }
end
|