Skip to content

Commit 3793c98

Browse files
Emmanuel T Odekebasvanbeek
authored andcommitted
proto: add Zipkin proto + converter (#88)
* proto: add Zipkin proto + converter Added: a) Zipkin v2 Proto3 definitions from https://github.com/openzipkin/zipkin-api/blob/6dfa27520abb62de7b01dba04683177afaa40c8c/zipkin.proto b) a helper Protobuf function ParseSpans to parse model.SpanModel-s from the serialized protobuf data as well as an end-to-end test. Fixes #87 Spawned by census-instrumentation/opencensus-service#155 * proto/*/convert_proto.go: rename zps to s, return err early Addressing review feedback: * Rename zps to s * On encountering any error during protoSpan->zipkinModelSapn, return ASAP * proto/*/v2: move payload to a global variable Requested in codereview feedback * proto/*/v2: make fmt.Errorf calls more descriptive Provide a more descriptive context in fmt.Errorf calls to match the style in: https://github.com/openzipkin/zipkin-go/blob/master/propagation/b3/shared.go * proto/*/v2: test and assert known failures Test to ensure that the failures that we know could happen e.g. * Missing/invalid length TraceID * Missing/invalid length SpanID * Invalid length ParentSpanID are caught and report errors instead of successfully parsing. Also tweaked the errors returned to be more descriptive and graceful.
1 parent 153cbaf commit 3793c98

File tree

5 files changed

+1096
-0
lines changed

5 files changed

+1096
-0
lines changed

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ test:
99
bench:
1010
go test -v -run - -bench . -benchmem ./...
1111

12+
.PHONY: protoc
13+
protoc:
14+
protoc --go_out=. proto/v2/zipkin.proto
15+
1216
.PHONY: lint
1317
lint:
1418
# Ignore grep's exit code since no match returns 1.

proto/v2/convert_proto.go

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// Copyright 2018 The OpenZipkin Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/*
16+
Package zipkin_proto3 adds support for the Zipkin protobuf definition to allow
17+
Go applications to consume model.SpanModel from protobuf serialized data.
18+
*/
19+
package zipkin_proto3
20+
21+
import (
22+
"errors"
23+
"fmt"
24+
"net"
25+
"time"
26+
27+
"github.com/golang/protobuf/proto"
28+
29+
zipkinmodel "github.com/openzipkin/zipkin-go/model"
30+
)
31+
32+
// ParseSpans parses zipkinmodel.SpanModel values from data serialized by Protobuf3.
33+
// debugWasSet is a boolean that toggles the Debug field of each Span. Its value
34+
// is usually retrieved from the transport headers when the "X-B3-Flags" header has a value of 1.
35+
func ParseSpans(protoBlob []byte, debugWasSet bool) (zss []*zipkinmodel.SpanModel, err error) {
36+
var listOfSpans ListOfSpans
37+
if err := proto.Unmarshal(protoBlob, &listOfSpans); err != nil {
38+
return nil, err
39+
}
40+
for _, zps := range listOfSpans.Spans {
41+
zms, err := protoSpanToModelSpan(zps, debugWasSet)
42+
if err != nil {
43+
return zss, err
44+
}
45+
zss = append(zss, zms)
46+
}
47+
return zss, nil
48+
}
49+
50+
var errNilZipkinSpan = errors.New("expecting a non-nil Span")
51+
52+
func protoSpanToModelSpan(s *Span, debugWasSet bool) (*zipkinmodel.SpanModel, error) {
53+
if s == nil {
54+
return nil, errNilZipkinSpan
55+
}
56+
if len(s.TraceId) != 16 {
57+
return nil, fmt.Errorf("invalid TraceID: has length %d yet wanted length 16", len(s.TraceId))
58+
}
59+
traceID, err := zipkinmodel.TraceIDFromHex(fmt.Sprintf("%x", s.TraceId))
60+
if err != nil {
61+
return nil, fmt.Errorf("invalid TraceID: %v", err)
62+
}
63+
64+
parentSpanID, _, err := protoSpanIDToModelSpanID(s.ParentId)
65+
if err != nil {
66+
return nil, fmt.Errorf("invalid ParentID: %v", err)
67+
}
68+
spanIDPtr, spanIDBlank, err := protoSpanIDToModelSpanID(s.Id)
69+
if err != nil {
70+
return nil, fmt.Errorf("invalid SpanID: %v", err)
71+
}
72+
if spanIDBlank || spanIDPtr == nil {
73+
// This is a logical error
74+
return nil, errors.New("expected a non-nil SpanID")
75+
}
76+
77+
zmsc := zipkinmodel.SpanContext{
78+
TraceID: traceID,
79+
ID: *spanIDPtr,
80+
ParentID: parentSpanID,
81+
Debug: debugWasSet,
82+
}
83+
zms := &zipkinmodel.SpanModel{
84+
SpanContext: zmsc,
85+
Name: s.Name,
86+
Kind: zipkinmodel.Kind(s.Kind.String()),
87+
Timestamp: microsToTime(s.Timestamp),
88+
Tags: s.Tags,
89+
Duration: microsToDuration(s.Duration),
90+
LocalEndpoint: protoEndpointToModelEndpoint(s.LocalEndpoint),
91+
RemoteEndpoint: protoEndpointToModelEndpoint(s.RemoteEndpoint),
92+
Shared: s.Shared,
93+
Annotations: protoAnnotationToModelAnnotations(s.Annotations),
94+
}
95+
96+
return zms, nil
97+
}
98+
99+
func microsToDuration(us uint64) time.Duration {
100+
// us to ns; ns are the units of Duration
101+
return time.Duration(us * 1e3)
102+
}
103+
104+
func protoEndpointToModelEndpoint(zpe *Endpoint) *zipkinmodel.Endpoint {
105+
if zpe == nil {
106+
return nil
107+
}
108+
return &zipkinmodel.Endpoint{
109+
ServiceName: zpe.ServiceName,
110+
IPv4: net.IP(zpe.Ipv4),
111+
IPv6: net.IP(zpe.Ipv6),
112+
Port: uint16(zpe.Port),
113+
}
114+
}
115+
116+
func protoSpanIDToModelSpanID(spanId []byte) (zid *zipkinmodel.ID, blank bool, err error) {
117+
if len(spanId) == 0 {
118+
return nil, true, nil
119+
}
120+
if len(spanId) != 8 {
121+
return nil, true, fmt.Errorf("has length %d yet wanted length 8", len(spanId))
122+
}
123+
124+
// Converting [8]byte --> uint64
125+
var u64 uint64
126+
u64 |= uint64(spanId[7]&0xFF) << 0
127+
u64 |= uint64(spanId[6]&0xFF) << 8
128+
u64 |= uint64(spanId[5]&0xFF) << 16
129+
u64 |= uint64(spanId[4]&0xFF) << 24
130+
u64 |= uint64(spanId[3]&0xFF) << 32
131+
u64 |= uint64(spanId[2]&0xFF) << 40
132+
u64 |= uint64(spanId[1]&0xFF) << 48
133+
u64 |= uint64(spanId[0]&0xFF) << 56
134+
zid_ := zipkinmodel.ID(u64)
135+
return &zid_, false, nil
136+
}
137+
138+
func protoAnnotationToModelAnnotations(zpa []*Annotation) (zma []zipkinmodel.Annotation) {
139+
for _, za := range zpa {
140+
if za != nil {
141+
zma = append(zma, zipkinmodel.Annotation{
142+
Timestamp: microsToTime(za.Timestamp),
143+
Value: za.Value,
144+
})
145+
}
146+
}
147+
148+
if len(zma) == 0 {
149+
return nil
150+
}
151+
return zma
152+
}
153+
154+
func microsToTime(us uint64) time.Time {
155+
return time.Unix(0, int64(us*1e3))
156+
}

0 commit comments

Comments
 (0)