Go outils de mesure logiciels

Gopher avec une loupe


Dans cet article, je voudrais partager un moyen de profiler et de tracer des programmes sur Go. Je vais vous montrer comment procéder en gardant le code flexible et propre.


TL; DR


La journalisation, la collecte de métriques et tout ce qui n'est pas lié à la fonctionnalité principale d'un code ne doit pas être à l'intérieur de ce code. Au lieu de cela, vous devez identifier les points de trace qui peuvent être utilisés pour mesurer le code par l'utilisateur.


En d'autres termes, la journalisation et la collecte de métriques sont des sous-ensembles de la trace .


Le code de trace du modèle peut être généré à l'aide de gtrace .


Problème


Supposons que nous ayons un package libet une structure lib.Client. Avant d'exécuter une demande, il lib.Clientvérifie la connexion:


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)
}

Que faire si nous voulons enregistrer dans le journal juste avant et juste après l'envoi du message ping? La première option consiste à intégrer l'enregistreur (ou son interface) dans 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
}

Si nous voulons collecter des mesures, nous pouvons faire de même:


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