├── .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 |
5 |
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 | 
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 |
Name
86 |
go 1.13.8
87 |
go 1.14 RC1
88 |
89 |
90 |
Execution count
91 |
Time
92 |
Execution count
93 |
Time
94 |
95 |
96 |
97 |
98 |
BenchmarkWorkerWithout-8
99 |
3255558
100 |
370 ns/op
101 |
3183910
102 |
368 ns/op
103 |
104 |
105 |
BenchmarkWorkerWithOne-8
106 |
25938
107 |
45698 ns/op
108 |
25643
109 |
46479 ns/op
110 |
111 |
112 |
BenchmarkWorkerWithThree-8
113 |
15656
114 |
78222 ns/op
115 |
19754
116 |
60131 ns/op
117 |
118 |
119 |
BenchmarkWorkerWithTen-8
120 |
5776
121 |
216798 ns/op
122 |
7322
123 |
171842 ns/op
124 |
125 |
126 |
BenchmarkWorkerWithConv-8
127 |
15374
128 |
79609 ns/op
129 |
19049
130 |
63818 ns/op
131 |
132 |
133 |
134 |
135 | ## License
136 |
137 | This project is provided under the [MIT license](LICENSE).
--------------------------------------------------------------------------------