├── .idea ├── .name ├── misc.xml ├── vcs.xml ├── .gitignore ├── dictionaries │ └── florin.xml ├── modules.xml ├── debugger.iml ├── deployment.xml └── runConfigurations │ └── gobench_with_debugger_tag.xml ├── go.mod ├── debugger-labels.png ├── no.go ├── common.go ├── debug.go ├── LICENSE ├── debugger_test.go └── README.md /.idea/.name: -------------------------------------------------------------------------------- 1 | debugger -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dlsniper/debugger 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /debugger-labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dlsniper/debugger/HEAD/debugger-labels.png -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/dictionaries/florin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | conv 5 | dlsniper 6 | noninfringement 7 | pprof 8 | pățan 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /no.go: -------------------------------------------------------------------------------- 1 | // +build !debugger 2 | 3 | package debugger 4 | 5 | import "net/http" 6 | 7 | // Middleware allows HTTP middleware to receive debugger labels 8 | func Middleware(f http.HandlerFunc, l MiddlewareLabels) http.HandlerFunc { 9 | _ = l 10 | return f 11 | } 12 | 13 | // SetLabels will set debugger labels for any function/method call 14 | func SetLabels(l Labels) { 15 | _ = l 16 | } 17 | -------------------------------------------------------------------------------- /.idea/debugger.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/deployment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/runConfigurations/gobench_with_debugger_tag.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /common.go: -------------------------------------------------------------------------------- 1 | // Debugger package allows an application to be compiled for production without 2 | // incurring any penalty due to instrumentation. 3 | // 4 | // When the time comes to use a debugger and inspect your code, use the 5 | // -tags=debugger build tag to switch the implementation to one that will set 6 | // labels that can be displayed in the debugger. 7 | // 8 | // The current implementation relies on pprof labels to be created and then 9 | // the debugger can read them and display them. 10 | package debugger 11 | 12 | import "net/http" 13 | 14 | // MiddlewareLabels generates labels that are aware of the request properties 15 | type MiddlewareLabels func(r *http.Request) []string 16 | 17 | // Labels generates the labels for a function/method 18 | type Labels func() []string 19 | -------------------------------------------------------------------------------- /debug.go: -------------------------------------------------------------------------------- 1 | // +build debugger 2 | 3 | package debugger 4 | 5 | import ( 6 | "context" 7 | "net/http" 8 | "runtime/pprof" 9 | ) 10 | 11 | // Middleware allows HTTP middleware to receive debugger labels 12 | func Middleware(f http.HandlerFunc, l MiddlewareLabels) http.HandlerFunc { 13 | return func(w http.ResponseWriter, r *http.Request) { 14 | l := pprof.Labels(l(r)...) 15 | 16 | pprof.Do(r.Context(), l, func(ctx context.Context) { 17 | c := r.Context() 18 | defer r.WithContext(c) 19 | 20 | f(w, r.WithContext(ctx)) 21 | }) 22 | } 23 | } 24 | 25 | var bgCtx = context.Background() 26 | 27 | // SetLabels will set debugger labels for any function/method call 28 | func SetLabels(l Labels) { 29 | ctx := pprof.WithLabels(bgCtx, pprof.Labels(l()...)) 30 | pprof.SetGoroutineLabels(ctx) 31 | } 32 | 33 | // SetLabelsWithCtx will set debugger labels for any function/method call using a custom context 34 | func SetLabelsWithCtx(ctx context.Context, l Labels) { 35 | pprof.SetGoroutineLabels(pprof.WithLabels(ctx, pprof.Labels(l()...))) 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Florin Pățan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /debugger_test.go: -------------------------------------------------------------------------------- 1 | // Important! Run tests/benchmarks with -tags=debugger 2 | // to include the actual labeling code 3 | 4 | package debugger_test 5 | 6 | import ( 7 | "strconv" 8 | "testing" 9 | 10 | "github.com/dlsniper/debugger" 11 | ) 12 | 13 | var result int 14 | 15 | func workerWithout(n int) int { 16 | if n != 1 { 17 | return n + workerWithout(n-1) 18 | } 19 | 20 | return 1 21 | } 22 | 23 | func workerWithOne(n int) int { 24 | debugger.SetLabels(func() []string { 25 | return []string{ 26 | "label1", "label1value", 27 | } 28 | }) 29 | 30 | if n != 1 { 31 | return n + workerWithOne(n-1) 32 | } 33 | 34 | return 1 35 | } 36 | 37 | func workerWithThree(n int) int { 38 | debugger.SetLabels(func() []string { 39 | return []string{ 40 | "label1", "label1value", 41 | "label2", "label2value", 42 | "label3", "label3value", 43 | } 44 | }) 45 | 46 | if n != 1 { 47 | return n + workerWithThree(n-1) 48 | } 49 | 50 | return 1 51 | } 52 | 53 | func workerWithTen(n int) int { 54 | debugger.SetLabels(func() []string { 55 | return []string{ 56 | "label1", "label1value", 57 | "label2", "label2value", 58 | "label3", "label3value", 59 | "label4", "label41value", 60 | "label5", "label5value", 61 | "label6", "label6value", 62 | "label7", "label7value", 63 | "label8", "label8value", 64 | "label9", "label9value", 65 | "label10", "label10value", 66 | } 67 | }) 68 | 69 | if n != 1 { 70 | return n + workerWithTen(n-1) 71 | } 72 | 73 | return 1 74 | } 75 | 76 | func workerWithConv(n int) int { 77 | debugger.SetLabels(func() []string { 78 | return []string{ 79 | "label1", "label1value", 80 | "label2", "label2value", 81 | "label3", strconv.Itoa(n), 82 | } 83 | }) 84 | 85 | if n != 1 { 86 | return n + workerWithConv(n-1) 87 | } 88 | 89 | return 1 90 | } 91 | 92 | func BenchmarkWorkerWithout(b *testing.B) { 93 | var res int 94 | for i := 0; i < b.N; i++ { 95 | res = workerWithout(100) 96 | } 97 | result = res 98 | } 99 | 100 | func BenchmarkWorkerWithOne(b *testing.B) { 101 | var res int 102 | for i := 0; i < b.N; i++ { 103 | res = workerWithOne(100) 104 | } 105 | result = res 106 | } 107 | 108 | func BenchmarkWorkerWithThree(b *testing.B) { 109 | var res int 110 | for i := 0; i < b.N; i++ { 111 | res = workerWithThree(100) 112 | } 113 | result = res 114 | } 115 | 116 | func BenchmarkWorkerWithTen(b *testing.B) { 117 | var res int 118 | for i := 0; i < b.N; i++ { 119 | res = workerWithTen(100) 120 | } 121 | result = res 122 | } 123 | 124 | func BenchmarkWorkerWithConv(b *testing.B) { 125 | var res int 126 | for i := 0; i < b.N; i++ { 127 | res = workerWithConv(100) 128 | } 129 | result = res 130 | } 131 | 132 | func init() { 133 | // force Go to include our variable in the results and not optimize any code based on it 134 | println(result) 135 | } 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Debugger Middleware 2 | 3 | This package provides a debugging middleware for Go applications to enable 4 | better display of goroutines in the debugger. 5 | 6 | It has nearly-zero performance penalty in production code when not actively used. 7 | 8 | ## How this looks like in the IDE 9 | 10 | Below you can see how this feature looks like in GoLand IDE: 11 | 12 | ![Debugger labels in GoLand](debugger-labels.png "Debugger labels in GoLand") 13 | 14 | ## How to use 15 | 16 | Include it in your application using one of the patterns below. 17 | 18 | Then, compile the application with `-tags debugger`, e.g. 19 | 20 | ```shell script 21 | go build -tags debugger 22 | ``` 23 | 24 | More details on how to use this can be found in this blog post: 25 | https://blog.jetbrains.com/go/2020/03/03/how-to-find-goroutines-during-debugging/ 26 | 27 | ### HTTP handlers 28 | 29 | In your code, replace the HTTP handler with the `Middleware` function call. 30 | 31 | Original: 32 | ```go 33 | router.HandleFunc("/", homeHandler) 34 | ``` 35 | 36 | Replacement: 37 | ```go 38 | router.HandleFunc("/", debugger.Middleware(homeHandler, func(r *http.Request) []string { 39 | return []string{ 40 | "path", r.RequestURI, 41 | } 42 | })) 43 | ``` 44 | 45 | ### Non-HTTP handlers 46 | 47 | For normal functions/methods, you can use the `SetLabels` / `SetLabelsCtx` functions 48 | to set the debugger labels. 49 | 50 | Original: 51 | ```go 52 | func sum(a, b int) int { 53 | return a+b 54 | } 55 | ``` 56 | 57 | Replacement: 58 | ```go 59 | func sum(a, b int) int { 60 | debugger.SetLabels(func() []string { 61 | return []string{ 62 | "a", strconv.Itoa(a), 63 | "b", strconv.Itoa(b), 64 | } 65 | }) 66 | 67 | return a+b 68 | } 69 | ``` 70 | 71 | ## Performance 72 | 73 | You can asses the performance of this library by running the included benchmarks 74 | in your environment. 75 | 76 | Here are the results from my own machine (Intel Core i7 6700HQ, 32GB RAM, Windows 10), 77 | when running with a `-count=5` pass, averaged across runs. 78 | 79 | Go 1.13.8 80 | 81 | Without labels: 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 |
Namego 1.13.8go 1.14 RC1
Execution countTimeExecution countTime
BenchmarkWorkerWithout-83255558370 ns/op3183910368 ns/op
BenchmarkWorkerWithOne-82593845698 ns/op2564346479 ns/op
BenchmarkWorkerWithThree-81565678222 ns/op1975460131 ns/op
BenchmarkWorkerWithTen-85776216798 ns/op7322171842 ns/op
BenchmarkWorkerWithConv-81537479609 ns/op1904963818 ns/op
134 | 135 | ## License 136 | 137 | This project is provided under the [MIT license](LICENSE). --------------------------------------------------------------------------------