Skip to content

Commit

Permalink
Instrumentation for github.com/bradfitz/gomemcache (#204)
Browse files Browse the repository at this point in the history
* Initial commit for gomemcache instrumentation

* Add gomemcache tests

* Add documentation and example

* PR feedback

- accept trace provider in config
- adjust example to reflect this change
- adjust example to use single copy of client with context
- define KV key constant for item key key
- adjust tests

* Configure CircleCI to run integration tests

* PR feedback - second round

- use operation type for operation names
- include multiple keys in span attribute, if applicable
- typos, naming convention fixes

Co-authored-by: Tyler Yahn <[email protected]>
  • Loading branch information
matej-g and MrAlias authored Aug 14, 2020
1 parent a851743 commit 9a03b4b
Show file tree
Hide file tree
Showing 15 changed files with 838 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,4 @@ workflows:
- integration:
matrix:
parameters:
target: [test-gocql, test-mongo-driver]
target: [test-gocql, test-mongo-driver, test-gomemcache]
14 changes: 14 additions & 0 deletions .circleci/wait.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ wait_for_mongo () {
exit 1
}

wait_for_gomemcache () {
for ((i = 0; i < 5; ++i)); do
if nc -z localhost 11211; then
exit 0
fi
echo "Gomemcache not yet available..."
sleep 10
done
echo "Timeout waiting for gomemcache to initialize"
exit 1
}

if [ -z "$CMD" ]; then
echo "CMD is undefined. exiting..."
exit 1
Expand All @@ -50,6 +62,8 @@ if [ "$CMD" == "cassandra" ]; then
wait_for_cassandra "$IMG_NAME"
elif [ "$CMD" == "mongo" ]; then
wait_for_mongo "$IMG_NAME"
elif [ "$CMD" == "gomemcache" ]; then
wait_for_gomemcache
else
echo "unknown CMD"
exit 1
Expand Down
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,18 @@ test-mongo-driver:
docker stop mongo-integ; \
fi

.PHONY: test-gomemcache
test-gomemcache:
@if ./.circleci/should_build.sh gomemcache; then \
set -e; \
docker run --name gomemcache-integ --rm -p 11211:11211 -d memcached; \
CMD=gomemcache IMG_NAME=gomemcache-integ ./.circleci/wait.sh; \
(cd instrumentation/github.com/bradfitz/gomemcache && \
$(GOTEST_WITH_COVERAGE) . && \
go tool cover -html=coverage.txt -o coverage.html); \
docker stop gomemcache-integ ; \
fi

.PHONY: check-clean-work-tree
check-clean-work-tree:
@if ! git diff --quiet; then \
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright The OpenTelemetry Authors
//
// 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 gomemcache

import (
"go.opentelemetry.io/otel/api/kv"
"go.opentelemetry.io/otel/api/standard"
)

type operation string

const (
operationAdd operation = "add"
operationCompareAndSwap operation = "cas"
operationDecrement operation = "decr"
operationDelete operation = "delete"
operationDeleteAll operation = "delete_all"
operationFlushAll operation = "flush_all"
operationGet operation = "get"
operationIncrement operation = "incr"
operationPing operation = "ping"
operationReplace operation = "replace"
operationSet operation = "set"
operationTouch operation = "touch"

mamcacheDBSystemValue = "memcached"

memcacheDBItemKeyName kv.Key = "db.memcached.item"
)

func memcacheDBSystem() kv.KeyValue {
return standard.DBSystemKey.String(mamcacheDBSystemValue)
}

func memcacheDBOperation(opName operation) kv.KeyValue {
return standard.DBOperationKey.String(string(opName))
}

func memcacheDBItemKeys(itemKeys ...string) kv.KeyValue {
if len(itemKeys) > 1 {
return memcacheDBItemKeyName.Array(itemKeys)
}

return memcacheDBItemKeyName.String(itemKeys[0])
}
41 changes: 41 additions & 0 deletions instrumentation/github.com/bradfitz/gomemcache/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright The OpenTelemetry Authors
//
// 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 gomemcache

import (
oteltrace "go.opentelemetry.io/otel/api/trace"
)

type config struct {
serviceName string
traceProvider oteltrace.Provider
}

// Option is used to configure the client.
type Option func(*config)

// WithTracer configures the client with the provided trace provider.
func WithTraceProvider(traceProvider oteltrace.Provider) Option {
return func(cfg *config) {
cfg.traceProvider = traceProvider
}
}

// WithServiceName sets the service name.
func WithServiceName(serviceName string) Option {
return func(cfg *config) {
cfg.serviceName = serviceName
}
}
20 changes: 20 additions & 0 deletions instrumentation/github.com/bradfitz/gomemcache/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright The OpenTelemetry Authors
//
// 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 gomemcache provides tracing instrumentation for
// the memcached client (https://github.com/bradfitz/gomemcache).
//
// The instrumentation works by wrapping the memcache client by calling `NewClientWithTracing` and
// tracing it's every operation.
package gomemcache // import "go.opentelemetry.io/contrib/instrumentation/github.com/bradfitz/gomemcache"
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright The OpenTelemetry Authors
#
# 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.
FROM golang:1.14-alpine AS base
COPY . /src/
WORKDIR /src/instrumentation/github.com/bradfitz/gomemcache

FROM base AS memcache-client
RUN go install ./example/client.go
CMD ["/go/bin/client"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# gomemcache instrumentation example

A simple example to demonstrate gomemcache client tracing instrumentation. It consists of two containers - `memcached-server`, which initializes and runs the Memcached server for this example, and `gomemcache-client`, which is the instrumented client.

In the example, the client invokes function `doMemcacheOperations()`, which is wrapped in a span. From within the function, the client will do a few example operations (add, get, delete with an intentional error) and cleans up the entries by calling `DeleteAll`.

These instructions expect you to have
[docker-compose](https://docs.docker.com/compose/) installed.

# Running the example

1. From within the `example` directory, bring up the project by running:

```sh
docker-compose up --detach
```

2. The instrumentation works with a `stdout` exporter, meaning the spans should be visible in the output of the `gomemcache-container`. To inspect the output, you can run:

```sh
docker-compose logs gomemcache-client
```

In the log, total of 5 spans should appear - the parent span `test-operations` and 4 child spans, each corresponding to one client operation. Additionally, the `Delete` operation span should also include `StatusCode` and `StatusMessage`, as this operation intentionally leads to an error.

3. After inspecting the client logs, the example can be cleaned up by running:

```sh
docker-compose down
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright The OpenTelemetry Authors
//
// 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 main

import (
"context"
"log"
"os"

"github.com/bradfitz/gomemcache/memcache"

"go.opentelemetry.io/contrib/instrumentation/github.com/bradfitz/gomemcache"
otelgomemcache "go.opentelemetry.io/contrib/instrumentation/github.com/bradfitz/gomemcache"
oteltrace "go.opentelemetry.io/otel/api/trace"

oteltracestdout "go.opentelemetry.io/otel/exporters/stdout"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)

func main() {
var host, port = os.Getenv("HOST"), "11211"

tp := initTracer()
ctx := context.Background()

c := otelgomemcache.NewClientWithTracing(
memcache.New(
host+":"+port,
),
gomemcache.WithTraceProvider(tp),
)

ctx, s := tp.Tracer("example-tracer").Start(ctx, "test-operations")
doMemcacheOperations(ctx, c)
s.End()
}

func doMemcacheOperations(ctx context.Context, c *otelgomemcache.Client) {
cc := c.WithContext(ctx)

err := cc.Add(&memcache.Item{
Key: "foo",
Value: []byte("bar"),
})
if err != nil {
log.Printf("Add failed: %s", err)
}

_, err = cc.Get("foo")
if err != nil {
log.Printf("Get failed: %s", err)
}

err = cc.Delete("baz")
if err != nil {
log.Printf("Delete failed: %s", err)
}

err = cc.DeleteAll()
if err != nil {
log.Printf("DeleteAll failed: %s", err)
}
}

func initTracer() oteltrace.Provider {
exporter, err := oteltracestdout.NewExporter(oteltracestdout.WithPrettyPrint())
if err != nil {
log.Fatal(err)
}
cfg := sdktrace.Config{
DefaultSampler: sdktrace.AlwaysSample(),
}
tp, err := sdktrace.NewProvider(
sdktrace.WithConfig(cfg),
sdktrace.WithSyncer(exporter),
)
if err != nil {
log.Fatal(err)
}

return tp
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright The OpenTelemetry Authors
#
# 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.
version: "3.7"
services:
gomemcache-client:
build:
dockerfile: $PWD/Dockerfile
context: ../../../../..
command:
- "/bin/sh"
- "-c"
- "/go/bin/client"
environment:
- HOST=memcached-server
networks:
- example
depends_on:
- memcached-server
memcached-server:
image: memcached:1.6.6-alpine
networks:
- example
networks:
example:
14 changes: 14 additions & 0 deletions instrumentation/github.com/bradfitz/gomemcache/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module go.opentelemetry.io/contrib/instrumentation/github.com/bradfitz/gomemcache

go 1.14

require (
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
github.com/stretchr/testify v1.6.1
go.opentelemetry.io/contrib v0.10.0
go.opentelemetry.io/otel v0.10.0
go.opentelemetry.io/otel/exporters/stdout v0.10.0
go.opentelemetry.io/otel/sdk v0.10.0
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940 // indirect
google.golang.org/grpc v1.30.0
)
Loading

0 comments on commit 9a03b4b

Please sign in to comment.