// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package observ // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ"

import (
	
	
	
	
	

	

	
	
	
	
	
	
	semconv 
	
)

const (
	// ScopeName is the unique name of the meter used for instrumentation.
	ScopeName = "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ"

	// SchemaURL is the schema URL of the metrics produced by this
	// instrumentation.
	SchemaURL = semconv.SchemaURL

	// Version is the current version of this instrumentation.
	//
	// This matches the version of the exporter.
	Version = internal.Version
)

var (
	measureAttrsPool = &sync.Pool{
		New: func() any {
			const  = 1 + // component.name
				1 + // component.type
				1 + // server.addr
				1 + // server.port
				1 + // error.type
				1 // rpc.grpc.status_code
			 := make([]attribute.KeyValue, 0, )
			// Return a pointer to a slice instead of a slice itself
			// to avoid allocations on every call.
			return &
		},
	}

	addOptPool = &sync.Pool{
		New: func() any {
			const  = 1 // WithAttributeSet
			 := make([]metric.AddOption, 0, )
			return &
		},
	}

	recordOptPool = &sync.Pool{
		New: func() any {
			const  = 1 // WithAttributeSet
			 := make([]metric.RecordOption, 0, )
			return &
		},
	}
)

func get[ any]( *sync.Pool) *[] { return .Get().(*[]) }

func put[ any]( *sync.Pool,  *[]) {
	* = (*)[:0] // Reset.
	.Put()
}

// ComponentName returns the component name for the exporter with the
// provided ID.
func ( int64) string {
	 := semconv.OTelComponentTypeOtlpGRPCSpanExporter.Value.AsString()
	return fmt.Sprintf("%s/%d", , )
}

// Instrumentation is experimental instrumentation for the exporter.
type Instrumentation struct {
	inflightSpans metric.Int64UpDownCounter
	exportedSpans metric.Int64Counter
	opDuration    metric.Float64Histogram

	attrs  []attribute.KeyValue
	addOpt metric.AddOption
	recOpt metric.RecordOption
}

// NewInstrumentation returns instrumentation for an OTLP over gPRC trace
// exporter with the provided ID using the global MeterProvider.
//
// The id should be the unique exporter instance ID. It is used
// to set the "component.name" attribute.
//
// The target is the endpoint the exporter is exporting to.
//
// If the experimental observability is disabled, nil is returned.
func ( int64,  string) (*Instrumentation, error) {
	if !x.Observability.Enabled() {
		return nil, nil
	}

	 := BaseAttrs(, )
	 := &Instrumentation{
		attrs:  ,
		addOpt: metric.WithAttributeSet(attribute.NewSet(...)),

		// Do not modify attrs (NewSet sorts in-place), make a new slice.
		recOpt: metric.WithAttributeSet(attribute.NewSet(append(
			// Default to OK status code.
			[]attribute.KeyValue{
				semconv.RPCResponseStatusCode(codes.OK.String()),
			},
			...,
		)...)),
	}

	 := otel.GetMeterProvider()
	 := .Meter(
		ScopeName,
		metric.WithInstrumentationVersion(Version),
		metric.WithSchemaURL(SchemaURL),
	)

	var  error

	,  := otelconv.NewSDKExporterSpanInflight()
	if  != nil {
		 = fmt.Errorf("failed to create span inflight metric: %w", )
		 = errors.Join(, )
	}
	.inflightSpans = .Inst()

	,  := otelconv.NewSDKExporterSpanExported()
	if  != nil {
		 = fmt.Errorf("failed to create span exported metric: %w", )
		 = errors.Join(, )
	}
	.exportedSpans = .Inst()

	,  := otelconv.NewSDKExporterOperationDuration()
	if  != nil {
		 = fmt.Errorf("failed to create operation duration metric: %w", )
		 = errors.Join(, )
	}
	.opDuration = .Inst()

	return , 
}

// BaseAttrs returns the base attributes for the exporter with the provided ID
// and target.
//
// The id should be the unique exporter instance ID. It is used
// to set the "component.name" attribute.
//
// The target is the gRPC target the exporter is exporting to. It is expected
// to be the output of the Client's CanonicalTarget method.
func ( int64,  string) []attribute.KeyValue {
	, ,  := ParseCanonicalTarget()
	if  != nil || ( == "" &&  < 0) {
		if  != nil {
			global.Debug("failed to parse target", "target", , "error", )
		}
		return []attribute.KeyValue{
			semconv.OTelComponentName(ComponentName()),
			semconv.OTelComponentTypeOtlpGRPCSpanExporter,
		}
	}

	// Do not use append so the slice is exactly allocated.

	if  < 0 {
		return []attribute.KeyValue{
			semconv.OTelComponentName(ComponentName()),
			semconv.OTelComponentTypeOtlpGRPCSpanExporter,
			semconv.ServerAddress(),
		}
	}

	if  == "" {
		return []attribute.KeyValue{
			semconv.OTelComponentName(ComponentName()),
			semconv.OTelComponentTypeOtlpGRPCSpanExporter,
			semconv.ServerPort(),
		}
	}

	return []attribute.KeyValue{
		semconv.OTelComponentName(ComponentName()),
		semconv.OTelComponentTypeOtlpGRPCSpanExporter,
		semconv.ServerAddress(),
		semconv.ServerPort(),
	}
}

// ExportSpans instruments the ExportSpans method of the exporter. It returns
// an [ExportOp] that must have its [ExportOp.End] method called when the
// ExportSpans method returns.
func ( *Instrumentation) ( context.Context,  int) ExportOp {
	 := time.Now()

	if .inflightSpans.Enabled() {
		 := get[metric.AddOption](addOptPool)
		defer put(addOptPool, )
		* = append(*, .addOpt)
		.inflightSpans.Add(, int64(), *...)
	}

	return ExportOp{
		ctx:    ,
		start:  ,
		nSpans: int64(),
		inst:   ,
	}
}

// ExportOp tracks the operation being observed by [Instrumentation.ExportSpans].
type ExportOp struct {
	ctx    context.Context
	start  time.Time
	nSpans int64

	inst *Instrumentation
}

// End completes the observation of the operation being observed by a call to
// [Instrumentation.ExportSpans].
//
// Any error that is encountered is provided as err.
//
// If err is not nil, all spans will be recorded as failures unless error is of
// type [internal.PartialSuccess]. In the case of a PartialSuccess, the number
// of successfully exported spans will be determined by inspecting the
// RejectedItems field of the PartialSuccess.
func ( ExportOp) ( error,  codes.Code) {
	 := get[metric.AddOption](addOptPool)
	defer put(addOptPool, )
	* = append(*, .inst.addOpt)

	if .inst.inflightSpans.Enabled(.ctx) {
		.inst.inflightSpans.Add(.ctx, -.nSpans, *...)
	}

	 := successful(.nSpans, )
	// Record successfully exported spans, even if the value is 0 which are
	// meaningful to distribution aggregations.
	if .inst.exportedSpans.Enabled(.ctx) {
		.inst.exportedSpans.Add(.ctx, , *...)
	}

	if  != nil && .inst.exportedSpans.Enabled(.ctx) {
		 := get[attribute.KeyValue](measureAttrsPool)
		defer put(measureAttrsPool, )
		* = append(*, .inst.attrs...)
		* = append(*, semconv.ErrorType())

		// Do not inefficiently make a copy of attrs by using
		// WithAttributes instead of WithAttributeSet.
		 := metric.WithAttributeSet(attribute.NewSet(*...))
		// Reset addOpt with new attribute set.
		* = append((*)[:0], )

		.inst.exportedSpans.Add(.ctx, .nSpans-, *...)
	}

	if .inst.opDuration.Enabled(.ctx) {
		 := get[metric.RecordOption](recordOptPool)
		defer put(recordOptPool, )
		* = append(*, .inst.recordOption(, ))

		 := time.Since(.start).Seconds()
		.inst.opDuration.Record(.ctx, , *...)
	}
}

// recordOption returns a RecordOption with attributes representing the
// outcome of the operation being recorded.
//
// If err is nil and code is codes.OK, the default recOpt of the
// Instrumentation is returned.
//
// If err is not nil or code is not codes.OK, a new RecordOption is returned
// with the base attributes of the Instrumentation plus the rpc.grpc.status_code
// attribute set to the provided code, and if err is not nil, the error.type
// attribute set to the type of the error.
func ( *Instrumentation) ( error,  codes.Code) metric.RecordOption {
	if  == nil &&  == codes.OK {
		return .recOpt
	}

	 := get[attribute.KeyValue](measureAttrsPool)
	defer put(measureAttrsPool, )
	* = append(*, .attrs...)

	* = append(*, semconv.RPCResponseStatusCode(.String()))
	if  != nil {
		* = append(*, semconv.ErrorType())
	}

	// Do not inefficiently make a copy of attrs by using WithAttributes
	// instead of WithAttributeSet.
	return metric.WithAttributeSet(attribute.NewSet(*...))
}

// successful returns the number of successfully exported spans out of the n
// that were exported based on the provided error.
//
// If err is nil, n is returned. All spans were successfully exported.
//
// If err is not nil and not an [internal.PartialSuccess] error, 0 is returned.
// It is assumed all spans failed to be exported.
//
// If err is an [internal.PartialSuccess] error, the number of successfully
// exported spans is computed by subtracting the RejectedItems field from n. If
// RejectedItems is negative, n is returned. If RejectedItems is greater than
// n, 0 is returned.
func successful( int64,  error) int64 {
	if  == nil {
		return  // All spans successfully exported.
	}
	// Split rejection calculation so successful is inlinable.
	return  - rejected(, )
}

var errPartialPool = &sync.Pool{
	New: func() any { return new(internal.PartialSuccess) },
}

// rejected returns how many out of the n spans exporter were rejected based on
// the provided non-nil err.
func rejected( int64,  error) int64 {
	 := errPartialPool.Get().(*internal.PartialSuccess)
	defer errPartialPool.Put()
	// Check for partial success.
	if errors.As(, ) {
		// Bound RejectedItems to [0, n]. This should not be needed,
		// but be defensive as this is from an external source.
		return min(max(.RejectedItems, 0), )
	}
	return  // All spans rejected.
}