Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Database/sql instrumentation #240

Merged
merged 28 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6ad2df8
Add infastracture for uprobes to QueryContext
RonFed Jul 13, 2023
ca189c7
Add injection for register ABI flag
RonFed Jul 15, 2023
65ad5b1
Add perf buffer events from ebpf to go program
RonFed Jul 18, 2023
0b3a9d3
Adding demo for http server plus db query
RonFed Jul 19, 2023
4065313
Demo tested and verified
RonFed Jul 23, 2023
0b923ed
Merge branch 'main' into database/sql_instrumentation
RonFed Jul 24, 2023
427ce9a
Align to new bpffs for multi processes
RonFed Jul 24, 2023
8520c7b
Instrument (* DB).queryDC from database/sql which is an internal func…
RonFed Jul 26, 2023
ab73a7e
Update Dockerfie in example and add jaeger image of traces
RonFed Jul 26, 2023
645f975
Add changelog and fix lint
RonFed Jul 27, 2023
15c04be
More lint fixing
RonFed Jul 27, 2023
68a2f02
Fix lint in example of httpPlusDb
RonFed Jul 28, 2023
ca1b1fd
Update pkg/instrumentors/bpf/database/sql/probe.go
RonFed Jul 28, 2023
3c56a10
Add OTEL_GO_AUTO_INCLUDE_SQL_QUERY env var to allow opt in for includ…
RonFed Jul 28, 2023
2375b74
Add e2e test for databasesql - WIP
RonFed Jul 29, 2023
21ed5a7
Update databasesql e2e test WIP
RonFed Jul 29, 2023
c1fef0f
Finalize e2e test for databasesql
RonFed Jul 30, 2023
6866435
Change env var name to OTEL_GO_AUTO_INCLUDE_DB_STATEMENT
RonFed Aug 1, 2023
8d57c56
Merge branch 'main' into database/sql_instrumentation
RonFed Aug 1, 2023
9b284b6
Update pkg/instrumentors/bpf/database/sql/probe.go
RonFed Aug 2, 2023
606ad10
Merge branch 'main' into database/sql_instrumentation
RonFed Aug 2, 2023
9fa5514
Remove .db files and create them from go
RonFed Aug 2, 2023
5c18535
Fix typo
RonFed Aug 5, 2023
0164f3d
Merge branch 'main' into database/sql_instrumentation
RonFed Aug 7, 2023
21be897
Use updated context propagation
RonFed Aug 7, 2023
5226b9a
Merge branch 'main' into database/sql_instrumentation
RonFed Aug 20, 2023
f224173
Allign database/sql instrumentation to the latest changes from main b…
RonFed Aug 20, 2023
7ba795a
Merge branch 'main' into database/sql_instrumentation
MikeGoldsmith Aug 22, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http
- Add ARM64 support. ([#82](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/82))
- Add `OTEL_GO_AUTO_SHOW_VERIFIER_LOG` environment variable to control whether
the verifier log is shown. ([#128](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/128))
- Add database/sql instrumentation ([#240](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/240))

### 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.
97 changes: 97 additions & 0 deletions examples/httpPlusdb/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// 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"

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

const sqlQuery = "SELECT * FROM contacts"

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

// NewServer creates a server struct after initialing rand.
func NewServer() *Server {
database, err := sql.Open("sqlite3", "test.db")
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))
}
}
Binary file added examples/httpPlusdb/test.db
RonFed marked this conversation as resolved.
Show resolved Hide resolved
Binary file not shown.
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
90 changes: 90 additions & 0 deletions pkg/instrumentors/bpf/database/sql/bpf/probe.bpf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#include "arguments.h"
#include "span_context.h"
#include "go_context.h"
#include "go_types.h"

char __license[] SEC("license") = "Dual MIT/GPL";

#define MAX_QUERY_SIZE 100
#define MAX_CONCURRENT 50

struct sql_request_t {
u64 start_time;
u64 end_time;
char query[MAX_QUERY_SIZE];
struct span_context sc;
struct span_context psc;
};

struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, void*);
__type(value, struct sql_request_t);
__uint(max_entries, MAX_CONCURRENT);
} context_to_sql_events SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
} events SEC(".maps");

// Injected in init
volatile const bool should_include_db_statement;

// This instrumentation attaches uprobe to the following function:
// func (db *DB) queryDC(ctx, txctx context.Context, dc *driverConn, releaseConn func(error), query string, args []any)
SEC("uprobe/queryDC")
int uprobe_queryDC(struct pt_regs *ctx) {
// argument positions
u64 context_ptr_pos = 3;
MikeGoldsmith marked this conversation as resolved.
Show resolved Hide resolved
u64 query_str_ptr_pos = 8;
u64 query_str_len_pos = 9;

struct sql_request_t sql_request = {0};
sql_request.start_time = bpf_ktime_get_ns();

if (should_include_db_statement) {
// Read Query string
void *query_str_ptr = get_argument(ctx, query_str_ptr_pos);
u64 query_str_len = (u64)get_argument(ctx, query_str_len_pos);
u64 query_size = MAX_QUERY_SIZE < query_str_len ? MAX_QUERY_SIZE : query_str_len;
bpf_probe_read(sql_request.query, query_size, query_str_ptr);
}

// Get goroutine as the key fro the SQL request context
RonFed marked this conversation as resolved.
Show resolved Hide resolved
void *goroutine = get_goroutine_address(ctx, context_ptr_pos);

// Get parent if exists
struct span_context *span_ctx = bpf_map_lookup_elem(&spans_in_progress, &goroutine);
if (span_ctx != NULL) {
// Set the parent context
bpf_probe_read(&sql_request.psc, sizeof(sql_request.psc), span_ctx);
copy_byte_arrays(sql_request.psc.TraceID, sql_request.sc.TraceID, TRACE_ID_SIZE);
generate_random_bytes(sql_request.sc.SpanID, SPAN_ID_SIZE);
} else {
sql_request.sc = generate_span_context();
}

bpf_map_update_elem(&context_to_sql_events, &goroutine, &sql_request, 0);
RonFed marked this conversation as resolved.
Show resolved Hide resolved
// TODO : is this realy necessery if this is a leaf
bpf_map_update_elem(&spans_in_progress, &goroutine, &sql_request.sc, 0);
return 0;
}

// This instrumentation attaches uprobe to the following function:
// func (db *DB) queryDC(ctx, txctx context.Context, dc *driverConn, releaseConn func(error), query string, args []any)
SEC("uprobe/queryDC")
int uuprobe_QueryDC_Returns(struct pt_regs *ctx) {
u64 context_ptr_pos = 3;
void *goroutine = get_goroutine_address(ctx, context_ptr_pos);
void *sqlReq_ptr = bpf_map_lookup_elem(&context_to_sql_events, &goroutine);

struct sql_request_t sqlReq = {0};
bpf_probe_read(&sqlReq, sizeof(sqlReq), sqlReq_ptr);
sqlReq.end_time = bpf_ktime_get_ns();

bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &sqlReq, sizeof(sqlReq));

bpf_map_delete_elem(&context_to_sql_events, &goroutine);
RonFed marked this conversation as resolved.
Show resolved Hide resolved
bpf_map_delete_elem(&spans_in_progress, &goroutine);
return 0;
}
Loading
Loading