pulumi/pkg/util/cmdutil/trace.go
Pat Gavlin 82204230e1
Improve tracing support. (#3238)
* Fix some tracing issues.

- Add endpoints for `startUpdate` and `postEngineEventsBatch` so that
  spans for these invocations have proper names
- Inject a tracing span when walking a plan so that resource operations
  are properly parented
- When handling gRPC calls, inject a tracing span into the call's
  metadata if no span is already present so that resource monitor and
  engine spans are properly parented
- Do not trace client gRPC invocations of the empty method so that these
  calls (which are used to determine server availability) do not muddy
  the trace. Note that I tried parenting these spans appropriately, but
  doing so broke the trace entirely.

With these changes, the only unparented span in a typical Pulumi
invocation is a single call to `getUser`. This span is unparented
because that call does not have a context available. Plumbing a context
into that particular call is surprisingly tricky, as it is often called
by other context-less functions.

* Make tracing support more flexible.

- Add support for writing trace data to a local file using Appdash
- Add support for viewing Appdash traces via the CLI
2019-09-16 14:16:43 -07:00

140 lines
3.6 KiB
Go

// Copyright 2016-2018, Pulumi Corporation.
//
// 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 cmdutil
import (
"io"
"log"
"net/url"
"os"
opentracing "github.com/opentracing/opentracing-go"
"github.com/pulumi/pulumi/pkg/util/contract"
jaeger "github.com/uber/jaeger-client-go"
"github.com/uber/jaeger-client-go/transport/zipkin"
"sourcegraph.com/sourcegraph/appdash"
appdash_opentracing "sourcegraph.com/sourcegraph/appdash/opentracing"
)
// TracingEndpoint is the Zipkin-compatible tracing endpoint where tracing data will be sent.
var TracingEndpoint string
var TracingToFile bool
var TracingRootSpan opentracing.Span
var traceCloser io.Closer
type localStore struct {
path string
store *appdash.MemoryStore
}
func (s *localStore) Close() error {
f, err := os.Create(s.path)
if err != nil {
return err
}
defer contract.IgnoreClose(f)
return s.store.Write(f)
}
func IsTracingEnabled() bool {
return TracingEndpoint != ""
}
// InitTracing initializes tracing
func InitTracing(name, rootSpanName, tracingEndpoint string) {
// If no tracing endpoint was provided, just return. The default global tracer is already a no-op tracer.
if tracingEndpoint == "" {
return
}
// Store the tracing endpoint
TracingEndpoint = tracingEndpoint
endpointURL, err := url.Parse(tracingEndpoint)
if err != nil {
log.Fatalf("invalid tracing endpoint: %v", err)
}
var tracer opentracing.Tracer
switch {
case endpointURL.Scheme == "file":
// If the endpoint is a file:// URL, use a local tracer.
TracingToFile = true
path := endpointURL.Path
if path == "" {
path = endpointURL.Opaque
}
if path == "" {
log.Fatalf("invalid tracing endpoint: %v", err)
}
store := &localStore{
path: path,
store: appdash.NewMemoryStore(),
}
traceCloser = store
collector := appdash.NewLocalCollector(store.store)
tracer = appdash_opentracing.NewTracer(collector)
case endpointURL.Scheme == "tcp":
// If the endpoint scheme is tcp, use an Appdash endpoint.
collector := appdash.NewRemoteCollector(tracingEndpoint)
traceCloser = collector
tracer = appdash_opentracing.NewTracer(collector)
default:
// Jaeger tracer can be initialized with a transport that will
// report tracing Spans to a Zipkin backend
transport, err := zipkin.NewHTTPTransport(
tracingEndpoint,
zipkin.HTTPBatchSize(1),
zipkin.HTTPLogger(jaeger.StdLogger),
)
if err != nil {
log.Fatalf("Cannot initialize HTTP transport: %v", err)
}
// create Jaeger tracer
t, closer := jaeger.NewTracer(
name,
jaeger.NewConstSampler(true), // sample all traces
jaeger.NewRemoteReporter(transport))
tracer, traceCloser = t, closer
}
// Set the ambient tracer
opentracing.SetGlobalTracer(tracer)
// If a root span was requested, start it now.
if rootSpanName != "" {
TracingRootSpan = tracer.StartSpan(rootSpanName)
}
}
// CloseTracing ensures that all pending spans have been flushed. It should be called before process exit.
func CloseTracing() {
if !IsTracingEnabled() {
return
}
if TracingRootSpan != nil {
TracingRootSpan.Finish()
}
contract.IgnoreClose(traceCloser)
}