├── .github └── workflows │ └── release.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── client ├── client.go ├── traceid.go └── traceid_test.go ├── cloud ├── config.go ├── crocospans.pb.go ├── crocospans.proto └── output.go ├── examples ├── test-config.js └── test.js ├── go.mod ├── go.sum └── tracing.go /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Set up Go 16 | uses: actions/setup-go@v2 17 | with: 18 | go-version: 1.17 19 | 20 | - name: Install xk6 21 | run: go install go.k6.io/xk6/cmd/xk6@latest 22 | 23 | - name: Build the binary 24 | run: xk6 build --with github.com/grafana/xk6-distributed-tracing=. 25 | 26 | - name: Upload binaries to release 27 | uses: svenstaro/upload-release-action@v2 28 | with: 29 | repo_token: ${{ secrets.GITHUB_TOKEN }} 30 | file: k6 31 | asset_name: k6-distributed-tracing 32 | tag: ${{ github.ref }} 33 | overwrite: true 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | examples/k6 3 | k6 4 | k6-distributed-tracing 5 | vendor 6 | gen -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DOCKER_BUILD=docker build 2 | 3 | DOCKER_RUN=docker run 4 | 5 | .PHONY: build 6 | build: 7 | xk6 build master --with github.com/grafana/xk6-distributed-tracing="${PWD}/../xk6-distributed-tracing" 8 | 9 | .PHONY: proto 10 | proto: 11 | $(DOCKER_RUN) -v ${PWD}/crocospans:/defs namely/protoc-all -f *.proto -l go 12 | cp -r ${PWD}/crocospans/gen/pb-go/*.pb.go ${PWD}/crocospans -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > ### ⚠️ Archived ⚠️ 2 | > 3 | > This extension has been archived and is no longer maintained by the Grafana team. 4 | > 5 | > At this time, the tracing feature is now available in the as part of the 6 | > [k6/experimental/tracing](https://grafana.com/docs/k6/latest/javascript-api/k6-experimental/tracing/) 7 | > module in [k6 OSS](https://grafana.com/docs/k6/latest/). 8 | > 9 | > Furthermore, [Distributed Tracing in Grafana Cloud k6](https://grafana.com/docs/cloud/k6/distributed-tracing/) 10 | > is now generally available in Grafana Cloud. 11 | > 12 | > USE AT YOUR OWN RISK! 13 | 14 | # xk6-distributed-tracing 15 | 16 | This extension adds distributed tracing support to [k6](https://github.com/grafana/k6)! 17 | 18 | That means that if you're testing an instrumented system, you can use this extension to start the traces on k6. 19 | 20 | Currently, it supports HTTP requests and the following propagation formats: `w3c`, `b3`, and `jaeger`. 21 | 22 | It is implemented using the [xk6](https://github.com/grafana/xk6) extension system. 23 | 24 | ## Build 25 | 26 | To build a `k6` binary with this extension, first ensure you have the prerequisites: 27 | 28 | - [Go toolchain](https://go101.org/article/go-toolchain.html) 29 | - Git 30 | 31 | Then: 32 | 33 | 1. Download `xk6`: 34 | 35 | ```bash 36 | $ go install go.k6.io/xk6/cmd/xk6@latest 37 | ``` 38 | 39 | 2. Build the binary: 40 | 41 | ```bash 42 | $ xk6 build --with github.com/grafana/xk6-distributed-tracing@latest 43 | ``` 44 | 45 | ## Example 46 | 47 | ```javascript 48 | import tracing, { Http } from 'k6/x/tracing'; 49 | import { sleep } from 'k6'; 50 | 51 | export let options = { 52 | vus: 1, 53 | iterations: 10, 54 | }; 55 | 56 | export function setup() { 57 | console.log(`Running xk6-distributed-tracing v${tracing.version}`, tracing); 58 | } 59 | 60 | export default function() { 61 | const http = new Http({ 62 | propagator: "w3c", 63 | }); 64 | const r = http.get('https://test-api.k6.io'); 65 | console.log(`trace_id=${r.trace_id}`); 66 | sleep(1); 67 | } 68 | ``` 69 | 70 | Result output: 71 | 72 | ```bash 73 | $ ./k6 run script.js 74 | 75 | /\ |‾‾| /‾‾/ /‾‾/ 76 | /\ / \ | |/ / / / 77 | / \/ \ | ( / ‾‾\ 78 | / \ | |\ \ | (‾) | 79 | / __________ \ |__| \__\ \_____/ .io 80 | 81 | execution: local 82 | script: script.js 83 | output: - 84 | 85 | scenarios: (100.00%) 1 scenario, 1 max VUs, 40s max duration (incl. graceful stop): 86 | * default: 1 looping VUs for 10s (gracefulStop: 30s) 87 | 88 | INFO[0000] Running xk6-distributed-tracing v0.2.0 source=console 89 | INFO[0000] trace_id=743fff0b96778539acb7139e72ea1e33 90 | INFO[0001] trace_id=365f4637a52526db1de2d30a5568ca3a 91 | INFO[0002] trace_id=c49e1df945049c5c3c8b59acc84d7d3b 92 | INFO[0003] trace_id=53e1937d56aa172b46d2310e3380dfe9 93 | INFO[0004] trace_id=d61e8757d35c9ca1780b88977ac56d72 94 | INFO[0005] trace_id=358e794ed636d268a918dcd2f3f9db0a 95 | INFO[0006] trace_id=992a959e09ee84f3905a215bec8b53a0 96 | INFO[0007] trace_id=aee11c64de11744ab5b66d5dd8ed361b 97 | INFO[0008] trace_id=c4dc45d857e99ede2bb902666457239d 98 | INFO[0009] trace_id=7623d10293d9f03c15deb8055935664e 99 | 100 | running (10.1s), 0/1 VUs, 10 complete and 0 interrupted iterations 101 | default ✓ [======================================] 1 VUs 10s 102 | 103 | █ setup 104 | 105 | data_received..............: 1.6 kB 156 B/s 106 | data_sent..................: 1.7 kB 165 B/s 107 | http_req_blocked...........: avg=223.43µs min=146.53µs med=217.39µs max=314.54µs p(90)=276.68µs p(95)=295.61µs 108 | http_req_connecting........: avg=137.18µs min=87.22µs med=130.17µs max=196.38µs p(90)=184.38µs p(95)=190.38µs 109 | http_req_duration..........: avg=6.58ms min=5.07ms med=6.45ms max=7.91ms p(90)=7.83ms p(95)=7.87ms 110 | http_req_receiving.........: avg=187.27µs min=94.29µs med=171.7µs max=295.67µs p(90)=293.28µs p(95)=294.48µs 111 | http_req_sending...........: avg=128.07µs min=94.64µs med=121.77µs max=175.65µs p(90)=160.41µs p(95)=168.03µs 112 | http_req_tls_handshaking...: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s 113 | http_req_waiting...........: avg=6.27ms min=4.83ms med=6.13ms max=7.64ms p(90)=7.56ms p(95)=7.6ms 114 | http_reqs..................: 10 0.991797/s 115 | iteration_duration.........: avg=916.48ms min=65.67µs med=1s max=1s p(90)=1s p(95)=1s 116 | iterations.................: 10 0.991797/s 117 | vus........................: 1 min=1 max=1 118 | vus_max....................: 1 min=1 max=1 119 | 120 | ``` 121 | -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "fmt" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/dop251/goja" 11 | "go.k6.io/k6/js/modules" 12 | k6HTTP "go.k6.io/k6/js/modules/k6/http" 13 | "go.k6.io/k6/metrics" 14 | ) 15 | 16 | type Options struct { 17 | Propagator string 18 | } 19 | 20 | type TracingClient struct { 21 | vu modules.VU 22 | httpRequest HttpRequestFunc 23 | 24 | options Options 25 | } 26 | 27 | type HTTPResponse struct { 28 | *k6HTTP.Response `js:"-"` 29 | TraceID string 30 | } 31 | 32 | type ( 33 | HttpRequestFunc func(method string, url goja.Value, args ...goja.Value) (*k6HTTP.Response, error) 34 | HttpFunc func(ctx context.Context, url goja.Value, args ...goja.Value) (*k6HTTP.Response, error) 35 | ) 36 | 37 | func New(vu modules.VU, requestFunc HttpRequestFunc, options Options) *TracingClient { 38 | return &TracingClient{ 39 | httpRequest: requestFunc, 40 | vu: vu, 41 | options: options, 42 | } 43 | } 44 | 45 | func requestToHttpFunc(method string, request HttpRequestFunc) HttpFunc { 46 | return func(ctx context.Context, url goja.Value, args ...goja.Value) (*k6HTTP.Response, error) { 47 | return request(method, url, args...) 48 | } 49 | } 50 | 51 | func (c *TracingClient) Get(url goja.Value, args ...goja.Value) (*HTTPResponse, error) { 52 | args = append([]goja.Value{goja.Null()}, args...) 53 | return c.WithTrace(requestToHttpFunc(http.MethodGet, c.httpRequest), "HTTP GET", url, args...) 54 | } 55 | 56 | func (c *TracingClient) Post(url goja.Value, args ...goja.Value) (*HTTPResponse, error) { 57 | return c.WithTrace(requestToHttpFunc(http.MethodPost, c.httpRequest), "HTTP POST", url, args...) 58 | } 59 | 60 | func (c *TracingClient) Put(url goja.Value, args ...goja.Value) (*HTTPResponse, error) { 61 | return c.WithTrace(requestToHttpFunc(http.MethodPut, c.httpRequest), "HTTP PUT", url, args...) 62 | } 63 | 64 | func (c *TracingClient) Del(url goja.Value, args ...goja.Value) (*HTTPResponse, error) { 65 | return c.WithTrace(requestToHttpFunc(http.MethodDelete, c.httpRequest), "HTTP DEL", url, args...) 66 | } 67 | 68 | func (c *TracingClient) Head(url goja.Value, args ...goja.Value) (*HTTPResponse, error) { 69 | return c.WithTrace(requestToHttpFunc(http.MethodHead, c.httpRequest), "HTTP HEAD", url, args...) 70 | } 71 | 72 | func (c *TracingClient) Patch(url goja.Value, args ...goja.Value) (*HTTPResponse, error) { 73 | return c.WithTrace(requestToHttpFunc(http.MethodPatch, c.httpRequest), "HTTP PATCH", url, args...) 74 | } 75 | 76 | func (c *TracingClient) Options(url goja.Value, args ...goja.Value) (*HTTPResponse, error) { 77 | return c.WithTrace(requestToHttpFunc(http.MethodOptions, c.httpRequest), "HTTP OPTIONS", url, args...) 78 | } 79 | 80 | func isNilly(val goja.Value) bool { 81 | return val == nil || goja.IsNull(val) || goja.IsUndefined(val) 82 | } 83 | 84 | func (c *TracingClient) WithTrace(fn HttpFunc, spanName string, url goja.Value, args ...goja.Value) (*HTTPResponse, error) { 85 | state := c.vu.State() 86 | if state == nil { 87 | return nil, fmt.Errorf("HTTP requests can only be made in the VU context") 88 | } 89 | 90 | traceID, err := Encode(TraceID{ 91 | Prefix: K6Prefix, 92 | Code: K6CloudCode, 93 | Time: time.Now(), 94 | }, rand.Reader) 95 | if err != nil { 96 | return nil, err 97 | } 98 | 99 | tracingHeaders, err := GenerateHeaderBasedOnPropagator(c.options.Propagator, traceID) 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | // This makes sure that the tracing header will always be added correctly to 105 | // the HTTP request headers, whether they were explicitly specified by the 106 | // user in the script or not. 107 | // 108 | // First we make sure to either get the existing request params, or create 109 | // them from scratch if they were not specified: 110 | rt := c.vu.Runtime() 111 | var params *goja.Object 112 | if len(args) < 2 { 113 | params = rt.NewObject() 114 | if len(args) == 0 { 115 | args = []goja.Value{goja.Null(), params} 116 | } else { 117 | args = append(args, params) 118 | } 119 | } else { 120 | jsParams := args[1] 121 | if isNilly(jsParams) { 122 | params = rt.NewObject() 123 | args[1] = params 124 | } else { 125 | params = jsParams.ToObject(rt) 126 | } 127 | } 128 | // Then we either augment the existing params.headers or create them: 129 | var headers *goja.Object 130 | if jsHeaders := params.Get("headers"); isNilly(jsHeaders) { 131 | headers = rt.NewObject() 132 | params.Set("headers", headers) 133 | } else { 134 | headers = jsHeaders.ToObject(rt) 135 | } 136 | for key, val := range tracingHeaders { 137 | headers.Set(key, val) 138 | } 139 | 140 | // TODO: set span_id as well as some other metadata? 141 | state.Tags.Modify(func(tagsAndMeta *metrics.TagsAndMeta) { 142 | tagsAndMeta.SetMetadata("trace_id", traceID) 143 | }) 144 | defer state.Tags.Modify(func(tagsAndMeta *metrics.TagsAndMeta) { 145 | tagsAndMeta.DeleteMetadata("trace_id") 146 | }) 147 | 148 | // This calls the actual request() function from k6/http with our augmented arguments 149 | res, e := fn(c.vu.Context(), url, args...) 150 | 151 | return &HTTPResponse{Response: res, TraceID: traceID}, e 152 | } 153 | -------------------------------------------------------------------------------- /client/traceid.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/hex" 6 | "fmt" 7 | "io" 8 | "math/rand" 9 | "net/http" 10 | "time" 11 | ) 12 | 13 | const ( 14 | PropagatorW3C = "w3c" 15 | HeaderNameW3C = "traceparent" 16 | PropagatorB3 = "b3" 17 | HeaderNameB3 = "b3" 18 | PropagatorJaeger = "jaeger" 19 | HeaderNameJaeger = "uber-trace-id" 20 | ) 21 | 22 | func GenerateHeaderBasedOnPropagator(propagator string, traceID string) (http.Header, error) { 23 | 24 | switch propagator { 25 | case PropagatorW3C: 26 | // Docs: https://www.w3.org/TR/trace-context/#version-format 27 | return http.Header{ 28 | HeaderNameW3C: {fmt.Sprintf("00-%s-%s-01", traceID, RandHexStringRunes(16))}, 29 | }, nil 30 | case PropagatorB3: 31 | // Docs: https://github.com/openzipkin/b3-propagation#single-header 32 | return http.Header{ 33 | HeaderNameB3: {fmt.Sprintf("%s-%s-1", traceID, RandHexStringRunes(8))}, 34 | }, nil 35 | case PropagatorJaeger: 36 | // Docs: https://www.jaegertracing.io/docs/1.29/client-libraries/#tracespan-identity 37 | return http.Header{ 38 | HeaderNameJaeger: {fmt.Sprintf("%s:%s:0:1", traceID, RandHexStringRunes(8))}, 39 | }, nil 40 | default: 41 | return nil, fmt.Errorf("unknown propagator: %s", propagator) 42 | } 43 | } 44 | 45 | var hexRunes = []rune("123456789abcdef") 46 | 47 | func RandHexStringRunes(n int) string { 48 | b := make([]rune, n) 49 | for i := range b { 50 | b[i] = hexRunes[rand.Intn(len(hexRunes))] 51 | } 52 | return string(b) 53 | } 54 | 55 | const ( 56 | K6Prefix = 0756 // Being 075 the ASCII code for 'K' :) 57 | K6CloudCode = 12 // To ingest and process the related spans in k6 Cloud. 58 | K6LocalCode = 33 // To not ingest and process the related spans, b/c they are part of a non-cloud run. 59 | Size = 16 // Size in bytes of the encoded TraceId 60 | ) 61 | 62 | // TraceID represents a trace-id format defined to identify k6 spans in this [design doc] 63 | // 64 | // [design doc]: https://docs.google.com/document/d/1AI139s2sssm8geZ31iwEC1DtXFdn2behKpagf5fFnr4/edit# 65 | type TraceID struct { 66 | // Prefix is the first 2 bytes of the trace-id, and is used to identify the trace as k6 67 | Prefix int16 68 | 69 | // Code is the third byte of the trace-id and is used to decide whether to process the trace or not, depending on the origin 70 | Code int8 71 | 72 | // Time is the time at which the trace-id was generated. 73 | // 74 | // The time component is used as a source of randomness, and to ensure 75 | // uniqueness of the trace-id. 76 | // 77 | // When encoded, it should be in a format occupying the last 8 bytes of 78 | // the trace-id, and should ideally be encoded as nanoseconds. 79 | Time time.Time 80 | } 81 | 82 | // IsValid returns true if the TraceID was generated either by a local or cloud run of k6 83 | func (t *TraceID) IsValid() bool { 84 | return t.Prefix == K6Prefix && (t.Code == K6CloudCode || t.Code == K6LocalCode) 85 | } 86 | 87 | // IsValidCloud returns true if the TraceID was generated by a cloud run of k6 88 | func (t *TraceID) IsValidCloud() bool { 89 | return t.Prefix == K6Prefix && t.Code == K6CloudCode 90 | } 91 | 92 | // Encode encodes the TraceID into a hex string. 93 | // 94 | // The trace id is first encoded as a 16 bytes sequence, as follows: 95 | // 1. Up to 2 bytes are encoded as the Prefix 96 | // 2. The third byte is the Code. 97 | // 3. Up to the following 8 bytes are Time. 98 | // 4. The remaining bytes are filled with random bytes. 99 | // 100 | // The resulting 16 bytes sequence is then encoded as a hex string 101 | func Encode(t TraceID, randomReader io.Reader) (string, error) { 102 | if !t.IsValid() { 103 | return "", fmt.Errorf("failed to encode traceID: %v", t) 104 | } 105 | 106 | buf := make([]byte, Size) 107 | 108 | // The `PutVarint` function encode the given value into 109 | // the provided buffer, and return the number of bytes written. Thus, it 110 | // allows us to keep track of the number of bytes written, as we go, and 111 | // to pack the values to use as less space as possible. 112 | n := binary.PutVarint(buf, int64(t.Prefix)) 113 | n += binary.PutVarint(buf[n:], int64(t.Code)) 114 | n += binary.PutVarint(buf[n:], t.Time.UnixNano()) 115 | 116 | // The rest of the space in the 16 bytes buffer, equivalent to the number 117 | // of available bytes left after writing the prefix, code and timestamp (index n) 118 | // is filled with random bytes. 119 | randomness := make([]byte, Size-len(buf[:n])) 120 | err := binary.Read(randomReader, binary.BigEndian, randomness) 121 | if err != nil { 122 | return "", err 123 | } 124 | 125 | buf = append(buf[:n], randomness[:]...) 126 | hx := hex.EncodeToString(buf) 127 | return hx, nil 128 | } 129 | -------------------------------------------------------------------------------- /client/traceid_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | type randReaderMock struct{} 10 | 11 | func (r randReaderMock) Read(p []byte) (n int, err error) { 12 | copy(p, []byte{115, 111, 109, 101}) 13 | 14 | return 4, nil 15 | } 16 | 17 | func TestTraceID_IsValid(t *testing.T) { 18 | type args struct { 19 | Prefix int16 20 | Code int8 21 | Time time.Time 22 | } 23 | tests := []struct { 24 | name string 25 | args args 26 | want bool 27 | }{ 28 | { 29 | name: "ReturnsTrueWithValidK6CloudCode", 30 | args: args{ 31 | Prefix: K6Prefix, 32 | Code: K6CloudCode, 33 | Time: time.Unix(123456789, 0), 34 | }, 35 | want: true, 36 | }, 37 | { 38 | name: "ReturnsTrueWithValidK6LocalCode", 39 | args: args{ 40 | Prefix: K6Prefix, 41 | Code: K6LocalCode, 42 | Time: time.Unix(123456789, 0), 43 | }, 44 | want: true, 45 | }, 46 | { 47 | name: "ReturnsFalseWithInvalidPrefix", 48 | args: args{ 49 | Prefix: 0, 50 | Code: K6CloudCode, 51 | Time: time.Unix(123456789, 0), 52 | }, 53 | want: false, 54 | }, 55 | { 56 | name: "ReturnsFalseWithInvalidCode", 57 | args: args{ 58 | Prefix: K6Prefix, 59 | Code: 0, 60 | Time: time.Unix(123456789, 0), 61 | }, 62 | want: false, 63 | }, 64 | } 65 | for _, tt := range tests { 66 | t.Run(tt.name, func(t *testing.T) { 67 | tr := TraceID{ 68 | Prefix: tt.args.Prefix, 69 | Code: tt.args.Code, 70 | Time: tt.args.Time, 71 | } 72 | 73 | got := tr.IsValid() 74 | 75 | assert.Equalf(t, tt.want, got, "%v.IsValid()", tr) 76 | }) 77 | } 78 | } 79 | 80 | func TestTraceID_IsValidCloud(t *testing.T) { 81 | type args struct { 82 | Prefix int16 83 | Code int8 84 | Time time.Time 85 | } 86 | tests := []struct { 87 | name string 88 | args args 89 | want bool 90 | }{ 91 | { 92 | name: "ReturnsTrueWithValidK6CloudCode", 93 | args: args{ 94 | Prefix: K6Prefix, 95 | Code: K6CloudCode, 96 | Time: time.Unix(123456789, 0), 97 | }, 98 | want: true, 99 | }, 100 | { 101 | name: "ReturnsFalseWithInvalidCode", 102 | args: args{ 103 | Prefix: K6Prefix, 104 | Code: K6LocalCode, 105 | Time: time.Unix(123456789, 0), 106 | }, 107 | want: false, 108 | }, 109 | { 110 | name: "ReturnsFalseWithInvalidPrefix", 111 | args: args{ 112 | Prefix: 0, 113 | Code: K6CloudCode, 114 | Time: time.Unix(123456789, 0), 115 | }, 116 | want: false, 117 | }, 118 | } 119 | for _, tt := range tests { 120 | t.Run(tt.name, func(t *testing.T) { 121 | tr := &TraceID{ 122 | Prefix: tt.args.Prefix, 123 | Code: tt.args.Code, 124 | Time: tt.args.Time, 125 | } 126 | 127 | got := tr.IsValidCloud() 128 | 129 | assert.Equalf(t, tt.want, got, "%v.IsValidCloud()", tr) 130 | }) 131 | } 132 | } 133 | 134 | func Test_Encode_ReturnsNoErrorAndCorrectlyEncodedStringWithValidTraceID(t *testing.T) { 135 | traceID := TraceID{ 136 | Prefix: K6Prefix, 137 | Code: K6CloudCode, 138 | Time: time.Unix(1629191640, 0), 139 | } 140 | 141 | hx, err := Encode(traceID, &randReaderMock{}) 142 | 143 | assert.NoError(t, err) 144 | assert.Equal(t, "dc071880c0e3d3c5ca869c2d736f6d65", hx) 145 | } 146 | 147 | func Test_Encode_ReturnsErrorWithInvalidTraceID(t *testing.T) { 148 | traceID := TraceID{ 149 | Prefix: 0, 150 | Code: 1, 151 | } 152 | 153 | _, err := Encode(traceID, &randReaderMock{}) 154 | 155 | assert.Error(t, err) 156 | } 157 | -------------------------------------------------------------------------------- /cloud/config.go: -------------------------------------------------------------------------------- 1 | package crocospans 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "time" 7 | 8 | "go.k6.io/k6/output" 9 | ) 10 | 11 | // Config is the config for the crocospans output. 12 | type Config struct { 13 | Endpoint string 14 | PushInterval time.Duration 15 | OrgID int64 16 | Token string 17 | 18 | // TODO: add other config fields? 19 | } 20 | 21 | // NewConfig creates a new Config instance from the provided output.Params 22 | func NewConfig(params output.Params) (Config, error) { 23 | cfg := Config{ 24 | // TODO: add default Endpoint value 25 | PushInterval: 1 * time.Second, 26 | } 27 | 28 | if params.ConfigArgument != "" { 29 | cfg.Endpoint = params.ConfigArgument 30 | } else if val, ok := params.Environment["XK6_CROCOSPANS_ENDPOINT"]; ok { 31 | cfg.Endpoint = val 32 | } 33 | if cfg.Endpoint == "" { 34 | return cfg, fmt.Errorf("missing crocospans endpoint, use '--out xk6-crocospans=http://endpoint' or the XK6_CROCOSPANS_ENDPOINT env var") 35 | } 36 | 37 | if val, ok := params.Environment["XK6_CROCOSPANS_PUSH_INTERVAL"]; ok { 38 | var err error 39 | cfg.PushInterval, err = time.ParseDuration(val) 40 | if err != nil { 41 | return cfg, fmt.Errorf("error parsing environment variable 'XK6_CROCOSPANS_PUSH_INTERVAL': %w", err) 42 | } 43 | } 44 | 45 | if val, ok := params.Environment["XK6_CROCOSPANS_ORG_ID"]; ok { 46 | var err error 47 | cfg.OrgID, err = strconv.ParseInt(val, 10, 64) 48 | if err != nil { 49 | return cfg, fmt.Errorf("error parsing environment variable 'XK6_CROCOSPANS_ORG_ID': %w", err) 50 | } 51 | } else { 52 | return cfg, fmt.Errorf("XK6_CROCOSPANS_ORG_ID is required") 53 | } 54 | 55 | if val, ok := params.Environment["XK6_CROCOSPANS_TOKEN"]; ok { 56 | cfg.Token = val 57 | } else if val, ok := params.Environment["K6_CLOUD_TOKEN"]; ok { 58 | cfg.Token = val 59 | } else { 60 | return cfg, fmt.Errorf("XK6_CROCOSPANS_TOKEN or K6_CLOUD_TOKEN is required") 61 | } 62 | 63 | // TODO: add more validation and options 64 | 65 | return cfg, nil 66 | } 67 | -------------------------------------------------------------------------------- /cloud/crocospans.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.28.1 4 | // protoc v3.21.6 5 | // source: crocospans.proto 6 | 7 | package crocospans 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type RequestBatch struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | SizeBytes int64 `protobuf:"varint,2,opt,name=SizeBytes,proto3" json:"SizeBytes,omitempty"` 29 | Count int64 `protobuf:"varint,3,opt,name=Count,proto3" json:"Count,omitempty"` 30 | Requests []*Request `protobuf:"bytes,4,rep,name=Requests,proto3" json:"Requests,omitempty"` 31 | } 32 | 33 | func (x *RequestBatch) Reset() { 34 | *x = RequestBatch{} 35 | if protoimpl.UnsafeEnabled { 36 | mi := &file_crocospans_proto_msgTypes[0] 37 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 38 | ms.StoreMessageInfo(mi) 39 | } 40 | } 41 | 42 | func (x *RequestBatch) String() string { 43 | return protoimpl.X.MessageStringOf(x) 44 | } 45 | 46 | func (*RequestBatch) ProtoMessage() {} 47 | 48 | func (x *RequestBatch) ProtoReflect() protoreflect.Message { 49 | mi := &file_crocospans_proto_msgTypes[0] 50 | if protoimpl.UnsafeEnabled && x != nil { 51 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 52 | if ms.LoadMessageInfo() == nil { 53 | ms.StoreMessageInfo(mi) 54 | } 55 | return ms 56 | } 57 | return mi.MessageOf(x) 58 | } 59 | 60 | // Deprecated: Use RequestBatch.ProtoReflect.Descriptor instead. 61 | func (*RequestBatch) Descriptor() ([]byte, []int) { 62 | return file_crocospans_proto_rawDescGZIP(), []int{0} 63 | } 64 | 65 | func (x *RequestBatch) GetSizeBytes() int64 { 66 | if x != nil { 67 | return x.SizeBytes 68 | } 69 | return 0 70 | } 71 | 72 | func (x *RequestBatch) GetCount() int64 { 73 | if x != nil { 74 | return x.Count 75 | } 76 | return 0 77 | } 78 | 79 | func (x *RequestBatch) GetRequests() []*Request { 80 | if x != nil { 81 | return x.Requests 82 | } 83 | return nil 84 | } 85 | 86 | type Request struct { 87 | state protoimpl.MessageState 88 | sizeCache protoimpl.SizeCache 89 | unknownFields protoimpl.UnknownFields 90 | 91 | StartTimeUnixNano uint64 `protobuf:"fixed64,1,opt,name=StartTimeUnixNano,proto3" json:"StartTimeUnixNano,omitempty"` 92 | EndTimeUnixNano uint64 `protobuf:"fixed64,2,opt,name=EndTimeUnixNano,proto3" json:"EndTimeUnixNano,omitempty"` 93 | TraceID string `protobuf:"bytes,3,opt,name=TraceID,proto3" json:"TraceID,omitempty"` 94 | TestRunID int64 `protobuf:"varint,4,opt,name=TestRunID,proto3" json:"TestRunID,omitempty"` 95 | Scenario string `protobuf:"bytes,5,opt,name=Scenario,proto3" json:"Scenario,omitempty"` 96 | Group string `protobuf:"bytes,6,opt,name=Group,proto3" json:"Group,omitempty"` 97 | HTTPUrl string `protobuf:"bytes,7,opt,name=HTTPUrl,proto3" json:"HTTPUrl,omitempty"` 98 | HTTPMethod string `protobuf:"bytes,8,opt,name=HTTPMethod,proto3" json:"HTTPMethod,omitempty"` 99 | HTTPStatus int64 `protobuf:"varint,9,opt,name=HTTPStatus,proto3" json:"HTTPStatus,omitempty"` 100 | } 101 | 102 | func (x *Request) Reset() { 103 | *x = Request{} 104 | if protoimpl.UnsafeEnabled { 105 | mi := &file_crocospans_proto_msgTypes[1] 106 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 107 | ms.StoreMessageInfo(mi) 108 | } 109 | } 110 | 111 | func (x *Request) String() string { 112 | return protoimpl.X.MessageStringOf(x) 113 | } 114 | 115 | func (*Request) ProtoMessage() {} 116 | 117 | func (x *Request) ProtoReflect() protoreflect.Message { 118 | mi := &file_crocospans_proto_msgTypes[1] 119 | if protoimpl.UnsafeEnabled && x != nil { 120 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 121 | if ms.LoadMessageInfo() == nil { 122 | ms.StoreMessageInfo(mi) 123 | } 124 | return ms 125 | } 126 | return mi.MessageOf(x) 127 | } 128 | 129 | // Deprecated: Use Request.ProtoReflect.Descriptor instead. 130 | func (*Request) Descriptor() ([]byte, []int) { 131 | return file_crocospans_proto_rawDescGZIP(), []int{1} 132 | } 133 | 134 | func (x *Request) GetStartTimeUnixNano() uint64 { 135 | if x != nil { 136 | return x.StartTimeUnixNano 137 | } 138 | return 0 139 | } 140 | 141 | func (x *Request) GetEndTimeUnixNano() uint64 { 142 | if x != nil { 143 | return x.EndTimeUnixNano 144 | } 145 | return 0 146 | } 147 | 148 | func (x *Request) GetTraceID() string { 149 | if x != nil { 150 | return x.TraceID 151 | } 152 | return "" 153 | } 154 | 155 | func (x *Request) GetTestRunID() int64 { 156 | if x != nil { 157 | return x.TestRunID 158 | } 159 | return 0 160 | } 161 | 162 | func (x *Request) GetScenario() string { 163 | if x != nil { 164 | return x.Scenario 165 | } 166 | return "" 167 | } 168 | 169 | func (x *Request) GetGroup() string { 170 | if x != nil { 171 | return x.Group 172 | } 173 | return "" 174 | } 175 | 176 | func (x *Request) GetHTTPUrl() string { 177 | if x != nil { 178 | return x.HTTPUrl 179 | } 180 | return "" 181 | } 182 | 183 | func (x *Request) GetHTTPMethod() string { 184 | if x != nil { 185 | return x.HTTPMethod 186 | } 187 | return "" 188 | } 189 | 190 | func (x *Request) GetHTTPStatus() int64 { 191 | if x != nil { 192 | return x.HTTPStatus 193 | } 194 | return 0 195 | } 196 | 197 | var File_crocospans_proto protoreflect.FileDescriptor 198 | 199 | var file_crocospans_proto_rawDesc = []byte{ 200 | 0x0a, 0x10, 0x63, 0x72, 0x6f, 0x63, 0x6f, 0x73, 0x70, 0x61, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 201 | 0x74, 0x6f, 0x12, 0x0a, 0x63, 0x72, 0x6f, 0x63, 0x6f, 0x73, 0x70, 0x61, 0x6e, 0x73, 0x22, 0x73, 202 | 0x0a, 0x0c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x1c, 203 | 0x0a, 0x09, 0x53, 0x69, 0x7a, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 204 | 0x03, 0x52, 0x09, 0x53, 0x69, 0x7a, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 205 | 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x43, 0x6f, 0x75, 206 | 0x6e, 0x74, 0x12, 0x2f, 0x0a, 0x08, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x04, 207 | 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x72, 0x6f, 0x63, 0x6f, 0x73, 0x70, 0x61, 0x6e, 208 | 0x73, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x08, 0x52, 0x65, 0x71, 0x75, 0x65, 209 | 0x73, 0x74, 0x73, 0x22, 0xa5, 0x02, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 210 | 0x2c, 0x0a, 0x11, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, 211 | 0x4e, 0x61, 0x6e, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x06, 0x52, 0x11, 0x53, 0x74, 0x61, 0x72, 212 | 0x74, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, 0x4e, 0x61, 0x6e, 0x6f, 0x12, 0x28, 0x0a, 213 | 0x0f, 0x45, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, 0x4e, 0x61, 0x6e, 0x6f, 214 | 0x18, 0x02, 0x20, 0x01, 0x28, 0x06, 0x52, 0x0f, 0x45, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x55, 215 | 0x6e, 0x69, 0x78, 0x4e, 0x61, 0x6e, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x54, 0x72, 0x61, 0x63, 0x65, 216 | 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x54, 0x72, 0x61, 0x63, 0x65, 0x49, 217 | 0x44, 0x12, 0x1c, 0x0a, 0x09, 0x54, 0x65, 0x73, 0x74, 0x52, 0x75, 0x6e, 0x49, 0x44, 0x18, 0x04, 218 | 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x54, 0x65, 0x73, 0x74, 0x52, 0x75, 0x6e, 0x49, 0x44, 0x12, 219 | 0x1a, 0x0a, 0x08, 0x53, 0x63, 0x65, 0x6e, 0x61, 0x72, 0x69, 0x6f, 0x18, 0x05, 0x20, 0x01, 0x28, 220 | 0x09, 0x52, 0x08, 0x53, 0x63, 0x65, 0x6e, 0x61, 0x72, 0x69, 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x47, 221 | 0x72, 0x6f, 0x75, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x47, 0x72, 0x6f, 0x75, 222 | 0x70, 0x12, 0x18, 0x0a, 0x07, 0x48, 0x54, 0x54, 0x50, 0x55, 0x72, 0x6c, 0x18, 0x07, 0x20, 0x01, 223 | 0x28, 0x09, 0x52, 0x07, 0x48, 0x54, 0x54, 0x50, 0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x48, 224 | 0x54, 0x54, 0x50, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 225 | 0x0a, 0x48, 0x54, 0x54, 0x50, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x48, 226 | 0x54, 0x54, 0x50, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 227 | 0x0a, 0x48, 0x54, 0x54, 0x50, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 228 | 0x2f, 0x3b, 0x63, 0x72, 0x6f, 0x63, 0x6f, 0x73, 0x70, 0x61, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 229 | 0x6f, 0x74, 0x6f, 0x33, 230 | } 231 | 232 | var ( 233 | file_crocospans_proto_rawDescOnce sync.Once 234 | file_crocospans_proto_rawDescData = file_crocospans_proto_rawDesc 235 | ) 236 | 237 | func file_crocospans_proto_rawDescGZIP() []byte { 238 | file_crocospans_proto_rawDescOnce.Do(func() { 239 | file_crocospans_proto_rawDescData = protoimpl.X.CompressGZIP(file_crocospans_proto_rawDescData) 240 | }) 241 | return file_crocospans_proto_rawDescData 242 | } 243 | 244 | var file_crocospans_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 245 | var file_crocospans_proto_goTypes = []interface{}{ 246 | (*RequestBatch)(nil), // 0: crocospans.RequestBatch 247 | (*Request)(nil), // 1: crocospans.Request 248 | } 249 | var file_crocospans_proto_depIdxs = []int32{ 250 | 1, // 0: crocospans.RequestBatch.Requests:type_name -> crocospans.Request 251 | 1, // [1:1] is the sub-list for method output_type 252 | 1, // [1:1] is the sub-list for method input_type 253 | 1, // [1:1] is the sub-list for extension type_name 254 | 1, // [1:1] is the sub-list for extension extendee 255 | 0, // [0:1] is the sub-list for field type_name 256 | } 257 | 258 | func init() { file_crocospans_proto_init() } 259 | func file_crocospans_proto_init() { 260 | if File_crocospans_proto != nil { 261 | return 262 | } 263 | if !protoimpl.UnsafeEnabled { 264 | file_crocospans_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 265 | switch v := v.(*RequestBatch); i { 266 | case 0: 267 | return &v.state 268 | case 1: 269 | return &v.sizeCache 270 | case 2: 271 | return &v.unknownFields 272 | default: 273 | return nil 274 | } 275 | } 276 | file_crocospans_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 277 | switch v := v.(*Request); i { 278 | case 0: 279 | return &v.state 280 | case 1: 281 | return &v.sizeCache 282 | case 2: 283 | return &v.unknownFields 284 | default: 285 | return nil 286 | } 287 | } 288 | } 289 | type x struct{} 290 | out := protoimpl.TypeBuilder{ 291 | File: protoimpl.DescBuilder{ 292 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 293 | RawDescriptor: file_crocospans_proto_rawDesc, 294 | NumEnums: 0, 295 | NumMessages: 2, 296 | NumExtensions: 0, 297 | NumServices: 0, 298 | }, 299 | GoTypes: file_crocospans_proto_goTypes, 300 | DependencyIndexes: file_crocospans_proto_depIdxs, 301 | MessageInfos: file_crocospans_proto_msgTypes, 302 | }.Build() 303 | File_crocospans_proto = out.File 304 | file_crocospans_proto_rawDesc = nil 305 | file_crocospans_proto_goTypes = nil 306 | file_crocospans_proto_depIdxs = nil 307 | } 308 | -------------------------------------------------------------------------------- /cloud/crocospans.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package crocospans; 3 | 4 | option go_package = "./;crocospans"; 5 | 6 | message RequestBatch { 7 | int64 SizeBytes = 2; 8 | 9 | int64 Count = 3; 10 | 11 | repeated Request Requests = 4; 12 | } 13 | 14 | message Request { 15 | fixed64 StartTimeUnixNano = 1; 16 | 17 | fixed64 EndTimeUnixNano = 2; 18 | 19 | string TraceID = 3; 20 | 21 | int64 TestRunID = 4; 22 | 23 | string Scenario = 5; 24 | 25 | string Group = 6; 26 | 27 | string HTTPUrl = 7; 28 | 29 | string HTTPMethod = 8; 30 | 31 | int64 HTTPStatus = 9; 32 | } -------------------------------------------------------------------------------- /cloud/output.go: -------------------------------------------------------------------------------- 1 | package crocospans 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "math/rand" 7 | "net/http" 8 | "strconv" 9 | sync "sync" 10 | "unsafe" 11 | 12 | "github.com/sirupsen/logrus" 13 | "google.golang.org/protobuf/proto" 14 | 15 | "go.k6.io/k6/lib/netext/httpext" 16 | "go.k6.io/k6/metrics" 17 | "go.k6.io/k6/output" 18 | ) 19 | 20 | // Output implements the k6 output.Output interface 21 | type Output struct { 22 | config Config 23 | 24 | testRunID int64 25 | 26 | httpClient *http.Client 27 | 28 | bufferLock sync.Mutex 29 | buffer []*httpext.Trail 30 | 31 | periodicFlusher *output.PeriodicFlusher 32 | logger logrus.FieldLogger 33 | } 34 | 35 | var _ output.Output = new(Output) 36 | 37 | // New creates an instance of the output 38 | func New(p output.Params) (*Output, error) { 39 | conf, err := NewConfig(p) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | return &Output{ 45 | config: conf, 46 | logger: p.Logger.WithField("component", "xk6-crocospans-output"), 47 | httpClient: http.DefaultClient, // TODO: some options here? 48 | }, nil 49 | } 50 | 51 | func (o *Output) Description() string { 52 | return fmt.Sprintf("xk6-crocospans (TestRunID: %d)", o.testRunID) 53 | } 54 | 55 | // AddMetricSamples adds the given metric samples to the internal buffer. 56 | func (o *Output) AddMetricSamples(samples []metrics.SampleContainer) { 57 | if len(samples) == 0 { 58 | return 59 | } 60 | o.bufferLock.Lock() 61 | defer o.bufferLock.Unlock() 62 | for _, s := range samples { 63 | // Only collect HTTP request samples for now 64 | // TODO: do some sort of sampling or processing? 65 | if httpSample, ok := s.(*httpext.Trail); ok { 66 | o.buffer = append(o.buffer, httpSample) 67 | } 68 | } 69 | } 70 | 71 | func (o *Output) Stop() error { 72 | o.logger.Debug("Stopping...") 73 | defer o.logger.Debug("Stopped!") 74 | o.periodicFlusher.Stop() 75 | 76 | // TODO: do we need to do something here? 77 | 78 | return nil 79 | } 80 | 81 | func (o *Output) Start() error { 82 | o.logger.Debug("Starting...") 83 | 84 | // TODO: initial set up we need to do? get the test run ID somehow? 85 | o.testRunID = 10000 + rand.Int63n(99999-10000) 86 | 87 | pf, err := output.NewPeriodicFlusher(o.config.PushInterval, o.flushMetrics) 88 | if err != nil { 89 | return err 90 | } 91 | o.logger.Debug("Started!") 92 | o.periodicFlusher = pf 93 | 94 | return nil 95 | } 96 | 97 | func (o *Output) flushMetrics() { 98 | o.bufferLock.Lock() 99 | bufferedTrails := o.buffer 100 | o.buffer = make([]*httpext.Trail, 0, len(bufferedTrails)) // TODO: optimize like output.SampleBuffer? 101 | o.bufferLock.Unlock() 102 | 103 | // TODO: do some sort of sampling or processing? 104 | 105 | requests := make([]*Request, 0, len(bufferedTrails)) 106 | 107 | for _, trail := range bufferedTrails { 108 | traceID, hasTrace := trail.Metadata["trace_id"] 109 | if !hasTrace { 110 | continue 111 | } 112 | 113 | totalDuration := trail.Blocked + trail.ConnDuration + trail.Duration 114 | startTime := trail.EndTime.Add(-totalDuration) 115 | 116 | getTag := func(name string) string { 117 | val, _ := trail.Tags.Get(name) 118 | return val 119 | } 120 | 121 | strStatus := getTag("status") 122 | status, err := strconv.ParseInt(strStatus, 10, 64) 123 | if err != nil { 124 | o.logger.Warnf("unexpected error parsing status '%s': %w", strStatus, err) 125 | continue 126 | } 127 | 128 | req := &Request{ 129 | TestRunID: o.testRunID, 130 | StartTimeUnixNano: uint64(startTime.UnixNano()), 131 | EndTimeUnixNano: uint64(trail.EndTime.UnixNano()), 132 | Group: getTag("group"), 133 | Scenario: getTag("scenario"), 134 | TraceID: traceID, 135 | HTTPUrl: getTag("url"), 136 | HTTPMethod: getTag("method"), 137 | HTTPStatus: status, 138 | } 139 | 140 | requests = append(requests, req) 141 | } 142 | 143 | md := &RequestBatch{ 144 | // TODO: FIXME: unsafe.Sizeof() here is almost certainly a bug and both 145 | // Count and SizeBytes should be unnecessary 146 | SizeBytes: int64(unsafe.Sizeof(requests)), 147 | Count: int64(len(requests)), 148 | Requests: requests, 149 | } 150 | 151 | mm, err := proto.Marshal(md) 152 | if err != nil { 153 | logrus.WithError(err).Error("Failed to marshal request metadata") 154 | } 155 | 156 | rq, _ := http.NewRequest("POST", o.config.Endpoint, bytes.NewBuffer(mm)) 157 | orgID := strconv.Itoa(int(o.config.OrgID)) 158 | rq.Header.Add("X-Scope-OrgID", orgID) 159 | rq.SetBasicAuth(orgID, o.config.Token) 160 | _, err = o.httpClient.Do(rq) 161 | if err != nil { 162 | logrus.WithError(err).Error("Failed to send request metadata") 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /examples/test-config.js: -------------------------------------------------------------------------------- 1 | import tracing, { Http } from 'k6/x/tracing'; 2 | import { sleep } from 'k6'; 3 | 4 | export let options = { 5 | vus: 1, 6 | duration: '10s', 7 | }; 8 | 9 | export function setup() { 10 | console.log(`Running xk6-distributed-tracing v${tracing.version}`); 11 | } 12 | 13 | export default function() { 14 | const http = new Http({propagator: "w3c"}); 15 | const r = http.get('https://test-api.k6.io'); 16 | 17 | console.log(`trace-id=${r.trace_id}`); 18 | sleep(1); 19 | } -------------------------------------------------------------------------------- /examples/test.js: -------------------------------------------------------------------------------- 1 | import tracing, { Http } from 'k6/x/tracing'; 2 | import { sleep } from 'k6'; 3 | 4 | export let options = { 5 | vus: 1, 6 | duration: '10s', 7 | }; 8 | 9 | export function setup() { 10 | console.log(`Running xk6-distributed-tracing v${tracing.version}`); 11 | } 12 | 13 | export default function() { 14 | const http = new Http(); 15 | const r = http.get('https://test-api.k6.io'); 16 | 17 | console.log(`trace-id=${r.trace_id}`); 18 | sleep(1); 19 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/grafana/xk6-distributed-tracing 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/dop251/goja v0.0.0-20221003171542-5ea1285e6c91 7 | go.k6.io/k6 v0.40.1-0.20221020144551-8a74171c8b43 8 | google.golang.org/protobuf v1.28.0 9 | ) 10 | 11 | require ( 12 | github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect 13 | github.com/PuerkitoBio/goquery v1.8.0 // indirect 14 | github.com/Soontao/goHttpDigestClient v0.0.0-20170320082612-6d28bb1415c5 // indirect 15 | github.com/andybalholm/brotli v1.0.4 // indirect 16 | github.com/andybalholm/cascadia v1.3.1 // indirect 17 | github.com/dlclark/regexp2 v1.7.0 // indirect 18 | github.com/fatih/color v1.13.0 // indirect 19 | github.com/go-sourcemap/sourcemap v2.1.4-0.20211119122758-180fcef48034+incompatible // indirect 20 | github.com/google/go-cmp v0.5.8 // indirect 21 | github.com/josharian/intern v1.0.0 // indirect 22 | github.com/klauspost/compress v1.15.11 // indirect 23 | github.com/mailru/easyjson v0.7.7 // indirect 24 | github.com/mattn/go-colorable v0.1.13 // indirect 25 | github.com/mattn/go-isatty v0.0.16 // indirect 26 | github.com/mstoykov/atlas v0.0.0-20220808085829-90340e9998bd // indirect 27 | github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect 28 | github.com/onsi/ginkgo v1.16.5 // indirect 29 | github.com/onsi/gomega v1.20.2 // indirect 30 | github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect 31 | github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e // indirect 32 | github.com/sirupsen/logrus v1.9.0 33 | github.com/spf13/afero v1.1.2 // indirect 34 | github.com/tidwall/gjson v1.14.3 // indirect 35 | github.com/tidwall/match v1.1.1 // indirect 36 | github.com/tidwall/pretty v1.2.1 // indirect 37 | golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be // indirect 38 | golang.org/x/net v0.0.0-20221002022538-bcab6841153b // indirect 39 | golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect 40 | golang.org/x/text v0.3.7 // indirect 41 | golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect 42 | gopkg.in/guregu/null.v3 v3.3.0 // indirect 43 | ) 44 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= 4 | github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU= 5 | github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= 6 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 7 | github.com/DataDog/datadog-go v0.0.0-20180330214955-e67964b4021a/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= 8 | github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U= 9 | github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= 10 | github.com/Soontao/goHttpDigestClient v0.0.0-20170320082612-6d28bb1415c5 h1:k+1+doEm31k0rRjCjLnGG3YRkuO9ljaEyS2ajZd6GK8= 11 | github.com/Soontao/goHttpDigestClient v0.0.0-20170320082612-6d28bb1415c5/go.mod h1:5Q4+CyR7+Q3VMG8f78ou+QSX/BNUNUx5W48eFRat8DQ= 12 | github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= 13 | github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 14 | github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= 15 | github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= 16 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 17 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 18 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 19 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 20 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 21 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 22 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 23 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 24 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 25 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 26 | github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= 27 | github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 28 | github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 29 | github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 30 | github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 31 | github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 32 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 33 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 34 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 35 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 36 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 37 | github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= 38 | github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= 39 | github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 40 | github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= 41 | github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= 42 | github.com/dop251/goja v0.0.0-20221003171542-5ea1285e6c91 h1:1PfaQuGdeJVnHHQ0tg0Jw7MXagyqaAupJyk35/QM3I4= 43 | github.com/dop251/goja v0.0.0-20221003171542-5ea1285e6c91/go.mod h1:yRkwfj0CBpOGre+TwBsqPV0IH0Pk73e4PXJOeNDboGs= 44 | github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= 45 | github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= 46 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 47 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 48 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 49 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 50 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 51 | github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= 52 | github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= 53 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 54 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= 55 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 56 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 57 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 58 | github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= 59 | github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= 60 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 61 | github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= 62 | github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= 63 | github.com/go-sourcemap/sourcemap v2.1.4-0.20211119122758-180fcef48034+incompatible h1:bopx7t9jyUNX1ebhr0G4gtQWmUOgwQRI0QsYhdYLgkU= 64 | github.com/go-sourcemap/sourcemap v2.1.4-0.20211119122758-180fcef48034+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= 65 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 66 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 67 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 68 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 69 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 70 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 71 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 72 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 73 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 74 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 75 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 76 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 77 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 78 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 79 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 80 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 81 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 82 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 83 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 84 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 85 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 86 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 87 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 88 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 89 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 90 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 91 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 92 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 93 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 94 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 95 | github.com/grafana/xk6-redis v0.1.1/go.mod h1:z7el1Tz8advY+ex419KfLbENzSQYgaA2lQYwMlt9yMM= 96 | github.com/grafana/xk6-timers v0.1.2/go.mod h1:XHmDIXAKe30NJMXrxKIKMFXx98etsCl0jBYktjsSURc= 97 | github.com/grafana/xk6-websockets v0.1.5/go.mod h1:+vlArhLMFFvNFgs5GUsw3RtzlDxli1G5SFMdC/QEUxU= 98 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 99 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 100 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 101 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 102 | github.com/influxdata/influxdb1-client v0.0.0-20190402204710-8ff2fc3824fc/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= 103 | github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= 104 | github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= 105 | github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ= 106 | github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E= 107 | github.com/jhump/protoreflect v1.12.0/go.mod h1:JytZfP5d0r8pVNLZvai7U/MCuTWITgrI4tTg7puQFKI= 108 | github.com/jhump/protoreflect v1.13.0/go.mod h1:JytZfP5d0r8pVNLZvai7U/MCuTWITgrI4tTg7puQFKI= 109 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 110 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 111 | github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 112 | github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= 113 | github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= 114 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 115 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 116 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 117 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 118 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 119 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 120 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 121 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 122 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 123 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 124 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 125 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 126 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 127 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 128 | github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= 129 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 130 | github.com/mccutchen/go-httpbin v1.1.2-0.20190116014521-c5cb2f4802fa h1:lx8ZnNPwjkXSzOROz0cg69RlErRXs+L3eDkggASWKLo= 131 | github.com/mccutchen/go-httpbin v1.1.2-0.20190116014521-c5cb2f4802fa/go.mod h1:fhpOYavp5g2K74XDl/ao2y4KvhqVtKlkg1e+0UaQv7I= 132 | github.com/mstoykov/atlas v0.0.0-20220808085829-90340e9998bd h1:x/wQ8/umYu2x0icx5wNNTSK1NlkYVmsgzQ+U6v4ijv0= 133 | github.com/mstoykov/atlas v0.0.0-20220808085829-90340e9998bd/go.mod h1:9vRHVuLCjoFfE3GT06X0spdOAO+Zzo4AMjdIwUHBvAk= 134 | github.com/mstoykov/envconfig v1.4.1-0.20220114105314-765c6d8c76f1 h1:94EkGmhXrVUEal+uLwFUf4fMXPhZpM5tYxuIsxrCCbI= 135 | github.com/mstoykov/envconfig v1.4.1-0.20220114105314-765c6d8c76f1/go.mod h1:vk/d9jpexY2Z9Bb0uB4Ndesss1Sr0Z9ZiGUrg5o9VGk= 136 | github.com/mstoykov/k6-taskqueue-lib v0.1.0/go.mod h1:PXdINulapvmzF545Auw++SCD69942FeNvUztaa9dVe4= 137 | github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= 138 | github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= 139 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 140 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 141 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 142 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 143 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 144 | github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 145 | github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= 146 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 147 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= 148 | github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= 149 | github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= 150 | github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= 151 | github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= 152 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 153 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 154 | github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= 155 | github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= 156 | github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= 157 | github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= 158 | github.com/onsi/gomega v1.20.2 h1:8uQq0zMgLEfa0vRrrBgaJF2gyW9Da9BmfGV+OyUzfkY= 159 | github.com/onsi/gomega v1.20.2/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= 160 | github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= 161 | github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= 162 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 163 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 164 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 165 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 166 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 167 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 168 | github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e h1:zWKUYT07mGmVBH+9UgnHXd/ekCK99C8EbDSAt5qsjXE= 169 | github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= 170 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 171 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 172 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 173 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= 174 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 175 | github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= 176 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 177 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 178 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 179 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 180 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 181 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 182 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 183 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 184 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 185 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 186 | github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 187 | github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= 188 | github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 189 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 190 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 191 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 192 | github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= 193 | github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 194 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 195 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 196 | github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 197 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 198 | go.k6.io/k6 v0.37.1-0.20220426072701-d105f5474bc3/go.mod h1:1bTdDsXTT2V3in3ZgdR15MDW6SQQh5nWni59tirqNB8= 199 | go.k6.io/k6 v0.38.2/go.mod h1:1bTdDsXTT2V3in3ZgdR15MDW6SQQh5nWni59tirqNB8= 200 | go.k6.io/k6 v0.40.1-0.20221017105932-3c97ec7d1231/go.mod h1:vaSQ1A2rnC+wrKRJ4ExZCT6kKLfAEYoaIY9UR0uAjjk= 201 | go.k6.io/k6 v0.40.1-0.20221020144551-8a74171c8b43 h1:8xcANo2wvMDjiavFhQkdOOWS1qkAqOTsvEYWuT0PEtI= 202 | go.k6.io/k6 v0.40.1-0.20221020144551-8a74171c8b43/go.mod h1:ZrgrR06UZbzZt9u+so/yQhlkJFH5gkJ3qCp1hwq1mEU= 203 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 204 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 205 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 206 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 207 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 208 | golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 209 | golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A= 210 | golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 211 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 212 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 213 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 214 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 215 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 216 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 217 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 218 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 219 | golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= 220 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 221 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 222 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 223 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 224 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 225 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 226 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 227 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 228 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 229 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 230 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 231 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 232 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 233 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 234 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 235 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= 236 | golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 237 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 238 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 239 | golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 240 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 241 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 242 | golang.org/x/net v0.0.0-20221002022538-bcab6841153b h1:6e93nYa3hNqAvLr0pD4PN1fFS+gKzp2zAXqrnTCstqU= 243 | golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 244 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 245 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 246 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 247 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 248 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 249 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 250 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 251 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 252 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 253 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 254 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 255 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 256 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 257 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 258 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 259 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 260 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 261 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 262 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 263 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 264 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 265 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 266 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 267 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 268 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 269 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 270 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 271 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 272 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 273 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 274 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 275 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 276 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 277 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 278 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 279 | golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 280 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 281 | golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 282 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 283 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 284 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 285 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 286 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 287 | golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI= 288 | golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 289 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 290 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 291 | golang.org/x/term v0.0.0-20220919170432-7a66f970e087/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 292 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 293 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 294 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 295 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 296 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 297 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 298 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 299 | golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 300 | golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y= 301 | golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 302 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 303 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 304 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 305 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 306 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 307 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 308 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 309 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 310 | golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 311 | golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= 312 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 313 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 314 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 315 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 316 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 317 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 318 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 319 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 320 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 321 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 322 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 323 | google.golang.org/genproto v0.0.0-20200903010400-9bfcb5116336/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 324 | google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08 h1:pc16UedxnxXXtGxHCSUhafAoVHQZ0yXl8ZelMH4EETc= 325 | google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= 326 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 327 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 328 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 329 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 330 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 331 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 332 | google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 333 | google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= 334 | google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= 335 | google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= 336 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 337 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 338 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 339 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 340 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 341 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 342 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 343 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 344 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 345 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 346 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 347 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 348 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 349 | google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= 350 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 351 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 352 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 353 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 354 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 355 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 356 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 357 | gopkg.in/guregu/null.v3 v3.3.0 h1:8j3ggqq+NgKt/O7mbFVUFKUMWN+l1AmT5jQmJ6nPh2c= 358 | gopkg.in/guregu/null.v3 v3.3.0/go.mod h1:E4tX2Qe3h7QdL+uZ3a0vqvYwKQsRSQKM5V4YltdgH9Y= 359 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 360 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 361 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 362 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 363 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 364 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 365 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 366 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 367 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 368 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 369 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 370 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 371 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 372 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 373 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 374 | -------------------------------------------------------------------------------- /tracing.go: -------------------------------------------------------------------------------- 1 | package tracing 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/dop251/goja" 7 | "github.com/grafana/xk6-distributed-tracing/client" 8 | crocospans "github.com/grafana/xk6-distributed-tracing/cloud" 9 | "go.k6.io/k6/js/common" 10 | "go.k6.io/k6/js/modules" 11 | k6HTTP "go.k6.io/k6/js/modules/k6/http" 12 | "go.k6.io/k6/output" 13 | ) 14 | 15 | const version = "0.2.0" 16 | 17 | func init() { 18 | modules.Register("k6/x/tracing", New()) 19 | 20 | output.RegisterExtension("xk6-crocospans", func(p output.Params) (output.Output, error) { 21 | return crocospans.New(p) 22 | }) 23 | } 24 | 25 | type ( 26 | // RootModule is the global module instance that will create DistributedTracing 27 | // instances for each VU. 28 | RootModule struct{} 29 | 30 | DistributedTracing struct { 31 | // modules.VU provides some useful methods for accessing internal k6 32 | // objects like the global context, VU state and goja runtime. 33 | vu modules.VU 34 | httpRequest client.HttpRequestFunc 35 | } 36 | ) 37 | 38 | // Ensure the interfaces are implemented correctly. 39 | var ( 40 | _ modules.Instance = &DistributedTracing{} 41 | _ modules.Module = &RootModule{} 42 | ) 43 | 44 | // New returns a pointer to a new RootModule instance. 45 | func New() *RootModule { 46 | return &RootModule{} 47 | } 48 | 49 | // NewModuleInstance implements the modules.Module interface and returns 50 | // a new instance for each VU. 51 | func (*RootModule) NewModuleInstance(vu modules.VU) modules.Instance { 52 | r := k6HTTP.New().NewModuleInstance(vu).Exports().Default.(*goja.Object).Get("request") 53 | var requestFunc client.HttpRequestFunc 54 | err := vu.Runtime().ExportTo(r, &requestFunc) 55 | if err != nil { 56 | panic(err) 57 | } 58 | return &DistributedTracing{vu: vu, httpRequest: requestFunc} 59 | } 60 | 61 | // Exports implements the modules.Instance interface and returns the exports 62 | // of the JS module. 63 | func (c *DistributedTracing) Exports() modules.Exports { 64 | return modules.Exports{ 65 | Named: map[string]interface{}{ 66 | "Http": c.http, 67 | "version": version, 68 | }, 69 | } 70 | } 71 | 72 | func (t *DistributedTracing) parseClientOptions(val goja.Value) (client.Options, error) { 73 | rt := t.vu.Runtime() 74 | opts := client.Options{ 75 | Propagator: client.PropagatorW3C, 76 | } 77 | 78 | if val == nil || goja.IsUndefined(val) || goja.IsNull(val) { 79 | return opts, nil 80 | } 81 | 82 | params := val.ToObject(rt) 83 | for _, k := range params.Keys() { 84 | switch k { 85 | case "propagator": 86 | opts.Propagator = params.Get(k).ToString().String() 87 | //TODO: validate 88 | default: 89 | return opts, fmt.Errorf("unknown HTTP tracing option '%s'", k) 90 | } 91 | } 92 | return opts, nil 93 | } 94 | 95 | func (t *DistributedTracing) http(call goja.ConstructorCall) *goja.Object { 96 | rt := t.vu.Runtime() 97 | opts, err := t.parseClientOptions(call.Argument(0)) 98 | if err != nil { 99 | common.Throw(rt, err) 100 | } 101 | 102 | return rt.ToValue(client.New(t.vu, t.httpRequest, opts)).ToObject(rt) 103 | } 104 | --------------------------------------------------------------------------------