1mod trace_ingest;
16pub mod trace_semconv;
17pub mod trace_types;
18
19use std::collections::HashSet;
20use std::sync::Arc;
21
22use api::v1::RowInsertRequests;
23use api::v1::value::ValueData;
24use async_trait::async_trait;
25use auth::{
26 OTLP_WRITE, PermissionChecker, PermissionCheckerRef, PermissionReq, PermissionTableTarget,
27 PermissionTableTargets,
28};
29use client::Output;
30use common_catalog::consts::{
31 METRICS_EXEMPLARS_TABLE, trace_operations_table_name, trace_services_table_name,
32};
33use common_error::ext::BoxedError;
34use common_query::prelude::{EXEMPLAR_METRIC_COLUMN, GREPTIME_PHYSICAL_TABLE};
35use common_telemetry::{tracing, warn};
36use opentelemetry_proto::tonic::collector::logs::v1::ExportLogsServiceRequest;
37use opentelemetry_proto::tonic::collector::trace::v1::ExportTraceServiceRequest;
38use operator::insert::{Inserter, admit_row_insert_batches, admit_write};
39use otel_arrow_rust::proto::opentelemetry::collector::metrics::v1::ExportMetricsServiceRequest;
40use pipeline::{GreptimePipelineParams, PipelineWay};
41use servers::error::{self, AuthSnafu, Result as ServerResult};
42use servers::http::prom_store::PHYSICAL_TABLE_PARAM;
43use servers::interceptor::{OpenTelemetryProtocolInterceptor, OpenTelemetryProtocolInterceptorRef};
44use servers::otlp;
45use servers::otlp::trace::span::TraceSpanGroup;
46use servers::query_handler::{
47 MetricsIngestOutcome, OpenTelemetryProtocolHandler, PipelineHandlerRef, TraceIngestOutcome,
48};
49use servers::request_memory_limiter::ServerMemoryLimiter;
50use session::context::QueryContextRef;
51use session::protocol_ctx::OtlpMetricCtx;
52use session::write_progress::WriteProgress;
53use snafu::{IntoError, ResultExt};
54use table::requests::{
55 OTLP_METRIC_COMPAT_KEY, OTLP_METRIC_COMPAT_PROM, SEMANTIC_PER_TABLE_INDEX_KEY,
56 SEMANTIC_PIPELINE, SEMANTIC_SIGNAL_TYPE, SEMANTIC_SOURCE, SIGNAL_TYPE_LOG, SIGNAL_TYPE_METRIC,
57 SOURCE_OPENTELEMETRY, TABLE_DATA_MODEL, TABLE_DATA_MODEL_LOG_V1,
58};
59
60pub(crate) use self::trace_ingest::TraceAuxCache;
61use self::trace_ingest::trace_conventions;
62use crate::instance::Instance;
63use crate::metrics::{OTLP_LOGS_ROWS, OTLP_METRICS_ROWS, OTLP_RESOURCE_INFO_WRITE_ERRORS};
64
65fn trace_permission_targets(
66 table_name: &str,
67 groups: &[TraceSpanGroup],
68 ctx: &QueryContextRef,
69) -> PermissionTableTargets {
70 let catalog = ctx.current_catalog();
71 let schema = ctx.current_schema();
72 if catalog.is_empty() || schema.is_empty() || table_name.is_empty() {
73 return PermissionTableTargets::Unresolved;
74 }
75
76 let has_spans = groups.iter().any(|group| !group.spans.is_empty());
77 if !has_spans {
78 return PermissionTableTargets::resolved(Vec::new());
79 }
80
81 let mut targets = vec![PermissionTableTarget::new(catalog, &schema, table_name)];
82 if groups
83 .iter()
84 .flat_map(|group| &group.spans)
85 .any(|span| span.service_name.is_some())
86 {
87 targets.extend([
88 PermissionTableTarget::new(catalog, &schema, trace_services_table_name(table_name)),
89 PermissionTableTarget::new(catalog, &schema, trace_operations_table_name(table_name)),
90 ]);
91 }
92
93 PermissionTableTargets::resolved(targets)
94}
95
96#[async_trait]
97impl OpenTelemetryProtocolHandler for Instance {
98 #[tracing::instrument(skip_all)]
99 async fn metrics(
100 &self,
101 request: ExportMetricsServiceRequest,
102 ctx: QueryContextRef,
103 memory_limiter: Option<(ServerMemoryLimiter, u64)>,
104 ) -> ServerResult<MetricsIngestOutcome> {
105 self.plugins
106 .get::<PermissionCheckerRef>()
107 .as_ref()
108 .check_permission(ctx.current_user(), PermissionReq::Action(OTLP_WRITE))
109 .context(AuthSnafu)?;
110
111 let interceptor_ref = self
112 .plugins
113 .get::<OpenTelemetryProtocolInterceptorRef<servers::error::Error>>();
114 interceptor_ref.pre_execute(ctx.clone())?;
115 let ctx = Arc::new(ctx.fork());
116
117 let mut metric_ctx = ctx
118 .protocol_ctx()
119 .get_otlp_metric_ctx()
120 .cloned()
121 .unwrap_or_default();
122 metric_ctx.resource_info = self.otlp_resource_info;
123
124 let mut prepared = otlp::metrics::prepare_metrics(request, &mut metric_ctx)?;
125 let targets = {
127 let catalog = ctx.current_catalog();
128 let schema = ctx.current_schema();
129 let mut table_names = HashSet::new();
130 prepared
131 .requests
132 .iter()
133 .filter(|request| table_names.insert(request.table_name.as_str()))
134 .map(|request| PermissionTableTarget::new(catalog, &schema, &request.table_name))
135 .collect::<Vec<_>>()
136 };
137 self.check_table_permission(
138 &ctx,
139 PermissionReq::Action(OTLP_WRITE),
140 PermissionTableTargets::resolved(targets),
141 )
142 .context(AuthSnafu)?;
143
144 let preflight = self
145 .inserter
146 .validate_and_normalize_otlp_metrics(
147 &mut prepared.requests,
148 &ctx,
149 metric_ctx.with_metric_engine,
150 ctx.extension(PHYSICAL_TABLE_PARAM)
151 .unwrap_or(GREPTIME_PHYSICAL_TABLE),
152 )
153 .await
154 .map_err(BoxedError::new)
155 .context(error::ExecuteGrpcQuerySnafu)?;
156 let rejections = prepared.point_rejections(preflight.rejections)?;
157 let otlp::metrics::MetricsConversion {
158 requests,
159 rows,
160 mut exemplars,
161 semantic_index,
162 resource_info,
163 mut outcome,
164 } = prepared.finish(rejections, &preflight.physical_tables)?;
165 if outcome.rejected_data_points > 0 {
166 warn!(
167 "Rejected {} OTLP metrics data points: {}",
168 outcome.rejected_data_points,
169 outcome.error_message.as_deref().unwrap_or_default()
170 );
171 }
172 if outcome.accepted_data_points == 0 {
173 return Ok(outcome);
174 }
175
176 let before = exemplars.len();
178 exemplars.retain(|exemplar| {
179 preflight
180 .physical_tables
181 .contains_key(exemplar.metric.as_ref())
182 });
183 servers::metrics::EXEMPLARS_DROPPED_TOTAL
184 .with_label_values(&["otlp"])
185 .inc_by((before - exemplars.len()) as u64);
186 if !exemplars.is_empty() {
187 self.check_table_permission(
188 &ctx,
189 PermissionReq::Action(OTLP_WRITE),
190 PermissionTableTargets::resolved(vec![PermissionTableTarget::new(
191 ctx.current_catalog(),
192 ctx.current_schema(),
193 METRICS_EXEMPLARS_TABLE,
194 )]),
195 )
196 .context(AuthSnafu)?;
197 }
198
199 let ctx = if !exemplars.is_empty()
200 && let Some((limiter, held_bytes)) = memory_limiter
201 {
202 let bytes =
203 servers::exemplar::estimated_row_bytes(std::iter::once(exemplars.as_slice()))
204 .and_then(|bytes| u64::try_from(bytes).ok())
205 .ok_or_else(|| {
206 error::MemoryLimitExceededSnafu.into_error(
207 common_memory_manager::Error::MemoryLimitExceeded {
208 requested_bytes: u64::MAX,
209 limit_bytes: limiter.limit_bytes(),
210 },
211 )
212 })?;
213 limiter.check_request_fits(bytes, held_bytes)?;
214 let reservation = Arc::new(if held_bytes > 0 {
216 limiter.try_acquire(bytes).ok_or_else(|| {
217 error::MemoryLimitExceededSnafu.into_error(
218 common_memory_manager::Error::MemoryLimitExceeded {
219 requested_bytes: bytes,
220 limit_bytes: limiter.limit_bytes(),
221 },
222 )
223 })?
224 } else {
225 limiter.acquire(bytes).await?
226 });
227 let mut ctx = ctx.fork();
228 ctx.set_write_progress(Arc::new(WriteProgress::with_reservation(reservation)));
230 Arc::new(ctx)
231 } else {
232 ctx
233 };
234 let exemplar_rows = exemplars.len();
235 let (exemplars, _) = servers::exemplar::into_rows(exemplars)?;
236 let total_rows = rows.checked_add(exemplar_rows).ok_or_else(|| {
237 error::InvalidOtlpMetricInputSnafu {
238 reason: "combined metric and exemplar row count overflow",
239 }
240 .build()
241 })?;
242 let ctx = admit_write(total_rows as u64, &ctx)
243 .await
244 .map_err(BoxedError::new)
245 .context(error::ExecuteGrpcQuerySnafu)?;
246 OTLP_METRICS_ROWS.inc_by(rows as u64);
247
248 let ctx = {
249 let mut c = (*ctx).clone();
250 c.set_extension(SEMANTIC_SIGNAL_TYPE, SIGNAL_TYPE_METRIC);
251 c.set_extension(SEMANTIC_SOURCE, SOURCE_OPENTELEMETRY);
252 if let Some(index) = semantic_index.encode(&c.current_schema()) {
256 c.set_extension(SEMANTIC_PER_TABLE_INDEX_KEY, index);
257 }
258 c.set_extension(OTLP_METRIC_COMPAT_KEY, OTLP_METRIC_COMPAT_PROM.to_string());
259 Arc::new(c)
260 };
261
262 if exemplar_rows > 0 {
263 let confirmed = Arc::new(ctx.with_confirmed_writes());
264 let source_names = exemplars
265 .inserts
266 .iter()
267 .filter_map(|request| request.rows.as_ref())
268 .flat_map(|rows| {
269 let index = rows
270 .schema
271 .iter()
272 .position(|c| c.column_name == EXEMPLAR_METRIC_COLUMN);
273 rows.rows.iter().filter_map(move |row| {
274 match &row.values.get(index?)?.value_data {
275 Some(ValueData::StringValue(name)) => Some(name.as_str()),
276 _ => None,
277 }
278 })
279 })
280 .collect::<HashSet<_>>();
281 let (source_requests, other): (Vec<_>, Vec<_>) = requests
282 .inserts
283 .into_iter()
284 .partition(|request| source_names.contains(request.table_name.as_str()));
285 let prepared = self
286 .inserter
287 .prepare_metric_row_inserts(
288 RowInsertRequests {
289 inserts: source_requests,
290 },
291 confirmed.clone(),
292 &self.statement_executor,
293 ctx.extension(PHYSICAL_TABLE_PARAM)
294 .unwrap_or(GREPTIME_PHYSICAL_TABLE)
295 .to_string(),
296 )
297 .await
298 .map_err(BoxedError::new)
299 .context(error::ExecuteGrpcQuerySnafu);
300 let source_ready = prepared.is_ok();
301 let sources = prepared
302 .as_ref()
303 .map(|request| request.source_tables().cloned().collect::<Vec<_>>())
304 .unwrap_or_default();
305 let (metric_output, other_output, exemplar_output) = tokio::join!(
306 async {
307 self.inserter
308 .handle_prepared_metric_inserts(prepared?, confirmed.clone())
309 .await
310 .map_err(BoxedError::new)
311 .context(error::ExecuteGrpcQuerySnafu)
312 },
313 async {
314 if other.is_empty() {
315 Ok(Output::new_with_affected_rows(0))
316 } else {
317 self.handle_otlp_metric_row_inserts(
318 RowInsertRequests { inserts: other },
319 confirmed.clone(),
320 &metric_ctx,
321 )
322 .await
323 }
324 },
325 async {
326 if !source_ready {
328 return Ok(Output::new_with_affected_rows(0));
329 }
330 self.inserter
331 .handle_exemplar_inserts(
332 exemplars,
333 confirmed.clone(),
334 &self.statement_executor,
335 &sources,
336 )
337 .await
338 .map_err(BoxedError::new)
339 .context(error::ExecuteGrpcQuerySnafu)
340 },
341 );
342 let mut progress = common_query::WriteProgress::default();
343 for output in [metric_output, other_output, exemplar_output] {
344 match output {
345 Ok(output) => {
346 outcome.write_cost += output.meta.cost;
347 progress.merge(output.write_progress().await);
348 }
349 Err(error) => {
350 progress.merge(common_query::WriteProgress::failed(BoxedError::new(error)))
351 }
352 }
353 }
354 progress
355 .into_result()
356 .context(error::ExecuteGrpcQuerySnafu)?;
357 } else {
358 let output = self
359 .handle_otlp_metric_row_inserts(requests, ctx.clone(), &metric_ctx)
360 .await?;
361 outcome.write_cost = output.meta.cost;
362 }
363
364 if let Some(resource_info) = resource_info {
368 let written = match self.check_row_insert_permission(
369 &resource_info,
370 &ctx,
371 PermissionReq::Action(OTLP_WRITE),
372 ) {
373 Ok(_) => self
374 .handle_row_inserts(resource_info, ctx, false, false)
375 .await
376 .map_err(BoxedError::new)
377 .map_err(|e| e.to_string()),
378 Err(e) => Err(e.to_string()),
379 };
380 match written {
381 Ok(descriptor_output) => outcome.write_cost += descriptor_output.meta.cost,
382 Err(e) => {
383 OTLP_RESOURCE_INFO_WRITE_ERRORS.inc();
384 warn!("Failed to write the OTLP resource descriptor table: {e}");
385 outcome.error_message.get_or_insert(format!(
386 "metric data was accepted, but writing the resource \
387 descriptor table `{}` failed: {e}",
388 otlp::metrics::OTEL_RESOURCE_INFO_TABLE_NAME
389 ));
390 }
391 }
392 }
393
394 Ok(outcome)
395 }
396
397 #[tracing::instrument(skip_all)]
398 async fn traces(
399 &self,
400 _pipeline_handler: PipelineHandlerRef,
401 request: ExportTraceServiceRequest,
402 pipeline: PipelineWay,
403 _pipeline_params: GreptimePipelineParams,
404 table_name: String,
405 ctx: QueryContextRef,
406 ) -> ServerResult<TraceIngestOutcome> {
407 self.plugins
408 .get::<PermissionCheckerRef>()
409 .as_ref()
410 .check_permission(ctx.current_user(), PermissionReq::Action(OTLP_WRITE))
411 .context(AuthSnafu)?;
412
413 let interceptor_ref = self
414 .plugins
415 .get::<OpenTelemetryProtocolInterceptorRef<servers::error::Error>>();
416 interceptor_ref.pre_execute(ctx.clone())?;
417 let ctx = Arc::new(ctx.fork());
418
419 let conventions = trace_conventions(&request);
421 let spans = otlp::trace::span::parse(request);
422 let targets = trace_permission_targets(&table_name, &spans, &ctx);
423 self.check_table_permission(&ctx, PermissionReq::Action(OTLP_WRITE), targets)
424 .context(AuthSnafu)?;
425 let rows = spans.iter().map(|group| group.spans.len() as u64).sum();
427 let ctx = admit_write(rows, &ctx)
428 .await
429 .map_err(BoxedError::new)
430 .context(error::ExecuteGrpcQuerySnafu)?;
431 self.ingest_trace_spans(&pipeline, table_name, spans, &conventions, ctx)
432 .await
433 }
434
435 #[tracing::instrument(skip_all)]
436 async fn logs(
437 &self,
438 pipeline_handler: PipelineHandlerRef,
439 request: ExportLogsServiceRequest,
440 pipeline: PipelineWay,
441 pipeline_params: GreptimePipelineParams,
442 table_name: String,
443 ctx: QueryContextRef,
444 ) -> ServerResult<Vec<Output>> {
445 self.plugins
446 .get::<PermissionCheckerRef>()
447 .as_ref()
448 .check_permission(ctx.current_user(), PermissionReq::Action(OTLP_WRITE))
449 .context(AuthSnafu)?;
450
451 let interceptor_ref = self
452 .plugins
453 .get::<OpenTelemetryProtocolInterceptorRef<servers::error::Error>>();
454 interceptor_ref.pre_execute(ctx.clone())?;
455 let ctx = Arc::new(ctx.fork());
456
457 let ctx = {
460 let mut c = (*ctx).clone();
461 c.set_extension(SEMANTIC_SIGNAL_TYPE, SIGNAL_TYPE_LOG);
462 c.set_extension(SEMANTIC_SOURCE, SOURCE_OPENTELEMETRY);
463 if matches!(pipeline, PipelineWay::OtlpLogDirect(_)) {
467 c.set_extension(TABLE_DATA_MODEL, TABLE_DATA_MODEL_LOG_V1);
468 c.set_extension(SEMANTIC_PIPELINE, TABLE_DATA_MODEL_LOG_V1);
469 }
472 Arc::new(c)
473 };
474
475 let opt_req = otlp::logs::to_grpc_insert_requests(
476 request,
477 pipeline,
478 pipeline_params,
479 table_name,
480 &ctx,
481 pipeline_handler,
482 )
483 .await?;
484
485 let mut batches = opt_req.as_req_iter(ctx).collect::<Vec<_>>();
486 for (temp_ctx, requests) in &batches {
487 self.check_row_insert_permission(requests, temp_ctx, PermissionReq::Action(OTLP_WRITE))
488 .context(AuthSnafu)?;
489 }
490 admit_row_insert_batches(&mut batches)
491 .await
492 .map_err(BoxedError::new)
493 .context(error::ExecuteGrpcQuerySnafu)?;
494
495 let mut outputs = Vec::with_capacity(batches.len());
496 for (temp_ctx, requests) in batches {
497 let cnt = requests
498 .inserts
499 .iter()
500 .filter_map(|r| r.rows.as_ref().map(|r| r.rows.len()))
501 .sum::<usize>();
502
503 let o = self
504 .handle_log_inserts(requests, temp_ctx)
505 .await
506 .inspect(|_| OTLP_LOGS_ROWS.inc_by(cnt as u64))
507 .map_err(BoxedError::new)
508 .context(error::ExecuteGrpcQuerySnafu)?;
509 outputs.push(o);
510 }
511
512 Ok(outputs)
513 }
514}
515
516impl Instance {
517 pub async fn handle_otlp_metric_row_inserts(
520 &self,
521 requests: RowInsertRequests,
522 ctx: QueryContextRef,
523 metric_ctx: &OtlpMetricCtx,
524 ) -> ServerResult<Output> {
525 let physical_table = ctx
526 .extension(PHYSICAL_TABLE_PARAM)
527 .unwrap_or(GREPTIME_PHYSICAL_TABLE)
528 .to_string();
529 let batcher = self
533 .logical_batcher()
534 .filter(|_| ctx.logical_batching_enabled() && metric_ctx.with_metric_engine);
535 let batcher = if batcher.is_some()
536 && self
537 .inserter
538 .can_batch_metric_rows(&requests, &ctx, &physical_table)
539 .await
540 .map_err(BoxedError::new)
541 .context(error::ExecuteGrpcQuerySnafu)?
542 {
543 batcher
544 } else {
545 None
546 };
547
548 if let Some(batcher) = batcher {
550 let (mut output, cost) = batcher
551 .submit_output_with(requests, ctx.clone(), |mut requests| {
552 let ctx = ctx.clone();
553 async move {
554 Inserter::meter_row_inserts(&mut requests, &ctx)
555 .await
556 .map_err(BoxedError::new)
557 .context(error::ExecuteGrpcQuerySnafu)
558 }
559 })
560 .await?;
561 output.meta.cost = cost as _;
562 Ok(output)
563 } else if !metric_ctx.with_metric_engine {
564 self.handle_row_inserts(requests, ctx.clone(), false, true)
565 .await
566 .map_err(BoxedError::new)
567 .context(error::ExecuteGrpcQuerySnafu)
568 } else {
569 self.handle_metric_row_inserts(requests, ctx.clone(), physical_table)
570 .await
571 .map_err(BoxedError::new)
572 .context(error::ExecuteGrpcQuerySnafu)
573 }
574 }
575}
576
577#[cfg(test)]
578mod tests {
579 use std::sync::Arc;
580
581 use auth::{PermissionTableTarget, PermissionTableTargets};
582 use opentelemetry_proto::tonic::collector::trace::v1::ExportTraceServiceRequest;
583 use opentelemetry_proto::tonic::common::v1::{AnyValue, KeyValue, any_value};
584 use opentelemetry_proto::tonic::resource::v1::Resource;
585 use opentelemetry_proto::tonic::trace::v1::{ResourceSpans, ScopeSpans, Span};
586 use session::context::QueryContext;
587
588 use super::trace_permission_targets;
589
590 #[test]
591 fn test_trace_permission_targets() {
592 let request = ExportTraceServiceRequest {
593 resource_spans: vec![ResourceSpans {
594 resource: Some(Resource {
595 attributes: vec![KeyValue {
596 key: "service.name".to_string(),
597 value: Some(AnyValue {
598 value: Some(any_value::Value::StringValue("frontend".to_string())),
599 }),
600 ..Default::default()
601 }],
602 ..Default::default()
603 }),
604 scope_spans: vec![ScopeSpans {
605 spans: vec![Span::default()],
606 ..Default::default()
607 }],
608 ..Default::default()
609 }],
610 };
611 let groups = servers::otlp::trace::span::parse(request);
612 let ctx = Arc::new(QueryContext::with("greptime", "public"));
613
614 assert_eq!(
615 PermissionTableTargets::Resolved(vec![
616 PermissionTableTarget::new("greptime", "public", "traces"),
617 PermissionTableTarget::new("greptime", "public", "traces_services"),
618 PermissionTableTarget::new("greptime", "public", "traces_operations"),
619 ]),
620 trace_permission_targets("traces", &groups, &ctx)
621 );
622 assert_eq!(
623 PermissionTableTargets::Unresolved,
624 trace_permission_targets("", &groups, &ctx)
625 );
626 }
627}