Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sdk/telemetry package #1148

Merged
merged 19 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions sdk/telemetry/attr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package telemetry

// Attr is a key-value pair.
type Attr struct {
Key string `json:"key,omitempty"`
Value Value `json:"value,omitempty"`
}

// String returns an Attr for a string value.
func String(key, value string) Attr {
return Attr{key, StringValue(value)}
}

// Int64 returns an Attr for an int64 value.
func Int64(key string, value int64) Attr {
return Attr{key, Int64Value(value)}
}

// Int returns an Attr for an int value.
func Int(key string, value int) Attr {
return Int64(key, int64(value))
}

// Float64 returns an Attr for a float64 value.
func Float64(key string, value float64) Attr {
return Attr{key, Float64Value(value)}
}

// Bool returns an Attr for a bool value.
func Bool(key string, value bool) Attr {
return Attr{key, BoolValue(value)}
}

// Bytes returns an Attr for a []byte value.
// The passed slice must not be changed after it is passed.
func Bytes(key string, value []byte) Attr {
return Attr{key, BytesValue(value)}
}

// Slice returns an Attr for a []Value value.
// The passed slice must not be changed after it is passed.
func Slice(key string, value ...Value) Attr {
return Attr{key, SliceValue(value...)}
}

// Map returns an Attr for a map value.
// The passed slice must not be changed after it is passed.
func Map(key string, value ...Attr) Attr {
return Attr{key, MapValue(value...)}
}

// Equal returns if a is equal to b.
func (a Attr) Equal(b Attr) bool {
return a.Key == b.Key && a.Value.Equal(b.Value)
}
38 changes: 38 additions & 0 deletions sdk/telemetry/attr_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package telemetry

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

var (
decoded = []Attr{
String("user", "Alice"),
Bool("admin", true),
Int64("floor", -2),
Float64("impact", 0.21362),
Slice("reports", StringValue("Bob"), StringValue("Dave")),
Map("favorites", String("food", "hot dog"), Int("number", 13)),
Bytes("secret", []byte("NUI4RUZGRjc5ODAzODEwM0QyNjlCNjMzODEzRkM2MEM=")),
}

encoded = []byte(`[{"key":"user","value":{"stringValue":"Alice"}},{"key":"admin","value":{"boolValue":true}},{"key":"floor","value":{"intValue":"-2"}},{"key":"impact","value":{"doubleValue":0.21362}},{"key":"reports","value":{"arrayValue":{"values":[{"stringValue":"Bob"},{"stringValue":"Dave"}]}}},{"key":"favorites","value":{"kvlistValue":{"values":[{"key":"food","value":{"stringValue":"hot dog"}},{"key":"number","value":{"intValue":"13"}}]}}},{"key":"secret","value":{"bytesValue":"TlVJNFJVWkdSamM1T0RBek9ERXdNMFF5TmpsQ05qTXpPREV6UmtNMk1FTT0="}}]`)
)

func TestAttrUnmarshal(t *testing.T) {
var got []Attr
require.NoError(t, json.Unmarshal(encoded, &got))
assert.Equal(t, decoded, got)
}

func TestAttrMarshal(t *testing.T) {
got, err := json.Marshal(decoded)
require.NoError(t, err)
assert.Equal(t, string(encoded), string(got))
}
70 changes: 70 additions & 0 deletions sdk/telemetry/bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package telemetry

import (
"bytes"
"encoding/json"
"testing"
"time"
)

var (
traceID = [16]byte{0x1}

spanID1 = [8]byte{0x1}
spanID2 = [8]byte{0x2}

now = time.Now()
nowPlus1 = now.Add(1 * time.Second)

spanA = &Span{
TraceID: traceID,
SpanID: spanID2,
ParentSpanID: spanID1,
Flags: 1,
Name: "span-a",
StartTime: now,
EndTime: nowPlus1,
Status: &Status{
Message: "test status",
Code: StatusCodeOK,
},
}

spanB = &Span{}

scopeSpans = &ScopeSpans{
Scope: &Scope{
Name: "TestTracer",
Version: "v0.1.0",
},
SchemaURL: "http://go.opentelemetry.io/test",
Spans: []*Span{spanA, spanB},
}
)

func BenchmarkJSONMarshalUnmarshal(b *testing.B) {
var out ScopeSpans

b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
var inBuf bytes.Buffer
enc := json.NewEncoder(&inBuf)
err := enc.Encode(scopeSpans)
if err != nil {
b.Fatal(err)
}

payload := inBuf.Bytes()

dec := json.NewDecoder(bytes.NewReader(payload))
err = dec.Decode(&out)
if err != nil {
b.Fatal(err)
}
}
_ = out
}
8 changes: 8 additions & 0 deletions sdk/telemetry/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

/*
Package telemetry provides a lightweight representations of OpenTelemetry
telemetry that is compatible with the OTLP JSON protobuf encoding.
*/
package telemetry
119 changes: 119 additions & 0 deletions sdk/telemetry/id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package telemetry

import (
"encoding/hex"
"errors"
"fmt"
)

const (
traceIDSize = 16
spanIDSize = 8
)

// TraceID is a custom data type that is used for all trace IDs.
type TraceID [traceIDSize]byte

// String returns the hex string representation form of a TraceID.
func (tid TraceID) String() string {
return hex.EncodeToString(tid[:])
}

// Size returns the size of the data to serialize.
func (tid TraceID) Size() int {
if tid.IsEmpty() {
return 0
}
return traceIDSize
}

// IsEmpty returns false if id contains at least one non-zero byte.
func (tid TraceID) IsEmpty() bool {
return tid == [traceIDSize]byte{}
}

// MarshalJSON converts the trace ID into a hex string enclosed in quotes.
func (tid TraceID) MarshalJSON() ([]byte, error) {
if tid.IsEmpty() {
return []byte(`""`), nil
}
return marshalJSON(tid[:])
}

// UnmarshalJSON inflates the trace ID from hex string, possibly enclosed in
// quotes.
func (tid *TraceID) UnmarshalJSON(data []byte) error {
*tid = [traceIDSize]byte{}
return unmarshalJSON(tid[:], data)
}

// SpanID is a custom data type that is used for all span IDs.
type SpanID [spanIDSize]byte

// String returns the hex string representation form of a SpanID.
func (sid SpanID) String() string {
return hex.EncodeToString(sid[:])
}

// Size returns the size of the data to serialize.
func (sid SpanID) Size() int {
if sid.IsEmpty() {
return 0
}
return spanIDSize
}

// IsEmpty returns true if the span ID contains at least one non-zero byte.
func (sid SpanID) IsEmpty() bool {
return sid == [spanIDSize]byte{}
}

// MarshalJSON converts span ID into a hex string enclosed in quotes.
func (sid SpanID) MarshalJSON() ([]byte, error) {
if sid.IsEmpty() {
return []byte(`""`), nil
}
return marshalJSON(sid[:])
}

// UnmarshalJSON decodes span ID from hex string, possibly enclosed in quotes.
func (sid *SpanID) UnmarshalJSON(data []byte) error {
*sid = [spanIDSize]byte{}
return unmarshalJSON(sid[:], data)
}

// marshalJSON converts id into a hex string enclosed in quotes.
func marshalJSON(id []byte) ([]byte, error) {
// Plus 2 quote chars at the start and end.
hexLen := hex.EncodedLen(len(id)) + 2

b := make([]byte, hexLen)
hex.Encode(b[1:hexLen-1], id)
b[0], b[hexLen-1] = '"', '"'

return b, nil
}

// unmarshalJSON inflates trace id from hex string, possibly enclosed in quotes.
func unmarshalJSON(dst []byte, src []byte) error {
if l := len(src); l >= 2 && src[0] == '"' && src[l-1] == '"' {
src = src[1 : l-1]
}
nLen := len(src)
if nLen == 0 {
return nil
}

if len(dst) != hex.DecodedLen(nLen) {
return errors.New("invalid length for ID")
}

_, err := hex.Decode(dst, src)
if err != nil {
return fmt.Errorf("cannot unmarshal ID from string '%s': %w", string(src), err)
}
return nil
}
67 changes: 67 additions & 0 deletions sdk/telemetry/number.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package telemetry

import (
"encoding/json"
"strconv"
)

// protoInt64 represents the protobuf encoding of integers which can be either
// strings or integers.
type protoInt64 int64

// Int64 returns the protoInt64 as an int64.
func (i *protoInt64) Int64() int64 { return int64(*i) }

// UnmarshalJSON decodes both strings and integers.
func (i *protoInt64) UnmarshalJSON(data []byte) error {
if data[0] == '"' {
var str string
if err := json.Unmarshal(data, &str); err != nil {
return err
}
parsedInt, err := strconv.ParseInt(str, 10, 64)
if err != nil {
return err
}
*i = protoInt64(parsedInt)
} else {
var parsedInt int64
if err := json.Unmarshal(data, &parsedInt); err != nil {
return err
}
*i = protoInt64(parsedInt)
}
return nil
}

// protoUint64 represents the protobuf encoding of integers which can be either
// strings or integers.
type protoUint64 uint64

// Int64 returns the protoUint64 as a uint64.
func (i *protoUint64) Uint64() uint64 { return uint64(*i) }

// UnmarshalJSON decodes both strings and integers.
func (i *protoUint64) UnmarshalJSON(data []byte) error {
if data[0] == '"' {
var str string
if err := json.Unmarshal(data, &str); err != nil {
return err
}
parsedUint, err := strconv.ParseUint(str, 10, 64)
if err != nil {
return err
}
*i = protoUint64(parsedUint)
} else {
var parsedUint uint64
if err := json.Unmarshal(data, &parsedUint); err != nil {
return err
}
*i = protoUint64(parsedUint)
}
return nil
}
15 changes: 15 additions & 0 deletions sdk/telemetry/resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package telemetry

// Resource information.
type Resource struct {
// Attrs are the set of attributes that describe the resource. Attribute
// keys MUST be unique (it is not allowed to have more than one attribute
// with the same key).
Attrs []Attr `json:"attributes"`
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
// DroppedAttrs is the number of dropped attributes. If the value
// is 0, then no attributes were dropped.
DroppedAttrs uint32 `json:"droppedAttributesCount,omitempty"`
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
}
12 changes: 12 additions & 0 deletions sdk/telemetry/scope.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package telemetry

// Scope is the identifying values of the instrumentation scope.
type Scope struct {
Name string `json:"name,omitempty"`
Version string `json:"version,omitempty"`
Attrs []Attr `json:"attributes"`
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
DroppedAttrs uint32 `json:"droppedAttributesCount,omitempty"`
}
Loading
Loading