Skip to content

Commit

Permalink
Merge pull request #240 from RonFed/database/sql_instrumentation
Browse files Browse the repository at this point in the history
Database/sql instrumentation
  • Loading branch information
MikeGoldsmith authored Aug 22, 2023
2 parents e717861 + 7ba795a commit e814306
Show file tree
Hide file tree
Showing 31 changed files with 1,303 additions and 15 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/e2e/k8s/sample-job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ spec:
value: "sample-app"
- name: OTEL_PROPAGATORS
value: "tracecontext,baggage"
- name: OTEL_GO_AUTO_INCLUDE_DB_STATEMENT
value: "true"
resources: {}
securityContext:
runAsUser: 0
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/kind.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
k8s-version: ["v1.26.0"]
library: ["gorillamux", "nethttp", "gin"]
library: ["gorillamux", "nethttp", "gin", "databasesql"]
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@ launcher/
opentelemetry-helm-charts/

# don't track temp e2e trace json files
test/**/traces-orig.json
test/**/traces-orig.json

# ignore db files created by the example or tests
*.db
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http

## [Unreleased]

### Added
- Add database/sql instrumentation ([#240](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/240))

### Changed

- The function signature of `"go.opentelemetry.io/auto/offsets-tracker/downloader".DownloadBinary` has changed.
Expand Down
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,11 @@ license-header-check:
exit 1; \
fi

.PHONY: fixture-nethttp fixture-gorillamux fixture-gin
.PHONY: fixture-nethttp fixture-gorillamux fixture-gin fixture-databasesql
fixture-nethttp: fixtures/nethttp
fixture-gorillamux: fixtures/gorillamux
fixture-gin: fixtures/gin
fixture-databasesql: fixtures/databasesql
fixtures/%: LIBRARY=$*
fixtures/%:
$(MAKE) docker-build
Expand Down
5 changes: 5 additions & 0 deletions examples/httpPlusdb/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM golang:1.20
WORKDIR /app
COPY . .
RUN go build -o main
ENTRYPOINT ["/app/main"]
19 changes: 19 additions & 0 deletions examples/httpPlusdb/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Example of Auto instrumentation of HTTP server + SQL database

This example shows a trace being generated which is composed of a http request and sql db handling -
both visible in the trace.

For testing auto instrumentation, we can use the docker compose.

To run the example, bring up the services using the command.

```
docker compose up
```

Now, you can hit the server using the below command
```
curl localhost:8080/query_db
Which will query the dummy sqlite database named test.db
```
Every hit to the server should generate a trace that we can observe in [Jaeger UI](http://localhost:16686/)
56 changes: 56 additions & 0 deletions examples/httpPlusdb/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
version: "3.9"

networks:
default:
name: http-db
driver: bridge

services:
http-plus-db:
depends_on:
- jaeger
build:
context: .
dockerfile: ./Dockerfile
pid: "host"
ports:
- "8080:8080"
volumes:
- shared-data:/app
- /proc:/host/proc
go-auto:
depends_on:
- http-plus-db
build:
context: ../..
dockerfile: Dockerfile
privileged: true
pid: "host"
environment:
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317
- OTEL_GO_AUTO_TARGET_EXE=/app/main
- OTEL_GO_AUTO_INCLUDE_DB_STATEMENT=true
- OTEL_SERVICE_NAME=httpPlusdb
- OTEL_PROPAGATORS=tracecontext,baggage
- CGO_ENABLED=1
volumes:
- shared-data:/app
- /proc:/host/proc

jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686"
- "14268:14268"
environment:
- COLLECTOR_OTLP_ENABLED=true
- LOG_LEVEL=debug
deploy:
resources:
limits:
memory: 300M
restart: unless-stopped


volumes:
shared-data:
12 changes: 12 additions & 0 deletions examples/httpPlusdb/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module go.opentelemetry.io/auto/examples/httpPlusdb

go 1.20

require go.uber.org/zap v1.24.0

require (
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
)

require github.com/mattn/go-sqlite3 v1.14.17
20 changes: 20 additions & 0 deletions examples/httpPlusdb/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Binary file added examples/httpPlusdb/http_Db_traces.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
136 changes: 136 additions & 0 deletions examples/httpPlusdb/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// 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 (
"database/sql"
"fmt"
"net/http"
"os"

_ "github.com/mattn/go-sqlite3"
"go.uber.org/zap"
)

const sqlQuery = "SELECT * FROM contacts"
const dbName = "test.db"
const tableDefinition = `CREATE TABLE contacts (
contact_id INTEGER PRIMARY KEY,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
phone TEXT NOT NULL UNIQUE);`
const tableInsertion = `INSERT INTO 'contacts'
('first_name', 'last_name', 'email', 'phone') VALUES
('Moshe', 'Levi', '[email protected]', '052-1234567');`

// Server is Http server that exposes multiple endpoints.
type Server struct {
db *sql.DB
}

// Create the db file.
func CreateDb() {
file, err := os.Create(dbName)
if err != nil {
panic(err)
}
err = file.Close()
if err != nil {
panic(err)
}
}

// NewServer creates a server struct after creating the DB and initializing it
// and creating a table named 'contacts' and adding a single row to it.
func NewServer() *Server {
CreateDb()

database, err := sql.Open("sqlite3", dbName)

if err != nil {
panic(err)
}

_, err = database.Exec(tableDefinition)

if err != nil {
panic(err)
}

_, err = database.Exec(tableInsertion)

if err != nil {
panic(err)
}

return &Server{
db: database,
}
}

func (s *Server) queryDb(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()

conn, err := s.db.Conn(ctx)

if err != nil {
panic(err)
}

rows, err := conn.QueryContext(req.Context(), sqlQuery)
if err != nil {
panic(err)
}

logger.Info("queryDb called")
for rows.Next() {
var id int
var firstName string
var lastName string
var email string
var phone string
err := rows.Scan(&id, &firstName, &lastName, &email, &phone)
if err != nil {
panic(err)
}
fmt.Fprintf(w, "ID: %d, firstName: %s, lastName: %s, email: %s, phone: %s\n", id, firstName, lastName, email, phone)
}
}

var logger *zap.Logger

func setupHandler(s *Server) *http.ServeMux {
mux := http.NewServeMux()
mux.HandleFunc("/query_db", s.queryDb)
return mux
}

func main() {
var err error
logger, err = zap.NewDevelopment()
if err != nil {
fmt.Printf("error creating zap logger, error:%v", err)
return
}
port := fmt.Sprintf(":%d", 8080)
logger.Info("starting http server", zap.String("port", port))

s := NewServer()
mux := setupHandler(s)
if err := http.ListenAndServe(port, mux); err != nil {
logger.Error("error running server", zap.Error(err))
}
}
20 changes: 19 additions & 1 deletion pkg/inject/injector.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,14 @@ type StructField struct {
Field string
}

// FlagField is used for configuring the ebpf programs by injecting boolean values.
type FlagField struct {
VarName string
Value bool
}

// Inject injects instrumentation for the provided library data type.
func (i *Injector) Inject(loadBpf loadBpfFunc, library string, libVersion string, fields []*StructField, initAlloc bool) (*ebpf.CollectionSpec, error) {
func (i *Injector) Inject(loadBpf loadBpfFunc, library string, libVersion string, fields []*StructField, flagFields []*FlagField, initAlloc bool) (*ebpf.CollectionSpec, error) {
spec, err := loadBpf()
if err != nil {
return nil, err
Expand All @@ -88,6 +94,11 @@ func (i *Injector) Inject(loadBpf loadBpfFunc, library string, libVersion string
if err := i.addCommonInjections(injectedVars, initAlloc); err != nil {
return nil, fmt.Errorf("adding instrumenter injections: %w", err)
}

if err := i.addConfigInjections(injectedVars, flagFields); err != nil {
return nil, fmt.Errorf("adding flags injections: %w", err)
}

log.Logger.V(0).Info("Injecting variables", "vars", injectedVars)
if len(injectedVars) > 0 {
err = spec.RewriteConstants(injectedVars)
Expand All @@ -112,6 +123,13 @@ func (i *Injector) addCommonInjections(varsMap map[string]interface{}, initAlloc
return nil
}

func (i *Injector) addConfigInjections(varsMap map[string]interface{}, flagFields []*FlagField) error {
for _, dm := range flagFields {
varsMap[dm.VarName] = dm.Value
}
return nil
}

func (i *Injector) getFieldOffset(structName string, fieldName string, libVersion string) (uint64, bool) {
strct, ok := i.data.Data[structName]
if !ok {
Expand Down
Loading

0 comments on commit e814306

Please sign in to comment.