mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-03-09 00:49:30 +00:00
Several packages are only used while running the e2e suite. These packages are less important to update, as the they can not influence the final executable that is part of the Ceph-CSI container-image. By moving these dependencies out of the main Ceph-CSI go.mod, it is easier to identify if a reported CVE affects Ceph-CSI, or only the testing (like most of the Kubernetes CVEs). Signed-off-by: Niels de Vos <ndevos@ibm.com>
433 lines
9.6 KiB
Go
433 lines
9.6 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package sdk
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/codes"
|
|
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
|
"go.opentelemetry.io/otel/trace"
|
|
"go.opentelemetry.io/otel/trace/noop"
|
|
|
|
"go.opentelemetry.io/auto/sdk/internal/telemetry"
|
|
)
|
|
|
|
type span struct {
|
|
noop.Span
|
|
|
|
spanContext trace.SpanContext
|
|
sampled atomic.Bool
|
|
|
|
mu sync.Mutex
|
|
traces *telemetry.Traces
|
|
span *telemetry.Span
|
|
}
|
|
|
|
func (s *span) SpanContext() trace.SpanContext {
|
|
if s == nil {
|
|
return trace.SpanContext{}
|
|
}
|
|
// s.spanContext is immutable, do not acquire lock s.mu.
|
|
return s.spanContext
|
|
}
|
|
|
|
func (s *span) IsRecording() bool {
|
|
if s == nil {
|
|
return false
|
|
}
|
|
|
|
return s.sampled.Load()
|
|
}
|
|
|
|
func (s *span) SetStatus(c codes.Code, msg string) {
|
|
if s == nil || !s.sampled.Load() {
|
|
return
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if s.span.Status == nil {
|
|
s.span.Status = new(telemetry.Status)
|
|
}
|
|
|
|
s.span.Status.Message = msg
|
|
|
|
switch c {
|
|
case codes.Unset:
|
|
s.span.Status.Code = telemetry.StatusCodeUnset
|
|
case codes.Error:
|
|
s.span.Status.Code = telemetry.StatusCodeError
|
|
case codes.Ok:
|
|
s.span.Status.Code = telemetry.StatusCodeOK
|
|
}
|
|
}
|
|
|
|
func (s *span) SetAttributes(attrs ...attribute.KeyValue) {
|
|
if s == nil || !s.sampled.Load() {
|
|
return
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
limit := maxSpan.Attrs
|
|
if limit == 0 {
|
|
// No attributes allowed.
|
|
s.span.DroppedAttrs += uint32(len(attrs))
|
|
return
|
|
}
|
|
|
|
m := make(map[string]int)
|
|
for i, a := range s.span.Attrs {
|
|
m[a.Key] = i
|
|
}
|
|
|
|
for _, a := range attrs {
|
|
val := convAttrValue(a.Value)
|
|
if val.Empty() {
|
|
s.span.DroppedAttrs++
|
|
continue
|
|
}
|
|
|
|
if idx, ok := m[string(a.Key)]; ok {
|
|
s.span.Attrs[idx] = telemetry.Attr{
|
|
Key: string(a.Key),
|
|
Value: val,
|
|
}
|
|
} else if limit < 0 || len(s.span.Attrs) < limit {
|
|
s.span.Attrs = append(s.span.Attrs, telemetry.Attr{
|
|
Key: string(a.Key),
|
|
Value: val,
|
|
})
|
|
m[string(a.Key)] = len(s.span.Attrs) - 1
|
|
} else {
|
|
s.span.DroppedAttrs++
|
|
}
|
|
}
|
|
}
|
|
|
|
// convCappedAttrs converts up to limit attrs into a []telemetry.Attr. The
|
|
// number of dropped attributes is also returned.
|
|
func convCappedAttrs(limit int, attrs []attribute.KeyValue) ([]telemetry.Attr, uint32) {
|
|
if limit == 0 {
|
|
return nil, uint32(len(attrs))
|
|
}
|
|
|
|
if limit < 0 {
|
|
// Unlimited.
|
|
return convAttrs(attrs), 0
|
|
}
|
|
|
|
limit = min(len(attrs), limit)
|
|
return convAttrs(attrs[:limit]), uint32(len(attrs) - limit)
|
|
}
|
|
|
|
func convAttrs(attrs []attribute.KeyValue) []telemetry.Attr {
|
|
if len(attrs) == 0 {
|
|
// Avoid allocations if not necessary.
|
|
return nil
|
|
}
|
|
|
|
out := make([]telemetry.Attr, 0, len(attrs))
|
|
for _, attr := range attrs {
|
|
key := string(attr.Key)
|
|
val := convAttrValue(attr.Value)
|
|
if val.Empty() {
|
|
continue
|
|
}
|
|
out = append(out, telemetry.Attr{Key: key, Value: val})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func convAttrValue(value attribute.Value) telemetry.Value {
|
|
switch value.Type() {
|
|
case attribute.BOOL:
|
|
return telemetry.BoolValue(value.AsBool())
|
|
case attribute.INT64:
|
|
return telemetry.Int64Value(value.AsInt64())
|
|
case attribute.FLOAT64:
|
|
return telemetry.Float64Value(value.AsFloat64())
|
|
case attribute.STRING:
|
|
v := truncate(maxSpan.AttrValueLen, value.AsString())
|
|
return telemetry.StringValue(v)
|
|
case attribute.BOOLSLICE:
|
|
slice := value.AsBoolSlice()
|
|
out := make([]telemetry.Value, 0, len(slice))
|
|
for _, v := range slice {
|
|
out = append(out, telemetry.BoolValue(v))
|
|
}
|
|
return telemetry.SliceValue(out...)
|
|
case attribute.INT64SLICE:
|
|
slice := value.AsInt64Slice()
|
|
out := make([]telemetry.Value, 0, len(slice))
|
|
for _, v := range slice {
|
|
out = append(out, telemetry.Int64Value(v))
|
|
}
|
|
return telemetry.SliceValue(out...)
|
|
case attribute.FLOAT64SLICE:
|
|
slice := value.AsFloat64Slice()
|
|
out := make([]telemetry.Value, 0, len(slice))
|
|
for _, v := range slice {
|
|
out = append(out, telemetry.Float64Value(v))
|
|
}
|
|
return telemetry.SliceValue(out...)
|
|
case attribute.STRINGSLICE:
|
|
slice := value.AsStringSlice()
|
|
out := make([]telemetry.Value, 0, len(slice))
|
|
for _, v := range slice {
|
|
v = truncate(maxSpan.AttrValueLen, v)
|
|
out = append(out, telemetry.StringValue(v))
|
|
}
|
|
return telemetry.SliceValue(out...)
|
|
}
|
|
return telemetry.Value{}
|
|
}
|
|
|
|
// truncate returns a truncated version of s such that it contains less than
|
|
// the limit number of characters. Truncation is applied by returning the limit
|
|
// number of valid characters contained in s.
|
|
//
|
|
// If limit is negative, it returns the original string.
|
|
//
|
|
// UTF-8 is supported. When truncating, all invalid characters are dropped
|
|
// before applying truncation.
|
|
//
|
|
// If s already contains less than the limit number of bytes, it is returned
|
|
// unchanged. No invalid characters are removed.
|
|
func truncate(limit int, s string) string {
|
|
// This prioritize performance in the following order based on the most
|
|
// common expected use-cases.
|
|
//
|
|
// - Short values less than the default limit (128).
|
|
// - Strings with valid encodings that exceed the limit.
|
|
// - No limit.
|
|
// - Strings with invalid encodings that exceed the limit.
|
|
if limit < 0 || len(s) <= limit {
|
|
return s
|
|
}
|
|
|
|
// Optimistically, assume all valid UTF-8.
|
|
var b strings.Builder
|
|
count := 0
|
|
for i, c := range s {
|
|
if c != utf8.RuneError {
|
|
count++
|
|
if count > limit {
|
|
return s[:i]
|
|
}
|
|
continue
|
|
}
|
|
|
|
_, size := utf8.DecodeRuneInString(s[i:])
|
|
if size == 1 {
|
|
// Invalid encoding.
|
|
b.Grow(len(s) - 1)
|
|
_, _ = b.WriteString(s[:i])
|
|
s = s[i:]
|
|
break
|
|
}
|
|
}
|
|
|
|
// Fast-path, no invalid input.
|
|
if b.Cap() == 0 {
|
|
return s
|
|
}
|
|
|
|
// Truncate while validating UTF-8.
|
|
for i := 0; i < len(s) && count < limit; {
|
|
c := s[i]
|
|
if c < utf8.RuneSelf {
|
|
// Optimization for single byte runes (common case).
|
|
_ = b.WriteByte(c)
|
|
i++
|
|
count++
|
|
continue
|
|
}
|
|
|
|
_, size := utf8.DecodeRuneInString(s[i:])
|
|
if size == 1 {
|
|
// We checked for all 1-byte runes above, this is a RuneError.
|
|
i++
|
|
continue
|
|
}
|
|
|
|
_, _ = b.WriteString(s[i : i+size])
|
|
i += size
|
|
count++
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
func (s *span) End(opts ...trace.SpanEndOption) {
|
|
if s == nil || !s.sampled.Swap(false) {
|
|
return
|
|
}
|
|
|
|
// s.end exists so the lock (s.mu) is not held while s.ended is called.
|
|
s.ended(s.end(opts))
|
|
}
|
|
|
|
func (s *span) end(opts []trace.SpanEndOption) []byte {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
cfg := trace.NewSpanEndConfig(opts...)
|
|
if t := cfg.Timestamp(); !t.IsZero() {
|
|
s.span.EndTime = cfg.Timestamp()
|
|
} else {
|
|
s.span.EndTime = time.Now()
|
|
}
|
|
|
|
b, _ := json.Marshal(s.traces) // TODO: do not ignore this error.
|
|
return b
|
|
}
|
|
|
|
// Expected to be implemented in eBPF.
|
|
//
|
|
//go:noinline
|
|
func (*span) ended(buf []byte) { ended(buf) }
|
|
|
|
// ended is used for testing.
|
|
var ended = func([]byte) {}
|
|
|
|
func (s *span) RecordError(err error, opts ...trace.EventOption) {
|
|
if s == nil || err == nil || !s.sampled.Load() {
|
|
return
|
|
}
|
|
|
|
cfg := trace.NewEventConfig(opts...)
|
|
|
|
attrs := cfg.Attributes()
|
|
attrs = append(attrs,
|
|
semconv.ExceptionType(typeStr(err)),
|
|
semconv.ExceptionMessage(err.Error()),
|
|
)
|
|
if cfg.StackTrace() {
|
|
buf := make([]byte, 2048)
|
|
n := runtime.Stack(buf, false)
|
|
attrs = append(attrs, semconv.ExceptionStacktrace(string(buf[0:n])))
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.addEvent(semconv.ExceptionEventName, cfg.Timestamp(), attrs)
|
|
}
|
|
|
|
func typeStr(i any) string {
|
|
t := reflect.TypeOf(i)
|
|
if t.PkgPath() == "" && t.Name() == "" {
|
|
// Likely a builtin type.
|
|
return t.String()
|
|
}
|
|
return fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
|
|
}
|
|
|
|
func (s *span) AddEvent(name string, opts ...trace.EventOption) {
|
|
if s == nil || !s.sampled.Load() {
|
|
return
|
|
}
|
|
|
|
cfg := trace.NewEventConfig(opts...)
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.addEvent(name, cfg.Timestamp(), cfg.Attributes())
|
|
}
|
|
|
|
// addEvent adds an event with name and attrs at tStamp to the span. The span
|
|
// lock (s.mu) needs to be held by the caller.
|
|
func (s *span) addEvent(name string, tStamp time.Time, attrs []attribute.KeyValue) {
|
|
limit := maxSpan.Events
|
|
|
|
if limit == 0 {
|
|
s.span.DroppedEvents++
|
|
return
|
|
}
|
|
|
|
if limit > 0 && len(s.span.Events) == limit {
|
|
// Drop head while avoiding allocation of more capacity.
|
|
copy(s.span.Events[:limit-1], s.span.Events[1:])
|
|
s.span.Events = s.span.Events[:limit-1]
|
|
s.span.DroppedEvents++
|
|
}
|
|
|
|
e := &telemetry.SpanEvent{Time: tStamp, Name: name}
|
|
e.Attrs, e.DroppedAttrs = convCappedAttrs(maxSpan.EventAttrs, attrs)
|
|
|
|
s.span.Events = append(s.span.Events, e)
|
|
}
|
|
|
|
func (s *span) AddLink(link trace.Link) {
|
|
if s == nil || !s.sampled.Load() {
|
|
return
|
|
}
|
|
|
|
l := maxSpan.Links
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if l == 0 {
|
|
s.span.DroppedLinks++
|
|
return
|
|
}
|
|
|
|
if l > 0 && len(s.span.Links) == l {
|
|
// Drop head while avoiding allocation of more capacity.
|
|
copy(s.span.Links[:l-1], s.span.Links[1:])
|
|
s.span.Links = s.span.Links[:l-1]
|
|
s.span.DroppedLinks++
|
|
}
|
|
|
|
s.span.Links = append(s.span.Links, convLink(link))
|
|
}
|
|
|
|
func convLinks(links []trace.Link) []*telemetry.SpanLink {
|
|
out := make([]*telemetry.SpanLink, 0, len(links))
|
|
for _, link := range links {
|
|
out = append(out, convLink(link))
|
|
}
|
|
return out
|
|
}
|
|
|
|
func convLink(link trace.Link) *telemetry.SpanLink {
|
|
l := &telemetry.SpanLink{
|
|
TraceID: telemetry.TraceID(link.SpanContext.TraceID()),
|
|
SpanID: telemetry.SpanID(link.SpanContext.SpanID()),
|
|
TraceState: link.SpanContext.TraceState().String(),
|
|
Flags: uint32(link.SpanContext.TraceFlags()),
|
|
}
|
|
l.Attrs, l.DroppedAttrs = convCappedAttrs(maxSpan.LinkAttrs, link.Attributes)
|
|
|
|
return l
|
|
}
|
|
|
|
func (s *span) SetName(name string) {
|
|
if s == nil || !s.sampled.Load() {
|
|
return
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.span.Name = name
|
|
}
|
|
|
|
func (*span) TracerProvider() trace.TracerProvider { return TracerProvider() }
|