diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d6947342913..6129c4d0676 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -199,6 +199,24 @@ updates: schedule: interval: weekly day: sunday + - package-ecosystem: gomod + directory: /instrgen/driver + labels: + - dependencies + - go + - Skip Changelog + schedule: + interval: weekly + day: sunday + - package-ecosystem: gomod + directory: /instrgen/driver/testdata/interface + labels: + - dependencies + - go + - Skip Changelog + schedule: + interval: weekly + day: sunday - package-ecosystem: gomod directory: /instrumentation/github.com/Shopify/sarama/otelsarama labels: diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d257a4934a..0e7903315a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added -- Add the new `go.opentelemetry.io/contrib/instrgen` package to provide auto-generated source code instrumentation. (#3068) +- Add the new `go.opentelemetry.io/contrib/instrgen` package to provide auto-generated source code instrumentation. (#3068, #3108) ## [1.16.0-rc.2/0.41.0-rc.2/0.10.0-rc.2] - 2023-03-23 diff --git a/instrgen/README.md b/instrgen/README.md index c60f0d25ed8..0afe79ceaa2 100644 --- a/instrgen/README.md +++ b/instrgen/README.md @@ -6,6 +6,32 @@ This package provides a code generation utility that instruments existing source :construction: This package is currently work in progress. +## How to use it + +In order to instrument your project you have to add following call in your entry point function, usually main +(you can look at testdata directory for reference) and invoke instrgen tool. + +``` +func main() { + rtlib.AutotelEntryPoint() +``` + +Instrgen requires three parameters: command, path to project and package(s) pattern we +would like to instrument. + +``` +./instrgen --inject [path to your go project] [package(s) pattern] +``` + +Below concrete example with one of test instrumentation that is part of the project. + +``` +./instrgen --inject ./testdata/basic ./... +``` + +```./...``` works like wildcard in this case and it will instrument all packages in this path, but it can be invoked with +specific package as well. + ### Compatibility The `instrgen` utility is based on the Go standard library and is platform agnostic. diff --git a/instrgen/driver/go.mod b/instrgen/driver/go.mod new file mode 100644 index 00000000000..c796007c120 --- /dev/null +++ b/instrgen/driver/go.mod @@ -0,0 +1,19 @@ +module go.opentelemetry.io/contrib/instrgen/instrgen + +go 1.19 + +replace go.opentelemetry.io/contrib/instrgen => ../ + +require ( + github.com/stretchr/testify v1.8.1 + go.opentelemetry.io/contrib/instrgen v0.0.0-00010101000000-000000000000 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/mod v0.7.0 // indirect + golang.org/x/sys v0.3.0 // indirect + golang.org/x/tools v0.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/instrgen/driver/go.sum b/instrgen/driver/go.sum new file mode 100644 index 00000000000..bf8140031b0 --- /dev/null +++ b/instrgen/driver/go.sum @@ -0,0 +1,24 @@ +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/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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/instrgen/driver/instrgen_test.go b/instrgen/driver/instrgen_test.go new file mode 100644 index 00000000000..79b31010994 --- /dev/null +++ b/instrgen/driver/instrgen_test.go @@ -0,0 +1,118 @@ +// 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. + +//go:build !windows + +package main + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + alib "go.opentelemetry.io/contrib/instrgen/lib" +) + +var testcases = map[string]string{ + "./testdata/basic": "./testdata/expected/basic", + "./testdata/selector": "./testdata/expected/selector", + "./testdata/interface": "./testdata/expected/interface", +} + +var failures []string + +func inject(t *testing.T, root string, packagePattern string) { + err := executeCommand("--inject-dump-ir", root, packagePattern) + require.NoError(t, err) +} + +func Test(t *testing.T) { + for k, v := range testcases { + inject(t, k, "./...") + files := alib.SearchFiles(k, ".go_pass_tracing") + expectedFiles := alib.SearchFiles(v, ".go") + numOfFiles := len(expectedFiles) + fmt.Println("Go Files:", len(files)) + fmt.Println("Expected Go Files:", len(expectedFiles)) + numOfComparisons := 0 + for _, file := range files { + fmt.Println(filepath.Base(file)) + for _, expectedFile := range expectedFiles { + fmt.Println(filepath.Base(expectedFile)) + if filepath.Base(file) == filepath.Base(expectedFile+"_pass_tracing") { + f1, err1 := os.ReadFile(file) + require.NoError(t, err1) + f2, err2 := os.ReadFile(expectedFile) + require.NoError(t, err2) + if !assert.True(t, bytes.Equal(f1, f2)) { + fmt.Println(k) + failures = append(failures, k) + } + numOfComparisons = numOfComparisons + 1 + } + } + } + if numOfFiles != numOfComparisons { + fmt.Println("numberOfComparisons:", numOfComparisons) + panic("not all files were compared") + } + _, err := Prune(k, "./...", false) + if err != nil { + fmt.Println("Prune failed") + } + } + for _, f := range failures { + fmt.Println("FAILURE : ", f) + } +} + +func TestCommands(t *testing.T) { + err := executeCommand("--dumpcfg", "./testdata/dummy", "./...") + require.NoError(t, err) + err = executeCommand("--rootfunctions", "./testdata/dummy", "./...") + require.NoError(t, err) + err = executeCommand("--prune", "./testdata/dummy", "./...") + require.NoError(t, err) + err = executeCommand("--inject", "./testdata/dummy", "./...") + require.NoError(t, err) + err = usage() + require.NoError(t, err) +} + +func TestCallGraph(t *testing.T) { + cg := makeCallGraph("./testdata/dummy", "./...") + dumpCallGraph(cg) + assert.Equal(t, len(cg), 0, "callgraph should contain 0 elems") + rf := makeRootFunctions("./testdata/dummy", "./...") + dumpRootFunctions(rf) + assert.Equal(t, len(rf), 0, "rootfunctions set should be empty") +} + +func TestArgs(t *testing.T) { + err := checkArgs(nil) + require.Error(t, err) + args := []string{"driver", "--inject", "", "./..."} + err = checkArgs(args) + require.NoError(t, err) +} + +func TestUnknown(t *testing.T) { + err := executeCommand("unknown", "a", "b") + require.Error(t, err) +} diff --git a/instrgen/driver/main.go b/instrgen/driver/main.go new file mode 100644 index 00000000000..0f5b4ddf963 --- /dev/null +++ b/instrgen/driver/main.go @@ -0,0 +1,183 @@ +// 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 ( + "errors" + "fmt" + "go/ast" + "log" + "os" + + alib "go.opentelemetry.io/contrib/instrgen/lib" +) + +func usage() error { + fmt.Println("\nusage driver --command [path to go project] [package pattern]") + fmt.Println("\tcommand:") + fmt.Println("\t\tinject (injects open telemetry calls into project code)") + fmt.Println("\t\tinject-dump-ir (injects open telemetry calls into project code and intermediate passes)") + fmt.Println("\t\tprune (prune open telemetry calls") + fmt.Println("\t\tdumpcfg (dumps control flow graph)") + fmt.Println("\t\trootfunctions (dumps root functions)") + return nil +} + +func makeAnalysis(projectPath string, packagePattern string, debug bool) *alib.PackageAnalysis { + var rootFunctions []alib.FuncDescriptor + + interfaces := alib.FindInterfaces(projectPath, packagePattern) + rootFunctions = append(rootFunctions, alib.FindRootFunctions(projectPath, packagePattern, "AutotelEntryPoint")...) + funcDecls := alib.FindFuncDecls(projectPath, packagePattern, interfaces) + backwardCallGraph := alib.BuildCallGraph(projectPath, packagePattern, funcDecls, interfaces) + fmt.Println("\n\tchild parent") + for k, v := range backwardCallGraph { + fmt.Print("\n\t", k) + fmt.Print(" ", v) + } + fmt.Println("") + analysis := &alib.PackageAnalysis{ + ProjectPath: projectPath, + PackagePattern: packagePattern, + RootFunctions: rootFunctions, + FuncDecls: funcDecls, + Callgraph: backwardCallGraph, + Interfaces: interfaces, + Debug: debug} + return analysis +} + +// Prune. +func Prune(projectPath string, packagePattern string, debug bool) ([]*ast.File, error) { + analysis := makeAnalysis(projectPath, packagePattern, debug) + return analysis.Execute(&alib.OtelPruner{}, otelPrunerPassSuffix) +} + +func makeCallGraph(projectPath string, packagePattern string) map[alib.FuncDescriptor][]alib.FuncDescriptor { + var funcDecls map[alib.FuncDescriptor]bool + var backwardCallGraph map[alib.FuncDescriptor][]alib.FuncDescriptor + + interfaces := alib.FindInterfaces(projectPath, packagePattern) + funcDecls = alib.FindFuncDecls(projectPath, packagePattern, interfaces) + backwardCallGraph = alib.BuildCallGraph(projectPath, packagePattern, funcDecls, interfaces) + return backwardCallGraph +} + +func makeRootFunctions(projectPath string, packagePattern string) []alib.FuncDescriptor { + var rootFunctions []alib.FuncDescriptor + rootFunctions = append(rootFunctions, alib.FindRootFunctions(projectPath, packagePattern, "AutotelEntryPoint")...) + return rootFunctions +} + +func dumpCallGraph(callGraph map[alib.FuncDescriptor][]alib.FuncDescriptor) { + fmt.Println("\n\tchild parent") + for k, v := range callGraph { + fmt.Print("\n\t", k) + fmt.Print(" ", v) + } +} + +func dumpRootFunctions(rootFunctions []alib.FuncDescriptor) { + fmt.Println("rootfunctions:") + for _, fun := range rootFunctions { + fmt.Println("\t" + fun.TypeHash()) + } +} + +func isDirectory(path string) (bool, error) { + fileInfo, err := os.Stat(path) + if err != nil { + return false, err + } + + return fileInfo.IsDir(), err +} + +// Parsing algorithm works as follows. It goes through all function +// decls and infer function bodies to find call to AutotelEntryPoint +// A parent function of this call will become root of instrumentation +// Each function call from this place will be instrumented automatically. +func executeCommand(command string, projectPath string, packagePattern string) error { + isDir, err := isDirectory(projectPath) + if !isDir { + _ = usage() + return errors.New("[path to go project] argument must be directory") + } + if err != nil { + return err + } + switch command { + case "--inject": + _, err := Prune(projectPath, packagePattern, false) + if err != nil { + return err + } + analysis := makeAnalysis(projectPath, packagePattern, false) + err = ExecutePasses(analysis) + if err != nil { + return err + } + fmt.Println("\tinstrumentation done") + return nil + case "--inject-dump-ir": + _, err := Prune(projectPath, packagePattern, true) + if err != nil { + return err + } + analysis := makeAnalysis(projectPath, packagePattern, true) + err = ExecutePassesDumpIr(analysis) + if err != nil { + return err + } + fmt.Println("\tinstrumentation done") + return nil + case "--dumpcfg": + backwardCallGraph := makeCallGraph(projectPath, packagePattern) + dumpCallGraph(backwardCallGraph) + return nil + case "--rootfunctions": + rootFunctions := makeRootFunctions(projectPath, packagePattern) + dumpRootFunctions(rootFunctions) + return nil + case "--prune": + _, err := Prune(projectPath, packagePattern, false) + if err != nil { + return err + } + return nil + default: + return errors.New("unknown command") + } +} + +func checkArgs(args []string) error { + if len(args) != 4 { + _ = usage() + return errors.New("wrong arguments") + } + return nil +} + +func main() { + fmt.Println("autotel compiler") + err := checkArgs(os.Args) + if err != nil { + return + } + err = executeCommand(os.Args[1], os.Args[2], os.Args[3]) + if err != nil { + log.Fatal(err) + } +} diff --git a/instrgen/driver/passes.go b/instrgen/driver/passes.go new file mode 100644 index 00000000000..6c3f81f2bcf --- /dev/null +++ b/instrgen/driver/passes.go @@ -0,0 +1,52 @@ +// 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 ( + "fmt" + + "go.opentelemetry.io/contrib/instrgen/lib" +) + +const ( + otelPrunerPassSuffix = "_pass_pruner" + contextPassFileSuffix = "_pass_ctx" + instrumentationPassFileSuffix = "_pass_tracing" +) + +// ExecutePassesDumpIr. +func ExecutePassesDumpIr(analysis *lib.PackageAnalysis) error { + fmt.Println("Instrumentation") + _, err := analysis.Execute(&lib.InstrumentationPass{}, "") + if err != nil { + return err + } + + fmt.Println("ContextPropagation") + _, err = analysis.Execute(&lib.ContextPropagationPass{}, instrumentationPassFileSuffix) + return err +} + +// ExecutePasses. +func ExecutePasses(analysis *lib.PackageAnalysis) error { + fmt.Println("Instrumentation") + _, err := analysis.Execute(&lib.InstrumentationPass{}, instrumentationPassFileSuffix) + if err != nil { + return err + } + fmt.Println("ContextPropagation") + _, err = analysis.Execute(&lib.ContextPropagationPass{}, contextPassFileSuffix) + return err +} diff --git a/instrgen/driver/testdata/basic/fib.go b/instrgen/driver/testdata/basic/fib.go new file mode 100644 index 00000000000..835a4899a86 --- /dev/null +++ b/instrgen/driver/testdata/basic/fib.go @@ -0,0 +1,52 @@ +// 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. + +//nolint:all +package main + +import ( + "fmt" +) + +func foo() { + + fmt.Println("foo") +} + +func FibonacciHelper(n uint) (uint64, error) { + + func() { + + foo() + }() + return Fibonacci(n) +} + +func Fibonacci(n uint) (uint64, error) { + + if n <= 1 { + return uint64(n), nil + } + + if n > 93 { + return 0, fmt.Errorf("unsupported fibonacci number %d: too large", n) + } + + var n2, n1 uint64 = 0, 1 + for i := uint(2); i < n; i++ { + n2, n1 = n1, n1+n2 + } + + return n2 + n1, nil +} diff --git a/instrgen/driver/testdata/basic/goroutines.go b/instrgen/driver/testdata/basic/goroutines.go new file mode 100644 index 00000000000..47d913c0305 --- /dev/null +++ b/instrgen/driver/testdata/basic/goroutines.go @@ -0,0 +1,36 @@ +// 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. + +//nolint:all +package main + +import ( + "fmt" + + "go.opentelemetry.io/contrib/instrgen/rtlib" +) + +func goroutines() { + + messages := make(chan string) + + go func() { + + messages <- "ping" + }() + + msg := <-messages + fmt.Println(msg) + +} diff --git a/instrgen/driver/testdata/basic/main.go b/instrgen/driver/testdata/basic/main.go new file mode 100644 index 00000000000..6bb99a5db64 --- /dev/null +++ b/instrgen/driver/testdata/basic/main.go @@ -0,0 +1,39 @@ +// 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. + +//nolint:all +package main + +import ( + "fmt" + + "go.opentelemetry.io/contrib/instrgen/rtlib" +) + +func recur(n int) { + + if n > 0 { + recur(n - 1) + } +} + +func main() { + + rtlib.AutotelEntryPoint() + fmt.Println(FibonacciHelper(10)) + recur(5) + goroutines() + pack() + methods() +} diff --git a/instrgen/driver/testdata/basic/methods.go b/instrgen/driver/testdata/basic/methods.go new file mode 100644 index 00000000000..6152e0bdd56 --- /dev/null +++ b/instrgen/driver/testdata/basic/methods.go @@ -0,0 +1,61 @@ +// 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. + +//nolint:all +package main + +import ( + "go.opentelemetry.io/contrib/instrgen/rtlib" +) + +type element struct { +} + +type driver struct { + e element +} + +type i interface { + anotherfoo(p int) int +} + +type impl struct { +} + +func (i impl) anotherfoo(p int) int { + + return 5 +} + +func anotherfoo(p int) int { + return 1 +} + +func (d driver) process(a int) { + +} + +func (e element) get(a int) { + +} + +func methods() { + + d := driver{} + d.process(10) + d.e.get(5) + var in i + in = impl{} + in.anotherfoo(10) +} diff --git a/instrgen/driver/testdata/basic/package.go b/instrgen/driver/testdata/basic/package.go new file mode 100644 index 00000000000..03e721c9450 --- /dev/null +++ b/instrgen/driver/testdata/basic/package.go @@ -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. + +//nolint:all +package main + +import ( + "os" + + "go.opentelemetry.io/contrib/instrgen/rtlib" +) + +func Close() error { + return nil +} + +func pack() { + + f, e := os.Create("temp") + defer f.Close() + if e != nil { + + } +} diff --git a/instrgen/driver/testdata/dummy/main.go b/instrgen/driver/testdata/dummy/main.go new file mode 100644 index 00000000000..5b219294793 --- /dev/null +++ b/instrgen/driver/testdata/dummy/main.go @@ -0,0 +1,19 @@ +// 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. + +//nolint:all +package main + +func main() { +} diff --git a/instrgen/driver/testdata/expected/basic/fib.go b/instrgen/driver/testdata/expected/basic/fib.go new file mode 100644 index 00000000000..700cc037ebb --- /dev/null +++ b/instrgen/driver/testdata/expected/basic/fib.go @@ -0,0 +1,62 @@ +// 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. + +//nolint:all +package main + +import ( + "fmt" + __atel_context "context" + __atel_otel "go.opentelemetry.io/otel" +) + +func foo(__atel_tracing_ctx __atel_context.Context,) { + __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("foo").Start(__atel_tracing_ctx, "foo") + _ = __atel_child_tracing_ctx + defer __atel_span.End() + fmt.Println("foo") +} + +func FibonacciHelper(__atel_tracing_ctx __atel_context.Context, n uint) (uint64, error) { + __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("FibonacciHelper").Start(__atel_tracing_ctx, "FibonacciHelper") + _ = __atel_child_tracing_ctx + defer __atel_span.End() + func() { + __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("anonymous").Start(__atel_child_tracing_ctx, "anonymous") + _ = __atel_child_tracing_ctx + defer __atel_span.End() + foo(__atel_child_tracing_ctx) + }() + return Fibonacci(__atel_child_tracing_ctx, n) +} + +func Fibonacci(__atel_tracing_ctx __atel_context.Context, n uint) (uint64, error) { + __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("Fibonacci").Start(__atel_tracing_ctx, "Fibonacci") + _ = __atel_child_tracing_ctx + defer __atel_span.End() + if n <= 1 { + return uint64(n), nil + } + + if n > 93 { + return 0, fmt.Errorf("unsupported fibonacci number %d: too large", n) + } + + var n2, n1 uint64 = 0, 1 + for i := uint(2); i < n; i++ { + n2, n1 = n1, n1+n2 + } + + return n2 + n1, nil +} diff --git a/instrgen/driver/testdata/expected/basic/goroutines.go b/instrgen/driver/testdata/expected/basic/goroutines.go new file mode 100644 index 00000000000..5579749a30e --- /dev/null +++ b/instrgen/driver/testdata/expected/basic/goroutines.go @@ -0,0 +1,42 @@ +// 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. + +//nolint:all +package main + +import ( + "fmt" + __atel_context "context" + + "go.opentelemetry.io/contrib/instrgen/rtlib" + __atel_otel "go.opentelemetry.io/otel" +) + +func goroutines(__atel_tracing_ctx __atel_context.Context,) { + __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("goroutines").Start(__atel_tracing_ctx, "goroutines") + _ = __atel_child_tracing_ctx + defer __atel_span.End() + messages := make(chan string) + + go func() { + __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("anonymous").Start(__atel_child_tracing_ctx, "anonymous") + _ = __atel_child_tracing_ctx + defer __atel_span.End() + messages <- "ping" + }() + + msg := <-messages + fmt.Println(msg) + +} diff --git a/instrgen/driver/testdata/expected/basic/main.go b/instrgen/driver/testdata/expected/basic/main.go new file mode 100644 index 00000000000..4314e8e627a --- /dev/null +++ b/instrgen/driver/testdata/expected/basic/main.go @@ -0,0 +1,49 @@ +// 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. + +//nolint:all +package main + +import ( + "fmt" + __atel_context "context" + + "go.opentelemetry.io/contrib/instrgen/rtlib" + __atel_otel "go.opentelemetry.io/otel" +) + +func recur(__atel_tracing_ctx __atel_context.Context, n int) { + __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("recur").Start(__atel_tracing_ctx, "recur") + _ = __atel_child_tracing_ctx + defer __atel_span.End() + if n > 0 { + recur(__atel_child_tracing_ctx, n-1) + } +} + +func main() { + __atel_ts := rtlib.NewTracingState() + defer rtlib.Shutdown(__atel_ts) + __atel_otel.SetTracerProvider(__atel_ts.Tp) + __atel_ctx := __atel_context.Background() + __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("main").Start(__atel_ctx, "main") + _ = __atel_child_tracing_ctx + defer __atel_span.End() + rtlib.AutotelEntryPoint() + fmt.Println(FibonacciHelper(__atel_child_tracing_ctx, 10)) + recur(__atel_child_tracing_ctx, 5) + goroutines(__atel_child_tracing_ctx) + pack(__atel_child_tracing_ctx) + methods(__atel_child_tracing_ctx) +} diff --git a/instrgen/driver/testdata/expected/basic/methods.go b/instrgen/driver/testdata/expected/basic/methods.go new file mode 100644 index 00000000000..5f0d60c9760 --- /dev/null +++ b/instrgen/driver/testdata/expected/basic/methods.go @@ -0,0 +1,74 @@ +// 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. + +//nolint:all +package main + +import ( + "go.opentelemetry.io/contrib/instrgen/rtlib" + __atel_otel "go.opentelemetry.io/otel" + __atel_context "context" +) + +type element struct { +} + +type driver struct { + e element +} + +type i interface { + anotherfoo(__atel_tracing_ctx __atel_context.Context, p int) int +} + +type impl struct { +} + +func (i impl) anotherfoo(__atel_tracing_ctx __atel_context.Context, p int) int { + __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("anotherfoo").Start(__atel_tracing_ctx, "anotherfoo") + _ = __atel_child_tracing_ctx + defer __atel_span.End() + return 5 +} + +func anotherfoo(p int) int { + return 1 +} + +func (d driver) process(__atel_tracing_ctx __atel_context.Context, a int) { + __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("process").Start(__atel_tracing_ctx, "process") + _ = __atel_child_tracing_ctx + defer __atel_span.End() + +} + +func (e element) get(__atel_tracing_ctx __atel_context.Context, a int) { + __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("get").Start(__atel_tracing_ctx, "get") + _ = __atel_child_tracing_ctx + defer __atel_span.End() + +} + +func methods(__atel_tracing_ctx __atel_context.Context,) { + __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("methods").Start(__atel_tracing_ctx, "methods") + _ = __atel_child_tracing_ctx + defer __atel_span.End() + + d := driver{} + d.process(__atel_child_tracing_ctx, 10) + d.e.get(__atel_child_tracing_ctx, 5) + var in i + in = impl{} + in.anotherfoo(__atel_child_tracing_ctx, 10) +} diff --git a/instrgen/driver/testdata/expected/basic/package.go b/instrgen/driver/testdata/expected/basic/package.go new file mode 100644 index 00000000000..5e990d62fa8 --- /dev/null +++ b/instrgen/driver/testdata/expected/basic/package.go @@ -0,0 +1,39 @@ +// 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. + +//nolint:all +package main + +import ( + "os" + __atel_context "context" + + "go.opentelemetry.io/contrib/instrgen/rtlib" + __atel_otel "go.opentelemetry.io/otel" +) + +func Close() error { + return nil +} + +func pack(__atel_tracing_ctx __atel_context.Context,) { + __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("pack").Start(__atel_tracing_ctx, "pack") + _ = __atel_child_tracing_ctx + defer __atel_span.End() + f, e := os.Create("temp") + defer f.Close() + if e != nil { + + } +} diff --git a/instrgen/driver/testdata/expected/basic/traces.txt b/instrgen/driver/testdata/expected/basic/traces.txt new file mode 100644 index 00000000000..ecf2f9e541f --- /dev/null +++ b/instrgen/driver/testdata/expected/basic/traces.txt @@ -0,0 +1,148 @@ +{ + "Name": "foo", + "SpanContext": { + "TraceID": "374c11217817c32b01876bb2e2aceae8", + "SpanID": "8c982b3e435c56e0", + "TraceFlags": "01", + "TraceState": "", + "Remote": false + }, + "Parent": { + "TraceID": "374c11217817c32b01876bb2e2aceae8", + "SpanID": "65c37661e2869798", + "TraceFlags": "01", + "TraceState": "", + "Remote": false + }, + "SpanKind": 1, + "StartTime": "0001-01-01T00:00:00Z", + "EndTime": "0001-01-01T00:00:00Z", + "Attributes": null, + "Events": null, + "Links": null, + "Status": { + "Code": "Unset", + "Description": "" + }, + "DroppedAttributes": 0, + "DroppedEvents": 0, + "DroppedLinks": 0, + "ChildSpanCount": 0, + "Resource": null, + "InstrumentationLibrary": { + "Name": "foo", + "Version": "", + "SchemaURL": "" + } +} +{ + "Name": "Fibonacci", + "SpanContext": { + "TraceID": "374c11217817c32b01876bb2e2aceae8", + "SpanID": "6676950f24fe09e2", + "TraceFlags": "01", + "TraceState": "", + "Remote": false + }, + "Parent": { + "TraceID": "374c11217817c32b01876bb2e2aceae8", + "SpanID": "65c37661e2869798", + "TraceFlags": "01", + "TraceState": "", + "Remote": false + }, + "SpanKind": 1, + "StartTime": "0001-01-01T00:00:00Z", + "EndTime": "0001-01-01T00:00:00Z", + "Attributes": null, + "Events": null, + "Links": null, + "Status": { + "Code": "Unset", + "Description": "" + }, + "DroppedAttributes": 0, + "DroppedEvents": 0, + "DroppedLinks": 0, + "ChildSpanCount": 0, + "Resource": null, + "InstrumentationLibrary": { + "Name": "Fibonacci", + "Version": "", + "SchemaURL": "" + } +} +{ + "Name": "FibonacciHelper", + "SpanContext": { + "TraceID": "374c11217817c32b01876bb2e2aceae8", + "SpanID": "65c37661e2869798", + "TraceFlags": "01", + "TraceState": "", + "Remote": false + }, + "Parent": { + "TraceID": "374c11217817c32b01876bb2e2aceae8", + "SpanID": "89a2f3b8fc474d6a", + "TraceFlags": "01", + "TraceState": "", + "Remote": false + }, + "SpanKind": 1, + "StartTime": "0001-01-01T00:00:00Z", + "EndTime": "0001-01-01T00:00:00Z", + "Attributes": null, + "Events": null, + "Links": null, + "Status": { + "Code": "Unset", + "Description": "" + }, + "DroppedAttributes": 0, + "DroppedEvents": 0, + "DroppedLinks": 0, + "ChildSpanCount": 2, + "Resource": null, + "InstrumentationLibrary": { + "Name": "FibonacciHelper", + "Version": "", + "SchemaURL": "" + } +} +{ + "Name": "main", + "SpanContext": { + "TraceID": "374c11217817c32b01876bb2e2aceae8", + "SpanID": "89a2f3b8fc474d6a", + "TraceFlags": "01", + "TraceState": "", + "Remote": false + }, + "Parent": { + "TraceID": "00000000000000000000000000000000", + "SpanID": "0000000000000000", + "TraceFlags": "00", + "TraceState": "", + "Remote": false + }, + "SpanKind": 1, + "StartTime": "0001-01-01T00:00:00Z", + "EndTime": "0001-01-01T00:00:00Z", + "Attributes": null, + "Events": null, + "Links": null, + "Status": { + "Code": "Unset", + "Description": "" + }, + "DroppedAttributes": 0, + "DroppedEvents": 0, + "DroppedLinks": 0, + "ChildSpanCount": 1, + "Resource": null, + "InstrumentationLibrary": { + "Name": "main", + "Version": "", + "SchemaURL": "" + } +} diff --git a/instrgen/driver/testdata/expected/interface/app/impl.go b/instrgen/driver/testdata/expected/interface/app/impl.go new file mode 100644 index 00000000000..19433eaeb6a --- /dev/null +++ b/instrgen/driver/testdata/expected/interface/app/impl.go @@ -0,0 +1,33 @@ +// 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. + +//nolint:all +package app + +import ( + "fmt" + __atel_context "context" + __atel_otel "go.opentelemetry.io/otel" +) + +type BasicSerializer struct { +} + +func (b BasicSerializer) Serialize(__atel_tracing_ctx __atel_context.Context,) { + __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("Serialize").Start(__atel_tracing_ctx, "Serialize") + _ = __atel_child_tracing_ctx + defer __atel_span.End() + + fmt.Println("Serialize") +} diff --git a/instrgen/driver/testdata/expected/interface/main.go b/instrgen/driver/testdata/expected/interface/main.go new file mode 100644 index 00000000000..01b73a74145 --- /dev/null +++ b/instrgen/driver/testdata/expected/interface/main.go @@ -0,0 +1,40 @@ +// 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. + +//nolint:all +package main + +import ( + . "go.opentelemetry.io/contrib/instrgen/testdata/interface/app" + __atel_otel "go.opentelemetry.io/otel" + __atel_context "context" + . "go.opentelemetry.io/contrib/instrgen/testdata/interface/serializer" + "go.opentelemetry.io/contrib/instrgen/rtlib" +) + +func main() { + __atel_ts := rtlib.NewTracingState() + defer rtlib.Shutdown(__atel_ts) + __atel_otel.SetTracerProvider(__atel_ts.Tp) + __atel_ctx := __atel_context.Background() + __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("main").Start(__atel_ctx, "main") + _ = __atel_child_tracing_ctx + defer __atel_span.End() + + rtlib.AutotelEntryPoint() + bs := BasicSerializer{} + var s Serializer + s = bs + s.Serialize(__atel_child_tracing_ctx) +} diff --git a/instrgen/driver/testdata/expected/interface/serializer/interface.go b/instrgen/driver/testdata/expected/interface/serializer/interface.go new file mode 100644 index 00000000000..be7ae9e4dab --- /dev/null +++ b/instrgen/driver/testdata/expected/interface/serializer/interface.go @@ -0,0 +1,22 @@ +// 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. + +//nolint:all +package serializer + +import __atel_context "context" + +type Serializer interface { + Serialize(__atel_tracing_ctx __atel_context.Context,) +} diff --git a/instrgen/driver/testdata/expected/selector/main.go b/instrgen/driver/testdata/expected/selector/main.go new file mode 100644 index 00000000000..ea7cf370b6d --- /dev/null +++ b/instrgen/driver/testdata/expected/selector/main.go @@ -0,0 +1,53 @@ +// 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. + +//nolint:all +package main + +import ( + "go.opentelemetry.io/contrib/instrgen/rtlib" + __atel_otel "go.opentelemetry.io/otel" + __atel_context "context" +) + +type Driver interface { + Foo(__atel_tracing_ctx __atel_context.Context, i int) +} + +type Impl struct { +} + +func (impl Impl) Foo(__atel_tracing_ctx __atel_context.Context, i int) { + __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("Foo").Start(__atel_tracing_ctx, "Foo") + _ = __atel_child_tracing_ctx + defer __atel_span.End() +} + +func main() { + __atel_ts := rtlib.NewTracingState() + defer rtlib.Shutdown(__atel_ts) + __atel_otel.SetTracerProvider(__atel_ts.Tp) + __atel_ctx := __atel_context.Background() + __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("main").Start(__atel_ctx, "main") + _ = __atel_child_tracing_ctx + defer __atel_span.End() + rtlib.AutotelEntryPoint() + a := []Driver{ + Impl{}, + } + var d Driver + d = Impl{} + d.Foo(__atel_child_tracing_ctx, 3) + a[0].Foo(__atel_child_tracing_ctx, 4) +} diff --git a/instrgen/driver/testdata/funwithoutpathtoroot/driver.go b/instrgen/driver/testdata/funwithoutpathtoroot/driver.go new file mode 100644 index 00000000000..c1499f2b33c --- /dev/null +++ b/instrgen/driver/testdata/funwithoutpathtoroot/driver.go @@ -0,0 +1,33 @@ +// 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. + +//nolint:all +package main + +import ( + "go.opentelemetry.io/contrib/instrgen/rtlib" +) + +func bar() { + +} + +func foo() { + bar() +} + +func main() { + rtlib.AutotelEntryPoint() + bar() +} diff --git a/instrgen/driver/testdata/interface/app/impl.go b/instrgen/driver/testdata/interface/app/impl.go new file mode 100644 index 00000000000..9e186959e61 --- /dev/null +++ b/instrgen/driver/testdata/interface/app/impl.go @@ -0,0 +1,28 @@ +// 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. + +//nolint:all +package app + +import ( + "fmt" +) + +type BasicSerializer struct { +} + +func (b BasicSerializer) Serialize() { + + fmt.Println("Serialize") +} diff --git a/instrgen/driver/testdata/interface/go.mod b/instrgen/driver/testdata/interface/go.mod new file mode 100644 index 00000000000..2c23d008bef --- /dev/null +++ b/instrgen/driver/testdata/interface/go.mod @@ -0,0 +1,17 @@ +module go.opentelemetry.io/contrib/instrgen/testdata/interface + +go 1.18 + +replace go.opentelemetry.io/contrib/instrgen => ../../.. + +require go.opentelemetry.io/contrib/instrgen v0.0.0-20221228173227-92e0588b124b + +require ( + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + go.opentelemetry.io/otel v1.11.2 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.11.2 // indirect + go.opentelemetry.io/otel/sdk v1.11.2 // indirect + go.opentelemetry.io/otel/trace v1.11.2 // indirect + golang.org/x/sys v0.3.0 // indirect +) diff --git a/instrgen/driver/testdata/interface/go.sum b/instrgen/driver/testdata/interface/go.sum new file mode 100644 index 00000000000..3bce3086182 --- /dev/null +++ b/instrgen/driver/testdata/interface/go.sum @@ -0,0 +1,20 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +go.opentelemetry.io/otel v1.11.2 h1:YBZcQlsVekzFsFbjygXMOXSs6pialIZxcjfO/mBDmR0= +go.opentelemetry.io/otel v1.11.2/go.mod h1:7p4EUV+AqgdlNV9gL97IgUZiVR3yrFXYo53f9BM3tRI= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.11.2 h1:BhEVgvuE1NWLLuMLvC6sif791F45KFHi5GhOs1KunZU= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.11.2/go.mod h1:bx//lU66dPzNT+Y0hHA12ciKoMOH9iixEwCqC1OeQWQ= +go.opentelemetry.io/otel/sdk v1.11.2 h1:GF4JoaEx7iihdMFu30sOyRx52HDHOkl9xQ8SMqNXUiU= +go.opentelemetry.io/otel/sdk v1.11.2/go.mod h1:wZ1WxImwpq+lVRo4vsmSOxdd+xwoUJ6rqyLc3SyX9aU= +go.opentelemetry.io/otel/trace v1.11.2 h1:Xf7hWSF2Glv0DE3MH7fBHvtpSBsjcBUe5MYAmZM/+y0= +go.opentelemetry.io/otel/trace v1.11.2/go.mod h1:4N+yC7QEz7TTsG9BSRLNAa63eg5E06ObSbKPmxQ/pKA= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/instrgen/driver/testdata/interface/main.go b/instrgen/driver/testdata/interface/main.go new file mode 100644 index 00000000000..5ec2cf8651d --- /dev/null +++ b/instrgen/driver/testdata/interface/main.go @@ -0,0 +1,31 @@ +// 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. + +//nolint:all +package main + +import ( + . "go.opentelemetry.io/contrib/instrgen/testdata/interface/app" + . "go.opentelemetry.io/contrib/instrgen/testdata/interface/serializer" + "go.opentelemetry.io/contrib/instrgen/rtlib" +) + +func main() { + + rtlib.AutotelEntryPoint() + bs := BasicSerializer{} + var s Serializer + s = bs + s.Serialize() +} diff --git a/instrgen/driver/testdata/interface/serializer/interface.go b/instrgen/driver/testdata/interface/serializer/interface.go new file mode 100644 index 00000000000..83ff55d2278 --- /dev/null +++ b/instrgen/driver/testdata/interface/serializer/interface.go @@ -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. + +//nolint:all +package serializer + +type Serializer interface { + Serialize() +} diff --git a/instrgen/driver/testdata/selector/main.go b/instrgen/driver/testdata/selector/main.go new file mode 100644 index 00000000000..696e0a36308 --- /dev/null +++ b/instrgen/driver/testdata/selector/main.go @@ -0,0 +1,43 @@ +// 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. + +//nolint:all +package main + +import ( + "go.opentelemetry.io/contrib/instrgen/rtlib" +) + +type Driver interface { + Foo(i int) +} + +type Impl struct { +} + +func (impl Impl) Foo(i int) { + +} + +func main() { + + rtlib.AutotelEntryPoint() + a := []Driver{ + Impl{}, + } + var d Driver + d = Impl{} + d.Foo(3) + a[0].Foo(4) +} diff --git a/instrgen/go.mod b/instrgen/go.mod index f15e3918619..b8429b11a5e 100644 --- a/instrgen/go.mod +++ b/instrgen/go.mod @@ -1,3 +1,18 @@ module go.opentelemetry.io/contrib/instrgen go 1.19 + +require ( + go.opentelemetry.io/otel v1.11.2 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.11.2 + go.opentelemetry.io/otel/sdk v1.11.2 + golang.org/x/tools v0.4.0 +) + +require ( + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + go.opentelemetry.io/otel/trace v1.11.2 // indirect + golang.org/x/mod v0.7.0 // indirect + golang.org/x/sys v0.3.0 // indirect +) diff --git a/instrgen/go.sum b/instrgen/go.sum new file mode 100644 index 00000000000..34de4a69aaf --- /dev/null +++ b/instrgen/go.sum @@ -0,0 +1,25 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +go.opentelemetry.io/otel v1.11.2 h1:YBZcQlsVekzFsFbjygXMOXSs6pialIZxcjfO/mBDmR0= +go.opentelemetry.io/otel v1.11.2/go.mod h1:7p4EUV+AqgdlNV9gL97IgUZiVR3yrFXYo53f9BM3tRI= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.11.2 h1:BhEVgvuE1NWLLuMLvC6sif791F45KFHi5GhOs1KunZU= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.11.2/go.mod h1:bx//lU66dPzNT+Y0hHA12ciKoMOH9iixEwCqC1OeQWQ= +go.opentelemetry.io/otel/sdk v1.11.2 h1:GF4JoaEx7iihdMFu30sOyRx52HDHOkl9xQ8SMqNXUiU= +go.opentelemetry.io/otel/sdk v1.11.2/go.mod h1:wZ1WxImwpq+lVRo4vsmSOxdd+xwoUJ6rqyLc3SyX9aU= +go.opentelemetry.io/otel/trace v1.11.2 h1:Xf7hWSF2Glv0DE3MH7fBHvtpSBsjcBUe5MYAmZM/+y0= +go.opentelemetry.io/otel/trace v1.11.2/go.mod h1:4N+yC7QEz7TTsG9BSRLNAa63eg5E06ObSbKPmxQ/pKA= +golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/instrgen/lib/analysis.go b/instrgen/lib/analysis.go new file mode 100644 index 00000000000..53ecf86885c --- /dev/null +++ b/instrgen/lib/analysis.go @@ -0,0 +1,141 @@ +// 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 lib // import "go.opentelemetry.io/contrib/instrgen/lib" + +import ( + "fmt" + "go/ast" + "go/printer" + "go/token" + "os" + + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/go/packages" +) + +// PackageAnalysis analyze all package set accrding to passed +// pattern. It requires an information about path, pattern, +// root functions - entry points, function declarations, +// and so on. +type PackageAnalysis struct { + ProjectPath string + PackagePattern string + RootFunctions []FuncDescriptor + FuncDecls map[FuncDescriptor]bool + Callgraph map[FuncDescriptor][]FuncDescriptor + Interfaces map[string]bool + Debug bool +} + +type importaction int + +const ( + // const that tells whether package should be imported. + Add importaction = iota + // or removed. + Remove +) + +// Stores an information about operations on packages. +// Currently packages can be imported with an aliases +// or without. +type Import struct { + NamedPackage string + Package string + ImportAction importaction +} + +// FileAnalysisPass executes an analysis for +// specific file node - translation unit. +type FileAnalysisPass interface { + Execute(node *ast.File, + analysis *PackageAnalysis, + pkg *packages.Package, + pkgs []*packages.Package) []Import +} + +func createFile(name string) (*os.File, error) { + var out *os.File + out, err := os.Create(name) + if err != nil { + defer out.Close() + } + return out, err +} + +func addImports(imports []Import, fset *token.FileSet, fileNode *ast.File) { + for _, imp := range imports { + if imp.ImportAction == Add { + if len(imp.NamedPackage) > 0 { + astutil.AddNamedImport(fset, fileNode, imp.NamedPackage, imp.Package) + } else { + astutil.AddImport(fset, fileNode, imp.Package) + } + } else { + if len(imp.NamedPackage) > 0 { + astutil.DeleteNamedImport(fset, fileNode, imp.NamedPackage, imp.Package) + } else { + astutil.DeleteImport(fset, fileNode, imp.Package) + } + } + } +} + +// Execute function, main entry point to analysis process. +func (analysis *PackageAnalysis) Execute(pass FileAnalysisPass, fileSuffix string) ([]*ast.File, error) { + fset := token.NewFileSet() + cfg := &packages.Config{Fset: fset, Mode: LoadMode, Dir: analysis.ProjectPath} + pkgs, err := packages.Load(cfg, analysis.PackagePattern) + if err != nil { + return nil, err + } + var fileNodeSet []*ast.File + for _, pkg := range pkgs { + fmt.Println("\t", pkg) + // fileNode represents a translationUnit + var fileNode *ast.File + for _, fileNode = range pkg.Syntax { + fmt.Println("\t\t", fset.File(fileNode.Pos()).Name()) + var out *os.File + out, err = createFile(fset.File(fileNode.Pos()).Name() + fileSuffix) + if err != nil { + return nil, err + } + if len(analysis.RootFunctions) == 0 { + e := printer.Fprint(out, fset, fileNode) + if e != nil { + return nil, e + } + continue + } + imports := pass.Execute(fileNode, analysis, pkg, pkgs) + addImports(imports, fset, fileNode) + e := printer.Fprint(out, fset, fileNode) + if e != nil { + return nil, e + } + if !analysis.Debug { + oldFileName := fset.File(fileNode.Pos()).Name() + fileSuffix + newFileName := fset.File(fileNode.Pos()).Name() + e = os.Rename(oldFileName, newFileName) + if e != nil { + return nil, e + } + } + fileNodeSet = append(fileNodeSet, fileNode) + } + } + return fileNodeSet, nil +} diff --git a/instrgen/lib/callgraph.go b/instrgen/lib/callgraph.go new file mode 100644 index 00000000000..e655d1d41c1 --- /dev/null +++ b/instrgen/lib/callgraph.go @@ -0,0 +1,392 @@ +// 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 lib // import "go.opentelemetry.io/contrib/instrgen/lib" + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "strings" + + "golang.org/x/tools/go/packages" +) + +// FuncDescriptor stores an information about +// id, type and if function requires custom instrumentation. +type FuncDescriptor struct { + Id string + DeclType string + CustomInjection bool +} + +// Function TypeHash. Each function is itentified by its +// id and type. +func (fd FuncDescriptor) TypeHash() string { + return fd.Id + fd.DeclType +} + +// LoadMode. Tells about needed information during analysis. +const LoadMode packages.LoadMode = packages.NeedName | + packages.NeedTypes | + packages.NeedSyntax | + packages.NeedTypesInfo | + packages.NeedFiles + +func getPkgs(projectPath string, packagePattern string, fset *token.FileSet) ([]*packages.Package, error) { + cfg := &packages.Config{Fset: fset, Mode: LoadMode, Dir: projectPath} + pkgs, err := packages.Load(cfg, packagePattern) + var packageSet []*packages.Package + if err != nil { + return nil, err + } + for _, pkg := range pkgs { + fmt.Println("\t", pkg) + packageSet = append(packageSet, pkg) + } + return packageSet, nil +} + +// FindRootFunctions looks for all root functions eg. entry points. +// Currently an entry point is a function that contains call of function +// passed as functionLabel paramaterer. +func FindRootFunctions(projectPath string, packagePattern string, functionLabel string) []FuncDescriptor { + fset := token.NewFileSet() + pkgs, _ := getPkgs(projectPath, packagePattern, fset) + var currentFun FuncDescriptor + var rootFunctions []FuncDescriptor + for _, pkg := range pkgs { + for _, node := range pkg.Syntax { + ast.Inspect(node, func(n ast.Node) bool { + switch xNode := n.(type) { + case *ast.CallExpr: + selector, ok := xNode.Fun.(*ast.SelectorExpr) + if ok { + if selector.Sel.Name == functionLabel { + rootFunctions = append(rootFunctions, currentFun) + } + } + case *ast.FuncDecl: + if pkg.TypesInfo.Defs[xNode.Name] != nil { + funId := pkg.TypesInfo.Defs[xNode.Name].Pkg().Path() + "." + pkg.TypesInfo.Defs[xNode.Name].Name() + currentFun = FuncDescriptor{funId, pkg.TypesInfo.Defs[xNode.Name].Type().String(), false} + fmt.Println("\t\t\tFuncDecl:", funId, pkg.TypesInfo.Defs[xNode.Name].Type().String()) + } + } + return true + }) + } + } + return rootFunctions +} + +// GetMostInnerAstIdent takes most inner identifier used for +// function call. For a.b.foo(), `b` will be the most inner identifier. +func GetMostInnerAstIdent(inSel *ast.SelectorExpr) *ast.Ident { + var l []*ast.Ident + var e ast.Expr + e = inSel + for e != nil { + if _, ok := e.(*ast.Ident); ok { + l = append(l, e.(*ast.Ident)) + break + } else if _, ok := e.(*ast.SelectorExpr); ok { + l = append(l, e.(*ast.SelectorExpr).Sel) + e = e.(*ast.SelectorExpr).X + } else if _, ok := e.(*ast.CallExpr); ok { + e = e.(*ast.CallExpr).Fun + } else if _, ok := e.(*ast.IndexExpr); ok { + e = e.(*ast.IndexExpr).X + } else if _, ok := e.(*ast.UnaryExpr); ok { + e = e.(*ast.UnaryExpr).X + } else if _, ok := e.(*ast.ParenExpr); ok { + e = e.(*ast.ParenExpr).X + } else if _, ok := e.(*ast.SliceExpr); ok { + e = e.(*ast.SliceExpr).X + } else if _, ok := e.(*ast.IndexListExpr); ok { + e = e.(*ast.IndexListExpr).X + } else if _, ok := e.(*ast.StarExpr); ok { + e = e.(*ast.StarExpr).X + } else if _, ok := e.(*ast.TypeAssertExpr); ok { + e = e.(*ast.TypeAssertExpr).X + } else if _, ok := e.(*ast.CompositeLit); ok { + // TODO dummy implementation + if len(e.(*ast.CompositeLit).Elts) == 0 { + e = e.(*ast.CompositeLit).Type + } else { + e = e.(*ast.CompositeLit).Elts[0] + } + } else if _, ok := e.(*ast.KeyValueExpr); ok { + e = e.(*ast.KeyValueExpr).Value + } else { + // TODO this is uncaught expression + panic("uncaught expression") + } + } + if len(l) < 2 { + panic("selector list should have at least 2 elems") + } + // caller or receiver is always + // at position 1, function is at 0 + return l[1] +} + +// GetPkgPathFromRecvInterface builds package path taking +// receiver interface into account. +func GetPkgPathFromRecvInterface(pkg *packages.Package, + pkgs []*packages.Package, funDeclNode *ast.FuncDecl, interfaces map[string]bool) string { + var pkgPath string + for _, v := range funDeclNode.Recv.List { + for _, dependentpkg := range pkgs { + for _, defs := range dependentpkg.TypesInfo.Defs { + if defs == nil { + continue + } + if _, ok := defs.Type().Underlying().(*types.Interface); !ok { + continue + } + if len(v.Names) == 0 || pkg.TypesInfo.Defs[v.Names[0]] == nil { + continue + } + funType := pkg.TypesInfo.Defs[v.Names[0]].Type() + + if types.Implements(funType, defs.Type().Underlying().(*types.Interface)) { + interfaceExists := interfaces[defs.Type().String()] + if interfaceExists { + pkgPath = defs.Type().String() + } + break + } + } + } + } + return pkgPath +} + +// GetPkgPathFromFunctionRecv build package path taking function receiver parameters. +func GetPkgPathFromFunctionRecv(pkg *packages.Package, + pkgs []*packages.Package, funDeclNode *ast.FuncDecl, interfaces map[string]bool) string { + pkgPath := GetPkgPathFromRecvInterface(pkg, pkgs, funDeclNode, interfaces) + if len(pkgPath) != 0 { + return pkgPath + } + for _, v := range funDeclNode.Recv.List { + if len(v.Names) == 0 { + continue + } + funType := pkg.TypesInfo.Defs[v.Names[0]].Type() + pkgPath = funType.String() + // We don't care if that's pointer, remove it from + // type id + if _, ok := funType.(*types.Pointer); ok { + pkgPath = strings.TrimPrefix(pkgPath, "*") + } + // We don't care if called via index, remove it from + // type id + if _, ok := funType.(*types.Slice); ok { + pkgPath = strings.TrimPrefix(pkgPath, "[]") + } + } + + return pkgPath +} + +// GetSelectorPkgPath builds packages path according to selector expr. +func GetSelectorPkgPath(sel *ast.SelectorExpr, pkg *packages.Package, pkgPath string) string { + caller := GetMostInnerAstIdent(sel) + if caller != nil && pkg.TypesInfo.Uses[caller] != nil { + if !strings.Contains(pkg.TypesInfo.Uses[caller].Type().String(), "invalid") { + pkgPath = pkg.TypesInfo.Uses[caller].Type().String() + // We don't care if that's pointer, remove it from + // type id + if _, ok := pkg.TypesInfo.Uses[caller].Type().(*types.Pointer); ok { + pkgPath = strings.TrimPrefix(pkgPath, "*") + } + // We don't care if called via index, remove it from + // type id + if _, ok := pkg.TypesInfo.Uses[caller].Type().(*types.Slice); ok { + pkgPath = strings.TrimPrefix(pkgPath, "[]") + } + } + } + return pkgPath +} + +// GetPkgNameFromUsesTable gets package name from uses table. +func GetPkgNameFromUsesTable(pkg *packages.Package, ident *ast.Ident) string { + var pkgPath string + if pkg.TypesInfo.Uses[ident].Pkg() != nil { + pkgPath = pkg.TypesInfo.Uses[ident].Pkg().Path() + } + return pkgPath +} + +// GetPkgNameFromDefsTable gets package name from uses table. +func GetPkgNameFromDefsTable(pkg *packages.Package, ident *ast.Ident) string { + var pkgPath string + if pkg.TypesInfo.Defs[ident] == nil { + return pkgPath + } + if pkg.TypesInfo.Defs[ident].Pkg() != nil { + pkgPath = pkg.TypesInfo.Defs[ident].Pkg().Path() + } + return pkgPath +} + +// GetPkgPathForFunction builds package path, delegates work to +// other helper functions defined above. +func GetPkgPathForFunction(pkg *packages.Package, + pkgs []*packages.Package, funDecl *ast.FuncDecl, interfaces map[string]bool) string { + if funDecl.Recv != nil { + return GetPkgPathFromFunctionRecv(pkg, pkgs, funDecl, interfaces) + } + return GetPkgNameFromDefsTable(pkg, funDecl.Name) +} + +// BuildCallGraph builds an information about flow graph +// in the following form child->parent. +func BuildCallGraph( + projectPath string, + packagePattern string, + funcDecls map[FuncDescriptor]bool, + interfaces map[string]bool) map[FuncDescriptor][]FuncDescriptor { + fset := token.NewFileSet() + pkgs, _ := getPkgs(projectPath, packagePattern, fset) + fmt.Println("BuildCallGraph") + currentFun := FuncDescriptor{"nil", "", false} + backwardCallGraph := make(map[FuncDescriptor][]FuncDescriptor) + for _, pkg := range pkgs { + fmt.Println("\t", pkg) + for _, node := range pkg.Syntax { + fmt.Println("\t\t", fset.File(node.Pos()).Name()) + ast.Inspect(node, func(n ast.Node) bool { + switch xNode := n.(type) { + case *ast.CallExpr: + if id, ok := xNode.Fun.(*ast.Ident); ok { + pkgPath := GetPkgNameFromUsesTable(pkg, id) + funId := pkgPath + "." + pkg.TypesInfo.Uses[id].Name() + fmt.Println("\t\t\tFuncCall:", funId, pkg.TypesInfo.Uses[id].Type().String(), + " @called : ", + fset.File(node.Pos()).Name()) + fun := FuncDescriptor{funId, pkg.TypesInfo.Uses[id].Type().String(), false} + if !Contains(backwardCallGraph[fun], currentFun) { + if funcDecls[fun] { + backwardCallGraph[fun] = append(backwardCallGraph[fun], currentFun) + } + } + } + if sel, ok := xNode.Fun.(*ast.SelectorExpr); ok { + if pkg.TypesInfo.Uses[sel.Sel] != nil { + pkgPath := GetPkgNameFromUsesTable(pkg, sel.Sel) + if sel.X != nil { + pkgPath = GetSelectorPkgPath(sel, pkg, pkgPath) + } + funId := pkgPath + "." + pkg.TypesInfo.Uses[sel.Sel].Name() + fmt.Println("\t\t\tFuncCall via selector:", funId, pkg.TypesInfo.Uses[sel.Sel].Type().String(), + " @called : ", + fset.File(node.Pos()).Name()) + fun := FuncDescriptor{funId, pkg.TypesInfo.Uses[sel.Sel].Type().String(), false} + if !Contains(backwardCallGraph[fun], currentFun) { + if funcDecls[fun] { + backwardCallGraph[fun] = append(backwardCallGraph[fun], currentFun) + } + } + } + } + case *ast.FuncDecl: + if pkg.TypesInfo.Defs[xNode.Name] != nil { + pkgPath := GetPkgPathForFunction(pkg, pkgs, xNode, interfaces) + funId := pkgPath + "." + pkg.TypesInfo.Defs[xNode.Name].Name() + funcDecls[FuncDescriptor{funId, pkg.TypesInfo.Defs[xNode.Name].Type().String(), false}] = true + currentFun = FuncDescriptor{funId, pkg.TypesInfo.Defs[xNode.Name].Type().String(), false} + fmt.Println("\t\t\tFuncDecl:", funId, pkg.TypesInfo.Defs[xNode.Name].Type().String()) + } + } + return true + }) + } + } + return backwardCallGraph +} + +// FindFuncDecls looks for all function declarations. +func FindFuncDecls(projectPath string, packagePattern string, interfaces map[string]bool) map[FuncDescriptor]bool { + fset := token.NewFileSet() + pkgs, _ := getPkgs(projectPath, packagePattern, fset) + fmt.Println("FindFuncDecls") + funcDecls := make(map[FuncDescriptor]bool) + for _, pkg := range pkgs { + fmt.Println("\t", pkg) + for _, node := range pkg.Syntax { + fmt.Println("\t\t", fset.File(node.Pos()).Name()) + ast.Inspect(node, func(n ast.Node) bool { + if funDeclNode, ok := n.(*ast.FuncDecl); ok { + pkgPath := GetPkgPathForFunction(pkg, pkgs, funDeclNode, interfaces) + if pkg.TypesInfo.Defs[funDeclNode.Name] != nil { + funId := pkgPath + "." + pkg.TypesInfo.Defs[funDeclNode.Name].Name() + fmt.Println("\t\t\tFuncDecl:", funId, pkg.TypesInfo.Defs[funDeclNode.Name].Type().String()) + funcDecls[FuncDescriptor{funId, pkg.TypesInfo.Defs[funDeclNode.Name].Type().String(), false}] = true + } + } + return true + }) + } + } + return funcDecls +} + +// FindInterfaces looks for all interfaces. +func FindInterfaces(projectPath string, packagePattern string) map[string]bool { + fset := token.NewFileSet() + pkgs, _ := getPkgs(projectPath, packagePattern, fset) + fmt.Println("FindInterfaces") + interaceTable := make(map[string]bool) + for _, pkg := range pkgs { + fmt.Println("\t", pkg) + for _, node := range pkg.Syntax { + fmt.Println("\t\t", fset.File(node.Pos()).Name()) + ast.Inspect(node, func(n ast.Node) bool { + if typeSpecNode, ok := n.(*ast.TypeSpec); ok { + if _, ok := typeSpecNode.Type.(*ast.InterfaceType); ok { + fmt.Println("\t\t\tInterface:", pkg.TypesInfo.Defs[typeSpecNode.Name].Type().String()) + interaceTable[pkg.TypesInfo.Defs[typeSpecNode.Name].Type().String()] = true + } + } + return true + }) + } + } + return interaceTable +} + +// InferRootFunctionsFromGraph tries to infer entry points from passed call graph. +func InferRootFunctionsFromGraph(callgraph map[FuncDescriptor][]FuncDescriptor) []FuncDescriptor { + var allFunctions map[FuncDescriptor]bool + var rootFunctions []FuncDescriptor + allFunctions = make(map[FuncDescriptor]bool) + for k, v := range callgraph { + allFunctions[k] = true + for _, childFun := range v { + allFunctions[childFun] = true + } + } + for k := range allFunctions { + _, exists := callgraph[k] + if !exists { + rootFunctions = append(rootFunctions, k) + } + } + return rootFunctions +} diff --git a/instrgen/lib/context_propagation.go b/instrgen/lib/context_propagation.go new file mode 100644 index 00000000000..e75588524c3 --- /dev/null +++ b/instrgen/lib/context_propagation.go @@ -0,0 +1,210 @@ +// 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 lib // import "go.opentelemetry.io/contrib/instrgen/lib" + +import ( + "fmt" + "go/ast" + + "golang.org/x/tools/go/packages" +) + +func isFunPartOfCallGraph(fun FuncDescriptor, callgraph map[FuncDescriptor][]FuncDescriptor) bool { + // TODO this is not optimap o(n) + for k, v := range callgraph { + if k.TypeHash() == fun.TypeHash() { + return true + } + for _, e := range v { + if fun.TypeHash() == e.TypeHash() { + return true + } + } + } + return false +} + +// ContextPropagationPass. +type ContextPropagationPass struct { +} + +// Execute. +func (pass *ContextPropagationPass) Execute( + node *ast.File, + analysis *PackageAnalysis, + pkg *packages.Package, + pkgs []*packages.Package) []Import { + var imports []Import + addImports := false + // below variable is used + // when callexpr is inside var decl + // instead of functiondecl + currentFun := FuncDescriptor{} + emitEmptyContext := func(callExpr *ast.CallExpr, fun FuncDescriptor, ctxArg *ast.Ident) { + addImports = true + if currentFun != (FuncDescriptor{}) { + visited := map[FuncDescriptor]bool{} + if isPath(analysis.Callgraph, currentFun, analysis.RootFunctions[0], visited) { + callExpr.Args = append([]ast.Expr{ctxArg}, callExpr.Args...) + } else { + contextTodo := &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_context", + }, + Sel: &ast.Ident{ + Name: "TODO", + }, + }, + Lparen: 62, + Ellipsis: 0, + } + callExpr.Args = append([]ast.Expr{contextTodo}, callExpr.Args...) + } + return + } + contextTodo := &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_context", + }, + Sel: &ast.Ident{ + Name: "TODO", + }, + }, + Lparen: 62, + Ellipsis: 0, + } + callExpr.Args = append([]ast.Expr{contextTodo}, callExpr.Args...) + } + emitCallExpr := func(ident *ast.Ident, n ast.Node, ctxArg *ast.Ident, pkgPath string) { + if callExpr, ok := n.(*ast.CallExpr); ok { + funId := pkgPath + "." + pkg.TypesInfo.Uses[ident].Name() + fun := FuncDescriptor{ + Id: funId, + DeclType: pkg.TypesInfo.Uses[ident].Type().String(), + CustomInjection: false} + found := analysis.FuncDecls[fun] + + // inject context parameter only + // to these functions for which function decl + // exists + + if found { + visited := map[FuncDescriptor]bool{} + if isPath(analysis.Callgraph, fun, analysis.RootFunctions[0], visited) { + fmt.Println("\t\t\tContextPropagation FuncCall:", funId, pkg.TypesInfo.Uses[ident].Type().String()) + emitEmptyContext(callExpr, fun, ctxArg) + } + } + } + } + ast.Inspect(node, func(n ast.Node) bool { + ctxArg := &ast.Ident{ + Name: "__atel_child_tracing_ctx", + } + ctxField := &ast.Field{ + Names: []*ast.Ident{ + { + Name: "__atel_tracing_ctx", + }, + }, + Type: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_context", + }, + Sel: &ast.Ident{ + Name: "Context", + }, + }, + } + switch xNode := n.(type) { + case *ast.FuncDecl: + pkgPath := GetPkgPathForFunction(pkg, pkgs, xNode, analysis.Interfaces) + funId := pkgPath + "." + pkg.TypesInfo.Defs[xNode.Name].Name() + fun := FuncDescriptor{ + Id: funId, + DeclType: pkg.TypesInfo.Defs[xNode.Name].Type().String(), + CustomInjection: false} + currentFun = fun + // inject context only + // functions available in the call graph + if !isFunPartOfCallGraph(fun, analysis.Callgraph) { + break + } + + if Contains(analysis.RootFunctions, fun) { + break + } + visited := map[FuncDescriptor]bool{} + + if isPath(analysis.Callgraph, fun, analysis.RootFunctions[0], visited) { + fmt.Println("\t\t\tContextPropagation FuncDecl:", funId, + pkg.TypesInfo.Defs[xNode.Name].Type().String()) + addImports = true + xNode.Type.Params.List = append([]*ast.Field{ctxField}, xNode.Type.Params.List...) + } + case *ast.CallExpr: + if ident, ok := xNode.Fun.(*ast.Ident); ok { + if pkg.TypesInfo.Uses[ident] == nil { + return false + } + pkgPath := GetPkgNameFromUsesTable(pkg, ident) + emitCallExpr(ident, n, ctxArg, pkgPath) + } + + if sel, ok := xNode.Fun.(*ast.SelectorExpr); ok { + if pkg.TypesInfo.Uses[sel.Sel] == nil { + return false + } + pkgPath := GetPkgNameFromUsesTable(pkg, sel.Sel) + if sel.X != nil { + pkgPath = GetSelectorPkgPath(sel, pkg, pkgPath) + } + emitCallExpr(sel.Sel, n, ctxArg, pkgPath) + } + + case *ast.TypeSpec: + iname := xNode.Name + iface, ok := xNode.Type.(*ast.InterfaceType) + if !ok { + return true + } + for _, method := range iface.Methods.List { + funcType, ok := method.Type.(*ast.FuncType) + if !ok { + return true + } + visited := map[FuncDescriptor]bool{} + pkgPath := GetPkgNameFromDefsTable(pkg, method.Names[0]) + funId := pkgPath + "." + iname.Name + "." + pkg.TypesInfo.Defs[method.Names[0]].Name() + fun := FuncDescriptor{ + Id: funId, + DeclType: pkg.TypesInfo.Defs[method.Names[0]].Type().String(), + CustomInjection: false} + if isPath(analysis.Callgraph, fun, analysis.RootFunctions[0], visited) { + fmt.Println("\t\t\tContext Propagation InterfaceType", fun.Id, fun.DeclType) + addImports = true + funcType.Params.List = append([]*ast.Field{ctxField}, funcType.Params.List...) + } + } + } + return true + }) + if addImports { + imports = append(imports, Import{"__atel_context", "context", Add}) + } + return imports +} diff --git a/instrgen/lib/instrumentation.go b/instrgen/lib/instrumentation.go new file mode 100644 index 00000000000..c59ff2a6c87 --- /dev/null +++ b/instrgen/lib/instrumentation.go @@ -0,0 +1,380 @@ +// 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 lib // import "go.opentelemetry.io/contrib/instrgen/lib" + +import ( + "fmt" + "go/ast" + "go/token" + + "golang.org/x/tools/go/packages" +) + +// InstrumentationPass. +type InstrumentationPass struct { +} + +func makeInitStmts(name string) []ast.Stmt { + childTracingSupress := &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "_", + }, + }, + Tok: token.ASSIGN, + Rhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_child_tracing_ctx", + }, + }, + } + s1 := + &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_ts", + }, + }, + Tok: token.DEFINE, + + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "rtlib", + }, + Sel: &ast.Ident{ + Name: "NewTracingState", + }, + }, + Lparen: 54, + Ellipsis: 0, + }, + }, + } + s2 := &ast.DeferStmt{ + Defer: 27, + Call: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "rtlib", + }, + Sel: &ast.Ident{ + Name: "Shutdown", + }, + }, + Lparen: 48, + Args: []ast.Expr{ + &ast.Ident{ + Name: "__atel_ts", + }, + }, + Ellipsis: 0, + }, + } + + s3 := &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_otel", + }, + Sel: &ast.Ident{ + Name: "SetTracerProvider", + }, + }, + Lparen: 49, + Args: []ast.Expr{ + &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_ts", + }, + Sel: &ast.Ident{ + Name: "Tp", + }, + }, + }, + Ellipsis: 0, + }, + } + s4 := &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_ctx", + }, + }, + Tok: token.DEFINE, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_context", + }, + Sel: &ast.Ident{ + Name: "Background", + }, + }, + Lparen: 52, + Ellipsis: 0, + }, + }, + } + s5 := &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_child_tracing_ctx", + }, + &ast.Ident{ + Name: "__atel_span", + }, + }, + Tok: token.DEFINE, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_otel", + }, + Sel: &ast.Ident{ + Name: "Tracer", + }, + }, + Lparen: 50, + Args: []ast.Expr{ + &ast.Ident{ + Name: `"` + name + `"`, + }, + }, + Ellipsis: 0, + }, + Sel: &ast.Ident{ + Name: "Start", + }, + }, + Lparen: 62, + Args: []ast.Expr{ + &ast.Ident{ + Name: "__atel_ctx", + }, + &ast.Ident{ + Name: `"` + name + `"`, + }, + }, + Ellipsis: 0, + }, + }, + } + + s6 := &ast.DeferStmt{ + Defer: 27, + Call: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_span", + }, + Sel: &ast.Ident{ + Name: "End", + }, + }, + Lparen: 41, + Ellipsis: 0, + }, + } + stmts := []ast.Stmt{s1, s2, s3, s4, s5, childTracingSupress, s6} + return stmts +} + +func makeSpanStmts(name string, paramName string) []ast.Stmt { + s1 := &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_child_tracing_ctx", + }, + &ast.Ident{ + Name: "__atel_span", + }, + }, + Tok: token.DEFINE, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_otel", + }, + Sel: &ast.Ident{ + Name: "Tracer", + }, + }, + Lparen: 50, + Args: []ast.Expr{ + &ast.Ident{ + Name: `"` + name + `"`, + }, + }, + Ellipsis: 0, + }, + Sel: &ast.Ident{ + Name: "Start", + }, + }, + Lparen: 62, + Args: []ast.Expr{ + &ast.Ident{ + Name: paramName, + }, + &ast.Ident{ + Name: `"` + name + `"`, + }, + }, + Ellipsis: 0, + }, + }, + } + + s2 := &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: "_", + }, + }, + Tok: token.ASSIGN, + Rhs: []ast.Expr{ + &ast.Ident{ + Name: "__atel_child_tracing_ctx", + }, + }, + } + + s3 := &ast.DeferStmt{ + Defer: 27, + Call: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "__atel_span", + }, + Sel: &ast.Ident{ + Name: "End", + }, + }, + Lparen: 41, + Ellipsis: 0, + }, + } + stmts := []ast.Stmt{s1, s2, s3} + return stmts +} + +// Execute. +func (pass *InstrumentationPass) Execute( + node *ast.File, + analysis *PackageAnalysis, + pkg *packages.Package, + pkgs []*packages.Package) []Import { + var imports []Import + addImports := false + addContext := false + // store all function literals positions + // that are part of assignment statement + // it's used to avoid injection into literal + // more than once + var functionLiteralPositions []token.Pos + ast.Inspect(node, func(n ast.Node) bool { + switch x := n.(type) { + case *ast.FuncDecl: + pkgPath := GetPkgPathForFunction(pkg, pkgs, x, analysis.Interfaces) + fundId := pkgPath + "." + pkg.TypesInfo.Defs[x.Name].Name() + fun := FuncDescriptor{ + Id: fundId, + DeclType: pkg.TypesInfo.Defs[x.Name].Type().String(), + CustomInjection: false} + // check if it's root function or + // one of function in call graph + // and emit proper ast nodes + _, exists := analysis.Callgraph[fun] + if !exists { + if !Contains(analysis.RootFunctions, fun) { + return false + } + } + for _, root := range analysis.RootFunctions { + visited := map[FuncDescriptor]bool{} + fmt.Println("\t\t\tInstrumentation FuncDecl:", fundId, pkg.TypesInfo.Defs[x.Name].Type().String()) + if isPath(analysis.Callgraph, fun, root, visited) && fun.TypeHash() != root.TypeHash() { + x.Body.List = append(makeSpanStmts(x.Name.Name, "__atel_tracing_ctx"), x.Body.List...) + addContext = true + addImports = true + } else { + // check whether this function is root function + if !Contains(analysis.RootFunctions, fun) { + return false + } + x.Body.List = append(makeInitStmts(x.Name.Name), x.Body.List...) + addContext = true + addImports = true + } + } + case *ast.AssignStmt: + for _, e := range x.Lhs { + if ident, ok := e.(*ast.Ident); ok { + _ = ident + pkgPath := "" + pkgPath = GetPkgNameFromDefsTable(pkg, ident) + if pkg.TypesInfo.Defs[ident] == nil { + return false + } + fundId := pkgPath + "." + pkg.TypesInfo.Defs[ident].Name() + fun := FuncDescriptor{ + Id: fundId, + DeclType: pkg.TypesInfo.Defs[ident].Type().String(), + CustomInjection: true} + _, exists := analysis.Callgraph[fun] + if exists { + return false + } + } + } + for _, e := range x.Rhs { + if funLit, ok := e.(*ast.FuncLit); ok { + functionLiteralPositions = append(functionLiteralPositions, funLit.Pos()) + funLit.Body.List = append(makeSpanStmts("anonymous", "__atel_child_tracing_ctx"), funLit.Body.List...) + addImports = true + addContext = true + } + } + case *ast.FuncLit: + for _, pos := range functionLiteralPositions { + if pos == x.Pos() { + return false + } + } + x.Body.List = append(makeSpanStmts("anonymous", "__atel_child_tracing_ctx"), x.Body.List...) + addImports = true + addContext = true + } + + return true + }) + if addContext { + imports = append(imports, Import{"__atel_context", "context", Add}) + } + if addImports { + imports = append(imports, Import{"__atel_otel", "go.opentelemetry.io/otel", Add}) + } + return imports +} diff --git a/instrgen/lib/otel_pruning.go b/instrgen/lib/otel_pruning.go new file mode 100644 index 00000000000..29362a3977a --- /dev/null +++ b/instrgen/lib/otel_pruning.go @@ -0,0 +1,157 @@ +// 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 lib // import "go.opentelemetry.io/contrib/instrgen/lib" + +import ( + "go/ast" + "strings" + + "golang.org/x/tools/go/packages" +) + +func removeStmt(slice []ast.Stmt, s int) []ast.Stmt { + return append(slice[:s], slice[s+1:]...) +} + +func removeField(slice []*ast.Field, s int) []*ast.Field { + return append(slice[:s], slice[s+1:]...) +} + +func removeExpr(slice []ast.Expr, s int) []ast.Expr { + return append(slice[:s], slice[s+1:]...) +} + +// OtelPruner. +type OtelPruner struct { +} + +func inspectFuncContent(fType *ast.FuncType, fBody *ast.BlockStmt) { + for index := 0; index < len(fType.Params.List); index++ { + param := fType.Params.List[index] + for _, ident := range param.Names { + if strings.Contains(ident.Name, "__atel_") { + fType.Params.List = removeField(fType.Params.List, index) + index-- + } + } + } + for index := 0; index < len(fBody.List); index++ { + stmt := fBody.List[index] + switch bodyStmt := stmt.(type) { + case *ast.AssignStmt: + if ident, ok := bodyStmt.Lhs[0].(*ast.Ident); ok { + if strings.Contains(ident.Name, "__atel_") { + fBody.List = removeStmt(fBody.List, index) + index-- + } + } + if ident, ok := bodyStmt.Rhs[0].(*ast.Ident); ok { + if strings.Contains(ident.Name, "__atel_") { + fBody.List = removeStmt(fBody.List, index) + index-- + } + } + case *ast.ExprStmt: + if call, ok := bodyStmt.X.(*ast.CallExpr); ok { + if sel, ok := call.Fun.(*ast.SelectorExpr); ok { + if strings.Contains(sel.Sel.Name, "SetTracerProvider") { + fBody.List = removeStmt(fBody.List, index) + index-- + } + } + } + case *ast.DeferStmt: + if sel, ok := bodyStmt.Call.Fun.(*ast.SelectorExpr); ok { + if strings.Contains(sel.Sel.Name, "Shutdown") { + if ident, ok := sel.X.(*ast.Ident); ok { + if strings.Contains(ident.Name, "rtlib") { + fBody.List = removeStmt(fBody.List, index) + index-- + } + } + } + if ident, ok := sel.X.(*ast.Ident); ok { + if strings.Contains(ident.Name, "__atel_") { + fBody.List = removeStmt(fBody.List, index) + index-- + } + } + } + } + } +} + +// Execute. +func (pass *OtelPruner) Execute( + node *ast.File, + analysis *PackageAnalysis, + pkg *packages.Package, + pkgs []*packages.Package) []Import { + var imports []Import + ast.Inspect(node, func(n ast.Node) bool { + switch x := n.(type) { + case *ast.FuncDecl: + inspectFuncContent(x.Type, x.Body) + case *ast.CallExpr: + for argIndex := 0; argIndex < len(x.Args); argIndex++ { + if ident, ok := x.Args[argIndex].(*ast.Ident); ok { + if strings.Contains(ident.Name, "__atel_") { + x.Args = removeExpr(x.Args, argIndex) + argIndex-- + } + } + } + for argIndex := 0; argIndex < len(x.Args); argIndex++ { + if c, ok := x.Args[argIndex].(*ast.CallExpr); ok { + if sel, ok := c.Fun.(*ast.SelectorExpr); ok { + if ident, ok := sel.X.(*ast.Ident); ok { + if strings.Contains(ident.Name, "__atel_") { + x.Args = removeExpr(x.Args, argIndex) + argIndex-- + } + } + } + } + } + case *ast.FuncLit: + inspectFuncContent(x.Type, x.Body) + case *ast.TypeSpec: + iface, ok := x.Type.(*ast.InterfaceType) + if !ok { + return true + } + for _, method := range iface.Methods.List { + funcType, ok := method.Type.(*ast.FuncType) + if !ok { + continue + } + for argIndex := 0; argIndex < len(funcType.Params.List); argIndex++ { + for _, ident := range funcType.Params.List[argIndex].Names { + if strings.Contains(ident.Name, "__atel_") { + funcType.Params.List = removeField(funcType.Params.List, argIndex) + argIndex-- + } + } + } + } + } + return true + }) + imports = append(imports, Import{"__atel_context", "context", Remove}) + imports = append(imports, Import{"__atel_otel", "go.opentelemetry.io/otel", Remove}) + imports = append(imports, Import{"__atel_otelhttp", "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp", Remove}) + + return imports +} diff --git a/instrgen/lib/tools.go b/instrgen/lib/tools.go new file mode 100644 index 00000000000..c09007dcaac --- /dev/null +++ b/instrgen/lib/tools.go @@ -0,0 +1,70 @@ +// 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 lib // import "go.opentelemetry.io/contrib/instrgen/lib" + +import ( + "os" + "path/filepath" +) + +// SearchFiles. +func SearchFiles(root string, ext string) []string { + var files []string + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if filepath.Ext(path) == ext { + files = append(files, path) + } + return nil + }) + if err != nil { + panic(err) + } + return files +} + +func isPath( + callGraph map[FuncDescriptor][]FuncDescriptor, + current FuncDescriptor, + goal FuncDescriptor, + visited map[FuncDescriptor]bool) bool { + if current == goal { + return true + } + + value, ok := callGraph[current] + if ok { + for _, child := range value { + exists := visited[child] + if exists { + continue + } + visited[child] = true + if isPath(callGraph, child, goal, visited) { + return true + } + } + } + return false +} + +// Contains. +func Contains(a []FuncDescriptor, x FuncDescriptor) bool { + for _, n := range a { + if x.TypeHash() == n.TypeHash() { + return true + } + } + return false +} diff --git a/instrgen/rtlib/rtlib.go b/instrgen/rtlib/rtlib.go new file mode 100644 index 00000000000..67a368f7ab4 --- /dev/null +++ b/instrgen/rtlib/rtlib.go @@ -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. + +// Basic runtime library + +package rtlib // import "go.opentelemetry.io/contrib/instrgen/rtlib" + +import ( + "context" + "io" + "log" + "os" + + "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" + "go.opentelemetry.io/otel/sdk/resource" + trace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" +) + +// TracingState type. +type TracingState struct { + Logger *log.Logger + File *os.File + Tp *trace.TracerProvider +} + +// NewTracingState. +func NewTracingState() TracingState { + var tracingState TracingState + tracingState.Logger = log.New(os.Stdout, "", 0) + + // Write telemetry data to a file. + var err error + tracingState.File, err = os.Create("traces.txt") + + if err != nil { + tracingState.Logger.Fatal(err) + } + var exp trace.SpanExporter + exp, err = NewExporter(tracingState.File) + if err != nil { + tracingState.Logger.Fatal(err) + } + tracingState.Tp = trace.NewTracerProvider( + trace.WithBatcher(exp), + trace.WithResource(NewResource()), + ) + return tracingState +} + +// NewExporter returns a console exporter. +func NewExporter(w io.Writer) (trace.SpanExporter, error) { + return stdouttrace.New( + stdouttrace.WithWriter(w), + // Use human readable output. + stdouttrace.WithPrettyPrint(), + // Do not print timestamps for the demo. + stdouttrace.WithoutTimestamps(), + ) +} + +// NewResource returns a resource describing this application. +func NewResource() *resource.Resource { + r, _ := resource.Merge( + resource.Default(), + resource.NewWithAttributes( + semconv.SchemaURL, + ), + ) + return r +} + +// Shutdown. +func Shutdown(ts TracingState) { + if err := ts.Tp.Shutdown(context.Background()); err != nil { + ts.Logger.Fatal(err) + } +} + +// AutoEntryPoint. +func AutotelEntryPoint() { + +}