tracer

package module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 17, 2019 License: MIT Imports: 6 Imported by: 1

README

🧶 tracer

Simple, lightweight tracing.

Build Quality Documentation Coverage Awesome

💡 Idea

The tracer provides API to trace execution flow.

func Do(ctx context.Context) {
	defer tracer.Fetch(ctx).Start().Stop()

	// do some heavy job
}

Full description of the idea is available here.

🏆 Motivation

At Avito, we use the Jaeger - a distributed tracing platform. It is handy in most cases, but at production, we also use sampling. So, what is a problem, you say?

I had 0.02% requests with a write: broken pipe error and it was difficult to find the appropriate one in the Sentry which also has trace related to it in the Jaeger.

For that reason, I wrote the simple solution to handle this specific case and found the bottleneck in our code quickly.

🤼‍♂️ How to

import (
	"context"
	"io"
	"net/http"
	"time"

	"github.com/kamilsk/tracer"
)

func InjectTracer(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		req = req.WithContext(tracer.Inject(req.Context(), make([]*tracer.Call, 0, 10)))
		handler.ServeHTTP(rw, req)
	})
}

func Handle(rw http.ResponseWriter, req *http.Request) {
	ctx, cancel := context.WithTimeout(req.Context(), time.Second)
	defer cancel()

	call := tracer.Fetch(req.Context()).Start(req.Header.Get("X-Request-Id"))
	defer call.Stop()

	...

	call.Checkpoint("serialize")
	data, err := FetchData(ctx, req.Body)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
		return
	}

	call.Checkpoint("store")
	if err := StoreIntoDatabase(ctx, data); err != nil {
		http.Error(rw,
			http.StatusText(http.StatusInternalServerError),
			http.StatusInternalServerError)
		return
	}
	rw.WriteHeader(http.StatusOK)
}

func FetchData(ctx context.Context, r io.Reader) (Data, error) {
	defer tracer.Fetch(ctx).Start().Stop()

	// fetch a data into a struct
}

func StoreIntoDatabase(ctx context.Context, data Data) error {
	defer tracer.Fetch(ctx).Start().Stop()

	// store the data into a database
}

Output:

allocates at call stack: 0, detailed call stack:
	call Handle [ca7a87c4-58d0-4fdf-857c-ef49fc3bf271]: 14.038083ms, allocates: 2
		checkpoint [serialize]: 1.163587ms
		checkpoint [store]: 2.436265ms
	call FetchData: 1.192829ms, allocates: 0
	call StoreIntoDatabase: 10.428663ms, allocates: 0

🧩 Integration

The library uses SemVer for versioning, and it is not BC-safe through major releases. You can use go modules or dep to manage its version.

$ go get -u github.com/kamilsk/tracer

$ dep ensure -add github.com/kamilsk/tracer

made with ❤️ for everyone

Documentation

Overview

Example
package main

import (
	"bytes"
	"context"
	"encoding/gob"
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/kamilsk/tracer"
)

type Data struct {
	Title    string `json:"title"`
	Subtitle string `json:"subtitle"`
}

func main() {
	rec := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodPost, "/message",
		strings.NewReader(`{"title": "tracer", "subtitle": "🧶 Simple, lightweight tracing mechanism."}`))
	req.Header.Set("X-Request-Id", "ca7a87c4-58d0-4fdf-857c-ef49fc3bf271")

	handler := InjectTracer(FlushTracer(http.HandlerFunc(Handle)))
	handler.ServeHTTP(rec, req)

	_, _ = io.Copy(os.Stdout, strings.NewReader(stabilize(rec.Body.String())))
}

func FlushTracer(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		handler.ServeHTTP(rw, req)
		_, _ = rw.Write([]byte(tracer.Fetch(req.Context()).String()))
	})
}

func InjectTracer(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		req = req.WithContext(tracer.Inject(req.Context(), make([]*tracer.Call, 0, 10)))
		handler.ServeHTTP(rw, req)
	})
}

func Handle(rw http.ResponseWriter, req *http.Request) {
	ctx, cancel := context.WithTimeout(req.Context(), time.Second)
	defer cancel()

	call := tracer.Fetch(req.Context()).Start(req.Header.Get("X-Request-Id"))
	defer call.Stop()

	time.Sleep(time.Millisecond)

	call.Checkpoint("serialize")
	data, err := FetchData(ctx, req.Body)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
		return
	}

	time.Sleep(time.Millisecond)

	call.Checkpoint("store")
	if err := StoreIntoDatabase(ctx, data); err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
		return
	}

	rw.WriteHeader(http.StatusOK)
}

func FetchData(ctx context.Context, r io.Reader) (Data, error) {
	defer tracer.Fetch(ctx).Start().Stop()

	time.Sleep(time.Millisecond)
	var data Data
	err := json.NewDecoder(r).Decode(&data)
	return data, err
}

func StoreIntoDatabase(ctx context.Context, data Data) error {
	defer tracer.Fetch(ctx).Start().Stop()

	time.Sleep(10 * time.Millisecond)
	return gob.NewEncoder(bytes.NewBuffer(nil)).Encode(data)
}

func stabilize(raw string) string {
	raw = strings.Replace(raw, "tracer_test.", "", -1)
	raw = regexp.MustCompile(`Handle (.+): (\d{2}\.\d+ms)`).ReplaceAllString(raw, "Handle $1: 12.345678ms")
	raw = regexp.MustCompile(`\[serialize]: (\d\.\d+ms)`).ReplaceAllString(raw, "[serialize]: 1.234567ms")
	raw = regexp.MustCompile(`\[store]: (\d\.\d+ms)`).ReplaceAllString(raw, "[store]: 1.234567ms")
	raw = regexp.MustCompile(`FetchData: (\d\.\d+ms)`).ReplaceAllString(raw, "FetchData: 1.234567ms")
	raw = regexp.MustCompile(`StoreIntoDatabase: (\d{2}\.\d+ms)`).ReplaceAllString(raw, "StoreIntoDatabase: 12.345678ms")
	return raw
}
Output:

allocates at call stack: 0, detailed call stack:
	call Handle [ca7a87c4-58d0-4fdf-857c-ef49fc3bf271]: 12.345678ms, allocates: 2
		checkpoint [serialize]: 1.234567ms
		checkpoint [store]: 1.234567ms
	call FetchData: 1.234567ms, allocates: 0
	call StoreIntoDatabase: 12.345678ms, allocates: 0

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Inject

func Inject(ctx context.Context, stack []*Call) context.Context

Inject returns a new context with injected into it the tracer.

func (server *server) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	req = req.WithContext(tracer.Inject(req.Context(), make([]*trace.Call, 0, 10)))
	server.routing.Handle(rw, req)
}

Types

type Call

type Call struct {
	// contains filtered or unexported fields
}

Call holds information about a current function call.

func (*Call) Checkpoint

func (call *Call) Checkpoint(labels ...string)

Checkpoint stores timestamp of a current execution position of the current call.

func Do(ctx context.Context) {
	call := tracer.Fetch(ctx).Start()
	defer call.Stop()
	...
	call.Checkpoint()
	...
	call.Checkpoint("id", "labelX", "labelY")
	...
}

func (*Call) Stop

func (call *Call) Stop()

Stop marks the end time of the current call.

func Do(ctx context.Context) {
	defer tracer.Fetch(ctx).Start().Stop()
	...
}

type CallerInfo

type CallerInfo struct {
	Entry uintptr // the entry address of the function
	Name  string  // the name of the function
	File  string  // the file name and
	Line  int     // line number of the source code of the function
}

CallerInfo holds information about a caller.

func Caller

func Caller(skip int) CallerInfo

Caller returns information about a caller at position after the skip steps.

func StoreToDatabase(data Payload) error {
	defer stats.NewTiming().Send(Caller(2).Name)

	// do something heavy
}

type Checkpoint

type Checkpoint struct {
	// contains filtered or unexported fields
}

Checkpoint holds information about a current execution position of a current call.

type Trace

type Trace struct {
	// contains filtered or unexported fields
}

Trace holds information about a current execution flow.

func Fetch

func Fetch(ctx context.Context) *Trace

Fetch tries to get the tracer from a context or returns safe nil.

tracer.Fetch(context.Background()).Start().Stop() // won't panic

func (*Trace) Start

func (trace *Trace) Start(labels ...string) *Call

Start creates a call entry and marks its start time.

func Do(ctx context.Context) {
	call := tracer.Fetch(ctx).Start("id", "labelX", "labelY")
	defer call.Stop()
}

func (*Trace) String

func (trace *Trace) String() string

String returns a string representation of the current execution flow.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL