Go alat pengukuran perangkat lunak

Gopher dengan kaca pembesar


Pada artikel ini, saya ingin berbagi cara untuk profil dan melacak program di Go. Saya akan menunjukkan kepada Anda bagaimana melakukan ini sambil menjaga kode fleksibel dan bersih.


TL; DR


Logging, kumpulan metrik, dan segala sesuatu yang tidak terhubung dengan fungsi utama dari kode apa pun tidak boleh ada di dalam kode ini. Sebagai gantinya, Anda perlu mengidentifikasi titik jejak yang dapat digunakan untuk mengukur kode oleh pengguna.


Dengan kata lain, pencatatan dan pengumpulan metrik adalah himpunan bagian dari jejak .


Kode jejak templat dapat dihasilkan menggunakan gtrace .


Masalah


Misalkan kita memiliki paket libdan beberapa struktur lib.Client. Sebelum menjalankan permintaan apa pun, ia lib.Clientmemeriksa koneksi:


package lib

type Client struct {
    conn net.Conn
}

func (c *Client) Request(ctx context.Context) error {
    if err := c.ping(ctx); err != nil {
        return err
    }
    // Some logic here.
}

func (c *Client) ping(ctx context.Context) error {
    return doPing(ctx, c.conn)
}

Bagaimana jika kita ingin merekam log tepat sebelum dan tepat setelah pesan ping dikirim? Opsi pertama adalah menyematkan logger (atau antarmuka) di Client:


package lib

type Client struct {
    Logger Logger

    conn net.Conn
}

func (c *Client) ping(ctx context.Context) (err error) {
    c.Logger.Info("ping started")
    err = doPing(ctx, c.conn)
    c.Logger.Info("ping done (err is %v)", err)
    return
}

Jika kami ingin mengumpulkan metrik apa pun, kami dapat melakukan hal yang sama:


package lib

type Client struct {
    Logger  Logger
    Metrics Registry

    conn net.Conn
}

func (c *Client) ping(ctx context.Context) (err error) {
    start := time.Now()
    c.Logger.Info("ping started")

    err = doPing(ctx, c.conn)

    c.Logger.Info("ping done (err is %v)", err)
    metric := c.Metrics.Get("ping_latency")
    metric.Send(time.Since(start))

    return err
}

, โ€“ . Client, , รณ , ( doPing()).


( Client) .


, , , ? , , ?


, Client.


, , - . , (SRP).


, Client ? , , ? , Go, , .


, :


,
.


, ( ), ( ).


, , .


, , httptrace Go.


, : OnPingStart() OnPingDone(), OnPing(), callback. OnPing() ping-, callback โ€“ . (, doPing()).


Client :


package lib

type Client struct {
    OnPing func() func(error)
    conn net.Conn
}

func (c *Client) ping(ctx context.Context) (err error) {
    done := c.OnPing()
    err = doPing(ctx, c.conn)
    done(err)
    return
}

, OnPing nil. :


func (c *Client) ping(ctx context.Context) (err error) {
    var done func(error)
    if fn := c.OnPing; fn != nil {
        done = fn()
    }
    err = doPing(ctx, c.conn)
    if done != nil {
        done(err)
    }
    return
}

SRP , .


, .



?
httptrace ClientTrace.compose(), n . n ( ).


( reflect). OnPing Client ClientTrace:


package lib

type Client struct {
    Trace ClientTrace
    conn net.Conn
}

type ClientTrace struct {
    OnPing func() func(error)
}

:


func (a ClientTrace) Compose(b ClientTrace) (c ClientTrace) {
    switch {
    case a.OnPing == nil:
        c.OnPing = b.OnPing
    case b.OnPing == nil:
        c.OnPing = a.OnPing
    default:
        c.OnPing = func() func(error) {
            doneA := a.OnPing()
            doneB := b.OnPing() 
            switch {
            case doneA == nil:
                return doneB
            case doneB == nil:
                return doneA
            default:
                return func(err error) {
                    doneA(err)
                    doneB(err)
                }
            }
        }
    }
    return c
}

, ? , .


:


package main

import (
    "log"

    "some/path/to/lib"
)

func main() {
    var trace lib.ClientTrace

    // Logging hooks.
    trace = trace.Compose(lib.ClientTrace{
        OnPing: func() func(error) {
            log.Println("ping start")
            return func(err error) {
                log.Println("ping done", err)
            }
        },
    })

    // Some metrics hooks.
    trace = trace.Compose(lib.ClientTrace{
        OnPing: func() func(error) {
            start := time.Now()
            return func(err error) {
                metric := stats.Get("ping_latency")
                metric.Send(time.Since(start))
            }
        },
    })

    c := lib.Client{
        Trace: trace,
    }
}


. ClientTrace context.Context, Client.Request():


package lib

type clientTraceContextKey struct{}

func ClientTrace(ctx context.Context) ClientTrace {
    t, _ := ctx.Value(clientTraceContextKey{})
    return t
}

func WithClientTrace(ctx context.Context, t ClientTrace) context.Context {
    prev := ContextClientTrace(ctx)
    return context.WithValue(ctx,
        clientTraceContextKey{},
        prev.Compose(t),
    )
}

. , !


?


, Vim ( - ), .


, , nil , reflection.


github.com/gobwas/gtrace


gtrace . , //gtrace:gen. - nil, .


.


:


package lib

//go:generate gtrace

//gtrace:gen
//gtrace:set context
type ClientTrace struct {
    OnPing func() func(error)
}

type Client struct {
    Trace ClientTrace
    conn net.Conn
}

func (c *Client) ping(ctx context.Context) (err error) {
    done := c.Trace.onPing(ctx)
    err = doPing(ctx, c.conn)
    done(err)
    return
}

go generate ClientTrace.


! gtrace , .


!


References



All Articles