Skip to main content

frontend/instance/
otlp.rs

1// Copyright 2023 Greptime Team
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
15mod trace_ingest;
16pub mod trace_semconv;
17pub mod trace_types;
18
19use std::sync::Arc;
20
21use async_trait::async_trait;
22use auth::{
23    OTLP_WRITE, PermissionChecker, PermissionCheckerRef, PermissionReq, PermissionTableTarget,
24    PermissionTableTargets,
25};
26use client::Output;
27use common_catalog::consts::{trace_operations_table_name, trace_services_table_name};
28use common_error::ext::BoxedError;
29use common_query::prelude::GREPTIME_PHYSICAL_TABLE;
30use common_query::{OutputData, OutputMeta};
31use common_telemetry::{tracing, warn};
32use opentelemetry_proto::tonic::collector::logs::v1::ExportLogsServiceRequest;
33use opentelemetry_proto::tonic::collector::trace::v1::ExportTraceServiceRequest;
34use operator::insert::{Inserter, admit_row_insert_batches, admit_write};
35use otel_arrow_rust::proto::opentelemetry::collector::metrics::v1::ExportMetricsServiceRequest;
36use pipeline::{GreptimePipelineParams, PipelineWay};
37use servers::error::{self, AuthSnafu, Result as ServerResult};
38use servers::http::prom_store::PHYSICAL_TABLE_PARAM;
39use servers::interceptor::{OpenTelemetryProtocolInterceptor, OpenTelemetryProtocolInterceptorRef};
40use servers::otlp;
41use servers::otlp::trace::span::TraceSpanGroup;
42use servers::query_handler::{
43    MetricsIngestOutcome, OpenTelemetryProtocolHandler, PipelineHandlerRef, TraceIngestOutcome,
44};
45use session::context::QueryContextRef;
46use snafu::ResultExt;
47use table::requests::{
48    OTLP_METRIC_COMPAT_KEY, OTLP_METRIC_COMPAT_PROM, SEMANTIC_PER_TABLE_INDEX_KEY,
49    SEMANTIC_SIGNAL_TYPE, SEMANTIC_SOURCE, SIGNAL_TYPE_LOG, SIGNAL_TYPE_METRIC,
50    SOURCE_OPENTELEMETRY,
51};
52
53use self::trace_ingest::trace_conventions;
54use crate::instance::Instance;
55use crate::metrics::{OTLP_LOGS_ROWS, OTLP_METRICS_ROWS, OTLP_RESOURCE_INFO_WRITE_ERRORS};
56
57fn trace_permission_targets(
58    table_name: &str,
59    groups: &[TraceSpanGroup],
60    ctx: &QueryContextRef,
61) -> PermissionTableTargets {
62    let catalog = ctx.current_catalog();
63    let schema = ctx.current_schema();
64    if catalog.is_empty() || schema.is_empty() || table_name.is_empty() {
65        return PermissionTableTargets::Unresolved;
66    }
67
68    let has_spans = groups.iter().any(|group| !group.spans.is_empty());
69    if !has_spans {
70        return PermissionTableTargets::resolved(Vec::new());
71    }
72
73    let mut targets = vec![PermissionTableTarget::new(catalog, &schema, table_name)];
74    if groups
75        .iter()
76        .flat_map(|group| &group.spans)
77        .any(|span| span.service_name.is_some())
78    {
79        targets.extend([
80            PermissionTableTarget::new(catalog, &schema, trace_services_table_name(table_name)),
81            PermissionTableTarget::new(catalog, &schema, trace_operations_table_name(table_name)),
82        ]);
83    }
84
85    PermissionTableTargets::resolved(targets)
86}
87
88#[async_trait]
89impl OpenTelemetryProtocolHandler for Instance {
90    #[tracing::instrument(skip_all)]
91    async fn metrics(
92        &self,
93        request: ExportMetricsServiceRequest,
94        ctx: QueryContextRef,
95    ) -> ServerResult<MetricsIngestOutcome> {
96        self.plugins
97            .get::<PermissionCheckerRef>()
98            .as_ref()
99            .check_permission(ctx.current_user(), PermissionReq::Action(OTLP_WRITE))
100            .context(AuthSnafu)?;
101
102        let interceptor_ref = self
103            .plugins
104            .get::<OpenTelemetryProtocolInterceptorRef<servers::error::Error>>();
105        interceptor_ref.pre_execute(ctx.clone())?;
106        let ctx = Arc::new(ctx.fork());
107
108        let input_names = request
109            .resource_metrics
110            .iter()
111            .flat_map(|r| r.scope_metrics.iter())
112            .flat_map(|s| s.metrics.iter().map(|m| m.name.clone()))
113            .collect::<Vec<_>>();
114
115        // See [`OtlpMetricCtx`] for details
116        let is_legacy = self.check_otlp_legacy(&input_names, &ctx).await?;
117
118        let mut metric_ctx = ctx
119            .protocol_ctx()
120            .get_otlp_metric_ctx()
121            .cloned()
122            .unwrap_or_default();
123        metric_ctx.is_legacy = is_legacy;
124        metric_ctx.resource_info = self.otlp_resource_info;
125
126        let otlp::metrics::MetricsConversion {
127            requests,
128            rows,
129            semantic_index,
130            resource_info,
131            mut outcome,
132        } = otlp::metrics::to_grpc_insert_requests(request, &mut metric_ctx)?;
133        if outcome.rejected_data_points > 0 {
134            warn!(
135                "Rejected {} OTLP metrics data points: {}",
136                outcome.rejected_data_points,
137                outcome.error_message.as_deref().unwrap_or_default()
138            );
139        }
140        if outcome.accepted_data_points == 0 {
141            return Ok(outcome);
142        }
143
144        self.check_row_insert_permission(&requests, &ctx, PermissionReq::Action(OTLP_WRITE))
145            .context(AuthSnafu)?;
146        let ctx = admit_write(rows as u64, &ctx)
147            .await
148            .map_err(BoxedError::new)
149            .context(error::ExecuteGrpcQuerySnafu)?;
150        self.cache_otlp_legacy(&input_names, &ctx, is_legacy)?;
151        OTLP_METRICS_ROWS.inc_by(rows as u64);
152
153        let ctx = {
154            let mut c = (*ctx).clone();
155            c.set_extension(SEMANTIC_SIGNAL_TYPE, SIGNAL_TYPE_METRIC);
156            c.set_extension(SEMANTIC_SOURCE, SOURCE_OPENTELEMETRY);
157            // Per-table metric specifics + resource/scope lineage ride this
158            // internal channel; the auto-create path folds them per schema and
159            // table name.
160            if let Some(index) = semantic_index.encode(&c.current_schema()) {
161                c.set_extension(SEMANTIC_PER_TABLE_INDEX_KEY, index);
162            }
163            if !is_legacy {
164                c.set_extension(OTLP_METRIC_COMPAT_KEY, OTLP_METRIC_COMPAT_PROM.to_string());
165            }
166            Arc::new(c)
167        };
168
169        let physical_table = ctx
170            .extension(PHYSICAL_TABLE_PARAM)
171            .unwrap_or(GREPTIME_PHYSICAL_TABLE)
172            .to_string();
173        // The bulk path converts each request's time index unit to the
174        // destination table's unit during batch alignment, so no pre-gate
175        // alignment is needed here.
176        let batcher = self.logical_batcher().filter(|_| {
177            ctx.logical_batching_enabled() && !metric_ctx.is_legacy && metric_ctx.with_metric_engine
178        });
179        let batcher = if batcher.is_some()
180            && self
181                .inserter
182                .can_batch_metric_rows(&requests, &ctx, &physical_table)
183                .await
184                .map_err(BoxedError::new)
185                .context(error::ExecuteGrpcQuerySnafu)?
186        {
187            batcher
188        } else {
189            None
190        };
191
192        // OTLP tables have one sample field in both the legacy and physical paths.
193        let output = if let Some(batcher) = batcher {
194            let (rows, cost) = batcher
195                .submit_with(requests, ctx.clone(), |mut requests| {
196                    let ctx = ctx.clone();
197                    async move {
198                        Inserter::meter_row_inserts(&mut requests, &ctx)
199                            .await
200                            .map_err(BoxedError::new)
201                            .context(error::ExecuteGrpcQuerySnafu)
202                    }
203                })
204                .await?;
205            Output::new(
206                OutputData::AffectedRows(rows as usize),
207                OutputMeta::new_with_cost(cost as _),
208            )
209        } else if metric_ctx.is_legacy || !metric_ctx.with_metric_engine {
210            self.handle_row_inserts(requests, ctx.clone(), false, true)
211                .await
212                .map_err(BoxedError::new)
213                .context(error::ExecuteGrpcQuerySnafu)?
214        } else {
215            self.handle_metric_row_inserts(requests, ctx.clone(), physical_table)
216                .await
217                .map_err(BoxedError::new)
218                .context(error::ExecuteGrpcQuerySnafu)?
219        };
220        outcome.write_cost = output.meta.cost;
221
222        // Derived enrichment follows the accepted metric submission, which may
223        // still be queued in asynchronous mode. Failures remain warning-only
224        // to avoid retrying metric data the server already accepted.
225        if let Some(resource_info) = resource_info {
226            let written = match self.check_row_insert_permission(
227                &resource_info,
228                &ctx,
229                PermissionReq::Action(OTLP_WRITE),
230            ) {
231                Ok(_) => self
232                    .handle_row_inserts(resource_info, ctx, false, false)
233                    .await
234                    .map_err(BoxedError::new)
235                    .map_err(|e| e.to_string()),
236                Err(e) => Err(e.to_string()),
237            };
238            match written {
239                Ok(descriptor_output) => outcome.write_cost += descriptor_output.meta.cost,
240                Err(e) => {
241                    OTLP_RESOURCE_INFO_WRITE_ERRORS.inc();
242                    warn!("Failed to write the OTLP resource descriptor table: {e}");
243                    outcome.error_message.get_or_insert(format!(
244                        "metric data was accepted, but writing the resource \
245                         descriptor table `{}` failed: {e}",
246                        otlp::metrics::OTEL_RESOURCE_INFO_TABLE_NAME
247                    ));
248                }
249            }
250        }
251
252        Ok(outcome)
253    }
254
255    #[tracing::instrument(skip_all)]
256    async fn traces(
257        &self,
258        _pipeline_handler: PipelineHandlerRef,
259        request: ExportTraceServiceRequest,
260        pipeline: PipelineWay,
261        _pipeline_params: GreptimePipelineParams,
262        table_name: String,
263        ctx: QueryContextRef,
264    ) -> ServerResult<TraceIngestOutcome> {
265        self.plugins
266            .get::<PermissionCheckerRef>()
267            .as_ref()
268            .check_permission(ctx.current_user(), PermissionReq::Action(OTLP_WRITE))
269            .context(AuthSnafu)?;
270
271        let interceptor_ref = self
272            .plugins
273            .get::<OpenTelemetryProtocolInterceptorRef<servers::error::Error>>();
274        interceptor_ref.pre_execute(ctx.clone())?;
275        let ctx = Arc::new(ctx.fork());
276
277        // `schema_url` is consumed by `parse`, so derive conventions first.
278        let conventions = trace_conventions(&request);
279        let spans = otlp::trace::span::parse(request);
280        let targets = trace_permission_targets(&table_name, &spans, &ctx);
281        self.check_table_permission(&ctx, PermissionReq::Action(OTLP_WRITE), targets)
282            .context(AuthSnafu)?;
283        // Count the external spans once, before chunking, retries, or derived tables.
284        let rows = spans.iter().map(|group| group.spans.len() as u64).sum();
285        let ctx = admit_write(rows, &ctx)
286            .await
287            .map_err(BoxedError::new)
288            .context(error::ExecuteGrpcQuerySnafu)?;
289        self.ingest_trace_spans(&pipeline, table_name, spans, &conventions, ctx)
290            .await
291    }
292
293    #[tracing::instrument(skip_all)]
294    async fn logs(
295        &self,
296        pipeline_handler: PipelineHandlerRef,
297        request: ExportLogsServiceRequest,
298        pipeline: PipelineWay,
299        pipeline_params: GreptimePipelineParams,
300        table_name: String,
301        ctx: QueryContextRef,
302    ) -> ServerResult<Vec<Output>> {
303        self.plugins
304            .get::<PermissionCheckerRef>()
305            .as_ref()
306            .check_permission(ctx.current_user(), PermissionReq::Action(OTLP_WRITE))
307            .context(AuthSnafu)?;
308
309        let interceptor_ref = self
310            .plugins
311            .get::<OpenTelemetryProtocolInterceptorRef<servers::error::Error>>();
312        interceptor_ref.pre_execute(ctx.clone())?;
313        let ctx = Arc::new(ctx.fork());
314
315        // `as_req_iter` clones this ctx into each `temp_ctx`, so identity set here
316        // reaches the context that drives table auto-create.
317        let ctx = {
318            let mut c = (*ctx).clone();
319            c.set_extension(SEMANTIC_SIGNAL_TYPE, SIGNAL_TYPE_LOG);
320            c.set_extension(SEMANTIC_SOURCE, SOURCE_OPENTELEMETRY);
321            Arc::new(c)
322        };
323
324        let opt_req = otlp::logs::to_grpc_insert_requests(
325            request,
326            pipeline,
327            pipeline_params,
328            table_name,
329            &ctx,
330            pipeline_handler,
331        )
332        .await?;
333
334        let mut batches = opt_req.as_req_iter(ctx).collect::<Vec<_>>();
335        for (temp_ctx, requests) in &batches {
336            self.check_row_insert_permission(requests, temp_ctx, PermissionReq::Action(OTLP_WRITE))
337                .context(AuthSnafu)?;
338        }
339        admit_row_insert_batches(&mut batches)
340            .await
341            .map_err(BoxedError::new)
342            .context(error::ExecuteGrpcQuerySnafu)?;
343
344        let mut outputs = Vec::with_capacity(batches.len());
345        for (temp_ctx, requests) in batches {
346            let cnt = requests
347                .inserts
348                .iter()
349                .filter_map(|r| r.rows.as_ref().map(|r| r.rows.len()))
350                .sum::<usize>();
351
352            let o = self
353                .handle_log_inserts(requests, temp_ctx)
354                .await
355                .inspect(|_| OTLP_LOGS_ROWS.inc_by(cnt as u64))
356                .map_err(BoxedError::new)
357                .context(error::ExecuteGrpcQuerySnafu)?;
358            outputs.push(o);
359        }
360
361        Ok(outputs)
362    }
363}
364
365#[cfg(test)]
366mod tests {
367    use std::sync::Arc;
368
369    use auth::{PermissionTableTarget, PermissionTableTargets};
370    use opentelemetry_proto::tonic::collector::trace::v1::ExportTraceServiceRequest;
371    use opentelemetry_proto::tonic::common::v1::{AnyValue, KeyValue, any_value};
372    use opentelemetry_proto::tonic::resource::v1::Resource;
373    use opentelemetry_proto::tonic::trace::v1::{ResourceSpans, ScopeSpans, Span};
374    use session::context::QueryContext;
375
376    use super::trace_permission_targets;
377
378    #[test]
379    fn test_trace_permission_targets() {
380        let request = ExportTraceServiceRequest {
381            resource_spans: vec![ResourceSpans {
382                resource: Some(Resource {
383                    attributes: vec![KeyValue {
384                        key: "service.name".to_string(),
385                        value: Some(AnyValue {
386                            value: Some(any_value::Value::StringValue("frontend".to_string())),
387                        }),
388                        ..Default::default()
389                    }],
390                    ..Default::default()
391                }),
392                scope_spans: vec![ScopeSpans {
393                    spans: vec![Span::default()],
394                    ..Default::default()
395                }],
396                ..Default::default()
397            }],
398        };
399        let groups = servers::otlp::trace::span::parse(request);
400        let ctx = Arc::new(QueryContext::with("greptime", "public"));
401
402        assert_eq!(
403            PermissionTableTargets::Resolved(vec![
404                PermissionTableTarget::new("greptime", "public", "traces"),
405                PermissionTableTarget::new("greptime", "public", "traces_services"),
406                PermissionTableTarget::new("greptime", "public", "traces_operations"),
407            ]),
408            trace_permission_targets("traces", &groups, &ctx)
409        );
410        assert_eq!(
411            PermissionTableTargets::Unresolved,
412            trace_permission_targets("", &groups, &ctx)
413        );
414    }
415}