// Copyright 2018, OpenCensus Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ocagent import ( "math" "time" "go.opencensus.io/trace" "go.opencensus.io/trace/tracestate" tracepb "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1" "github.com/golang/protobuf/ptypes/timestamp" ) const ( maxAnnotationEventsPerSpan = 32 maxMessageEventsPerSpan = 128 ) func ocSpanToProtoSpan(sd *trace.SpanData) *tracepb.Span { if sd == nil { return nil } var namePtr *tracepb.TruncatableString if sd.Name != "" { namePtr = &tracepb.TruncatableString{Value: sd.Name} } return &tracepb.Span{ TraceId: sd.TraceID[:], SpanId: sd.SpanID[:], ParentSpanId: sd.ParentSpanID[:], Status: ocStatusToProtoStatus(sd.Status), StartTime: timeToTimestamp(sd.StartTime), EndTime: timeToTimestamp(sd.EndTime), Links: ocLinksToProtoLinks(sd.Links), Kind: ocSpanKindToProtoSpanKind(sd.SpanKind), Name: namePtr, Attributes: ocAttributesToProtoAttributes(sd.Attributes), TimeEvents: ocTimeEventsToProtoTimeEvents(sd.Annotations, sd.MessageEvents), Tracestate: ocTracestateToProtoTracestate(sd.Tracestate), } } var blankStatus trace.Status func ocStatusToProtoStatus(status trace.Status) *tracepb.Status { if status == blankStatus { return nil } return &tracepb.Status{ Code: status.Code, Message: status.Message, } } func ocLinksToProtoLinks(links []trace.Link) *tracepb.Span_Links { if len(links) == 0 { return nil } sl := make([]*tracepb.Span_Link, 0, len(links)) for _, ocLink := range links { // This redefinition is necessary to prevent ocLink.*ID[:] copies // being reused -- in short we need a new ocLink per iteration. ocLink := ocLink sl = append(sl, &tracepb.Span_Link{ TraceId: ocLink.TraceID[:], SpanId: ocLink.SpanID[:], Type: ocLinkTypeToProtoLinkType(ocLink.Type), }) } return &tracepb.Span_Links{ Link: sl, } } func ocLinkTypeToProtoLinkType(oct trace.LinkType) tracepb.Span_Link_Type { switch oct { case trace.LinkTypeChild: return tracepb.Span_Link_CHILD_LINKED_SPAN case trace.LinkTypeParent: return tracepb.Span_Link_PARENT_LINKED_SPAN default: return tracepb.Span_Link_TYPE_UNSPECIFIED } } func ocAttributesToProtoAttributes(attrs map[string]interface{}) *tracepb.Span_Attributes { if len(attrs) == 0 { return nil } outMap := make(map[string]*tracepb.AttributeValue) for k, v := range attrs { switch v := v.(type) { case bool: outMap[k] = &tracepb.AttributeValue{Value: &tracepb.AttributeValue_BoolValue{BoolValue: v}} case int: outMap[k] = &tracepb.AttributeValue{Value: &tracepb.AttributeValue_IntValue{IntValue: int64(v)}} case int64: outMap[k] = &tracepb.AttributeValue{Value: &tracepb.AttributeValue_IntValue{IntValue: v}} case string: outMap[k] = &tracepb.AttributeValue{ Value: &tracepb.AttributeValue_StringValue{ StringValue: &tracepb.TruncatableString{Value: v}, }, } } } return &tracepb.Span_Attributes{ AttributeMap: outMap, } } // This code is mostly copied from // https://github.com/census-ecosystem/opencensus-go-exporter-stackdriver/blob/master/trace_proto.go#L46 func ocTimeEventsToProtoTimeEvents(as []trace.Annotation, es []trace.MessageEvent) *tracepb.Span_TimeEvents { if len(as) == 0 && len(es) == 0 { return nil } timeEvents := &tracepb.Span_TimeEvents{} var annotations, droppedAnnotationsCount int var messageEvents, droppedMessageEventsCount int // Transform annotations for i, a := range as { if annotations >= maxAnnotationEventsPerSpan { droppedAnnotationsCount = len(as) - i break } annotations++ timeEvents.TimeEvent = append(timeEvents.TimeEvent, &tracepb.Span_TimeEvent{ Time: timeToTimestamp(a.Time), Value: transformAnnotationToTimeEvent(&a), }, ) } // Transform message events for i, e := range es { if messageEvents >= maxMessageEventsPerSpan { droppedMessageEventsCount = len(es) - i break } messageEvents++ timeEvents.TimeEvent = append(timeEvents.TimeEvent, &tracepb.Span_TimeEvent{ Time: timeToTimestamp(e.Time), Value: transformMessageEventToTimeEvent(&e), }, ) } // Process dropped counter timeEvents.DroppedAnnotationsCount = clip32(droppedAnnotationsCount) timeEvents.DroppedMessageEventsCount = clip32(droppedMessageEventsCount) return timeEvents } func transformAnnotationToTimeEvent(a *trace.Annotation) *tracepb.Span_TimeEvent_Annotation_ { return &tracepb.Span_TimeEvent_Annotation_{ Annotation: &tracepb.Span_TimeEvent_Annotation{ Description: &tracepb.TruncatableString{Value: a.Message}, Attributes: ocAttributesToProtoAttributes(a.Attributes), }, } } func transformMessageEventToTimeEvent(e *trace.MessageEvent) *tracepb.Span_TimeEvent_MessageEvent_ { return &tracepb.Span_TimeEvent_MessageEvent_{ MessageEvent: &tracepb.Span_TimeEvent_MessageEvent{ Type: tracepb.Span_TimeEvent_MessageEvent_Type(e.EventType), Id: uint64(e.MessageID), UncompressedSize: uint64(e.UncompressedByteSize), CompressedSize: uint64(e.CompressedByteSize), }, } } // clip32 clips an int to the range of an int32. func clip32(x int) int32 { if x < math.MinInt32 { return math.MinInt32 } if x > math.MaxInt32 { return math.MaxInt32 } return int32(x) } func timeToTimestamp(t time.Time) *timestamp.Timestamp { nanoTime := t.UnixNano() return ×tamp.Timestamp{ Seconds: nanoTime / 1e9, Nanos: int32(nanoTime % 1e9), } } func ocSpanKindToProtoSpanKind(kind int) tracepb.Span_SpanKind { switch kind { case trace.SpanKindClient: return tracepb.Span_CLIENT case trace.SpanKindServer: return tracepb.Span_SERVER default: return tracepb.Span_SPAN_KIND_UNSPECIFIED } } func ocTracestateToProtoTracestate(ts *tracestate.Tracestate) *tracepb.Span_Tracestate { if ts == nil { return nil } return &tracepb.Span_Tracestate{ Entries: ocTracestateEntriesToProtoTracestateEntries(ts.Entries()), } } func ocTracestateEntriesToProtoTracestateEntries(entries []tracestate.Entry) []*tracepb.Span_Tracestate_Entry { protoEntries := make([]*tracepb.Span_Tracestate_Entry, 0, len(entries)) for _, entry := range entries { protoEntries = append(protoEntries, &tracepb.Span_Tracestate_Entry{ Key: entry.Key, Value: entry.Value, }) } return protoEntries }