This topic describes the Tracing Analysis feature in a Go runtime environment.
Background
Alibaba Cloud Tracing Analysis adopts the OpenTracing standard and is compatible with open source communities. Tracing Analysis provides developers of distributed applications with various features. For example, it enables developers to query and diagnose distributed traces, dynamically discover distributed topologies, and summarize application performance in real time.
Function Compute integrates with Tracing Analysis. You can use Jaeger SDK or OpenTelemetry to upload trace information so that you can track function invocation. Tracing Analysis helps you analyze and diagnose performance bottlenecks in a serverless architecture. This improves development and diagnostics efficiency in serverless scenarios.
Overview
You can configure Tracing Analysis in the Function Compute console. For more information, see Enable Tracing Analysis.
After you enable Tracing Analysis for a service, Function Compute automatically records the time consumed in the system. The time includes the cold start time, execution time of an initializer function, and execution time of a function. For more information about system spans in the following figure, see Span name description.
You can record the time consumed for the function on the business side. For example, you can record the time taken to access services such as ApsaraDB RDS and File Storage NAS within the function by using Create custom spans.
Sample code
Tracing Analysis for Function Compute in a Go runtime environment allows you to create custom spans by using the following methods based on the Jaeger implementation of the OpenTracing specification.
Use the SDK for OpenTelemetry (recommended)
In the Go code, you can use the SDK for OpenTelemetry to perform code instrumentation and report the data to Tracing Analysis. For the complete sample code, see golang-tracing-openTelemetry.
- Add dependencies.
go get github.com/aliyun/fc-runtime-go-sdk go get go get go.opentelemetry.io/otel go get go.opentelemetry.io/otel/sdk go get go.opentelemetry.io/otel/exporters/jaeger
- Report the data to Tracing Analysis.
func HandleRequest(ctx context.Context, event MyEvent) (string, error) { // Obtain the tracing information of the context in Function Compute. fctx, ok := fccontext.FromContext(ctx) if !ok { return "", fmt.Errorf("failed to get FcContext") } spanCtx, endpoint, err := getFcTracingInfo(fctx) if err != nil { return "", fmt.Errorf("failed to getFcTracingInfo, error: %v", err) } // Create a tracer provider. tp, err := NewTracerProvider(endpoint) if err != nil { return "", fmt.Errorf("OpenTracingJaegerEndpoint: %s, error: %v", fctx.Tracing.JaegerEndpoint, err) } // Set the tracer provider to a global tracer provider. otel.SetTracerProvider(tp) if err != nil { return "", fmt.Errorf("failed to getFcSpanCtx, error: %v", err) } // Create a custom span. startMySpan(trace.ContextWithSpanContext(ctx, spanCtx)) return fmt.Sprintf("hello world! Hello, %s! ", event.Name), nil }
- Create a
tracer provider
that provides access to tracers.func tracerProvider(url string) (*tracesdk.TracerProvider, error) { // Create a Jaeger exporter. exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url))) if err != nil { return nil, err } tp := tracesdk.NewTracerProvider( // Register an exporter. tracesdk.WithBatcher(exp), // Record the application information in the resource. tracesdk.WithResource(resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceNameKey.String("FCTracer"), attribute.String("environment", "production"), attribute.Int64("ID", 1), )), ) return tp, nil }
- Obtain the tracing information of the Context and converts the OpenTracing SpanContext to an OpenTelemetry SpanContext.
func getFcTracingInfo(fctx *fccontext.FcContext) (trace.SpanContext, string, error) { // Obtain the tracing information. spanContext := trace.SpanContext{} endpoint := fctx.Tracing.JaegerEndpoint OpenTracingSpanContext := fctx.Tracing.OpenTracingSpanContext if len(endpoint) == 0 { return spanContext, endpoint, fmt.Errorf("invalid jaeger endpoint") } spanContextSlice := strings.Split(OpenTracingSpanContext, ":") // Pad zeros in front of the trace ID. tid, err := trace.TraceIDFromHex("0000000000000000" + spanContextSlice[0]) if err != nil { return spanContext, endpoint, err } fid := trace.FlagsSampled spanContext = spanContext.WithTraceID(tid).WithTraceFlags(fid).WithRemote(true) return spanContext, endpoint, nil }
- Create a
tracer
and use the converted OpenTelemetry SpanContext to create child spans. A span represents a continuously executable code snippet that is named and timed in an invocation trace. You can also create child spans based on the span.func startMySpan(ctx context.Context) { // Use a global tracer provider. tr := otel.Tracer("fc-Trace") ctx, parentSpan := tr.Start(ctx, "fc-operation") defer parentSpan.End() parentSpan.SetAttributes(attribute.Key("version").String("fc-v1")) time.Sleep(150 * time.Millisecond) child(ctx) } func child(ctx context.Context) { tr := otel.Tracer("fc-Trace") _, childSpan := tr.Start(ctx, "fc-operation-child") defer childSpan.End() time.Sleep(100 * time.Millisecond) childSpan.AddEvent("timeout") }
Use the SDK for Jaeger
You can use the SDK for Jaeger to perform code instrumentation and report the data to Tracing Analysis. For the complete sample code, see golang-tracing.
- Add dependencies.
go get github.com/aliyun/fc-runtime-go-sdk go get github.com/opentracing/opentracing-go go get github.com/uber/jaeger-client-go
- Report the data to Tracing Analysis.
func HandleRequest(ctx context.Context, event MyEvent) (string, error) { // Obtain the tracing information of the context in Function Compute. fctx, _ := fccontext.FromContext(ctx) endpoint := fctx.Tracing.JaegerEndpoint OpenTracingSpanContext := fctx.Tracing.OpenTracingSpanContext if len(endpoint) == 0 { return "", fmt.Errorf("invalid jaeger endpoint") } // Create a tracer. tracer, closer := NewJaegerTracer("FCTracer", endpoint) defer closer.Close() // Restore the SpanContext. spanContext, err := jaeger.ContextFromString(OpenTracingSpanContext) if err != nil { return "", fmt.Errorf("OpenTracingSpanContext: %s, error: %v", fctx.Tracing.OpenTracingSpanContext, err) } // Create a custom span. startMySpan(spanContext, tracer) return fmt.Sprintf("hello world! Hello, %s! ", event.Name), nil }
- Create a
tracer
object that provides access to tracers.func NewJaegerTracer(service, endpoint string) (opentracing.Tracer, io.Closer) { sender := transport.NewHTTPTransport(endpoint) tracer, closer := jaeger.NewTracer(service, jaeger.NewConstSampler(true), jaeger.NewRemoteReporter(sender)) return tracer, closer }
- Convert the SpanContext and create a custom span. You can also create child spans based on the span.
func startMySpan(context jaeger.SpanContext, tracer opentracing.Tracer) { parentSpan := tracer.StartSpan("MyFCSpan", opentracing.ChildOf(context)) defer parentSpan.Finish() parentSpan.SetOperationName("fc-operation") parentSpan.SetTag("version", "fc-v1") time.Sleep(150 * time.Millisecond) // Enable a child span. childSpan := tracer.StartSpan("fc-operation-child", opentracing.ChildOf(parentSpan.Context())) defer childSpan.Finish() time.Sleep(100 * time.Millisecond) childSpan.LogFields( log.String("type", "cache timeout"), log.Int("waited.millis", 100)) }