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
215
plugins/inputs/zipkin/README.md
Normal file
215
plugins/inputs/zipkin/README.md
Normal file
|
@ -0,0 +1,215 @@
|
|||
# Zipkin Input Plugin
|
||||
|
||||
This plugin implements the Zipkin http server to gather trace and timing data
|
||||
needed to troubleshoot latency problems in microservice architectures.
|
||||
|
||||
__Please Note:__ This plugin is experimental; Its data schema may be subject to
|
||||
change based on its main usage cases and the evolution of the OpenTracing
|
||||
standard.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This plugin will create high cardinality data, so please take this into
|
||||
> account when sending data to your output!
|
||||
|
||||
## Service Input <!-- @/docs/includes/service_input.md -->
|
||||
|
||||
This plugin is a service input. Normal plugins gather metrics determined by the
|
||||
interval setting. Service plugins start a service to listen and wait for
|
||||
metrics or events to occur. Service plugins have two key differences from
|
||||
normal plugins:
|
||||
|
||||
1. The global or plugin specific `interval` setting may not apply
|
||||
2. The CLI options of `--test`, `--test-wait`, and `--once` may not produce
|
||||
output for this plugin
|
||||
|
||||
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
|
||||
|
||||
In addition to the plugin-specific configuration settings, plugins support
|
||||
additional global and plugin configuration settings. These settings are used to
|
||||
modify metrics, tags, and field or create aliases and configure ordering, etc.
|
||||
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
|
||||
|
||||
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml @sample.conf
|
||||
# Gather data from a Zipkin server including trace and timing data
|
||||
[[inputs.zipkin]]
|
||||
## URL path for span data
|
||||
# path = "/api/v1/spans"
|
||||
|
||||
## Port on which Telegraf listens
|
||||
# port = 9411
|
||||
|
||||
## Maximum duration before timing out read of the request
|
||||
# read_timeout = "10s"
|
||||
## Maximum duration before timing out write of the response
|
||||
# write_timeout = "10s"
|
||||
```
|
||||
|
||||
The plugin accepts spans in `JSON` or `thrift` if the `Content-Type` is
|
||||
`application/json` or `application/x-thrift`, respectively. If `Content-Type`
|
||||
is not set, then the plugin assumes it is `JSON` format.
|
||||
|
||||
## Tracing
|
||||
|
||||
This plugin uses Annotations tags and fields to track data from spans
|
||||
|
||||
- `TRACE` is a set of spans that share a single root span. Traces are built by
|
||||
collecting all Spans that share a traceId.
|
||||
- `SPAN` is a set of Annotations and BinaryAnnotations that correspond to a
|
||||
particular RPC.
|
||||
- `Annotations` create a metric for each annotation & binary annotation of a
|
||||
span. This records an occurrence in time at the beginning and end of each
|
||||
request.
|
||||
|
||||
Annotations may have the following values:
|
||||
- `CS` (client start) marks the beginning of the span, a request is made.
|
||||
- `SR` (server receive) marks the point in time the server receives the request
|
||||
and starts processing it. Network latency & clock jitters distinguish this
|
||||
from `CS`.
|
||||
- `SS` (server send) marks the point in time the server is finished processing
|
||||
and sends a request back to client. The difference to `SR` denotes the
|
||||
amount of time it took to process the request.
|
||||
- `CR` (client receive) marks the end of the span, with the client receiving
|
||||
the response from server. RPC is considered complete with this annotation.
|
||||
|
||||
## Metrics
|
||||
|
||||
- `duration_ns` the time in nanoseconds between the end and beginning of a span
|
||||
|
||||
### Tags
|
||||
|
||||
- `id` the 64-bit ID of the span.
|
||||
- `parent_id` an ID associated with a particular child span. If there is no
|
||||
child span, `parent_id` is equal to `id`
|
||||
- `trace_id` the 64-bit or 128-bit ID of a particular trace. Every span in a
|
||||
trace uses this ID.
|
||||
- `name` defines a span
|
||||
|
||||
#### Annotations have these additional tags
|
||||
|
||||
- `service_name` defines a service
|
||||
- `annotation` the value of an annotation
|
||||
- `endpoint_host` listening IPv4 address and, if present, port
|
||||
|
||||
#### Binary Annotations have these additional tag
|
||||
|
||||
- `service_name` defines a service
|
||||
- `annotation` the value of an annotation
|
||||
- `endpoint_host` listening IPv4 address and, if present, port
|
||||
- `annotation_key` label describing the annotation
|
||||
|
||||
## Example Output
|
||||
|
||||
The Zipkin data
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"trace_id": 2505404965370368069,
|
||||
"name": "Child",
|
||||
"id": 8090652509916334619,
|
||||
"parent_id": 22964302721410078,
|
||||
"annotations": [],
|
||||
"binary_annotations": [
|
||||
{
|
||||
"key": "lc",
|
||||
"value": "dHJpdmlhbA==",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": 0,
|
||||
"service_name": "trivial"
|
||||
}
|
||||
}
|
||||
],
|
||||
"timestamp": 1498688360851331,
|
||||
"duration": 53106
|
||||
},
|
||||
{
|
||||
"trace_id": 2505404965370368069,
|
||||
"name": "Child",
|
||||
"id": 103618986556047333,
|
||||
"parent_id": 22964302721410078,
|
||||
"annotations": [],
|
||||
"binary_annotations": [
|
||||
{
|
||||
"key": "lc",
|
||||
"value": "dHJpdmlhbA==",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": 0,
|
||||
"service_name": "trivial"
|
||||
}
|
||||
}
|
||||
],
|
||||
"timestamp": 1498688360904552,
|
||||
"duration": 50410
|
||||
},
|
||||
{
|
||||
"trace_id": 2505404965370368069,
|
||||
"name": "Parent",
|
||||
"id": 22964302721410078,
|
||||
"annotations": [
|
||||
{
|
||||
"timestamp": 1498688360851325,
|
||||
"value": "Starting child #0",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": 0,
|
||||
"service_name": "trivial"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": 1498688360904545,
|
||||
"value": "Starting child #1",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": 0,
|
||||
"service_name": "trivial"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": 1498688360954992,
|
||||
"value": "A Log",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": 0,
|
||||
"service_name": "trivial"
|
||||
}
|
||||
}
|
||||
],
|
||||
"binary_annotations": [
|
||||
{
|
||||
"key": "lc",
|
||||
"value": "dHJpdmlhbA==",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": 0,
|
||||
"service_name": "trivial"
|
||||
}
|
||||
}
|
||||
],
|
||||
"timestamp": 1498688360851318,
|
||||
"duration": 103680
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
generated the following metrics
|
||||
|
||||
```text
|
||||
zipkin,id=7047c59776af8a1b,name=child,parent_id=5195e96239641e,service_name=trivial,trace_id=22c4fc8ab3669045 duration_ns=53106000i 1498688360851331000
|
||||
zipkin,annotation=trivial,annotation_key=lc,endpoint_host=127.0.0.1,id=7047c59776af8a1b,name=child,parent_id=5195e96239641e,service_name=trivial,trace_id=22c4fc8ab3669045 duration_ns=53106000i 1498688360851331000
|
||||
zipkin,id=17020eb55a8bfe5,name=child,parent_id=5195e96239641e,service_name=trivial,trace_id=22c4fc8ab3669045 duration_ns=50410000i 1498688360904552000
|
||||
zipkin,annotation=trivial,annotation_key=lc,endpoint_host=127.0.0.1,id=17020eb55a8bfe5,name=child,parent_id=5195e96239641e,service_name=trivial,trace_id=22c4fc8ab3669045 duration_ns=50410000i 1498688360904552000
|
||||
zipkin,id=5195e96239641e,name=parent,parent_id=5195e96239641e,service_name=trivial,trace_id=22c4fc8ab3669045 duration_ns=103680000i 1498688360851318000
|
||||
zipkin,annotation=Starting\ child\ #0,endpoint_host=127.0.0.1,id=5195e96239641e,name=parent,parent_id=5195e96239641e,service_name=trivial,trace_id=22c4fc8ab3669045 duration_ns=103680000i 1498688360851318000
|
||||
zipkin,annotation=Starting\ child\ #1,endpoint_host=127.0.0.1,id=5195e96239641e,name=parent,parent_id=5195e96239641e,service_name=trivial,trace_id=22c4fc8ab3669045 duration_ns=103680000i 1498688360851318000
|
||||
zipkin,annotation=A\ Log,endpoint_host=127.0.0.1,id=5195e96239641e,name=parent,parent_id=5195e96239641e,service_name=trivial,trace_id=22c4fc8ab3669045 duration_ns=103680000i 1498688360851318000
|
||||
zipkin,annotation=trivial,annotation_key=lc,endpoint_host=127.0.0.1,id=5195e96239641e,name=parent,parent_id=5195e96239641e,service_name=trivial,trace_id=22c4fc8ab3669045 duration_ns=103680000i 1498688360851318000
|
||||
```
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
This is a development testing cli tool meant to stress the zipkin telegraf plugin.
|
||||
It writes a specified number of zipkin spans to the plugin endpoint, with other
|
||||
parameters which dictate batch size and flush timeout.
|
||||
|
||||
Usage as follows:
|
||||
|
||||
`./stress_test_write -batch_size=<batch_size> -max_backlog=<max_span_buffer_backlog> -batch_interval=<batch_interval_in_seconds> \
|
||||
-span_count<number_of_spans_to_write> -zipkin_host=<zipkin_service_hostname>`
|
||||
|
||||
Or with a timer:
|
||||
|
||||
`time ./stress_test_write -batch_size=<batch_size> -max_backlog=<max_span_buffer_backlog> -batch_interval=<batch_interval_in_seconds> \
|
||||
-span_count<number_of_spans_to_write> -zipkin_host=<zipkin_service_hostname>`
|
||||
|
||||
However, the flag defaults work just fine for a good write stress test (and are what
|
||||
this tool has mainly been tested with), so there shouldn't be much need to
|
||||
manually tweak the parameters.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
otlog "github.com/opentracing/opentracing-go/log"
|
||||
zipkinot "github.com/openzipkin-contrib/zipkin-go-opentracing"
|
||||
"github.com/openzipkin/zipkin-go"
|
||||
zipkinhttp "github.com/openzipkin/zipkin-go/reporter/http"
|
||||
)
|
||||
|
||||
var (
|
||||
batchSize int
|
||||
maxBackLog int
|
||||
batchTimeInterval int
|
||||
spanCount int
|
||||
zipkinServerHost string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.IntVar(&batchSize, "batch_size", 10000, "")
|
||||
flag.IntVar(&maxBackLog, "max_backlog", 100000, "")
|
||||
flag.IntVar(&batchTimeInterval, "batch_interval", 1, "")
|
||||
flag.IntVar(&spanCount, "span_count", 100000, "")
|
||||
flag.StringVar(&zipkinServerHost, "zipkin_host", "localhost", "")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
var hostname = fmt.Sprintf("http://%s:9411/api/v1/spans", zipkinServerHost)
|
||||
reporter := zipkinhttp.NewReporter(
|
||||
hostname,
|
||||
zipkinhttp.BatchSize(batchSize),
|
||||
zipkinhttp.MaxBacklog(maxBackLog),
|
||||
zipkinhttp.BatchInterval(time.Duration(batchTimeInterval)*time.Second),
|
||||
)
|
||||
defer reporter.Close()
|
||||
|
||||
endpoint, err := zipkin.NewEndpoint("Trivial", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
log.Panicf("Error: %v\n", err)
|
||||
}
|
||||
|
||||
nativeTracer, err := zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(endpoint))
|
||||
if err != nil {
|
||||
log.Panicf("Error: %v\n", err)
|
||||
}
|
||||
|
||||
tracer := zipkinot.Wrap(nativeTracer)
|
||||
|
||||
log.Printf("Writing %d spans to zipkin server at %s\n", spanCount, hostname)
|
||||
for i := 0; i < spanCount; i++ {
|
||||
parent := tracer.StartSpan("Parent")
|
||||
parent.LogFields(otlog.Message(fmt.Sprintf("Trace%d", i)))
|
||||
parent.Finish()
|
||||
}
|
||||
log.Println("Done. Flushing remaining spans...")
|
||||
}
|
148
plugins/inputs/zipkin/cmd/thrift_serialize/thrift_serialize.go
Normal file
148
plugins/inputs/zipkin/cmd/thrift_serialize/thrift_serialize.go
Normal file
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
A small cli utility meant to convert json to zipkin thrift binary format, and
|
||||
vice versa.
|
||||
|
||||
To convert from json to thrift,
|
||||
the json is unmarshalled, converted to zipkincore.Span structures, and
|
||||
marshalled into thrift binary protocol. The json must be in an array format (even if it only has one object),
|
||||
because the tool automatically tries to unmarshal the json into an array of structs.
|
||||
|
||||
To convert from thrift to json,
|
||||
the opposite process must happen. The thrift binary data must be read into an array of
|
||||
zipkin span structures, and those spans must be marshalled into json.
|
||||
|
||||
Usage:
|
||||
|
||||
./thrift_serialize -input <input-file> -output <output-file> -deserialize<true|false>
|
||||
|
||||
If `deserialize` is set to true (false by default), the tool will interpret the input file as
|
||||
thrift, and write it as json to the output file.
|
||||
Otherwise, the input file will be interpreted as json, and the output will be encoded as thrift.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/apache/thrift/lib/go/thrift"
|
||||
|
||||
"github.com/influxdata/telegraf/plugins/inputs/zipkin/codec/thrift/gen-go/zipkincore"
|
||||
)
|
||||
|
||||
var (
|
||||
filename string
|
||||
outFileName string
|
||||
inputType string
|
||||
)
|
||||
|
||||
const usage = `./json_serialize -input <input> -output output -input-type<json|thrift>`
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&filename, "input", "", usage)
|
||||
flag.StringVar(&outFileName, "output", "", usage)
|
||||
flag.StringVar(&inputType, "input-type", "thrift", usage)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
contents, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
log.Fatalf("Error reading file: %v\n", err)
|
||||
}
|
||||
|
||||
switch inputType {
|
||||
case "json":
|
||||
raw, err := jsonToZipkinThrift(contents)
|
||||
if err != nil {
|
||||
log.Fatalf("%v\n", err)
|
||||
}
|
||||
if err := os.WriteFile(outFileName, raw, 0640); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
case "thrift":
|
||||
raw, err := thriftToJSONSpans(contents)
|
||||
if err != nil {
|
||||
log.Fatalf("%v\n", err)
|
||||
}
|
||||
if err := os.WriteFile(outFileName, raw, 0640); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
default:
|
||||
log.Fatalf("Unsupported input type")
|
||||
}
|
||||
}
|
||||
|
||||
func jsonToZipkinThrift(jsonRaw []byte) ([]byte, error) {
|
||||
if len(jsonRaw) == 0 {
|
||||
return nil, errors.New("no data")
|
||||
}
|
||||
|
||||
if string(jsonRaw)[0] != '[' {
|
||||
return nil, errors.New("cannot unmarshal non array type")
|
||||
}
|
||||
|
||||
var spans []*zipkincore.Span
|
||||
err := json.Unmarshal(jsonRaw, &spans)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshalling: %w", err)
|
||||
}
|
||||
|
||||
var zspans []*zipkincore.Span
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
zspans = append(zspans, spans...)
|
||||
|
||||
buf := thrift.NewTMemoryBuffer()
|
||||
transport := thrift.NewTBinaryProtocolConf(buf, nil)
|
||||
|
||||
if err = transport.WriteListBegin(context.Background(), thrift.STRUCT, len(spans)); err != nil {
|
||||
return nil, fmt.Errorf("error in beginning thrift write: %w", err)
|
||||
}
|
||||
|
||||
for _, span := range zspans {
|
||||
err = span.Write(context.Background(), transport)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error converting zipkin struct to thrift: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = transport.WriteListEnd(context.Background()); err != nil {
|
||||
return nil, fmt.Errorf("error finishing thrift write: %w", err)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func thriftToJSONSpans(thriftData []byte) ([]byte, error) {
|
||||
buffer := thrift.NewTMemoryBuffer()
|
||||
buffer.Write(thriftData)
|
||||
|
||||
transport := thrift.NewTBinaryProtocolConf(buffer, nil)
|
||||
_, size, err := transport.ReadListBegin(context.Background())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in ReadListBegin: %w", 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, fmt.Errorf("error reading into zipkin struct: %w", err)
|
||||
}
|
||||
spans = append(spans, zs)
|
||||
}
|
||||
|
||||
err = transport.ReadListEnd(context.Background())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error ending thrift read: %w", err)
|
||||
}
|
||||
|
||||
return json.MarshalIndent(spans, "", " ")
|
||||
}
|
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)
|
||||
})
|
||||
}
|
||||
}
|
79
plugins/inputs/zipkin/convert.go
Normal file
79
plugins/inputs/zipkin/convert.go
Normal file
|
@ -0,0 +1,79 @@
|
|||
package zipkin
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/plugins/inputs/zipkin/trace"
|
||||
)
|
||||
|
||||
// lineProtocolConverter implements the recorder interface;
|
||||
// it is a type meant to encapsulate the storage of zipkin tracing data in telegraf as line protocol.
|
||||
type lineProtocolConverter struct {
|
||||
acc telegraf.Accumulator
|
||||
}
|
||||
|
||||
// newLineProtocolConverter returns an instance of lineProtocolConverter that will add to the given telegraf.Accumulator
|
||||
func newLineProtocolConverter(acc telegraf.Accumulator) *lineProtocolConverter {
|
||||
return &lineProtocolConverter{
|
||||
acc: acc,
|
||||
}
|
||||
}
|
||||
|
||||
// record is lineProtocolConverter's implementation of the record method of the recorder interface;
|
||||
// it takes a trace as input, and adds it to an internal telegraf.Accumulator.
|
||||
func (l *lineProtocolConverter) record(t trace.Trace) error {
|
||||
for _, s := range t {
|
||||
fields := map[string]interface{}{
|
||||
"duration_ns": s.Duration.Nanoseconds(),
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"id": s.ID,
|
||||
"parent_id": s.ParentID,
|
||||
"trace_id": s.TraceID,
|
||||
"name": formatName(s.Name),
|
||||
"service_name": formatName(s.ServiceName),
|
||||
}
|
||||
l.acc.AddFields("zipkin", fields, tags, s.Timestamp)
|
||||
|
||||
for _, a := range s.Annotations {
|
||||
tags := map[string]string{
|
||||
"id": s.ID,
|
||||
"parent_id": s.ParentID,
|
||||
"trace_id": s.TraceID,
|
||||
"name": formatName(s.Name),
|
||||
"service_name": formatName(a.ServiceName),
|
||||
"annotation": a.Value,
|
||||
"endpoint_host": a.Host,
|
||||
}
|
||||
l.acc.AddFields("zipkin", fields, tags, s.Timestamp)
|
||||
}
|
||||
|
||||
for _, b := range s.BinaryAnnotations {
|
||||
tags := map[string]string{
|
||||
"id": s.ID,
|
||||
"parent_id": s.ParentID,
|
||||
"trace_id": s.TraceID,
|
||||
"name": formatName(s.Name),
|
||||
"service_name": formatName(b.ServiceName),
|
||||
"annotation": b.Value,
|
||||
"endpoint_host": b.Host,
|
||||
"annotation_key": b.Key,
|
||||
}
|
||||
l.acc.AddFields("zipkin", fields, tags, s.Timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *lineProtocolConverter) error(err error) {
|
||||
l.acc.AddError(err)
|
||||
}
|
||||
|
||||
// formatName formats name and service name Zipkin forces span and service names to be lowercase:
|
||||
// https://github.com/openzipkin/zipkin/pull/805
|
||||
func formatName(name string) string {
|
||||
return strings.ToLower(name)
|
||||
}
|
348
plugins/inputs/zipkin/convert_test.go
Normal file
348
plugins/inputs/zipkin/convert_test.go
Normal file
|
@ -0,0 +1,348 @@
|
|||
package zipkin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/plugins/inputs/zipkin/trace"
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
func TestLineProtocolConverter_Record(t *testing.T) {
|
||||
mockAcc := testutil.Accumulator{}
|
||||
type fields struct {
|
||||
acc telegraf.Accumulator
|
||||
}
|
||||
type args struct {
|
||||
t trace.Trace
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantErr bool
|
||||
want []testutil.Metric
|
||||
}{
|
||||
{
|
||||
name: "threespan",
|
||||
fields: fields{
|
||||
acc: &mockAcc,
|
||||
},
|
||||
args: args{
|
||||
t: trace.Trace{
|
||||
{
|
||||
ID: "8090652509916334619",
|
||||
TraceID: "2505404965370368069",
|
||||
Name: "Child",
|
||||
ParentID: "22964302721410078",
|
||||
Timestamp: time.Unix(0, 1498688360851331000).UTC(),
|
||||
Duration: time.Duration(53106) * time.Microsecond,
|
||||
ServiceName: "trivial",
|
||||
BinaryAnnotations: []trace.BinaryAnnotation{
|
||||
{
|
||||
Key: "lc",
|
||||
Value: "dHJpdmlhbA==",
|
||||
Host: "2130706433:0",
|
||||
ServiceName: "trivial",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "103618986556047333",
|
||||
TraceID: "2505404965370368069",
|
||||
Name: "Child",
|
||||
ParentID: "22964302721410078",
|
||||
Timestamp: time.Unix(0, 1498688360904552000).UTC(),
|
||||
Duration: time.Duration(50410) * time.Microsecond,
|
||||
ServiceName: "trivial",
|
||||
BinaryAnnotations: []trace.BinaryAnnotation{
|
||||
{
|
||||
Key: "lc",
|
||||
Value: "dHJpdmlhbA==",
|
||||
Host: "2130706433:0",
|
||||
ServiceName: "trivial",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "22964302721410078",
|
||||
TraceID: "2505404965370368069",
|
||||
Name: "Parent",
|
||||
ParentID: "22964302721410078",
|
||||
Timestamp: time.Unix(0, 1498688360851318000).UTC(),
|
||||
Duration: time.Duration(103680) * time.Microsecond,
|
||||
ServiceName: "trivial",
|
||||
Annotations: []trace.Annotation{
|
||||
{
|
||||
Timestamp: time.Unix(0, 1498688360851325000).UTC(),
|
||||
Value: "Starting child #0",
|
||||
Host: "2130706433:0",
|
||||
ServiceName: "trivial",
|
||||
},
|
||||
{
|
||||
Timestamp: time.Unix(0, 1498688360904545000).UTC(),
|
||||
Value: "Starting child #1",
|
||||
Host: "2130706433:0",
|
||||
ServiceName: "trivial",
|
||||
},
|
||||
{
|
||||
Timestamp: time.Unix(0, 1498688360954992000).UTC(),
|
||||
Value: "A Log",
|
||||
Host: "2130706433:0",
|
||||
ServiceName: "trivial",
|
||||
},
|
||||
},
|
||||
BinaryAnnotations: []trace.BinaryAnnotation{
|
||||
{
|
||||
Key: "lc",
|
||||
Value: "dHJpdmlhbA==",
|
||||
Host: "2130706433:0",
|
||||
ServiceName: "trivial",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []testutil.Metric{
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"id": "8090652509916334619",
|
||||
"parent_id": "22964302721410078",
|
||||
"trace_id": "2505404965370368069",
|
||||
"service_name": "trivial",
|
||||
"name": "child",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(53106) * time.Microsecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(0, 1498688360851331000).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"id": "8090652509916334619",
|
||||
"parent_id": "22964302721410078",
|
||||
"trace_id": "2505404965370368069",
|
||||
"name": "child",
|
||||
"service_name": "trivial",
|
||||
"annotation": "dHJpdmlhbA==",
|
||||
"endpoint_host": "2130706433:0",
|
||||
"annotation_key": "lc",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(53106) * time.Microsecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(0, 1498688360851331000).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"id": "103618986556047333",
|
||||
"parent_id": "22964302721410078",
|
||||
"trace_id": "2505404965370368069",
|
||||
"service_name": "trivial",
|
||||
"name": "child",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(50410) * time.Microsecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(0, 1498688360904552000).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"id": "103618986556047333",
|
||||
"parent_id": "22964302721410078",
|
||||
"trace_id": "2505404965370368069",
|
||||
"name": "child",
|
||||
"service_name": "trivial",
|
||||
"annotation": "dHJpdmlhbA==",
|
||||
"endpoint_host": "2130706433:0",
|
||||
"annotation_key": "lc",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(50410) * time.Microsecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(0, 1498688360904552000).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"id": "22964302721410078",
|
||||
"parent_id": "22964302721410078",
|
||||
"trace_id": "2505404965370368069",
|
||||
"service_name": "trivial",
|
||||
"name": "parent",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(103680) * time.Microsecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(0, 1498688360851318000).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"service_name": "trivial",
|
||||
"annotation": "Starting child #0",
|
||||
"endpoint_host": "2130706433:0",
|
||||
"id": "22964302721410078",
|
||||
"parent_id": "22964302721410078",
|
||||
"trace_id": "2505404965370368069",
|
||||
"name": "parent",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(103680) * time.Microsecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(0, 1498688360851318000).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"service_name": "trivial",
|
||||
"annotation": "Starting child #1",
|
||||
"endpoint_host": "2130706433:0",
|
||||
"id": "22964302721410078",
|
||||
"parent_id": "22964302721410078",
|
||||
"trace_id": "2505404965370368069",
|
||||
"name": "parent",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(103680) * time.Microsecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(0, 1498688360851318000).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"parent_id": "22964302721410078",
|
||||
"trace_id": "2505404965370368069",
|
||||
"name": "parent",
|
||||
"service_name": "trivial",
|
||||
"annotation": "A Log",
|
||||
"endpoint_host": "2130706433:0",
|
||||
"id": "22964302721410078",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(103680) * time.Microsecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(0, 1498688360851318000).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"trace_id": "2505404965370368069",
|
||||
"service_name": "trivial",
|
||||
"annotation": "dHJpdmlhbA==",
|
||||
"annotation_key": "lc",
|
||||
"id": "22964302721410078",
|
||||
"parent_id": "22964302721410078",
|
||||
"name": "parent",
|
||||
"endpoint_host": "2130706433:0",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(103680) * time.Microsecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(0, 1498688360851318000).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
|
||||
// Test data from distributed trace repo sample json
|
||||
// https://github.com/mattkanwisher/distributedtrace/blob/master/testclient/sample.json
|
||||
{
|
||||
name: "distributed_trace_sample",
|
||||
fields: fields{
|
||||
acc: &mockAcc,
|
||||
},
|
||||
args: args{
|
||||
t: trace.Trace{
|
||||
{
|
||||
ID: "6802735349851856000",
|
||||
TraceID: "0:6802735349851856000",
|
||||
Name: "main.dud",
|
||||
ParentID: "6802735349851856000",
|
||||
Timestamp: time.Unix(1, 0).UTC(),
|
||||
Duration: 1,
|
||||
ServiceName: "trivial",
|
||||
Annotations: []trace.Annotation{
|
||||
{
|
||||
Timestamp: time.Unix(0, 1433330263415871000).UTC(),
|
||||
Value: "cs",
|
||||
Host: "0:9410",
|
||||
ServiceName: "go-zipkin-testclient",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []testutil.Metric{
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"id": "6802735349851856000",
|
||||
"parent_id": "6802735349851856000",
|
||||
"trace_id": "0:6802735349851856000",
|
||||
"name": "main.dud",
|
||||
"service_name": "trivial",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(1) * time.Nanosecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(1, 0).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"annotation": "cs",
|
||||
"endpoint_host": "0:9410",
|
||||
"id": "6802735349851856000",
|
||||
"parent_id": "6802735349851856000",
|
||||
"trace_id": "0:6802735349851856000",
|
||||
"name": "main.dud",
|
||||
"service_name": "go-zipkin-testclient",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(1) * time.Nanosecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(1, 0).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockAcc.ClearMetrics()
|
||||
l := &lineProtocolConverter{
|
||||
acc: tt.fields.acc,
|
||||
}
|
||||
err := l.record(tt.args.t)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
got := make([]testutil.Metric, 0, len(mockAcc.Metrics))
|
||||
for _, metric := range mockAcc.Metrics {
|
||||
got = append(got, *metric)
|
||||
}
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
141
plugins/inputs/zipkin/handler.go
Normal file
141
plugins/inputs/zipkin/handler.go
Normal file
|
@ -0,0 +1,141 @@
|
|||
package zipkin
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/influxdata/telegraf/plugins/inputs/zipkin/codec"
|
||||
json_v1 "github.com/influxdata/telegraf/plugins/inputs/zipkin/codec/jsonV1"
|
||||
"github.com/influxdata/telegraf/plugins/inputs/zipkin/codec/thrift"
|
||||
)
|
||||
|
||||
// spanHandler is an implementation of a handler which accepts zipkin thrift span data and sends it to the recorder
|
||||
type spanHandler struct {
|
||||
path string
|
||||
recorder recorder
|
||||
}
|
||||
|
||||
func newSpanHandler(path string) *spanHandler {
|
||||
return &spanHandler{
|
||||
path: path,
|
||||
}
|
||||
}
|
||||
|
||||
func cors(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if origin := r.Header.Get("Origin"); origin != "" {
|
||||
w.Header().Set(`Access-Control-Allow-Origin`, origin)
|
||||
w.Header().Set(`Access-Control-Allow-Methods`, strings.Join([]string{
|
||||
`OPTIONS`,
|
||||
`POST`,
|
||||
}, ", "))
|
||||
|
||||
w.Header().Set(`Access-Control-Allow-Headers`, strings.Join([]string{
|
||||
`Accept`,
|
||||
`Accept-Encoding`,
|
||||
`Content-Length`,
|
||||
`Content-Type`,
|
||||
}, ", "))
|
||||
|
||||
w.Header().Set(`Access-Control-Expose-Headers`, strings.Join([]string{
|
||||
`Date`,
|
||||
}, ", "))
|
||||
}
|
||||
|
||||
if r.Method == "OPTIONS" {
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// register implements the Service interface. Register accepts zipkin thrift data
|
||||
// POSTed to the path of the mux router
|
||||
func (s *spanHandler) register(router *mux.Router, recorder recorder) error {
|
||||
handler := cors(http.HandlerFunc(s.spans))
|
||||
router.Handle(s.path, handler).Methods("POST", "OPTIONS")
|
||||
s.recorder = recorder
|
||||
return nil
|
||||
}
|
||||
|
||||
// spans handles zipkin thrift spans
|
||||
func (s *spanHandler) spans(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
body := r.Body
|
||||
var err error
|
||||
// Handle gzip decoding of the body
|
||||
if r.Header.Get("Content-Encoding") == "gzip" {
|
||||
body, err = gzip.NewReader(r.Body)
|
||||
if err != nil {
|
||||
s.recorder.error(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer body.Close()
|
||||
}
|
||||
|
||||
decoder, err := contentDecoder(r)
|
||||
if err != nil {
|
||||
s.recorder.error(err)
|
||||
w.WriteHeader(http.StatusUnsupportedMediaType)
|
||||
}
|
||||
|
||||
octets, err := io.ReadAll(body)
|
||||
if err != nil {
|
||||
s.recorder.error(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
spans, err := decoder.Decode(octets)
|
||||
if err != nil {
|
||||
s.recorder.error(err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
trace, err := codec.NewTrace(spans)
|
||||
if err != nil {
|
||||
s.recorder.error(err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.recorder.record(trace); err != nil {
|
||||
s.recorder.error(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// contentDecoder returns a Decoder that is able to produce Traces from bytes.
|
||||
// Failure should yield an HTTP 415 (`http.StatusUnsupportedMediaType`)
|
||||
// If a Content-Type is not set, zipkin assumes application/json
|
||||
func contentDecoder(r *http.Request) (codec.Decoder, error) {
|
||||
contentType := r.Header.Get("Content-Type")
|
||||
if contentType == "" {
|
||||
return &json_v1.JSON{}, nil
|
||||
}
|
||||
|
||||
for _, v := range strings.Split(contentType, ",") {
|
||||
t, _, err := mime.ParseMediaType(v)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if t == "application/json" {
|
||||
return &json_v1.JSON{}, nil
|
||||
} else if t == "application/x-thrift" {
|
||||
return &thrift.Thrift{}, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("unknown Content-Type: %s", contentType)
|
||||
}
|
133
plugins/inputs/zipkin/handler_test.go
Normal file
133
plugins/inputs/zipkin/handler_test.go
Normal file
|
@ -0,0 +1,133 @@
|
|||
package zipkin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf/plugins/inputs/zipkin/trace"
|
||||
)
|
||||
|
||||
type mockRecorder struct {
|
||||
data trace.Trace
|
||||
err error
|
||||
}
|
||||
|
||||
func (m *mockRecorder) record(t trace.Trace) error {
|
||||
m.data = t
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockRecorder) error(err error) {
|
||||
m.err = err
|
||||
}
|
||||
|
||||
func TestSpanHandler(t *testing.T) {
|
||||
dat, err := os.ReadFile("testdata/threespans.dat")
|
||||
if err != nil {
|
||||
t.Fatalf("Could not find file %s\n", "testdata/threespans.dat")
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(
|
||||
"POST",
|
||||
"http://server.local/api/v1/spans",
|
||||
io.NopCloser(
|
||||
bytes.NewReader(dat)))
|
||||
|
||||
r.Header.Set("Content-Type", "application/x-thrift")
|
||||
handler := newSpanHandler("/api/v1/spans")
|
||||
mockRecorder := &mockRecorder{}
|
||||
handler.recorder = mockRecorder
|
||||
|
||||
handler.spans(w, r)
|
||||
require.Equal(t, http.StatusNoContent, w.Code)
|
||||
|
||||
got := mockRecorder.data
|
||||
|
||||
parentID := strconv.FormatInt(22964302721410078, 16)
|
||||
want := trace.Trace{
|
||||
{
|
||||
Name: "Child",
|
||||
ID: "7047c59776af8a1b",
|
||||
TraceID: "22c4fc8ab3669045",
|
||||
ParentID: parentID,
|
||||
Timestamp: time.Unix(0, 1498688360851331*int64(time.Microsecond)).UTC(),
|
||||
Duration: time.Duration(53106) * time.Microsecond,
|
||||
ServiceName: "trivial",
|
||||
Annotations: make([]trace.Annotation, 0),
|
||||
BinaryAnnotations: []trace.BinaryAnnotation{
|
||||
{
|
||||
Key: "lc",
|
||||
Value: "trivial",
|
||||
Host: "127.0.0.1",
|
||||
ServiceName: "trivial",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Child",
|
||||
ID: "17020eb55a8bfe5",
|
||||
TraceID: "22c4fc8ab3669045",
|
||||
ParentID: parentID,
|
||||
Timestamp: time.Unix(0, 1498688360904552*int64(time.Microsecond)).UTC(),
|
||||
Duration: time.Duration(50410) * time.Microsecond,
|
||||
ServiceName: "trivial",
|
||||
Annotations: make([]trace.Annotation, 0),
|
||||
BinaryAnnotations: []trace.BinaryAnnotation{
|
||||
{
|
||||
Key: "lc",
|
||||
Value: "trivial",
|
||||
Host: "127.0.0.1",
|
||||
ServiceName: "trivial",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Parent",
|
||||
ID: "5195e96239641e",
|
||||
TraceID: "22c4fc8ab3669045",
|
||||
ParentID: parentID,
|
||||
Timestamp: time.Unix(0, 1498688360851318*int64(time.Microsecond)).UTC(),
|
||||
Duration: time.Duration(103680) * time.Microsecond,
|
||||
ServiceName: "trivial",
|
||||
Annotations: []trace.Annotation{
|
||||
{
|
||||
Timestamp: time.Unix(0, 1498688360851325*int64(time.Microsecond)).UTC(),
|
||||
Value: "Starting child #0",
|
||||
Host: "127.0.0.1",
|
||||
ServiceName: "trivial",
|
||||
},
|
||||
{
|
||||
Timestamp: time.Unix(0, 1498688360904545*int64(time.Microsecond)).UTC(),
|
||||
Value: "Starting child #1",
|
||||
Host: "127.0.0.1",
|
||||
ServiceName: "trivial",
|
||||
},
|
||||
{
|
||||
Timestamp: time.Unix(0, 1498688360954992*int64(time.Microsecond)).UTC(),
|
||||
Value: "A Log",
|
||||
Host: "127.0.0.1",
|
||||
ServiceName: "trivial",
|
||||
},
|
||||
},
|
||||
BinaryAnnotations: []trace.BinaryAnnotation{
|
||||
{
|
||||
Key: "lc",
|
||||
Value: "trivial",
|
||||
Host: "127.0.0.1",
|
||||
ServiceName: "trivial",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
require.Equal(t, want, got)
|
||||
}
|
12
plugins/inputs/zipkin/sample.conf
Normal file
12
plugins/inputs/zipkin/sample.conf
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Gather data from a Zipkin server including trace and timing data
|
||||
[[inputs.zipkin]]
|
||||
## URL path for span data
|
||||
# path = "/api/v1/spans"
|
||||
|
||||
## Port on which Telegraf listens
|
||||
# port = 9411
|
||||
|
||||
## Maximum duration before timing out read of the request
|
||||
# read_timeout = "10s"
|
||||
## Maximum duration before timing out write of the response
|
||||
# write_timeout = "10s"
|
BIN
plugins/inputs/zipkin/testdata/cli_microservice.dat
vendored
Normal file
BIN
plugins/inputs/zipkin/testdata/cli_microservice.dat
vendored
Normal file
Binary file not shown.
BIN
plugins/inputs/zipkin/testdata/distributed_trace_sample.dat
vendored
Normal file
BIN
plugins/inputs/zipkin/testdata/distributed_trace_sample.dat
vendored
Normal file
Binary file not shown.
188
plugins/inputs/zipkin/testdata/json/brave-tracer-example.json
vendored
Normal file
188
plugins/inputs/zipkin/testdata/json/brave-tracer-example.json
vendored
Normal file
|
@ -0,0 +1,188 @@
|
|||
[
|
||||
{
|
||||
"traceId": "7312f822d43d0fd8",
|
||||
"id": "b26412d1ac16767d",
|
||||
"name": "http:/hi2",
|
||||
"parentId": "7312f822d43d0fd8",
|
||||
"annotations": [
|
||||
{
|
||||
"timestamp": 1503031538791000,
|
||||
"value": "sr",
|
||||
"endpoint": {
|
||||
"serviceName": "test",
|
||||
"ipv4": "192.168.0.8",
|
||||
"port": 8010
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": 1503031538794000,
|
||||
"value": "ss",
|
||||
"endpoint": {
|
||||
"serviceName": "test",
|
||||
"ipv4": "192.168.0.8",
|
||||
"port": 8010
|
||||
}
|
||||
}
|
||||
],
|
||||
"binaryAnnotations": [
|
||||
{
|
||||
"key": "mvc.controller.class",
|
||||
"value": "Demo2Application",
|
||||
"endpoint": {
|
||||
"serviceName": "test",
|
||||
"ipv4": "192.168.0.8",
|
||||
"port": 8010
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "mvc.controller.method",
|
||||
"value": "hi2",
|
||||
"endpoint": {
|
||||
"serviceName": "test",
|
||||
"ipv4": "192.168.0.8",
|
||||
"port": 8010
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "spring.instance_id",
|
||||
"value": "192.168.0.8:test:8010",
|
||||
"endpoint": {
|
||||
"serviceName": "test",
|
||||
"ipv4": "192.168.0.8",
|
||||
"port": 8010
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"traceId": "7312f822d43d0fd8",
|
||||
"id": "b26412d1ac16767d",
|
||||
"name": "http:/hi2",
|
||||
"parentId": "7312f822d43d0fd8",
|
||||
"timestamp": 1503031538786000,
|
||||
"duration": 10000,
|
||||
"annotations": [
|
||||
{
|
||||
"timestamp": 1503031538786000,
|
||||
"value": "cs",
|
||||
"endpoint": {
|
||||
"serviceName": "test",
|
||||
"ipv4": "192.168.0.8",
|
||||
"port": 8010
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": 1503031538796000,
|
||||
"value": "cr",
|
||||
"endpoint": {
|
||||
"serviceName": "test",
|
||||
"ipv4": "192.168.0.8",
|
||||
"port": 8010
|
||||
}
|
||||
}
|
||||
],
|
||||
"binaryAnnotations": [
|
||||
{
|
||||
"key": "http.host",
|
||||
"value": "localhost",
|
||||
"endpoint": {
|
||||
"serviceName": "test",
|
||||
"ipv4": "192.168.0.8",
|
||||
"port": 8010
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.method",
|
||||
"value": "GET",
|
||||
"endpoint": {
|
||||
"serviceName": "test",
|
||||
"ipv4": "192.168.0.8",
|
||||
"port": 8010
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.path",
|
||||
"value": "/hi2",
|
||||
"endpoint": {
|
||||
"serviceName": "test",
|
||||
"ipv4": "192.168.0.8",
|
||||
"port": 8010
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.url",
|
||||
"value": "http://localhost:8010/hi2",
|
||||
"endpoint": {
|
||||
"serviceName": "test",
|
||||
"ipv4": "192.168.0.8",
|
||||
"port": 8010
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "spring.instance_id",
|
||||
"value": "192.168.0.8:test:8010",
|
||||
"endpoint": {
|
||||
"serviceName": "test",
|
||||
"ipv4": "192.168.0.8",
|
||||
"port": 8010
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"traceId": "7312f822d43d0fd8",
|
||||
"id": "7312f822d43d0fd8",
|
||||
"name": "http:/hi",
|
||||
"timestamp": 1503031538778000,
|
||||
"duration": 23393,
|
||||
"annotations": [
|
||||
{
|
||||
"timestamp": 1503031538778000,
|
||||
"value": "sr",
|
||||
"endpoint": {
|
||||
"serviceName": "test",
|
||||
"ipv4": "192.168.0.8",
|
||||
"port": 8010
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": 1503031538801000,
|
||||
"value": "ss",
|
||||
"endpoint": {
|
||||
"serviceName": "test",
|
||||
"ipv4": "192.168.0.8",
|
||||
"port": 8010
|
||||
}
|
||||
}
|
||||
],
|
||||
"binaryAnnotations": [
|
||||
{
|
||||
"key": "mvc.controller.class",
|
||||
"value": "Demo2Application",
|
||||
"endpoint": {
|
||||
"serviceName": "test",
|
||||
"ipv4": "192.168.0.8",
|
||||
"port": 8010
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "mvc.controller.method",
|
||||
"value": "hi",
|
||||
"endpoint": {
|
||||
"serviceName": "test",
|
||||
"ipv4": "192.168.0.8",
|
||||
"port": 8010
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "spring.instance_id",
|
||||
"value": "192.168.0.8:test:8010",
|
||||
"endpoint": {
|
||||
"serviceName": "test",
|
||||
"ipv4": "192.168.0.8",
|
||||
"port": 8010
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
407
plugins/inputs/zipkin/testdata/json/cli_microservice.json
vendored
Normal file
407
plugins/inputs/zipkin/testdata/json/cli_microservice.json
vendored
Normal file
|
@ -0,0 +1,407 @@
|
|||
[
|
||||
{
|
||||
"trace_id": 243463817635710260,
|
||||
"name": "Concat",
|
||||
"id": 3383422996321511664,
|
||||
"parent_id": 4574092882326506380,
|
||||
"annotations": [
|
||||
{
|
||||
"timestamp": 1499817952283903,
|
||||
"value": "cs",
|
||||
"host": {
|
||||
"ipv4": 0,
|
||||
"port": 0,
|
||||
"service_name": "cli"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": 1499817952286792,
|
||||
"value": "cr",
|
||||
"host": {
|
||||
"ipv4": 0,
|
||||
"port": 0,
|
||||
"service_name": "cli"
|
||||
}
|
||||
}
|
||||
],
|
||||
"binary_annotations": [
|
||||
{
|
||||
"key": "http.path",
|
||||
"value": "L2NvbmNhdC8=",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 0,
|
||||
"port": 0,
|
||||
"service_name": "cli"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.url",
|
||||
"value": "aHR0cDovL2xvY2FsaG9zdDo2MTAwMS9jb25jYXQv",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 0,
|
||||
"port": 0,
|
||||
"service_name": "cli"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "peer.hostname",
|
||||
"value": "bG9jYWxob3N0",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 0,
|
||||
"port": 0,
|
||||
"service_name": "cli"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "span.kind",
|
||||
"value": "Y2xpZW50",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 0,
|
||||
"port": 0,
|
||||
"service_name": "cli"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.method",
|
||||
"value": "R0VU",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 0,
|
||||
"port": 0,
|
||||
"service_name": "cli"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.host",
|
||||
"value": "bG9jYWxob3N0OjYxMDAx",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 0,
|
||||
"port": 0,
|
||||
"service_name": "cli"
|
||||
}
|
||||
}
|
||||
],
|
||||
"timestamp": 1499817952283903,
|
||||
"duration": 2888,
|
||||
"trace_id_high": 8269862291023777619
|
||||
},
|
||||
{
|
||||
"trace_id": 243463817635710260,
|
||||
"name": "Sum",
|
||||
"id": 6036416808826525494,
|
||||
"parent_id": 4574092882326506380,
|
||||
"annotations": [
|
||||
{
|
||||
"timestamp": 1499817952286828,
|
||||
"value": "cs",
|
||||
"host": {
|
||||
"ipv4": 0,
|
||||
"port": 0,
|
||||
"service_name": "cli"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": 1499817952333847,
|
||||
"value": "cr",
|
||||
"host": {
|
||||
"ipv4": 0,
|
||||
"port": 0,
|
||||
"service_name": "cli"
|
||||
}
|
||||
}
|
||||
],
|
||||
"binary_annotations": [
|
||||
{
|
||||
"key": "span.kind",
|
||||
"value": "Y2xpZW50",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 0,
|
||||
"port": 0,
|
||||
"service_name": "cli"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.method",
|
||||
"value": "R0VU",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 0,
|
||||
"port": 0,
|
||||
"service_name": "cli"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.host",
|
||||
"value": "bG9jYWxob3N0OjYxMDAx",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 0,
|
||||
"port": 0,
|
||||
"service_name": "cli"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.path",
|
||||
"value": "L3N1bS8=",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 0,
|
||||
"port": 0,
|
||||
"service_name": "cli"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "http.url",
|
||||
"value": "aHR0cDovL2xvY2FsaG9zdDo2MTAwMS9zdW0v",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 0,
|
||||
"port": 0,
|
||||
"service_name": "cli"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "peer.hostname",
|
||||
"value": "bG9jYWxob3N0",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 0,
|
||||
"port": 0,
|
||||
"service_name": "cli"
|
||||
}
|
||||
}
|
||||
],
|
||||
"timestamp": 1499817952286828,
|
||||
"duration": 47019,
|
||||
"trace_id_high": 8269862291023777619
|
||||
},
|
||||
{
|
||||
"trace_id": 243463817635710260,
|
||||
"name": "Run",
|
||||
"id": 4574092882326506380,
|
||||
"annotations": [
|
||||
{
|
||||
"timestamp": 1499817952283897,
|
||||
"value": "Call Concat",
|
||||
"host": {
|
||||
"ipv4": 0,
|
||||
"port": 0,
|
||||
"service_name": "cli"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": 1499817952286824,
|
||||
"value": "Call Sum",
|
||||
"host": {
|
||||
"ipv4": 0,
|
||||
"port": 0,
|
||||
"service_name": "cli"
|
||||
}
|
||||
}
|
||||
],
|
||||
"binary_annotations": [
|
||||
{
|
||||
"key": "lc",
|
||||
"value": "Y2xp",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 0,
|
||||
"port": 0,
|
||||
"service_name": "cli"
|
||||
}
|
||||
}
|
||||
],
|
||||
"timestamp": 1499817952283881,
|
||||
"duration": 50014,
|
||||
"trace_id_high": 8269862291023777619
|
||||
}
|
||||
]
|
||||
|
||||
[
|
||||
{
|
||||
"trace_id": 243463817635710260,
|
||||
"name": "myComplexQuery",
|
||||
"id": 4254041670140233539,
|
||||
"parent_id": 8633460035494236932,
|
||||
"annotations": [
|
||||
{
|
||||
"timestamp": 1499817952307418,
|
||||
"value": "cs",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": -4534,
|
||||
"service_name": "svc2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": 1499817952331909,
|
||||
"value": "cr",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": -4534,
|
||||
"service_name": "svc2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"binary_annotations": [
|
||||
{
|
||||
"key": "sa",
|
||||
"value": "UG9zdGdyZVNRTA==",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": 5432,
|
||||
"service_name": "PostgreSQL",
|
||||
"ipv6": "AAAAAAAAAAAAAAAAAAAAAQ=="
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "query",
|
||||
"value": "U0VMRUNUIHJlY2lwZXMgRlJPTSBjb29rYm9vayBXSEVSRSB0b3BpYyA9ICd3b3JsZCBkb21pbmF0aW9uJw==",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": -4534,
|
||||
"service_name": "svc2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "span.kind",
|
||||
"value": "cmVzb3VyY2U=",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": -4534,
|
||||
"service_name": "svc2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "peer.service",
|
||||
"value": "UG9zdGdyZVNRTA==",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": -4534,
|
||||
"service_name": "svc2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "peer.hostname",
|
||||
"value": "bG9jYWxob3N0",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": -4534,
|
||||
"service_name": "svc2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "peer.port",
|
||||
"value": "AAAVOA==",
|
||||
"annotation_type": "I32",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": -4534,
|
||||
"service_name": "svc2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"timestamp": 1499817952307418,
|
||||
"duration": 24491,
|
||||
"trace_id_high": 8269862291023777619
|
||||
},
|
||||
{
|
||||
"trace_id": 243463817635710260,
|
||||
"name": "Sum",
|
||||
"id": 8633460035494236932,
|
||||
"parent_id": 6036416808826525494,
|
||||
"annotations": [
|
||||
{
|
||||
"timestamp": 1499817952287147,
|
||||
"value": "sr",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": -4534,
|
||||
"service_name": "svc2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": 1499817952333348,
|
||||
"value": "ss",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": -4534,
|
||||
"service_name": "svc2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": 1499817952296675,
|
||||
"value": "MyEventAnnotation",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": -4534,
|
||||
"service_name": "svc2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"binary_annotations": [
|
||||
{
|
||||
"key": "span.kind",
|
||||
"value": "c2VydmVy",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": -4534,
|
||||
"service_name": "svc2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "serverSide",
|
||||
"value": "aGVyZQ==",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": -4534,
|
||||
"service_name": "svc2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "c3ZjMg==",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": -4534,
|
||||
"service_name": "svc2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "key1",
|
||||
"value": "dmFsdWUx",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": -4534,
|
||||
"service_name": "svc2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "key2",
|
||||
"value": "AAAAAgAAAAA=",
|
||||
"annotation_type": "I32",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": -4534,
|
||||
"service_name": "svc2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"trace_id_high": 8269862291023777619
|
||||
}
|
||||
]
|
30
plugins/inputs/zipkin/testdata/json/distributed_trace_sample.json
vendored
Normal file
30
plugins/inputs/zipkin/testdata/json/distributed_trace_sample.json
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
[{
|
||||
"trace_id": 6802735349851856000,
|
||||
"name": "main.dud",
|
||||
"id": 6802735349851856000,
|
||||
"parent_id": null,
|
||||
"annotations": [
|
||||
{
|
||||
"timestamp": 1433330263415871,
|
||||
"value": "cs",
|
||||
"host": {
|
||||
"ipv4": 0,
|
||||
"port": 9410,
|
||||
"service_name": "go-zipkin-testclient"
|
||||
},
|
||||
"duration": null
|
||||
},
|
||||
{
|
||||
"timestamp": 1433330263415872,
|
||||
"value": "cr",
|
||||
"host": {
|
||||
"ipv4": 0,
|
||||
"port": 9410,
|
||||
"service_name": "go-zipkin-testclient"
|
||||
},
|
||||
"duration": null
|
||||
}
|
||||
],
|
||||
"binary_annotations": [],
|
||||
"debug": true
|
||||
}]
|
92
plugins/inputs/zipkin/testdata/json/threespans.json
vendored
Normal file
92
plugins/inputs/zipkin/testdata/json/threespans.json
vendored
Normal file
|
@ -0,0 +1,92 @@
|
|||
[
|
||||
{
|
||||
"trace_id": 2505404965370368069,
|
||||
"name": "Child",
|
||||
"id": 8090652509916334619,
|
||||
"parent_id": 22964302721410078,
|
||||
"annotations": [],
|
||||
"binary_annotations": [
|
||||
{
|
||||
"key": "lc",
|
||||
"value": "dHJpdmlhbA==",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": 0,
|
||||
"service_name": "trivial"
|
||||
}
|
||||
}
|
||||
],
|
||||
"timestamp": 1498688360851331,
|
||||
"duration": 53106
|
||||
},
|
||||
{
|
||||
"trace_id": 2505404965370368069,
|
||||
"name": "Child",
|
||||
"id": 103618986556047333,
|
||||
"parent_id": 22964302721410078,
|
||||
"annotations": [],
|
||||
"binary_annotations": [
|
||||
{
|
||||
"key": "lc",
|
||||
"value": "dHJpdmlhbA==",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": 0,
|
||||
"service_name": "trivial"
|
||||
}
|
||||
}
|
||||
],
|
||||
"timestamp": 1498688360904552,
|
||||
"duration": 50410
|
||||
},
|
||||
{
|
||||
"trace_id": 2505404965370368069,
|
||||
"name": "Parent",
|
||||
"id": 22964302721410078,
|
||||
"annotations": [
|
||||
{
|
||||
"timestamp": 1498688360851325,
|
||||
"value": "Starting child #0",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": 0,
|
||||
"service_name": "trivial"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": 1498688360904545,
|
||||
"value": "Starting child #1",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": 0,
|
||||
"service_name": "trivial"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": 1498688360954992,
|
||||
"value": "A Log",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": 0,
|
||||
"service_name": "trivial"
|
||||
}
|
||||
}
|
||||
],
|
||||
"binary_annotations": [
|
||||
{
|
||||
"key": "lc",
|
||||
"value": "dHJpdmlhbA==",
|
||||
"annotation_type": "STRING",
|
||||
"host": {
|
||||
"ipv4": 2130706433,
|
||||
"port": 0,
|
||||
"service_name": "trivial"
|
||||
}
|
||||
}
|
||||
],
|
||||
"timestamp": 1498688360851318,
|
||||
"duration": 103680
|
||||
}
|
||||
]
|
BIN
plugins/inputs/zipkin/testdata/threespans.dat
vendored
Normal file
BIN
plugins/inputs/zipkin/testdata/threespans.dat
vendored
Normal file
Binary file not shown.
41
plugins/inputs/zipkin/trace/trace.go
Normal file
41
plugins/inputs/zipkin/trace/trace.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package trace
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Trace is an array (or a series) of spans
|
||||
type Trace []Span
|
||||
|
||||
// Span represents a specific zipkin span. It holds the majority of the same
|
||||
// data as a zipkin span sent via the thrift protocol, but is presented in a
|
||||
// format which is more straightforward for storage purposes.
|
||||
type Span struct {
|
||||
ID string
|
||||
TraceID string // zipkin traceid high concat with traceid
|
||||
Name string
|
||||
ParentID string
|
||||
ServiceName string
|
||||
Timestamp time.Time // If zipkin input is nil then time.Now()
|
||||
Duration time.Duration
|
||||
Annotations []Annotation
|
||||
BinaryAnnotations []BinaryAnnotation
|
||||
}
|
||||
|
||||
// BinaryAnnotation represents a zipkin binary annotation. It contains
|
||||
// all of the same fields as might be found in its zipkin counterpart.
|
||||
type BinaryAnnotation struct {
|
||||
Key string
|
||||
Value string
|
||||
Host string // annotation.endpoint.ipv4 + ":" + annotation.endpoint.port
|
||||
ServiceName string
|
||||
}
|
||||
|
||||
// Annotation represents an ordinary zipkin annotation. It contains the data fields
|
||||
// which will become fields/tags in influxdb
|
||||
type Annotation struct {
|
||||
Timestamp time.Time
|
||||
Value string
|
||||
Host string // annotation.endpoint.ipv4 + ":" + annotation.endpoint.port
|
||||
ServiceName string
|
||||
}
|
150
plugins/inputs/zipkin/zipkin.go
Normal file
150
plugins/inputs/zipkin/zipkin.go
Normal file
|
@ -0,0 +1,150 @@
|
|||
//go:generate ../../../tools/readme_config_includer/generator
|
||||
package zipkin
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
"github.com/influxdata/telegraf/plugins/inputs/zipkin/trace"
|
||||
)
|
||||
|
||||
//go:embed sample.conf
|
||||
var sampleConfig string
|
||||
|
||||
var (
|
||||
// defaultNetwork is the network to listen on; use only in tests.
|
||||
defaultNetwork = "tcp"
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultPort is the default port zipkin listens on, which zipkin implementations expect.
|
||||
defaultPort = 9411
|
||||
|
||||
// defaultRoute is the default route zipkin uses, and zipkin implementations expect.
|
||||
defaultRoute = "/api/v1/spans"
|
||||
|
||||
// defaultShutdownTimeout is the max amount of time telegraf will wait for the plugin to shut down
|
||||
defaultShutdownTimeout = 5 * time.Second
|
||||
|
||||
defaultReadTimeout = 10 * time.Second
|
||||
defaultWriteTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
type Zipkin struct {
|
||||
Port int `toml:"port"`
|
||||
Path string `toml:"path"`
|
||||
ReadTimeout config.Duration `toml:"read_timeout"`
|
||||
WriteTimeout config.Duration `toml:"write_timeout"`
|
||||
|
||||
Log telegraf.Logger `toml:"-"`
|
||||
|
||||
address string
|
||||
handler handler
|
||||
server *http.Server
|
||||
waitGroup *sync.WaitGroup
|
||||
}
|
||||
|
||||
// recorder represents a type which can record zipkin trace data as well as any accompanying errors, and process that data.
|
||||
type recorder interface {
|
||||
record(trace.Trace) error
|
||||
error(error)
|
||||
}
|
||||
|
||||
// handler represents a type which can register itself with a router for http routing, and a recorder for trace data collection.
|
||||
type handler interface {
|
||||
register(router *mux.Router, recorder recorder) error
|
||||
}
|
||||
|
||||
func (*Zipkin) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (z *Zipkin) Start(acc telegraf.Accumulator) error {
|
||||
if z.ReadTimeout < config.Duration(time.Second) {
|
||||
z.ReadTimeout = config.Duration(defaultReadTimeout)
|
||||
}
|
||||
if z.WriteTimeout < config.Duration(time.Second) {
|
||||
z.WriteTimeout = config.Duration(defaultWriteTimeout)
|
||||
}
|
||||
|
||||
z.handler = newSpanHandler(z.Path)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
z.waitGroup = &wg
|
||||
|
||||
router := mux.NewRouter()
|
||||
converter := newLineProtocolConverter(acc)
|
||||
if err := z.handler.register(router, converter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
z.server = &http.Server{
|
||||
Handler: router,
|
||||
ReadTimeout: time.Duration(z.ReadTimeout),
|
||||
WriteTimeout: time.Duration(z.WriteTimeout),
|
||||
}
|
||||
|
||||
addr := ":" + strconv.Itoa(z.Port)
|
||||
ln, err := net.Listen(defaultNetwork, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
z.address = ln.Addr().String()
|
||||
z.Log.Infof("Started the zipkin listener on %s", z.address)
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
z.listen(ln, acc)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*Zipkin) Gather(telegraf.Accumulator) error { return nil }
|
||||
|
||||
func (z *Zipkin) Stop() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultShutdownTimeout)
|
||||
|
||||
defer z.waitGroup.Wait()
|
||||
defer cancel()
|
||||
|
||||
z.server.Shutdown(ctx) //nolint:errcheck // Ignore the returned error as we cannot do anything about it anyway
|
||||
}
|
||||
|
||||
// listen creates a http server on the zipkin instance it is called with, and
|
||||
// serves http until it is stopped by Zipkin's (*Zipkin).Stop() method.
|
||||
func (z *Zipkin) listen(ln net.Listener, acc telegraf.Accumulator) {
|
||||
if err := z.server.Serve(ln); err != nil {
|
||||
// Because of the clean shutdown in `(*Zipkin).Stop()`
|
||||
// We're expecting a server closed error at some point
|
||||
// So we don't want to display it as an error.
|
||||
// This interferes with telegraf's internal data collection,
|
||||
// by making it appear as if a serious error occurred.
|
||||
if err != http.ErrServerClosed {
|
||||
acc.AddError(fmt.Errorf("error listening: %w", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add("zipkin", func() telegraf.Input {
|
||||
return &Zipkin{
|
||||
Path: defaultRoute,
|
||||
Port: defaultPort,
|
||||
}
|
||||
})
|
||||
}
|
662
plugins/inputs/zipkin/zipkin_test.go
Normal file
662
plugins/inputs/zipkin/zipkin_test.go
Normal file
|
@ -0,0 +1,662 @@
|
|||
package zipkin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
func TestZipkinPlugin(t *testing.T) {
|
||||
mockAcc := testutil.Accumulator{}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
datafile string // data file which contains test data
|
||||
contentType string
|
||||
wantErr bool
|
||||
want []testutil.Metric
|
||||
}{
|
||||
{
|
||||
name: "threespan",
|
||||
datafile: "testdata/threespans.dat",
|
||||
contentType: "application/x-thrift",
|
||||
want: []testutil.Metric{
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"id": "7047c59776af8a1b",
|
||||
"parent_id": "5195e96239641e",
|
||||
"trace_id": "22c4fc8ab3669045",
|
||||
"service_name": "trivial",
|
||||
"name": "child",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(53106) * time.Microsecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(0, 1498688360851331000).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"id": "7047c59776af8a1b",
|
||||
"parent_id": "5195e96239641e",
|
||||
"trace_id": "22c4fc8ab3669045",
|
||||
"name": "child",
|
||||
"service_name": "trivial",
|
||||
"annotation": "trivial", // base64: dHJpdmlhbA==
|
||||
"endpoint_host": "127.0.0.1",
|
||||
"annotation_key": "lc",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(53106) * time.Microsecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(0, 1498688360851331000).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"id": "17020eb55a8bfe5",
|
||||
"parent_id": "5195e96239641e",
|
||||
"trace_id": "22c4fc8ab3669045",
|
||||
"service_name": "trivial",
|
||||
"name": "child",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(50410) * time.Microsecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(0, 1498688360904552000).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"id": "17020eb55a8bfe5",
|
||||
"parent_id": "5195e96239641e",
|
||||
"trace_id": "22c4fc8ab3669045",
|
||||
"name": "child",
|
||||
"service_name": "trivial",
|
||||
"annotation": "trivial", // base64: dHJpdmlhbA==
|
||||
"endpoint_host": "127.0.0.1",
|
||||
"annotation_key": "lc",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(50410) * time.Microsecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(0, 1498688360904552000).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"id": "5195e96239641e",
|
||||
"parent_id": "5195e96239641e",
|
||||
"trace_id": "22c4fc8ab3669045",
|
||||
"service_name": "trivial",
|
||||
"name": "parent",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(103680) * time.Microsecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(0, 1498688360851318000).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"service_name": "trivial",
|
||||
"annotation": "Starting child #0",
|
||||
"endpoint_host": "127.0.0.1",
|
||||
"id": "5195e96239641e",
|
||||
"parent_id": "5195e96239641e",
|
||||
"trace_id": "22c4fc8ab3669045",
|
||||
"name": "parent",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(103680) * time.Microsecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(0, 1498688360851318000).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"service_name": "trivial",
|
||||
"annotation": "Starting child #1",
|
||||
"endpoint_host": "127.0.0.1",
|
||||
"id": "5195e96239641e",
|
||||
"parent_id": "5195e96239641e",
|
||||
"trace_id": "22c4fc8ab3669045",
|
||||
"name": "parent",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(103680) * time.Microsecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(0, 1498688360851318000).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"parent_id": "5195e96239641e",
|
||||
"trace_id": "22c4fc8ab3669045",
|
||||
"name": "parent",
|
||||
"service_name": "trivial",
|
||||
"annotation": "A Log",
|
||||
"endpoint_host": "127.0.0.1",
|
||||
"id": "5195e96239641e",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(103680) * time.Microsecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(0, 1498688360851318000).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"trace_id": "22c4fc8ab3669045",
|
||||
"service_name": "trivial",
|
||||
"annotation": "trivial", // base64: dHJpdmlhbA==
|
||||
"annotation_key": "lc",
|
||||
"id": "5195e96239641e",
|
||||
"parent_id": "5195e96239641e",
|
||||
"name": "parent",
|
||||
"endpoint_host": "127.0.0.1",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(103680) * time.Microsecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(0, 1498688360851318000).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "distributed_trace_sample",
|
||||
datafile: "testdata/distributed_trace_sample.dat",
|
||||
contentType: "application/x-thrift",
|
||||
want: []testutil.Metric{
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"id": "5e682bc21ce99c80",
|
||||
"parent_id": "5e682bc21ce99c80",
|
||||
"trace_id": "5e682bc21ce99c80",
|
||||
"service_name": "go-zipkin-testclient",
|
||||
"name": "main.dud",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(1) * time.Microsecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(0, 1433330263415871*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"annotation": "cs",
|
||||
"endpoint_host": "0.0.0.0:9410",
|
||||
"id": "5e682bc21ce99c80",
|
||||
"parent_id": "5e682bc21ce99c80",
|
||||
"trace_id": "5e682bc21ce99c80",
|
||||
"name": "main.dud",
|
||||
"service_name": "go-zipkin-testclient",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(1) * time.Microsecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(0, 1433330263415871*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"annotation": "cr",
|
||||
"endpoint_host": "0.0.0.0:9410",
|
||||
"id": "5e682bc21ce99c80",
|
||||
"parent_id": "5e682bc21ce99c80",
|
||||
"trace_id": "5e682bc21ce99c80",
|
||||
"name": "main.dud",
|
||||
"service_name": "go-zipkin-testclient",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": (time.Duration(1) * time.Microsecond).Nanoseconds(),
|
||||
},
|
||||
Time: time.Unix(0, 1433330263415871*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "JSON rather than thrift",
|
||||
datafile: "testdata/json/brave-tracer-example.json",
|
||||
contentType: "application/json",
|
||||
want: []testutil.Metric{
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"id": "b26412d1ac16767d",
|
||||
"name": "http:/hi2",
|
||||
"parent_id": "7312f822d43d0fd8",
|
||||
"service_name": "test",
|
||||
"trace_id": "7312f822d43d0fd8",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": int64(3000000),
|
||||
},
|
||||
Time: time.Unix(0, 1503031538791000*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"annotation": "sr",
|
||||
"endpoint_host": "192.168.0.8:8010",
|
||||
"id": "b26412d1ac16767d",
|
||||
"name": "http:/hi2",
|
||||
"parent_id": "7312f822d43d0fd8",
|
||||
"service_name": "test",
|
||||
"trace_id": "7312f822d43d0fd8",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": int64(3000000),
|
||||
},
|
||||
Time: time.Unix(0, 1503031538791000*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"annotation": "ss",
|
||||
"endpoint_host": "192.168.0.8:8010",
|
||||
"id": "b26412d1ac16767d",
|
||||
"name": "http:/hi2",
|
||||
"parent_id": "7312f822d43d0fd8",
|
||||
"service_name": "test",
|
||||
"trace_id": "7312f822d43d0fd8",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": int64(3000000),
|
||||
},
|
||||
Time: time.Unix(0, 1503031538791000*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"annotation": "Demo2Application",
|
||||
"annotation_key": "mvc.controller.class",
|
||||
"endpoint_host": "192.168.0.8:8010",
|
||||
"id": "b26412d1ac16767d",
|
||||
"name": "http:/hi2",
|
||||
"parent_id": "7312f822d43d0fd8",
|
||||
"service_name": "test",
|
||||
"trace_id": "7312f822d43d0fd8",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": int64(3000000),
|
||||
},
|
||||
Time: time.Unix(0, 1503031538791000*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"annotation": "hi2",
|
||||
"annotation_key": "mvc.controller.method",
|
||||
"endpoint_host": "192.168.0.8:8010",
|
||||
"id": "b26412d1ac16767d",
|
||||
"name": "http:/hi2",
|
||||
"parent_id": "7312f822d43d0fd8",
|
||||
"service_name": "test",
|
||||
"trace_id": "7312f822d43d0fd8",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": int64(3000000),
|
||||
},
|
||||
Time: time.Unix(0, 1503031538791000*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"annotation": "192.168.0.8:test:8010",
|
||||
"annotation_key": "spring.instance_id",
|
||||
"endpoint_host": "192.168.0.8:8010",
|
||||
"id": "b26412d1ac16767d",
|
||||
"name": "http:/hi2",
|
||||
"parent_id": "7312f822d43d0fd8",
|
||||
"service_name": "test",
|
||||
"trace_id": "7312f822d43d0fd8",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": int64(3000000),
|
||||
},
|
||||
Time: time.Unix(0, 1503031538791000*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"id": "b26412d1ac16767d",
|
||||
"name": "http:/hi2",
|
||||
"parent_id": "7312f822d43d0fd8",
|
||||
"service_name": "test",
|
||||
"trace_id": "7312f822d43d0fd8",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": int64(10000000),
|
||||
},
|
||||
Time: time.Unix(0, 1503031538786000*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"annotation": "cs",
|
||||
"endpoint_host": "192.168.0.8:8010",
|
||||
"id": "b26412d1ac16767d",
|
||||
"name": "http:/hi2",
|
||||
"parent_id": "7312f822d43d0fd8",
|
||||
"service_name": "test",
|
||||
"trace_id": "7312f822d43d0fd8",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": int64(10000000),
|
||||
},
|
||||
Time: time.Unix(0, 1503031538786000*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"annotation": "cr",
|
||||
"endpoint_host": "192.168.0.8:8010",
|
||||
"id": "b26412d1ac16767d",
|
||||
"name": "http:/hi2",
|
||||
"parent_id": "7312f822d43d0fd8",
|
||||
"service_name": "test",
|
||||
"trace_id": "7312f822d43d0fd8",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": int64(10000000),
|
||||
},
|
||||
Time: time.Unix(0, 1503031538786000*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"annotation": "localhost",
|
||||
"annotation_key": "http.host",
|
||||
"endpoint_host": "192.168.0.8:8010",
|
||||
"id": "b26412d1ac16767d",
|
||||
"name": "http:/hi2",
|
||||
"parent_id": "7312f822d43d0fd8",
|
||||
"service_name": "test",
|
||||
"trace_id": "7312f822d43d0fd8",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": int64(10000000),
|
||||
},
|
||||
Time: time.Unix(0, 1503031538786000*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"annotation": "GET",
|
||||
"annotation_key": "http.method",
|
||||
"endpoint_host": "192.168.0.8:8010",
|
||||
"id": "b26412d1ac16767d",
|
||||
"name": "http:/hi2",
|
||||
"parent_id": "7312f822d43d0fd8",
|
||||
"service_name": "test",
|
||||
"trace_id": "7312f822d43d0fd8",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": int64(10000000),
|
||||
},
|
||||
Time: time.Unix(0, 1503031538786000*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"annotation": "/hi2",
|
||||
"annotation_key": "http.path",
|
||||
"endpoint_host": "192.168.0.8:8010",
|
||||
"id": "b26412d1ac16767d",
|
||||
"name": "http:/hi2",
|
||||
"parent_id": "7312f822d43d0fd8",
|
||||
"service_name": "test",
|
||||
"trace_id": "7312f822d43d0fd8",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": int64(10000000),
|
||||
},
|
||||
Time: time.Unix(0, 1503031538786000*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"annotation": "http://localhost:8010/hi2",
|
||||
"annotation_key": "http.url",
|
||||
"endpoint_host": "192.168.0.8:8010",
|
||||
"id": "b26412d1ac16767d",
|
||||
"name": "http:/hi2",
|
||||
"parent_id": "7312f822d43d0fd8",
|
||||
"service_name": "test",
|
||||
"trace_id": "7312f822d43d0fd8",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": int64(10000000),
|
||||
},
|
||||
Time: time.Unix(0, 1503031538786000*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"annotation": "192.168.0.8:test:8010",
|
||||
"annotation_key": "spring.instance_id",
|
||||
"endpoint_host": "192.168.0.8:8010",
|
||||
"id": "b26412d1ac16767d",
|
||||
"name": "http:/hi2",
|
||||
"parent_id": "7312f822d43d0fd8",
|
||||
"service_name": "test",
|
||||
"trace_id": "7312f822d43d0fd8",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": int64(10000000),
|
||||
},
|
||||
Time: time.Unix(0, 1503031538786000*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"id": "7312f822d43d0fd8",
|
||||
"name": "http:/hi",
|
||||
"parent_id": "7312f822d43d0fd8",
|
||||
"service_name": "test",
|
||||
"trace_id": "7312f822d43d0fd8",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": int64(23393000),
|
||||
},
|
||||
Time: time.Unix(0, 1503031538778000*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"annotation": "sr",
|
||||
"endpoint_host": "192.168.0.8:8010",
|
||||
"id": "7312f822d43d0fd8",
|
||||
"name": "http:/hi",
|
||||
"parent_id": "7312f822d43d0fd8",
|
||||
"service_name": "test",
|
||||
"trace_id": "7312f822d43d0fd8",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": int64(23393000),
|
||||
},
|
||||
Time: time.Unix(0, 1503031538778000*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"annotation": "ss",
|
||||
"endpoint_host": "192.168.0.8:8010",
|
||||
"id": "7312f822d43d0fd8",
|
||||
"name": "http:/hi",
|
||||
"parent_id": "7312f822d43d0fd8",
|
||||
"service_name": "test",
|
||||
"trace_id": "7312f822d43d0fd8",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": int64(23393000),
|
||||
},
|
||||
Time: time.Unix(0, 1503031538778000*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"annotation": "Demo2Application",
|
||||
"annotation_key": "mvc.controller.class",
|
||||
"endpoint_host": "192.168.0.8:8010",
|
||||
"id": "7312f822d43d0fd8",
|
||||
"name": "http:/hi",
|
||||
"parent_id": "7312f822d43d0fd8",
|
||||
"service_name": "test",
|
||||
"trace_id": "7312f822d43d0fd8",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": int64(23393000),
|
||||
},
|
||||
Time: time.Unix(0, 1503031538778000*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"annotation": "hi",
|
||||
"annotation_key": "mvc.controller.method",
|
||||
"endpoint_host": "192.168.0.8:8010",
|
||||
"id": "7312f822d43d0fd8",
|
||||
"name": "http:/hi",
|
||||
"parent_id": "7312f822d43d0fd8",
|
||||
"service_name": "test",
|
||||
"trace_id": "7312f822d43d0fd8",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": int64(23393000),
|
||||
},
|
||||
Time: time.Unix(0, 1503031538778000*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
{
|
||||
Measurement: "zipkin",
|
||||
Tags: map[string]string{
|
||||
"annotation": "192.168.0.8:test:8010",
|
||||
"annotation_key": "spring.instance_id",
|
||||
"endpoint_host": "192.168.0.8:8010",
|
||||
"id": "7312f822d43d0fd8",
|
||||
"name": "http:/hi",
|
||||
"parent_id": "7312f822d43d0fd8",
|
||||
"service_name": "test",
|
||||
"trace_id": "7312f822d43d0fd8",
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"duration_ns": int64(23393000),
|
||||
},
|
||||
Time: time.Unix(0, 1503031538778000*int64(time.Microsecond)).UTC(),
|
||||
Type: telegraf.Untyped,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Workaround for Go 1.8
|
||||
// https://github.com/golang/go/issues/18806
|
||||
defaultNetwork = "tcp4"
|
||||
|
||||
z := &Zipkin{
|
||||
Log: testutil.Logger{},
|
||||
Path: "/api/v1/spans",
|
||||
Port: 0,
|
||||
}
|
||||
|
||||
err := z.Start(&mockAcc)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to start zipkin server")
|
||||
}
|
||||
|
||||
defer z.Stop()
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockAcc.ClearMetrics()
|
||||
if err := postThriftData(tt.datafile, z.address, tt.contentType); err != nil {
|
||||
t.Fatalf("Posting data to http endpoint /api/v1/spans failed. Error: %s\n", err)
|
||||
}
|
||||
mockAcc.Wait(
|
||||
len(tt.want),
|
||||
) // Since the server is running concurrently, we need to wait for the number of data points we want to test to be added to the Accumulator.
|
||||
if len(mockAcc.Errors) > 0 != tt.wantErr {
|
||||
t.Fatalf("Got unexpected errors. want error = %v, errors = %v\n", tt.wantErr, mockAcc.Errors)
|
||||
}
|
||||
|
||||
var got []testutil.Metric
|
||||
for _, m := range mockAcc.Metrics {
|
||||
got = append(got, *m)
|
||||
}
|
||||
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
mockAcc.ClearMetrics()
|
||||
z.Stop()
|
||||
// Make sure there is no erroneous error on shutdown
|
||||
if len(mockAcc.Errors) != 0 {
|
||||
t.Fatal("Expected no errors on shutdown")
|
||||
}
|
||||
}
|
||||
|
||||
func postThriftData(datafile, address, contentType string) error {
|
||||
dat, err := os.ReadFile(datafile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read from data file %s", datafile)
|
||||
}
|
||||
|
||||
endpoint := fmt.Sprintf("http://%s/api/v1/spans", address)
|
||||
req, err := http.NewRequest("POST", endpoint, bytes.NewReader(dat))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create new POST request for %q: %w", endpoint, err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while making HTTP POST request to zipkin endpoint %q: %w", endpoint, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue