Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
227
plugins/inputs/zipkin/codec/codec.go
Normal file
227
plugins/inputs/zipkin/codec/codec.go
Normal file
|
@ -0,0 +1,227 @@
|
|||
package codec
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf/plugins/inputs/zipkin/codec/thrift/gen-go/zipkincore"
|
||||
"github.com/influxdata/telegraf/plugins/inputs/zipkin/trace"
|
||||
)
|
||||
|
||||
// now is a mockable time for now
|
||||
var now = time.Now
|
||||
|
||||
// DefaultServiceName when the span does not have any serviceName
|
||||
const DefaultServiceName = "unknown"
|
||||
|
||||
// Decoder decodes the bytes and returns a trace
|
||||
type Decoder interface {
|
||||
// Decode decodes the given byte slice into a slice of Spans.
|
||||
Decode(octets []byte) ([]Span, error)
|
||||
}
|
||||
|
||||
// Span represents a span created by instrumentation in RPC clients or servers.
|
||||
type Span interface {
|
||||
// Trace returns the trace ID of the span.
|
||||
Trace() (string, error)
|
||||
// SpanID returns the span ID.
|
||||
SpanID() (string, error)
|
||||
// Parent returns the parent span ID.
|
||||
Parent() (string, error)
|
||||
// Name returns the name of the span.
|
||||
Name() string
|
||||
// Annotations returns the annotations of the span.
|
||||
Annotations() []Annotation
|
||||
// BinaryAnnotations returns the binary annotations of the span.
|
||||
BinaryAnnotations() ([]BinaryAnnotation, error)
|
||||
// Timestamp returns the timestamp of the span.
|
||||
Timestamp() time.Time
|
||||
// Duration returns the duration of the span.
|
||||
Duration() time.Duration
|
||||
}
|
||||
|
||||
// Annotation represents an event that explains latency with a timestamp.
|
||||
type Annotation interface {
|
||||
// Timestamp returns the timestamp of the annotation.
|
||||
Timestamp() time.Time
|
||||
// Value returns the value of the annotation.
|
||||
Value() string
|
||||
// Host returns the endpoint associated with the annotation.
|
||||
Host() Endpoint
|
||||
}
|
||||
|
||||
// BinaryAnnotation represents tags applied to a Span to give it context.
|
||||
type BinaryAnnotation interface {
|
||||
// Key returns the key of the binary annotation.
|
||||
Key() string
|
||||
// Value returns the value of the binary annotation.
|
||||
Value() string
|
||||
// Host returns the endpoint associated with the binary annotation.
|
||||
Host() Endpoint
|
||||
}
|
||||
|
||||
// Endpoint represents the network context of a service recording an annotation.
|
||||
type Endpoint interface {
|
||||
// Host returns the host address of the endpoint.
|
||||
Host() string
|
||||
// Name returns the name of the service associated with the endpoint.
|
||||
Name() string
|
||||
}
|
||||
|
||||
// defaultEndpoint is used if the annotations have no endpoints.
|
||||
type defaultEndpoint struct{}
|
||||
|
||||
// Host returns 0.0.0.0; used when the host is unknown.
|
||||
func (*defaultEndpoint) Host() string { return "0.0.0.0" }
|
||||
|
||||
// Name returns "unknown" when an endpoint doesn't exist.
|
||||
func (*defaultEndpoint) Name() string { return DefaultServiceName }
|
||||
|
||||
// MicroToTime converts zipkin's native time of microseconds into time.Time.
|
||||
func MicroToTime(micro int64) time.Time {
|
||||
return time.Unix(0, micro*int64(time.Microsecond)).UTC()
|
||||
}
|
||||
|
||||
// NewTrace converts a slice of Spans into a new Trace.
|
||||
func NewTrace(spans []Span) (trace.Trace, error) {
|
||||
tr := make(trace.Trace, len(spans))
|
||||
for i, span := range spans {
|
||||
bin, err := span.BinaryAnnotations()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endpoint := serviceEndpoint(span.Annotations(), bin)
|
||||
id, err := span.SpanID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tid, err := span.Trace()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pid, err := parentID(span)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tr[i] = trace.Span{
|
||||
ID: id,
|
||||
TraceID: tid,
|
||||
Name: span.Name(),
|
||||
Timestamp: guessTimestamp(span),
|
||||
Duration: convertDuration(span),
|
||||
ParentID: pid,
|
||||
ServiceName: endpoint.Name(),
|
||||
Annotations: NewAnnotations(span.Annotations(), endpoint),
|
||||
BinaryAnnotations: NewBinaryAnnotations(bin, endpoint),
|
||||
}
|
||||
}
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
// NewAnnotations converts a slice of Annotation into a slice of new Annotations
|
||||
func NewAnnotations(annotations []Annotation, endpoint Endpoint) []trace.Annotation {
|
||||
formatted := make([]trace.Annotation, 0, len(annotations))
|
||||
for _, annotation := range annotations {
|
||||
formatted = append(formatted, trace.Annotation{
|
||||
Host: endpoint.Host(),
|
||||
ServiceName: endpoint.Name(),
|
||||
Timestamp: annotation.Timestamp(),
|
||||
Value: annotation.Value(),
|
||||
})
|
||||
}
|
||||
|
||||
return formatted
|
||||
}
|
||||
|
||||
// NewBinaryAnnotations is very similar to NewAnnotations, but it
|
||||
// converts BinaryAnnotations instead of the normal Annotation
|
||||
func NewBinaryAnnotations(annotations []BinaryAnnotation, endpoint Endpoint) []trace.BinaryAnnotation {
|
||||
formatted := make([]trace.BinaryAnnotation, 0, len(annotations))
|
||||
for _, annotation := range annotations {
|
||||
formatted = append(formatted, trace.BinaryAnnotation{
|
||||
Host: endpoint.Host(),
|
||||
ServiceName: endpoint.Name(),
|
||||
Key: annotation.Key(),
|
||||
Value: annotation.Value(),
|
||||
})
|
||||
}
|
||||
return formatted
|
||||
}
|
||||
|
||||
func minMax(span Span) (low, high time.Time) {
|
||||
low = now().UTC()
|
||||
high = time.Time{}.UTC()
|
||||
for _, annotation := range span.Annotations() {
|
||||
ts := annotation.Timestamp()
|
||||
if !ts.IsZero() && ts.Before(low) {
|
||||
low = ts
|
||||
}
|
||||
if !ts.IsZero() && ts.After(high) {
|
||||
high = ts
|
||||
}
|
||||
}
|
||||
if high.IsZero() {
|
||||
high = low
|
||||
}
|
||||
return low, high
|
||||
}
|
||||
|
||||
func guessTimestamp(span Span) time.Time {
|
||||
ts := span.Timestamp()
|
||||
if !ts.IsZero() {
|
||||
return ts
|
||||
}
|
||||
|
||||
low, _ := minMax(span)
|
||||
return low
|
||||
}
|
||||
|
||||
func convertDuration(span Span) time.Duration {
|
||||
duration := span.Duration()
|
||||
if duration != 0 {
|
||||
return duration
|
||||
}
|
||||
low, high := minMax(span)
|
||||
return high.Sub(low)
|
||||
}
|
||||
|
||||
func parentID(span Span) (string, error) {
|
||||
// A parent ID of "" means that this is a parent span. In this case,
|
||||
// we set the parent ID of the span to be its own id, so it points to
|
||||
// itself.
|
||||
id, err := span.Parent()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if id != "" {
|
||||
return id, nil
|
||||
}
|
||||
return span.SpanID()
|
||||
}
|
||||
|
||||
func serviceEndpoint(ann []Annotation, bann []BinaryAnnotation) Endpoint {
|
||||
for _, a := range ann {
|
||||
switch a.Value() {
|
||||
case zipkincore.SERVER_RECV, zipkincore.SERVER_SEND, zipkincore.CLIENT_RECV, zipkincore.CLIENT_SEND:
|
||||
if a.Host() != nil && a.Host().Name() != "" {
|
||||
return a.Host()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, a := range bann {
|
||||
if a.Key() == zipkincore.LOCAL_COMPONENT && a.Host() != nil && a.Host().Name() != "" {
|
||||
return a.Host()
|
||||
}
|
||||
}
|
||||
// Unable to find any "standard" endpoint host, so, use any that exist in the regular annotations
|
||||
for _, a := range ann {
|
||||
if a.Host() != nil && a.Host().Name() != "" {
|
||||
return a.Host()
|
||||
}
|
||||
}
|
||||
return &defaultEndpoint{}
|
||||
}
|
613
plugins/inputs/zipkin/codec/codec_test.go
Normal file
613
plugins/inputs/zipkin/codec/codec_test.go
Normal file
|
@ -0,0 +1,613 @@
|
|||
package codec
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf/plugins/inputs/zipkin/trace"
|
||||
)
|
||||
|
||||
func Test_MicroToTime(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
micro int64
|
||||
want time.Time
|
||||
}{
|
||||
{
|
||||
name: "given zero micro seconds expected unix time zero",
|
||||
micro: 0,
|
||||
want: time.Unix(0, 0).UTC(),
|
||||
},
|
||||
{
|
||||
name: "given a million micro seconds expected unix time one",
|
||||
micro: 1000000,
|
||||
want: time.Unix(1, 0).UTC(),
|
||||
},
|
||||
{
|
||||
name: "given a million micro seconds expected unix time one",
|
||||
micro: 1503031538791000,
|
||||
want: time.Unix(0, 1503031538791000000).UTC(),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.want, MicroToTime(tt.micro))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_minMax(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
span *MockSpan
|
||||
now func() time.Time
|
||||
wantMin time.Time
|
||||
wantMax time.Time
|
||||
}{
|
||||
{
|
||||
name: "Single annotation",
|
||||
span: &MockSpan{
|
||||
Anno: []Annotation{
|
||||
&MockAnnotation{
|
||||
Time: time.Unix(0, 0).UTC().Add(time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantMin: time.Unix(1, 0).UTC(),
|
||||
wantMax: time.Unix(1, 0).UTC(),
|
||||
},
|
||||
{
|
||||
name: "Three annotations",
|
||||
span: &MockSpan{
|
||||
Anno: []Annotation{
|
||||
&MockAnnotation{
|
||||
Time: time.Unix(0, 0).UTC().Add(1 * time.Second),
|
||||
},
|
||||
&MockAnnotation{
|
||||
Time: time.Unix(0, 0).UTC().Add(2 * time.Second),
|
||||
},
|
||||
&MockAnnotation{
|
||||
Time: time.Unix(0, 0).UTC().Add(3 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantMin: time.Unix(1, 0).UTC(),
|
||||
wantMax: time.Unix(3, 0).UTC(),
|
||||
},
|
||||
{
|
||||
name: "Annotations are in the future",
|
||||
span: &MockSpan{
|
||||
Anno: []Annotation{
|
||||
&MockAnnotation{
|
||||
Time: time.Unix(0, 0).UTC().Add(3 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantMin: time.Unix(2, 0).UTC(),
|
||||
wantMax: time.Unix(3, 0).UTC(),
|
||||
now: func() time.Time {
|
||||
return time.Unix(2, 0).UTC()
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "No Annotations",
|
||||
span: &MockSpan{},
|
||||
wantMin: time.Unix(2, 0).UTC(),
|
||||
wantMax: time.Unix(2, 0).UTC(),
|
||||
now: func() time.Time {
|
||||
return time.Unix(2, 0).UTC()
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.now != nil {
|
||||
now = tt.now
|
||||
}
|
||||
gotMin, gotMax := minMax(tt.span)
|
||||
require.Equal(t, tt.wantMin, gotMin)
|
||||
require.Equal(t, tt.wantMax, gotMax)
|
||||
|
||||
now = time.Now
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_guessTimestamp(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
span Span
|
||||
now func() time.Time
|
||||
want time.Time
|
||||
}{
|
||||
{
|
||||
name: "simple timestamp",
|
||||
span: &MockSpan{
|
||||
Time: time.Unix(2, 0).UTC(),
|
||||
},
|
||||
want: time.Unix(2, 0).UTC(),
|
||||
},
|
||||
{
|
||||
name: "zero timestamp",
|
||||
span: &MockSpan{
|
||||
Time: time.Time{},
|
||||
},
|
||||
now: func() time.Time {
|
||||
return time.Unix(2, 0).UTC()
|
||||
},
|
||||
want: time.Unix(2, 0).UTC(),
|
||||
},
|
||||
{
|
||||
name: "zero timestamp with single annotation",
|
||||
span: &MockSpan{
|
||||
Time: time.Time{},
|
||||
Anno: []Annotation{
|
||||
&MockAnnotation{
|
||||
Time: time.Unix(0, 0).UTC(),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: time.Unix(0, 0).UTC(),
|
||||
},
|
||||
{
|
||||
name: "zero timestamp with two annotations",
|
||||
span: &MockSpan{
|
||||
Time: time.Time{},
|
||||
Anno: []Annotation{
|
||||
&MockAnnotation{
|
||||
Time: time.Unix(0, 0).UTC(),
|
||||
},
|
||||
&MockAnnotation{
|
||||
Time: time.Unix(2, 0).UTC(),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: time.Unix(0, 0).UTC(),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.now != nil {
|
||||
now = tt.now
|
||||
}
|
||||
require.Equal(t, tt.want, guessTimestamp(tt.span))
|
||||
now = time.Now
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_convertDuration(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
span Span
|
||||
want time.Duration
|
||||
}{
|
||||
{
|
||||
name: "simple duration",
|
||||
span: &MockSpan{
|
||||
Dur: time.Hour,
|
||||
},
|
||||
want: time.Hour,
|
||||
},
|
||||
{
|
||||
name: "no timestamp, but, 2 seconds between annotations",
|
||||
span: &MockSpan{
|
||||
Anno: []Annotation{
|
||||
&MockAnnotation{
|
||||
Time: time.Unix(0, 0).UTC().Add(1 * time.Second),
|
||||
},
|
||||
&MockAnnotation{
|
||||
Time: time.Unix(0, 0).UTC().Add(2 * time.Second),
|
||||
},
|
||||
&MockAnnotation{
|
||||
Time: time.Unix(0, 0).UTC().Add(3 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: 2 * time.Second,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.want, convertDuration(tt.span))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parentID(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
span Span
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "has parent id",
|
||||
span: &MockSpan{
|
||||
ParentID: "6b221d5bc9e6496c",
|
||||
},
|
||||
want: "6b221d5bc9e6496c",
|
||||
},
|
||||
{
|
||||
name: "no parent, so use id",
|
||||
span: &MockSpan{
|
||||
ID: "abceasyas123",
|
||||
},
|
||||
want: "abceasyas123",
|
||||
},
|
||||
{
|
||||
name: "bad parent value",
|
||||
span: &MockSpan{
|
||||
Error: errors.New("mommie dearest"),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parentID(tt.span)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_serviceEndpoint(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ann []Annotation
|
||||
bann []BinaryAnnotation
|
||||
want Endpoint
|
||||
}{
|
||||
{
|
||||
name: "Annotation with server receive",
|
||||
ann: []Annotation{
|
||||
&MockAnnotation{
|
||||
Val: "battery",
|
||||
H: &MockEndpoint{
|
||||
name: "aa",
|
||||
},
|
||||
},
|
||||
&MockAnnotation{
|
||||
Val: "sr",
|
||||
H: &MockEndpoint{
|
||||
name: "me",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &MockEndpoint{
|
||||
name: "me",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Annotation with no standard values",
|
||||
ann: []Annotation{
|
||||
&MockAnnotation{
|
||||
Val: "noop",
|
||||
},
|
||||
&MockAnnotation{
|
||||
Val: "aa",
|
||||
H: &MockEndpoint{
|
||||
name: "battery",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &MockEndpoint{
|
||||
name: "battery",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Annotation with no endpoints",
|
||||
ann: []Annotation{
|
||||
&MockAnnotation{
|
||||
Val: "noop",
|
||||
},
|
||||
},
|
||||
want: &defaultEndpoint{},
|
||||
},
|
||||
{
|
||||
name: "Binary annotation with local component",
|
||||
bann: []BinaryAnnotation{
|
||||
&MockBinaryAnnotation{
|
||||
K: "noop",
|
||||
H: &MockEndpoint{
|
||||
name: "aa",
|
||||
},
|
||||
},
|
||||
&MockBinaryAnnotation{
|
||||
K: "lc",
|
||||
H: &MockEndpoint{
|
||||
name: "me",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &MockEndpoint{
|
||||
name: "me",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.want, serviceEndpoint(tt.ann, tt.bann))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewBinaryAnnotations(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
annotations []BinaryAnnotation
|
||||
endpoint Endpoint
|
||||
want []trace.BinaryAnnotation
|
||||
}{
|
||||
{
|
||||
name: "Should override annotation with endpoint",
|
||||
annotations: []BinaryAnnotation{
|
||||
&MockBinaryAnnotation{
|
||||
K: "mykey",
|
||||
V: "myvalue",
|
||||
H: &MockEndpoint{
|
||||
host: "noop",
|
||||
name: "noop",
|
||||
},
|
||||
},
|
||||
},
|
||||
endpoint: &MockEndpoint{
|
||||
host: "myhost",
|
||||
name: "myservice",
|
||||
},
|
||||
want: []trace.BinaryAnnotation{
|
||||
{
|
||||
Host: "myhost",
|
||||
ServiceName: "myservice",
|
||||
Key: "mykey",
|
||||
Value: "myvalue",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.want, NewBinaryAnnotations(tt.annotations, tt.endpoint))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAnnotations(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
annotations []Annotation
|
||||
endpoint Endpoint
|
||||
want []trace.Annotation
|
||||
}{
|
||||
{
|
||||
name: "Should override annotation with endpoint",
|
||||
annotations: []Annotation{
|
||||
&MockAnnotation{
|
||||
Time: time.Unix(0, 0).UTC(),
|
||||
Val: "myvalue",
|
||||
H: &MockEndpoint{
|
||||
host: "noop",
|
||||
name: "noop",
|
||||
},
|
||||
},
|
||||
},
|
||||
endpoint: &MockEndpoint{
|
||||
host: "myhost",
|
||||
name: "myservice",
|
||||
},
|
||||
want: []trace.Annotation{
|
||||
{
|
||||
Host: "myhost",
|
||||
ServiceName: "myservice",
|
||||
Timestamp: time.Unix(0, 0).UTC(),
|
||||
Value: "myvalue",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.want, NewAnnotations(tt.annotations, tt.endpoint))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewTrace(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
spans []Span
|
||||
now func() time.Time
|
||||
want trace.Trace
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "empty span",
|
||||
spans: []Span{
|
||||
&MockSpan{},
|
||||
},
|
||||
now: func() time.Time {
|
||||
return time.Unix(0, 0).UTC()
|
||||
},
|
||||
want: trace.Trace{
|
||||
trace.Span{
|
||||
ServiceName: "unknown",
|
||||
Timestamp: time.Unix(0, 0).UTC(),
|
||||
Annotations: make([]trace.Annotation, 0),
|
||||
BinaryAnnotations: make([]trace.BinaryAnnotation, 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "span has no id",
|
||||
spans: []Span{
|
||||
&MockSpan{
|
||||
Error: errors.New("span has no id"),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "complete span",
|
||||
spans: []Span{
|
||||
&MockSpan{
|
||||
TraceID: "tid",
|
||||
ID: "id",
|
||||
ParentID: "",
|
||||
ServiceName: "me",
|
||||
Anno: []Annotation{
|
||||
&MockAnnotation{
|
||||
Time: time.Unix(1, 0).UTC(),
|
||||
Val: "myval",
|
||||
H: &MockEndpoint{
|
||||
host: "myhost",
|
||||
name: "myname",
|
||||
},
|
||||
},
|
||||
},
|
||||
Time: time.Unix(0, 0).UTC(),
|
||||
Dur: 2 * time.Second,
|
||||
},
|
||||
},
|
||||
now: func() time.Time {
|
||||
return time.Unix(0, 0).UTC()
|
||||
},
|
||||
want: trace.Trace{
|
||||
trace.Span{
|
||||
ID: "id",
|
||||
ParentID: "id",
|
||||
TraceID: "tid",
|
||||
Name: "me",
|
||||
ServiceName: "myname",
|
||||
Timestamp: time.Unix(0, 0).UTC(),
|
||||
Duration: 2 * time.Second,
|
||||
Annotations: []trace.Annotation{
|
||||
{
|
||||
Timestamp: time.Unix(1, 0).UTC(),
|
||||
Value: "myval",
|
||||
Host: "myhost",
|
||||
ServiceName: "myname",
|
||||
},
|
||||
},
|
||||
BinaryAnnotations: make([]trace.BinaryAnnotation, 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.now != nil {
|
||||
now = tt.now
|
||||
}
|
||||
got, err := NewTrace(tt.spans)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, tt.want, got)
|
||||
now = time.Now
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type MockSpan struct {
|
||||
TraceID string
|
||||
ID string
|
||||
ParentID string
|
||||
ServiceName string
|
||||
Anno []Annotation
|
||||
BinAnno []BinaryAnnotation
|
||||
Time time.Time
|
||||
Dur time.Duration
|
||||
Error error
|
||||
}
|
||||
|
||||
func (m *MockSpan) Trace() (string, error) {
|
||||
return m.TraceID, m.Error
|
||||
}
|
||||
|
||||
func (m *MockSpan) SpanID() (string, error) {
|
||||
return m.ID, m.Error
|
||||
}
|
||||
|
||||
func (m *MockSpan) Parent() (string, error) {
|
||||
return m.ParentID, m.Error
|
||||
}
|
||||
|
||||
func (m *MockSpan) Name() string {
|
||||
return m.ServiceName
|
||||
}
|
||||
|
||||
func (m *MockSpan) Annotations() []Annotation {
|
||||
return m.Anno
|
||||
}
|
||||
|
||||
func (m *MockSpan) BinaryAnnotations() ([]BinaryAnnotation, error) {
|
||||
return m.BinAnno, m.Error
|
||||
}
|
||||
|
||||
func (m *MockSpan) Timestamp() time.Time {
|
||||
return m.Time
|
||||
}
|
||||
|
||||
func (m *MockSpan) Duration() time.Duration {
|
||||
return m.Dur
|
||||
}
|
||||
|
||||
type MockAnnotation struct {
|
||||
Time time.Time
|
||||
Val string
|
||||
H Endpoint
|
||||
}
|
||||
|
||||
func (m *MockAnnotation) Timestamp() time.Time {
|
||||
return m.Time
|
||||
}
|
||||
|
||||
func (m *MockAnnotation) Value() string {
|
||||
return m.Val
|
||||
}
|
||||
|
||||
func (m *MockAnnotation) Host() Endpoint {
|
||||
return m.H
|
||||
}
|
||||
|
||||
type MockEndpoint struct {
|
||||
host string
|
||||
name string
|
||||
}
|
||||
|
||||
func (e *MockEndpoint) Host() string {
|
||||
return e.host
|
||||
}
|
||||
|
||||
func (e *MockEndpoint) Name() string {
|
||||
return e.name
|
||||
}
|
||||
|
||||
type MockBinaryAnnotation struct {
|
||||
Time time.Time
|
||||
K string
|
||||
V string
|
||||
H Endpoint
|
||||
}
|
||||
|
||||
func (b *MockBinaryAnnotation) Key() string {
|
||||
return b.K
|
||||
}
|
||||
|
||||
func (b *MockBinaryAnnotation) Value() string {
|
||||
return b.V
|
||||
}
|
||||
|
||||
func (b *MockBinaryAnnotation) Host() Endpoint {
|
||||
return b.H
|
||||
}
|
271
plugins/inputs/zipkin/codec/jsonV1/jsonV1.go
Normal file
271
plugins/inputs/zipkin/codec/jsonV1/jsonV1.go
Normal file
|
@ -0,0 +1,271 @@
|
|||
package json_v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf/plugins/inputs/zipkin/codec"
|
||||
"github.com/influxdata/telegraf/plugins/inputs/zipkin/codec/thrift/gen-go/zipkincore"
|
||||
)
|
||||
|
||||
// JSON decodes spans from bodies `POST`ed to the spans endpoint
|
||||
type JSON struct{}
|
||||
|
||||
// Decode unmarshals and validates the JSON body
|
||||
func (*JSON) Decode(octets []byte) ([]codec.Span, error) {
|
||||
var spans []span
|
||||
err := json.Unmarshal(octets, &spans)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := make([]codec.Span, 0, len(spans))
|
||||
for i := range spans {
|
||||
if err := spans[i].validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, &spans[i])
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
type span struct {
|
||||
TraceID string `json:"traceId"`
|
||||
SpanName string `json:"name"`
|
||||
ParentID string `json:"parentId,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Time *int64 `json:"timestamp,omitempty"`
|
||||
Dur *int64 `json:"duration,omitempty"`
|
||||
Debug bool `json:"debug,omitempty"`
|
||||
Anno []annotation `json:"annotations"`
|
||||
BAnno []binaryAnnotation `json:"binaryAnnotations"`
|
||||
}
|
||||
|
||||
func (s *span) validate() error {
|
||||
var err error
|
||||
check := func(f func() (string, error)) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = f()
|
||||
}
|
||||
|
||||
check(s.Trace)
|
||||
check(s.SpanID)
|
||||
check(s.Parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = s.BinaryAnnotations()
|
||||
return err
|
||||
}
|
||||
|
||||
// Trace returns the trace ID of the span and an error if the trace ID is empty.
|
||||
func (s *span) Trace() (string, error) {
|
||||
if s.TraceID == "" {
|
||||
return "", errors.New("trace ID cannot be null")
|
||||
}
|
||||
return traceIDFromString(s.TraceID)
|
||||
}
|
||||
|
||||
// SpanID returns the span ID of the span and returns an error if the span ID is empty.
|
||||
func (s *span) SpanID() (string, error) {
|
||||
if s.ID == "" {
|
||||
return "", errors.New("span ID cannot be null")
|
||||
}
|
||||
return idFromString(s.ID)
|
||||
}
|
||||
|
||||
// Parent returns the parent span ID of the span.
|
||||
func (s *span) Parent() (string, error) {
|
||||
if s.ParentID == "" {
|
||||
return "", nil
|
||||
}
|
||||
return idFromString(s.ParentID)
|
||||
}
|
||||
|
||||
// Name returns the name of the span.
|
||||
func (s *span) Name() string {
|
||||
return s.SpanName
|
||||
}
|
||||
|
||||
// Annotations returns the annotations of the span as a slice of codec.Annotation.
|
||||
func (s *span) Annotations() []codec.Annotation {
|
||||
res := make([]codec.Annotation, 0, len(s.Anno))
|
||||
for i := range s.Anno {
|
||||
res = append(res, &s.Anno[i])
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// BinaryAnnotations returns the binary annotations of the span as a slice of codec.BinaryAnnotation.
|
||||
func (s *span) BinaryAnnotations() ([]codec.BinaryAnnotation, error) {
|
||||
res := make([]codec.BinaryAnnotation, 0, len(s.BAnno))
|
||||
for i, a := range s.BAnno {
|
||||
if a.Key() != "" && a.Value() == "" {
|
||||
return nil, fmt.Errorf("no value for key %s at binaryAnnotations[%d]", a.K, i)
|
||||
}
|
||||
if a.Value() != "" && a.Key() == "" {
|
||||
return nil, fmt.Errorf("no key at binaryAnnotations[%d]", i)
|
||||
}
|
||||
res = append(res, &s.BAnno[i])
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Timestamp returns the timestamp of the span as a time.Time object.
|
||||
// It returns a zero time if the timestamp is not set.
|
||||
func (s *span) Timestamp() time.Time {
|
||||
if s.Time == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return codec.MicroToTime(*s.Time)
|
||||
}
|
||||
|
||||
// Duration returns the duration of the span as a time.Duration object.
|
||||
// It returns zero if the duration is not set.
|
||||
func (s *span) Duration() time.Duration {
|
||||
if s.Dur == nil {
|
||||
return 0
|
||||
}
|
||||
return time.Duration(*s.Dur) * time.Microsecond
|
||||
}
|
||||
|
||||
type annotation struct {
|
||||
Endpoint *endpoint `json:"endpoint,omitempty"`
|
||||
Time int64 `json:"timestamp"`
|
||||
Val string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// Timestamp returns the timestamp of the annotation as a time.Time object.
|
||||
func (a *annotation) Timestamp() time.Time {
|
||||
return codec.MicroToTime(a.Time)
|
||||
}
|
||||
|
||||
// Value returns the value of the annotation as a string.
|
||||
func (a *annotation) Value() string {
|
||||
return a.Val
|
||||
}
|
||||
|
||||
// Host returns the endpoint associated with the annotation as a codec.Endpoint.
|
||||
func (a *annotation) Host() codec.Endpoint {
|
||||
return a.Endpoint
|
||||
}
|
||||
|
||||
type binaryAnnotation struct {
|
||||
K string `json:"key"`
|
||||
V json.RawMessage `json:"value"`
|
||||
Type string `json:"type"`
|
||||
Endpoint *endpoint `json:"endpoint,omitempty"`
|
||||
}
|
||||
|
||||
// Key returns the key of the binary annotation as a string.
|
||||
func (b *binaryAnnotation) Key() string {
|
||||
return b.K
|
||||
}
|
||||
|
||||
// Value returns the value of the binary annotation as a string.
|
||||
func (b *binaryAnnotation) Value() string {
|
||||
t, err := zipkincore.AnnotationTypeFromString(b.Type)
|
||||
// Assume this is a string if we cannot tell the type
|
||||
if err != nil {
|
||||
t = zipkincore.AnnotationType_STRING
|
||||
}
|
||||
|
||||
switch t {
|
||||
case zipkincore.AnnotationType_BOOL:
|
||||
var v bool
|
||||
err := json.Unmarshal(b.V, &v)
|
||||
if err == nil {
|
||||
return strconv.FormatBool(v)
|
||||
}
|
||||
case zipkincore.AnnotationType_BYTES:
|
||||
return string(b.V)
|
||||
case zipkincore.AnnotationType_I16, zipkincore.AnnotationType_I32, zipkincore.AnnotationType_I64:
|
||||
var v int64
|
||||
err := json.Unmarshal(b.V, &v)
|
||||
if err == nil {
|
||||
return strconv.FormatInt(v, 10)
|
||||
}
|
||||
case zipkincore.AnnotationType_DOUBLE:
|
||||
var v float64
|
||||
err := json.Unmarshal(b.V, &v)
|
||||
if err == nil {
|
||||
return strconv.FormatFloat(v, 'f', -1, 64)
|
||||
}
|
||||
case zipkincore.AnnotationType_STRING:
|
||||
var v string
|
||||
err := json.Unmarshal(b.V, &v)
|
||||
if err == nil {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Host returns the endpoint associated with the binary annotation as a codec.Endpoint.
|
||||
func (b *binaryAnnotation) Host() codec.Endpoint {
|
||||
return b.Endpoint
|
||||
}
|
||||
|
||||
type endpoint struct {
|
||||
ServiceName string `json:"serviceName"`
|
||||
Ipv4 string `json:"ipv4"`
|
||||
Ipv6 string `json:"ipv6,omitempty"`
|
||||
Port int `json:"port"`
|
||||
}
|
||||
|
||||
// Host returns the host of the endpoint as a string.
|
||||
func (e *endpoint) Host() string {
|
||||
if e.Port != 0 {
|
||||
return fmt.Sprintf("%s:%d", e.Ipv4, e.Port)
|
||||
}
|
||||
return e.Ipv4
|
||||
}
|
||||
|
||||
// Name returns the service name of the endpoint.
|
||||
func (e *endpoint) Name() string {
|
||||
return e.ServiceName
|
||||
}
|
||||
|
||||
// traceIDFromString creates a TraceID from a hexadecimal string
|
||||
func traceIDFromString(s string) (string, error) {
|
||||
var hi, lo uint64
|
||||
var err error
|
||||
if len(s) > 32 {
|
||||
return "", fmt.Errorf("length of TraceID cannot be greater than 32 hex characters: %s", s)
|
||||
} else if len(s) > 16 {
|
||||
hiLen := len(s) - 16
|
||||
if hi, err = strconv.ParseUint(s[0:hiLen], 16, 64); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if lo, err = strconv.ParseUint(s[hiLen:], 16, 64); err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
if lo, err = strconv.ParseUint(s, 16, 64); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if hi == 0 {
|
||||
return strconv.FormatUint(lo, 16), nil
|
||||
}
|
||||
return fmt.Sprintf("%x%016x", hi, lo), nil
|
||||
}
|
||||
|
||||
// idFromString validates the ID and returns it in hexadecimal format.
|
||||
func idFromString(s string) (string, error) {
|
||||
if len(s) > 16 {
|
||||
return "", fmt.Errorf("length of ID cannot be greater than 16 hex characters: %s", s)
|
||||
}
|
||||
id, err := strconv.ParseUint(s, 16, 64)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strconv.FormatUint(id, 16), nil
|
||||
}
|
894
plugins/inputs/zipkin/codec/jsonV1/jsonV1_test.go
Normal file
894
plugins/inputs/zipkin/codec/jsonV1/jsonV1_test.go
Normal file
|
@ -0,0 +1,894 @@
|
|||
package json_v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf/plugins/inputs/zipkin/codec"
|
||||
)
|
||||
|
||||
func TestJSON_Decode(t *testing.T) {
|
||||
addr := func(i int64) *int64 { return &i }
|
||||
tests := []struct {
|
||||
name string
|
||||
octets []byte
|
||||
want []codec.Span
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "bad json is error",
|
||||
octets: []byte(`
|
||||
[
|
||||
{
|
||||
]`),
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Decodes simple trace",
|
||||
octets: []byte(`
|
||||
[
|
||||
{
|
||||
"traceId": "6b221d5bc9e6496c",
|
||||
"name": "get-traces",
|
||||
"id": "6b221d5bc9e6496c"
|
||||
}
|
||||
]`),
|
||||
want: []codec.Span{
|
||||
&span{
|
||||
TraceID: "6b221d5bc9e6496c",
|
||||
SpanName: "get-traces",
|
||||
ID: "6b221d5bc9e6496c",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Decodes two spans",
|
||||
octets: []byte(`
|
||||
[
|
||||
{
|
||||
"traceId": "6b221d5bc9e6496c",
|
||||
"name": "get-traces",
|
||||
"id": "6b221d5bc9e6496c"
|
||||
},
|
||||
{
|
||||
"traceId": "6b221d5bc9e6496c",
|
||||
"name": "get-traces",
|
||||
"id": "c6946e9cb5d122b6",
|
||||
"parentId": "6b221d5bc9e6496c",
|
||||
"duration": 10000
|
||||
}
|
||||
]`),
|
||||
want: []codec.Span{
|
||||
&span{
|
||||
TraceID: "6b221d5bc9e6496c",
|
||||
SpanName: "get-traces",
|
||||
ID: "6b221d5bc9e6496c",
|
||||
},
|
||||
&span{
|
||||
TraceID: "6b221d5bc9e6496c",
|
||||
SpanName: "get-traces",
|
||||
ID: "c6946e9cb5d122b6",
|
||||
ParentID: "6b221d5bc9e6496c",
|
||||
Dur: addr(10000),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Decodes trace with timestamp",
|
||||
octets: []byte(`
|
||||
[
|
||||
{
|
||||
"traceId": "6b221d5bc9e6496c",
|
||||
"name": "get-traces",
|
||||
"id": "6b221d5bc9e6496c",
|
||||
"timestamp": 1503031538791000
|
||||
}
|
||||
]`),
|
||||
want: []codec.Span{
|
||||
&span{
|
||||
TraceID: "6b221d5bc9e6496c",
|
||||
SpanName: "get-traces",
|
||||
ID: "6b221d5bc9e6496c",
|
||||
Time: addr(1503031538791000),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Decodes simple trace with high and low trace id",
|
||||
octets: []byte(`
|
||||
[
|
||||
{
|
||||
"traceId": "48485a3953bb61246b221d5bc9e6496c",
|
||||
"name": "get-traces",
|
||||
"id": "6b221d5bc9e6496c"
|
||||
}
|
||||
]`),
|
||||
want: []codec.Span{
|
||||
&span{
|
||||
TraceID: "48485a3953bb61246b221d5bc9e6496c",
|
||||
SpanName: "get-traces",
|
||||
ID: "6b221d5bc9e6496c",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Error when trace id is null",
|
||||
octets: []byte(`
|
||||
[
|
||||
{
|
||||
"traceId": null,
|
||||
"name": "get-traces",
|
||||
"id": "6b221d5bc9e6496c"
|
||||
}
|
||||
]`),
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "ignore null parentId",
|
||||
octets: []byte(`
|
||||
[
|
||||
{
|
||||
"traceId": "48485a3953bb61246b221d5bc9e6496c",
|
||||
"name": "get-traces",
|
||||
"id": "6b221d5bc9e6496c",
|
||||
"parentId": null
|
||||
}
|
||||
]`),
|
||||
want: []codec.Span{
|
||||
&span{
|
||||
TraceID: "48485a3953bb61246b221d5bc9e6496c",
|
||||
SpanName: "get-traces",
|
||||
ID: "6b221d5bc9e6496c",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ignore null timestamp",
|
||||
octets: []byte(`
|
||||
[
|
||||
{
|
||||
"traceId": "48485a3953bb61246b221d5bc9e6496c",
|
||||
"name": "get-traces",
|
||||
"id": "6b221d5bc9e6496c",
|
||||
"timestamp": null
|
||||
}
|
||||
]`),
|
||||
want: []codec.Span{
|
||||
&span{
|
||||
TraceID: "48485a3953bb61246b221d5bc9e6496c",
|
||||
SpanName: "get-traces",
|
||||
ID: "6b221d5bc9e6496c",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ignore null duration",
|
||||
octets: []byte(`
|
||||
[
|
||||
{
|
||||
"traceId": "48485a3953bb61246b221d5bc9e6496c",
|
||||
"name": "get-traces",
|
||||
"id": "6b221d5bc9e6496c",
|
||||
"duration": null
|
||||
}
|
||||
]`),
|
||||
want: []codec.Span{
|
||||
&span{
|
||||
TraceID: "48485a3953bb61246b221d5bc9e6496c",
|
||||
SpanName: "get-traces",
|
||||
ID: "6b221d5bc9e6496c",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ignore null annotation endpoint",
|
||||
octets: []byte(`
|
||||
[
|
||||
{
|
||||
"traceId": "48485a3953bb61246b221d5bc9e6496c",
|
||||
"name": "get-traces",
|
||||
"id": "6b221d5bc9e6496c",
|
||||
"annotations": [
|
||||
{
|
||||
"timestamp": 1461750491274000,
|
||||
"value": "cs",
|
||||
"endpoint": null
|
||||
}
|
||||
]
|
||||
}
|
||||
]`),
|
||||
want: []codec.Span{
|
||||
&span{
|
||||
TraceID: "48485a3953bb61246b221d5bc9e6496c",
|
||||
SpanName: "get-traces",
|
||||
ID: "6b221d5bc9e6496c",
|
||||
Anno: []annotation{
|
||||
{
|
||||
Time: 1461750491274000,
|
||||
Val: "cs",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ignore null binary annotation endpoint",
|
||||
octets: []byte(`
|
||||
[
|
||||
{
|
||||
"traceId": "48485a3953bb61246b221d5bc9e6496c",
|
||||
"name": "get-traces",
|
||||
"id": "6b221d5bc9e6496c",
|
||||
"binaryAnnotations": [
|
||||
{
|
||||
"key": "lc",
|
||||
"value": "JDBCSpanStore",
|
||||
"endpoint": null
|
||||
}
|
||||
]
|
||||
}
|
||||
]`),
|
||||
want: []codec.Span{
|
||||
&span{
|
||||
TraceID: "48485a3953bb61246b221d5bc9e6496c",
|
||||
SpanName: "get-traces",
|
||||
ID: "6b221d5bc9e6496c",
|
||||
BAnno: []binaryAnnotation{
|
||||
{
|
||||
K: "lc",
|
||||
V: json.RawMessage(`"JDBCSpanStore"`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Error when binary annotation has no key",
|
||||
octets: []byte(`
|
||||
[
|
||||
{
|
||||
"traceId": "48485a3953bb61246b221d5bc9e6496c",
|
||||
"name": "get-traces",
|
||||
"id": "6b221d5bc9e6496c",
|
||||
"binaryAnnotations": [
|
||||
{
|
||||
"value": "JDBCSpanStore",
|
||||
"endpoint": null
|
||||
}
|
||||
]
|
||||
}
|
||||
]`),
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Error when binary annotation has no value",
|
||||
octets: []byte(`
|
||||
[
|
||||
{
|
||||
"traceId": "48485a3953bb61246b221d5bc9e6496c",
|
||||
"name": "get-traces",
|
||||
"id": "6b221d5bc9e6496c",
|
||||
"binaryAnnotations": [
|
||||
{
|
||||
"key": "lc",
|
||||
"endpoint": null
|
||||
}
|
||||
]
|
||||
}
|
||||
]`),
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "binary annotation with endpoint",
|
||||
octets: []byte(`
|
||||
[
|
||||
{
|
||||
"traceId": "48485a3953bb61246b221d5bc9e6496c",
|
||||
"name": "get-traces",
|
||||
"id": "6b221d5bc9e6496c",
|
||||
"binaryAnnotations": [
|
||||
{
|
||||
"key": "lc",
|
||||
"value": "JDBCSpanStore",
|
||||
"endpoint": {
|
||||
"serviceName": "service",
|
||||
"port": 65535
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]`),
|
||||
want: []codec.Span{
|
||||
&span{
|
||||
TraceID: "48485a3953bb61246b221d5bc9e6496c",
|
||||
SpanName: "get-traces",
|
||||
ID: "6b221d5bc9e6496c",
|
||||
BAnno: []binaryAnnotation{
|
||||
{
|
||||
K: "lc",
|
||||
V: json.RawMessage(`"JDBCSpanStore"`),
|
||||
Endpoint: &endpoint{
|
||||
ServiceName: "service",
|
||||
Port: 65535,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "binary annotation with double value",
|
||||
octets: []byte(`
|
||||
[
|
||||
{
|
||||
"traceId": "48485a3953bb61246b221d5bc9e6496c",
|
||||
"name": "get-traces",
|
||||
"id": "6b221d5bc9e6496c",
|
||||
"binaryAnnotations": [
|
||||
{
|
||||
"key": "num",
|
||||
"value": 1.23456789,
|
||||
"type": "DOUBLE"
|
||||
}
|
||||
]
|
||||
}
|
||||
]`),
|
||||
want: []codec.Span{
|
||||
&span{
|
||||
TraceID: "48485a3953bb61246b221d5bc9e6496c",
|
||||
SpanName: "get-traces",
|
||||
ID: "6b221d5bc9e6496c",
|
||||
BAnno: []binaryAnnotation{
|
||||
{
|
||||
K: "num",
|
||||
V: json.RawMessage{0x31, 0x2e, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39},
|
||||
Type: "DOUBLE",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "binary annotation with integer value",
|
||||
octets: []byte(`
|
||||
[
|
||||
{
|
||||
"traceId": "48485a3953bb61246b221d5bc9e6496c",
|
||||
"name": "get-traces",
|
||||
"id": "6b221d5bc9e6496c",
|
||||
"binaryAnnotations": [
|
||||
{
|
||||
"key": "num",
|
||||
"value": 1,
|
||||
"type": "I16"
|
||||
}
|
||||
]
|
||||
}
|
||||
]`),
|
||||
want: []codec.Span{
|
||||
&span{
|
||||
TraceID: "48485a3953bb61246b221d5bc9e6496c",
|
||||
SpanName: "get-traces",
|
||||
ID: "6b221d5bc9e6496c",
|
||||
BAnno: []binaryAnnotation{
|
||||
{
|
||||
K: "num",
|
||||
V: json.RawMessage{0x31},
|
||||
Type: "I16",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "binary annotation with bool value",
|
||||
octets: []byte(`
|
||||
[
|
||||
{
|
||||
"traceId": "48485a3953bb61246b221d5bc9e6496c",
|
||||
"name": "get-traces",
|
||||
"id": "6b221d5bc9e6496c",
|
||||
"binaryAnnotations": [
|
||||
{
|
||||
"key": "num",
|
||||
"value": true,
|
||||
"type": "BOOL"
|
||||
}
|
||||
]
|
||||
}
|
||||
]`),
|
||||
want: []codec.Span{
|
||||
&span{
|
||||
TraceID: "48485a3953bb61246b221d5bc9e6496c",
|
||||
SpanName: "get-traces",
|
||||
ID: "6b221d5bc9e6496c",
|
||||
BAnno: []binaryAnnotation{
|
||||
{
|
||||
K: "num",
|
||||
V: json.RawMessage(`true`),
|
||||
Type: "BOOL",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "binary annotation with bytes value",
|
||||
octets: []byte(`
|
||||
[
|
||||
{
|
||||
"traceId": "48485a3953bb61246b221d5bc9e6496c",
|
||||
"name": "get-traces",
|
||||
"id": "6b221d5bc9e6496c",
|
||||
"binaryAnnotations": [
|
||||
{
|
||||
"key": "num",
|
||||
"value": "1",
|
||||
"type": "BYTES"
|
||||
}
|
||||
]
|
||||
}
|
||||
]`),
|
||||
want: []codec.Span{
|
||||
&span{
|
||||
TraceID: "48485a3953bb61246b221d5bc9e6496c",
|
||||
SpanName: "get-traces",
|
||||
ID: "6b221d5bc9e6496c",
|
||||
BAnno: []binaryAnnotation{
|
||||
{
|
||||
K: "num",
|
||||
V: json.RawMessage(`"1"`),
|
||||
Type: "BYTES",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
j := &JSON{}
|
||||
got, err := j.Decode(tt.octets)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_span_Trace(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
TraceID string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Trace IDs cannot be null",
|
||||
TraceID: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "converts hex string correctly",
|
||||
TraceID: "deadbeef",
|
||||
want: "deadbeef",
|
||||
},
|
||||
{
|
||||
name: "converts high and low trace id correctly",
|
||||
TraceID: "48485a3953bb61246b221d5bc9e6496c",
|
||||
want: "48485a3953bb61246b221d5bc9e6496c",
|
||||
},
|
||||
{
|
||||
name: "errors when string isn't hex",
|
||||
TraceID: "oxdeadbeef",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "errors when id is too long",
|
||||
TraceID: "1234567890abcdef1234567890abcdef1",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &span{
|
||||
TraceID: tt.TraceID,
|
||||
}
|
||||
got, err := s.Trace()
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_span_SpanID(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ID string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Span IDs cannot be null",
|
||||
ID: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "validates known id correctly",
|
||||
ID: "b26412d1ac16767d",
|
||||
want: "b26412d1ac16767d",
|
||||
},
|
||||
{
|
||||
name: "validates hex string correctly",
|
||||
ID: "deadbeef",
|
||||
want: "deadbeef",
|
||||
},
|
||||
{
|
||||
name: "errors when string isn't hex",
|
||||
ID: "oxdeadbeef",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "errors when id is too long",
|
||||
ID: "1234567890abcdef1",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &span{
|
||||
ID: tt.ID,
|
||||
}
|
||||
got, err := s.SpanID()
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_span_Parent(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ParentID string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "when there is no parent return empty string",
|
||||
ParentID: "",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "validates hex string correctly",
|
||||
ParentID: "deadbeef",
|
||||
want: "deadbeef",
|
||||
},
|
||||
{
|
||||
name: "errors when string isn't hex",
|
||||
ParentID: "oxdeadbeef",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "errors when parent id is too long",
|
||||
ParentID: "1234567890abcdef1",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &span{
|
||||
ParentID: tt.ParentID,
|
||||
}
|
||||
got, err := s.Parent()
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_span_Timestamp(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
Time *int64
|
||||
want time.Time
|
||||
}{
|
||||
{
|
||||
name: "converts to microseconds",
|
||||
Time: func(i int64) *int64 { return &i }(3000000),
|
||||
want: time.Unix(3, 0).UTC(),
|
||||
},
|
||||
{
|
||||
name: "nil time should be zero time",
|
||||
Time: nil,
|
||||
want: time.Time{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &span{
|
||||
Time: tt.Time,
|
||||
}
|
||||
require.Equal(t, tt.want, s.Timestamp())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_span_Duration(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dur *int64
|
||||
want time.Duration
|
||||
}{
|
||||
{
|
||||
name: "converts from 3 microseconds",
|
||||
dur: func(i int64) *int64 { return &i }(3000000),
|
||||
want: 3 * time.Second,
|
||||
},
|
||||
{
|
||||
name: "nil time should be zero duration",
|
||||
dur: nil,
|
||||
want: 0,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &span{
|
||||
Dur: tt.dur,
|
||||
}
|
||||
require.Equal(t, tt.want, s.Duration())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_annotation(t *testing.T) {
|
||||
type fields struct {
|
||||
Endpoint *endpoint
|
||||
Time int64
|
||||
Val string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
tm time.Time
|
||||
val string
|
||||
endpoint *endpoint
|
||||
}{
|
||||
{
|
||||
name: "returns all fields",
|
||||
fields: fields{
|
||||
Time: 3000000,
|
||||
Val: "myvalue",
|
||||
Endpoint: &endpoint{
|
||||
ServiceName: "myservice",
|
||||
Ipv4: "127.0.0.1",
|
||||
Port: 443,
|
||||
},
|
||||
},
|
||||
tm: time.Unix(3, 0).UTC(),
|
||||
val: "myvalue",
|
||||
endpoint: &endpoint{
|
||||
ServiceName: "myservice",
|
||||
Ipv4: "127.0.0.1",
|
||||
Port: 443,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
an := annotation(tt.fields)
|
||||
a := &an
|
||||
require.Equal(t, tt.tm, a.Timestamp())
|
||||
require.Equal(t, tt.val, a.Value())
|
||||
require.Equal(t, tt.endpoint, a.Host())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_binaryAnnotation(t *testing.T) {
|
||||
type fields struct {
|
||||
K string
|
||||
V json.RawMessage
|
||||
Type string
|
||||
Endpoint *endpoint
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
key string
|
||||
value string
|
||||
endpoint *endpoint
|
||||
}{
|
||||
{
|
||||
name: "returns all fields",
|
||||
fields: fields{
|
||||
K: "key",
|
||||
V: json.RawMessage(`"value"`),
|
||||
Endpoint: &endpoint{
|
||||
ServiceName: "myservice",
|
||||
Ipv4: "127.0.0.1",
|
||||
Port: 443,
|
||||
},
|
||||
},
|
||||
key: "key",
|
||||
value: "value",
|
||||
endpoint: &endpoint{
|
||||
ServiceName: "myservice",
|
||||
Ipv4: "127.0.0.1",
|
||||
Port: 443,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
bin := binaryAnnotation(tt.fields)
|
||||
b := &bin
|
||||
require.Equal(t, tt.key, b.Key())
|
||||
require.Equal(t, tt.value, b.Value())
|
||||
require.Equal(t, tt.endpoint, b.Host())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_endpoint_Host(t *testing.T) {
|
||||
type fields struct {
|
||||
Ipv4 string
|
||||
Port int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "with port",
|
||||
fields: fields{
|
||||
Ipv4: "127.0.0.1",
|
||||
Port: 443,
|
||||
},
|
||||
want: "127.0.0.1:443",
|
||||
},
|
||||
{
|
||||
name: "no port",
|
||||
fields: fields{
|
||||
Ipv4: "127.0.0.1",
|
||||
},
|
||||
want: "127.0.0.1",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
e := &endpoint{
|
||||
Ipv4: tt.fields.Ipv4,
|
||||
Port: tt.fields.Port,
|
||||
}
|
||||
require.Equal(t, tt.want, e.Host())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_endpoint_Name(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ServiceName string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "has service name",
|
||||
ServiceName: "myservicename",
|
||||
want: "myservicename",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
e := &endpoint{
|
||||
ServiceName: tt.ServiceName,
|
||||
}
|
||||
require.Equal(t, tt.want, e.Name())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceIDFromString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
s string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Convert hex string id",
|
||||
s: "6b221d5bc9e6496c",
|
||||
want: "6b221d5bc9e6496c",
|
||||
},
|
||||
{
|
||||
name: "error : id too long",
|
||||
s: "1234567890abcdef1234567890abcdef1",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "error : not parsable",
|
||||
s: "howdyhowdyhowdy",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Convert hex string with high/low",
|
||||
s: "48485a3953bb61246b221d5bc9e6496c",
|
||||
want: "48485a3953bb61246b221d5bc9e6496c",
|
||||
},
|
||||
{
|
||||
name: "errors in high",
|
||||
s: "ERR85a3953bb61246b221d5bc9e6496c",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "errors in low",
|
||||
s: "48485a3953bb61246b221d5bc9e64ERR",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := traceIDFromString(tt.s)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIDFromString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
s string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "validates hex string id",
|
||||
s: "6b221d5bc9e6496c",
|
||||
want: "6b221d5bc9e6496c",
|
||||
},
|
||||
{
|
||||
name: "error : id too long",
|
||||
s: "1234567890abcdef1",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "error : not parsable",
|
||||
s: "howdyhowdyhowdy",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := idFromString(tt.s)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
// Code generated by Thrift Compiler (0.14.2). DO NOT EDIT.
|
||||
|
||||
package zipkincore
|
||||
|
||||
var GoUnusedProtection__ int
|
|
@ -0,0 +1,48 @@
|
|||
// Code generated by Thrift Compiler (0.14.2). DO NOT EDIT.
|
||||
|
||||
package zipkincore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/apache/thrift/lib/go/thrift"
|
||||
)
|
||||
|
||||
// (needed to ensure safety because of naive import list construction.)
|
||||
var _ = thrift.ZERO
|
||||
var _ = fmt.Printf
|
||||
var _ = context.Background
|
||||
var _ = time.Now
|
||||
var _ = bytes.Equal
|
||||
|
||||
const CLIENT_SEND = "cs"
|
||||
const CLIENT_RECV = "cr"
|
||||
const SERVER_SEND = "ss"
|
||||
const SERVER_RECV = "sr"
|
||||
const MESSAGE_SEND = "ms"
|
||||
const MESSAGE_RECV = "mr"
|
||||
const WIRE_SEND = "ws"
|
||||
const WIRE_RECV = "wr"
|
||||
const CLIENT_SEND_FRAGMENT = "csf"
|
||||
const CLIENT_RECV_FRAGMENT = "crf"
|
||||
const SERVER_SEND_FRAGMENT = "ssf"
|
||||
const SERVER_RECV_FRAGMENT = "srf"
|
||||
const HTTP_HOST = "http.host"
|
||||
const HTTP_METHOD = "http.method"
|
||||
const HTTP_PATH = "http.path"
|
||||
const HTTP_ROUTE = "http.route"
|
||||
const HTTP_URL = "http.url"
|
||||
const HTTP_STATUS_CODE = "http.status_code"
|
||||
const HTTP_REQUEST_SIZE = "http.request.size"
|
||||
const HTTP_RESPONSE_SIZE = "http.response.size"
|
||||
const LOCAL_COMPONENT = "lc"
|
||||
const ERROR = "error"
|
||||
const CLIENT_ADDR = "ca"
|
||||
const SERVER_ADDR = "sa"
|
||||
const MESSAGE_ADDR = "ma"
|
||||
|
||||
func init() {
|
||||
}
|
1564
plugins/inputs/zipkin/codec/thrift/gen-go/zipkincore/zipkinCore.go
Normal file
1564
plugins/inputs/zipkin/codec/thrift/gen-go/zipkincore/zipkinCore.go
Normal file
File diff suppressed because it is too large
Load diff
220
plugins/inputs/zipkin/codec/thrift/thrift.go
Normal file
220
plugins/inputs/zipkin/codec/thrift/thrift.go
Normal file
|
@ -0,0 +1,220 @@
|
|||
package thrift
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/apache/thrift/lib/go/thrift"
|
||||
|
||||
"github.com/influxdata/telegraf/plugins/inputs/zipkin/codec"
|
||||
"github.com/influxdata/telegraf/plugins/inputs/zipkin/codec/thrift/gen-go/zipkincore"
|
||||
)
|
||||
|
||||
// Thrift decodes binary data to create a Trace
|
||||
type Thrift struct{}
|
||||
|
||||
// Decode unmarshals and validates bytes in thrift format
|
||||
func (*Thrift) Decode(octets []byte) ([]codec.Span, error) {
|
||||
spans, err := unmarshalThrift(octets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := make([]codec.Span, 0, len(spans))
|
||||
for _, s := range spans {
|
||||
res = append(res, &span{s})
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// unmarshalThrift converts raw bytes in thrift format to a slice of spans
|
||||
func unmarshalThrift(body []byte) ([]*zipkincore.Span, error) {
|
||||
buffer := thrift.NewTMemoryBuffer()
|
||||
buffer.Write(body)
|
||||
|
||||
transport := thrift.NewTBinaryProtocolConf(buffer, nil)
|
||||
_, size, err := transport.ReadListBegin(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spans := make([]*zipkincore.Span, 0, size)
|
||||
for i := 0; i < size; i++ {
|
||||
zs := &zipkincore.Span{}
|
||||
if err := zs.Read(context.Background(), transport); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spans = append(spans, zs)
|
||||
}
|
||||
|
||||
if err := transport.ReadListEnd(context.Background()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return spans, nil
|
||||
}
|
||||
|
||||
var _ codec.Endpoint = &endpoint{}
|
||||
|
||||
type endpoint struct {
|
||||
*zipkincore.Endpoint
|
||||
}
|
||||
|
||||
// Host returns the host address of the endpoint as a string.
|
||||
func (e *endpoint) Host() string {
|
||||
ipv4 := func(addr int32) string {
|
||||
buf := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(buf, uint32(addr))
|
||||
return net.IP(buf).String()
|
||||
}
|
||||
|
||||
if e.Endpoint == nil {
|
||||
return ipv4(int32(0))
|
||||
}
|
||||
if e.Endpoint.GetPort() == 0 {
|
||||
return ipv4(e.Endpoint.GetIpv4())
|
||||
}
|
||||
// Zipkin uses a signed int16 for the port, but, warns us that they actually treat it
|
||||
// as an unsigned int16. So, we convert from int16 to int32 followed by taking & 0xffff
|
||||
// to convert from signed to unsigned
|
||||
// https://github.com/openzipkin/zipkin/blob/57dc2ec9c65fe6144e401c0c933b4400463a69df/zipkin/src/main/java/zipkin/Endpoint.java#L44
|
||||
return ipv4(e.Endpoint.GetIpv4()) + ":" + strconv.FormatInt(int64(int(e.Endpoint.GetPort())&0xffff), 10)
|
||||
}
|
||||
|
||||
// Name returns the name of the service associated with the endpoint as a string.
|
||||
func (e *endpoint) Name() string {
|
||||
if e.Endpoint == nil {
|
||||
return codec.DefaultServiceName
|
||||
}
|
||||
return e.Endpoint.GetServiceName()
|
||||
}
|
||||
|
||||
var _ codec.BinaryAnnotation = &binaryAnnotation{}
|
||||
|
||||
type binaryAnnotation struct {
|
||||
*zipkincore.BinaryAnnotation
|
||||
}
|
||||
|
||||
// Key returns the key of the binary annotation as a string.
|
||||
func (b *binaryAnnotation) Key() string {
|
||||
return b.BinaryAnnotation.GetKey()
|
||||
}
|
||||
|
||||
// Value returns the value of the binary annotation as a string.
|
||||
func (b *binaryAnnotation) Value() string {
|
||||
return string(b.BinaryAnnotation.GetValue())
|
||||
}
|
||||
|
||||
// Host returns the endpoint associated with the binary annotation as a codec.Endpoint.
|
||||
func (b *binaryAnnotation) Host() codec.Endpoint {
|
||||
if b.BinaryAnnotation.Host == nil {
|
||||
return nil
|
||||
}
|
||||
return &endpoint{b.BinaryAnnotation.Host}
|
||||
}
|
||||
|
||||
var _ codec.Annotation = &annotation{}
|
||||
|
||||
type annotation struct {
|
||||
*zipkincore.Annotation
|
||||
}
|
||||
|
||||
// Timestamp returns the timestamp of the annotation as a time.Time object.
|
||||
func (a *annotation) Timestamp() time.Time {
|
||||
ts := a.Annotation.GetTimestamp()
|
||||
if ts == 0 {
|
||||
return time.Time{}
|
||||
}
|
||||
return codec.MicroToTime(ts)
|
||||
}
|
||||
|
||||
// Value returns the value of the annotation as a string.
|
||||
func (a *annotation) Value() string {
|
||||
return a.Annotation.GetValue()
|
||||
}
|
||||
|
||||
// Host returns the endpoint associated with the annotation as a codec.Endpoint.
|
||||
func (a *annotation) Host() codec.Endpoint {
|
||||
if a.Annotation.Host == nil {
|
||||
return nil
|
||||
}
|
||||
return &endpoint{a.Annotation.Host}
|
||||
}
|
||||
|
||||
var _ codec.Span = &span{}
|
||||
|
||||
type span struct {
|
||||
*zipkincore.Span
|
||||
}
|
||||
|
||||
// Trace returns the trace ID of the span and an error if the trace ID is invalid.
|
||||
func (s *span) Trace() (string, error) {
|
||||
if s.Span.GetTraceIDHigh() == 0 && s.Span.GetTraceID() == 0 {
|
||||
return "", errors.New("span does not have a trace ID")
|
||||
}
|
||||
|
||||
if s.Span.GetTraceIDHigh() == 0 {
|
||||
return fmt.Sprintf("%x", s.Span.GetTraceID()), nil
|
||||
}
|
||||
return fmt.Sprintf("%x%016x", s.Span.GetTraceIDHigh(), s.Span.GetTraceID()), nil
|
||||
}
|
||||
|
||||
// SpanID returns the span ID of the span and an error if the span ID is invalid.
|
||||
func (s *span) SpanID() (string, error) {
|
||||
return formatID(s.Span.GetID()), nil
|
||||
}
|
||||
|
||||
// Parent returns the parent span ID of the span and an error if the parent ID is invalid.
|
||||
func (s *span) Parent() (string, error) {
|
||||
id := s.Span.GetParentID()
|
||||
if id != 0 {
|
||||
return formatID(id), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Name returns the name of the span.
|
||||
func (s *span) Name() string {
|
||||
return s.Span.GetName()
|
||||
}
|
||||
|
||||
// Annotations returns the annotations of the span as a slice of codec.Annotation.
|
||||
func (s *span) Annotations() []codec.Annotation {
|
||||
res := make([]codec.Annotation, 0, len(s.Span.Annotations))
|
||||
for _, ann := range s.Span.Annotations {
|
||||
res = append(res, &annotation{ann})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// BinaryAnnotations returns the binary annotations of the span as a slice of codec.BinaryAnnotation and an error if the binary annotations cannot be retrieved.
|
||||
func (s *span) BinaryAnnotations() ([]codec.BinaryAnnotation, error) {
|
||||
res := make([]codec.BinaryAnnotation, 0, len(s.Span.BinaryAnnotations))
|
||||
for _, ann := range s.Span.BinaryAnnotations {
|
||||
res = append(res, &binaryAnnotation{ann})
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Timestamp returns the timestamp of the span as a time.Time object.
|
||||
func (s *span) Timestamp() time.Time {
|
||||
ts := s.Span.GetTimestamp()
|
||||
if ts == 0 {
|
||||
return time.Time{}
|
||||
}
|
||||
return codec.MicroToTime(ts)
|
||||
}
|
||||
|
||||
// Duration returns the duration of the span as a time.Duration object.
|
||||
func (s *span) Duration() time.Duration {
|
||||
return time.Duration(s.Span.GetDuration()) * time.Microsecond
|
||||
}
|
||||
|
||||
// formatID formats the given ID as a hexadecimal string.
|
||||
func formatID(id int64) string {
|
||||
return strconv.FormatInt(id, 16)
|
||||
}
|
206
plugins/inputs/zipkin/codec/thrift/thrift_test.go
Normal file
206
plugins/inputs/zipkin/codec/thrift/thrift_test.go
Normal file
|
@ -0,0 +1,206 @@
|
|||
package thrift
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf/plugins/inputs/zipkin/codec/thrift/gen-go/zipkincore"
|
||||
)
|
||||
|
||||
func Test_endpointHost(t *testing.T) {
|
||||
type args struct {
|
||||
h *zipkincore.Endpoint
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Host Found",
|
||||
args: args{
|
||||
h: &zipkincore.Endpoint{
|
||||
Ipv4: 1234,
|
||||
Port: 8888,
|
||||
},
|
||||
},
|
||||
want: "0.0.4.210:8888",
|
||||
},
|
||||
{
|
||||
name: "No Host",
|
||||
args: args{
|
||||
h: nil,
|
||||
},
|
||||
want: "0.0.0.0",
|
||||
},
|
||||
{
|
||||
name: "int overflow zipkin uses an int16 type as an unsigned int 16.",
|
||||
args: args{
|
||||
h: &zipkincore.Endpoint{
|
||||
Ipv4: 1234,
|
||||
Port: -1,
|
||||
},
|
||||
},
|
||||
want: "0.0.4.210:65535",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
e := endpoint{tt.args.h}
|
||||
require.Equal(t, tt.want, e.Host())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_endpointName(t *testing.T) {
|
||||
type args struct {
|
||||
h *zipkincore.Endpoint
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Found ServiceName",
|
||||
args: args{
|
||||
h: &zipkincore.Endpoint{
|
||||
ServiceName: "zipkin",
|
||||
},
|
||||
},
|
||||
want: "zipkin",
|
||||
},
|
||||
{
|
||||
name: "No ServiceName",
|
||||
args: args{
|
||||
h: nil,
|
||||
},
|
||||
want: "unknown",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
e := endpoint{tt.args.h}
|
||||
require.Equal(t, tt.want, e.Name())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalThrift(t *testing.T) {
|
||||
addr := func(i int64) *int64 { return &i }
|
||||
tests := []struct {
|
||||
name string
|
||||
filename string
|
||||
want []*zipkincore.Span
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "threespans",
|
||||
filename: "../../testdata/threespans.dat",
|
||||
want: []*zipkincore.Span{
|
||||
{
|
||||
TraceID: 2505404965370368069,
|
||||
Name: "Child",
|
||||
ID: 8090652509916334619,
|
||||
ParentID: addr(22964302721410078),
|
||||
Timestamp: addr(1498688360851331),
|
||||
Duration: addr(53106),
|
||||
Annotations: make([]*zipkincore.Annotation, 0),
|
||||
BinaryAnnotations: []*zipkincore.BinaryAnnotation{
|
||||
{
|
||||
Key: "lc",
|
||||
AnnotationType: zipkincore.AnnotationType_STRING,
|
||||
Value: []byte("trivial"),
|
||||
Host: &zipkincore.Endpoint{
|
||||
Ipv4: 2130706433,
|
||||
ServiceName: "trivial",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
TraceID: 2505404965370368069,
|
||||
Name: "Child",
|
||||
ID: 103618986556047333,
|
||||
ParentID: addr(22964302721410078),
|
||||
Timestamp: addr(1498688360904552),
|
||||
Duration: addr(50410),
|
||||
Annotations: make([]*zipkincore.Annotation, 0),
|
||||
BinaryAnnotations: []*zipkincore.BinaryAnnotation{
|
||||
{
|
||||
Key: "lc",
|
||||
AnnotationType: zipkincore.AnnotationType_STRING,
|
||||
Value: []byte("trivial"),
|
||||
Host: &zipkincore.Endpoint{
|
||||
Ipv4: 2130706433,
|
||||
ServiceName: "trivial",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
TraceID: 2505404965370368069,
|
||||
Name: "Parent",
|
||||
ID: 22964302721410078,
|
||||
Timestamp: addr(1498688360851318),
|
||||
Duration: addr(103680),
|
||||
Annotations: []*zipkincore.Annotation{
|
||||
{
|
||||
Timestamp: 1498688360851325,
|
||||
Value: "Starting child #0",
|
||||
Host: &zipkincore.Endpoint{
|
||||
Ipv4: 2130706433,
|
||||
ServiceName: "trivial",
|
||||
},
|
||||
},
|
||||
{
|
||||
Timestamp: 1498688360904545,
|
||||
Value: "Starting child #1",
|
||||
Host: &zipkincore.Endpoint{
|
||||
Ipv4: 2130706433,
|
||||
ServiceName: "trivial",
|
||||
},
|
||||
},
|
||||
{
|
||||
Timestamp: 1498688360954992,
|
||||
Value: "A Log",
|
||||
Host: &zipkincore.Endpoint{
|
||||
Ipv4: 2130706433,
|
||||
ServiceName: "trivial",
|
||||
},
|
||||
},
|
||||
},
|
||||
BinaryAnnotations: []*zipkincore.BinaryAnnotation{
|
||||
{
|
||||
Key: "lc",
|
||||
AnnotationType: zipkincore.AnnotationType_STRING,
|
||||
Value: []byte("trivial"),
|
||||
Host: &zipkincore.Endpoint{
|
||||
Ipv4: 2130706433,
|
||||
ServiceName: "trivial",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
dat, err := os.ReadFile(tt.filename)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not find file %s\n", tt.filename)
|
||||
}
|
||||
|
||||
got, err := unmarshalThrift(dat)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue