Skip to content

Commit

Permalink
Add Trace Visualization
Browse files Browse the repository at this point in the history
We can leverage the Websequence Diagram JS library to automatically
generate the UML request sequence graphs.

Ideally we'd use NSQ or another queue system to push to in async
fashion. To keep things simple the Trace server is in-memory, and
available via HTTP reqeusts.

#2
  • Loading branch information
harlow committed Sep 14, 2015
1 parent de62bff commit 7904945
Show file tree
Hide file tree
Showing 18 changed files with 177 additions and 324 deletions.
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
web: go run api.v1/main.go
trace: go run api.trace/main.go
auth: go run service.auth/main.go
geo: go run service.geo/main.go
profile: go run service.profile/main.go
Expand Down
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ _Note:_ Data for each of the services is stored in JSON flat files under the `/d
Protobuf v3 are required:

$ brew install protobuf --devel

Install the protoc-gen libraries:

$ go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
Expand Down Expand Up @@ -84,6 +84,20 @@ The JSON response:
}
```

### Tracing the requests

To help visualize the request/response patterns between services there is an additional trace server that will boot w/ the services.

The web service generates the first trace ID. It prints to the logs:

```
18:43:05 web.1 | 2015/09/13 18:43:05 traceId=baec5498-2f4f-439c-629b-18b57d974888
```

When can then take the trace ID and view the sequence diagram here:

http://localhost:5001/?traceId=[TRACE_ID]

### Credits

This example codebase was heavily inspired by the following talks/repositories:
Expand Down
37 changes: 37 additions & 0 deletions api.trace/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package client

import (
"fmt"
"time"
"log"
"net/http"
"net/url"

"github.com/nu7hatch/gouuid"
)

func NewTraceID() string {
t, _ := uuid.NewV4()
return t.String()
}

func post(traceID string, msg string) {
_, err := http.PostForm("http://localhost:5001", url.Values{
"traceId": {traceID},
"msg": {msg},
})
if err != nil {
log.Panic("Could not connect")
}
}

func Req(traceID string, from string, to string, action string) {
msg := fmt.Sprintf("%v->%v: %v\n", from, to, action)
post(traceID, msg)
}

func Rep(traceID string, from string, to string, startTime time.Time) {
elapsedTime := time.Since(startTime)
msg := fmt.Sprintf("%v-->%v: %v\n", from, to, elapsedTime)
post(traceID, msg)
}
62 changes: 62 additions & 0 deletions api.trace/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package main

import (
"flag"
"fmt"
"log"
"net/http"
)

// newServer returns a trace server with an
// initialzed slice of events
func newServer() *traceServer {
s := &traceServer{}
s.events = make(map[string][]string)
return s
}

type traceServer struct {
events map[string][]string
}

func (s traceServer) requestHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
r.ParseForm()

traceID := r.PostFormValue("traceId")
if traceID == "" {
http.Error(w, "Param traceId required", http.StatusBadRequest)
return
}

msg := r.PostFormValue("msg")
if msg == "" {
http.Error(w, "Param msg required", http.StatusBadRequest)
return
}

s.events[traceID] = append(s.events[traceID], msg)
} else {
traceID := r.URL.Query().Get("traceId")
if traceID == "" {
http.Error(w, "Param traceID required", http.StatusBadRequest)
return
}

fmt.Fprintln(w, "<div class=wsd wsd_style=\"default\" ><pre>")
for _, msg := range s.events[traceID] {
fmt.Fprintln(w, msg)
}
fmt.Fprintln(w, "</pre></div>")
fmt.Fprintln(w, "<script type=\"text/javascript\" src=\"http://www.websequencediagrams.com/service.js\"></script>")
}
}

func main() {
var port = flag.String("port", "5001", "The server port")
flag.Parse()

s := newServer()
http.HandleFunc("/", s.requestHandler)
log.Fatal(http.ListenAndServe(":"+*port, nil))
}
12 changes: 7 additions & 5 deletions api.v1/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import (
geo "github.com/harlow/go-micro-services/service.geo/lib"
profile "github.com/harlow/go-micro-services/service.profile/lib"
rate "github.com/harlow/go-micro-services/service.rate/lib"
trace "github.com/harlow/go-micro-services/api.trace/client"

profile_pb "github.com/harlow/go-micro-services/service.profile/proto"
rate_plan_pb "github.com/harlow/go-micro-services/service.rate/proto"

"github.com/harlow/auth_token"
"github.com/harlow/go-micro-services/trace"
"golang.org/x/net/context"
"google.golang.org/grpc/metadata"
)
Expand All @@ -41,12 +41,14 @@ type apiServer struct {
}

func (s apiServer) requestHandler(w http.ResponseWriter, r *http.Request) {
t := trace.NewTracer()
t.In("www", "api.v1")
defer t.Out("api.v1", "www", time.Now())
// trace call to request handler
traceID := trace.NewTraceID()
trace.Req(traceID, "www", "api.v1", "")
defer trace.Rep(traceID, "api.v1", "www", time.Now())
log.Printf("traceId=%s", traceID)

// context and metadata
md := metadata.Pairs("traceID", t.TraceID, "from", "api.v1")
md := metadata.Pairs("traceID", traceID, "from", "api.v1")
ctx := context.Background()
ctx = metadata.NewContext(ctx, md)

Expand Down
23 changes: 11 additions & 12 deletions service.auth/lib/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package lib
import (
"time"

auth "github.com/harlow/go-micro-services/service.auth/proto"
trace "github.com/harlow/go-micro-services/trace"
pb "github.com/harlow/go-micro-services/service.auth/proto"
trace "github.com/harlow/go-micro-services/api.trace/client"

"golang.org/x/net/context"
"google.golang.org/grpc"
Expand All @@ -13,7 +13,7 @@ import (

type Client struct {
conn *grpc.ClientConn
client auth.AuthClient
client pb.AuthClient
}

func NewClient(addr string) (*Client, error) {
Expand All @@ -22,29 +22,28 @@ func NewClient(addr string) (*Client, error) {
return nil, err
}

client := auth.NewAuthClient(conn)
client := pb.NewAuthClient(conn)

return &Client{
conn: conn,
client: client,
}, nil
}

func (c Client) Close() error {
return c.conn.Close()
}

func (c Client) VerifyToken(ctx context.Context, authToken string) error {
md, _ := metadata.FromContext(ctx)

t := trace.Tracer{TraceID: md["traceID"]}
t.Req(md["from"], "service.auth", "VerifyToken")
defer t.Rep("service.auth", md["from"], time.Now())
trace.Req(md["traceID"], md["from"], "service.auth", "VerifyToken")
defer trace.Rep(md["traceID"], "service.auth", md["from"], time.Now())

args := &auth.Args{AuthToken: authToken}
args := &pb.Args{AuthToken: authToken}
if _, err := c.client.VerifyToken(ctx, args); err != nil {
return err
}

return nil
}

func (c Client) Close() error {
return c.conn.Close()
}
11 changes: 4 additions & 7 deletions service.auth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import (
"io/ioutil"
"log"
"net"
"time"
// "time"

pb "github.com/harlow/go-micro-services/service.auth/proto"
trace "github.com/harlow/go-micro-services/trace"
// trace "github.com/harlow/go-micro-services/api.trace/client"

"golang.org/x/net/context"
"google.golang.org/grpc"
Expand All @@ -21,7 +21,7 @@ import (
// newServer creates a new authServer and loads the customers from
// JSON file into customers map
func newServer(dataPath string) *authServer {
s := &authServer{serverName: "service.auth"}
s := &authServer{}
s.loadCustomers(dataPath)
return s
}
Expand All @@ -35,10 +35,7 @@ type authServer struct {
// VerifyToken finds a customer by authentication token.
func (s *authServer) VerifyToken(ctx context.Context, args *pb.Args) (*pb.Customer, error) {
md, _ := metadata.FromContext(ctx)

t := trace.Tracer{TraceID: md["traceID"]}
t.In(s.serverName, md["from"])
defer t.Out(md["from"], s.serverName, time.Now())
log.Printf("traceID=%s", md["traceID"])

customer := s.customers[args.AuthToken]
if customer == nil {
Expand Down
16 changes: 8 additions & 8 deletions service.geo/lib/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"time"

pb "github.com/harlow/go-micro-services/service.geo/proto"
trace "github.com/harlow/go-micro-services/trace"
trace "github.com/harlow/go-micro-services/api.trace/client"

"golang.org/x/net/context"
"google.golang.org/grpc"
Expand All @@ -30,15 +30,11 @@ func NewClient(addr string) (*Client, error) {
}, nil
}

func (c Client) Close() error {
return c.conn.Close()
}

func (c Client) HotelsWithinBoundedBox(ctx context.Context, latitude int32, longitude int32) ([]int32, error) {
md, _ := metadata.FromContext(ctx)
t := trace.Tracer{TraceID: md["traceID"]}
t.Req(md["from"], "service.geo", "BoundedBox")
defer t.Rep("service.geo", md["from"], time.Now())

trace.Req(md["traceID"], md["from"], "service.geo", "HotelsWithinBoundedBox")
defer trace.Rep(md["traceID"], "service.geo", md["from"], time.Now())

rect := &pb.Rectangle{
Lo: &pb.Point{Latitude: 400000000, Longitude: -750000000},
Expand All @@ -53,3 +49,7 @@ func (c Client) HotelsWithinBoundedBox(ctx context.Context, latitude int32, long

return reply.HotelIds, nil
}

func (c Client) Close() error {
return c.conn.Close()
}
12 changes: 4 additions & 8 deletions service.geo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import (
"log"
"math"
"net"
"time"
// "time"

pb "github.com/harlow/go-micro-services/service.geo/proto"
trace "github.com/harlow/go-micro-services/trace"
// trace "github.com/harlow/go-micro-services/api.trace/client"

"golang.org/x/net/context"
"google.golang.org/grpc"
Expand All @@ -21,7 +21,7 @@ import (
// newServer creates a new geoServer
// loads the locations from JSON data file
func newServer(dataPath string) *geoServer {
s := &geoServer{serverName: "service.geo"}
s := &geoServer{}
s.loadLocations(dataPath)
return s
}
Expand All @@ -32,17 +32,13 @@ type location struct {
}

type geoServer struct {
serverName string
locations []location
}

// BoundedBox returns all hotels contained within a given rectangle.
func (s *geoServer) BoundedBox(ctx context.Context, rect *pb.Rectangle) (*pb.Reply, error) {
md, _ := metadata.FromContext(ctx)

t := trace.Tracer{TraceID: md["traceID"]}
t.In(s.serverName, md["from"])
defer t.Out(md["from"], s.serverName, time.Now())
log.Printf("traceID=%s", md["traceID"])

reply := new(pb.Reply)
for _, loc := range s.locations {
Expand Down
17 changes: 8 additions & 9 deletions service.profile/lib/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"time"

pb "github.com/harlow/go-micro-services/service.profile/proto"
trace "github.com/harlow/go-micro-services/trace"
trace "github.com/harlow/go-micro-services/api.trace/client"

"golang.org/x/net/context"
"google.golang.org/grpc"
Expand All @@ -18,11 +18,6 @@ type ProfileReply struct {
Err error
}

type Client struct {
conn *grpc.ClientConn
client pb.ProfileClient
}

func NewClient(addr string) (*Client, error) {
conn, err := grpc.Dial(addr)
if err != nil {
Expand All @@ -37,12 +32,16 @@ func NewClient(addr string) (*Client, error) {
}, nil
}

type Client struct {
conn *grpc.ClientConn
client pb.ProfileClient
}

func (c Client) GetHotels(ctx context.Context, hotelIDs []int32) ProfileReply {
md, _ := metadata.FromContext(ctx)

t := trace.Tracer{TraceID: md["traceID"]}
t.Req(md["from"], "service.profile", "GetHotels")
defer t.Rep("service.profile", md["from"], time.Now())
trace.Req(md["traceID"], md["from"], "service.profile", "GetHotels")
defer trace.Rep(md["traceID"], "service.profile", md["from"], time.Now())

args := &pb.Args{HotelIds: hotelIDs}
reply, err := c.client.GetHotels(ctx, args)
Expand Down
Loading

0 comments on commit 7904945

Please sign in to comment.