Skip to content

Commit

Permalink
chore(bpfassets): Refactor to reduce the API exposed by bpfassets (#1413
Browse files Browse the repository at this point in the history
)

* chore(bpf): Don't re-export constants

These constants are available in the config package already and do not
need to be re-exported under a different name.

Signed-off-by: Dave Tucker <dave@dtucker.co.uk>

* chore(bpf): Remove global variables from pkg/bpfassets

This reduces the API exposed by the pkg/bpfassets and
also makes it easier to test.

Signed-off-by: Dave Tucker <dave@dtucker.co.uk>

* chore(Makefile): Fix test run control

Remove the verbose test targets. You can enable this
with make VERBOSE=1 test.

Ensure e2e tests aren't run in the normal test target
as they will fail.

Skip bpfassets tests as they need sudo.

Compile bpfassets tests to a tmpdir and then run them.

Fix assertions in the bpfassets test to skip cpuFreq
given that this isn't populated, and to add more
assertions in the data that comes from `processes`.

Split test run into unit-test, bpf-test and bench
targets.

Signed-off-by: Dave Tucker <dave@dtucker.co.uk>

---------

Signed-off-by: Dave Tucker <dave@dtucker.co.uk>
  • Loading branch information
dave-tucker committed May 15, 2024
1 parent 937799d commit e0ec4fd
Show file tree
Hide file tree
Showing 94 changed files with 9,444 additions and 4,724 deletions.
82 changes: 41 additions & 41 deletions .github/workflows/unit_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,29 @@ jobs:
unit_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.1
with:
persist-credentials: false
fetch-depth: 0
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Get ginkgo
run: make ginkgo-set
env:
- uses: actions/checkout@v4.1.1
with:
persist-credentials: false
fetch-depth: 0
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Get ginkgo
run: make ginkgo-set
env:
GOPATH: /home/runner/go
GOBIN: /home/runner/go/bin
- name: Prepare environment
run: |
- name: Prepare environment
run: |
sudo apt-get install -y cpuid clang
cd doc/ && sudo ./dev/prepare_dev_env.sh && cd -
git config --global --add safe.directory /kepler
- name: install libbpf
uses: sustainable-computing-io/kepler-action@v0.0.6
with:
ebpfprovider: libbpf
- name: Run
run: |
- name: install libbpf
uses: sustainable-computing-io/kepler-action@v0.0.6
with:
ebpfprovider: libbpf
- name: Run
run: |
sudo apt remove libbpf-dev
mkdir temp-libbpf
cd temp-libbpf
Expand All @@ -47,30 +47,30 @@ jobs:
sudo make install_uapi_headers
sudo prefix=/usr BUILD_STATIC_ONLY=y make install
cd ../../../
ATTACHER_TAG=libbpf make test-verbose
make VERBOSE=1 test
go tool cover -func=coverage.out -o=coverage.out
- name: Go Coverage Badge # Pass the `coverage.out` output to this action
uses: tj-actions/coverage-badge-go@v2
with:
filename: coverage.out
- name: Go Coverage Badge # Pass the `coverage.out` output to this action
uses: tj-actions/coverage-badge-go@v2
with:
filename: coverage.out

- name: Verify Changed files
uses: tj-actions/verify-changed-files@v19
id: verify-changed-files
with:
files: README.md
- name: Verify Changed files
uses: tj-actions/verify-changed-files@v19
id: verify-changed-files
with:
files: README.md

- name: Commit changes
if: github.event_name != 'pull_request' && steps.verify-changed-files.outputs.files_changed == 'true'
run: |
git config --local user.email "bot@sustainable-computing.io"
git config --local user.name "sustainable-computing-bot"
git add README.md
git commit -m "bot: Updated coverage badge." -s
- name: Commit changes
if: github.event_name != 'pull_request' && steps.verify-changed-files.outputs.files_changed == 'true'
run: |
git config --local user.email "bot@sustainable-computing.io"
git config --local user.name "sustainable-computing-bot"
git add README.md
git commit -m "bot: Updated coverage badge." -s
- name: Push changes
if: github.event_name != 'pull_request' && steps.verify-changed-files.outputs.files_changed == 'true'
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GH_BOT_SECRET }}
branch: ${{ github.head_ref }}
- name: Push changes
if: github.event_name != 'pull_request' && steps.verify-changed-files.outputs.files_changed == 'true'
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GH_BOT_SECRET }}
branch: ${{ github.head_ref }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ local-dev-cluster

# object files
*.o

# test coverage
coverage.out
62 changes: 43 additions & 19 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,10 @@ clean-cross-build:
build: clean_build_local _build_local copy_build_local ## Build binary and copy to $(OUTPUT_DIR)/bin
.PHONY: build

_build_local: ## Build Kepler binary locally.
_build_ebpf_local:
@make -C bpfassets/libbpf

_build_local: _build_ebpf_local ## Build Kepler binary locally.
@echo TAGS=$(GO_BUILD_TAGS)
@mkdir -p "$(CROSS_BUILD_BINDIR)/$(GOOS)_$(GOARCH)"
+@$(GOENV) go build -v -tags ${GO_BUILD_TAGS} -o $(CROSS_BUILD_BINDIR)/$(GOOS)_$(GOARCH)/kepler -ldflags $(LDFLAGS) ./cmd/exporter/exporter.go
Expand Down Expand Up @@ -229,17 +231,20 @@ cross-build: clean_build_local cross-build-linux-amd64 cross-build-linux-arm64 c
.PHONY: cross-build

## toolkit ###
.PHONY: tidy-vendor
tidy-vendor:
go mod tidy -v
go mod vendor

.PHONY: ginkgo-set
ginkgo-set:
mkdir -p $(GOBIN)
mkdir -p $(ENVTEST_ASSETS_DIR)
@test -f $(ENVTEST_ASSETS_DIR)/ginkgo || \
(go install -mod=mod github.com/onsi/ginkgo/v2/ginkgo@v2.4.0 && \
cp $(GOBIN)/ginkgo $(ENVTEST_ASSETS_DIR)/ginkgo)

.PHONY: container_test
container_test:
$(CTR_CMD) run --rm \
-v $(base_dir):/kepler:Z\
Expand All @@ -254,32 +259,51 @@ container_test:
cd doc/ && \
./dev/prepare_dev_env.sh && \
cd - && git config --global --add safe.directory /kepler && \
make test-container-verbose'
make VERBOSE=1 unit-test bench'

test: ginkgo-set tidy-vendor
@echo TAGS=$(GO_TEST_TAGS)
@$(GOENV) go test -tags $(GO_TEST_TAGS) ./... --race --bench=. -cover --count=1 --vet=all -v
VERBOSE ?= 0
TMPDIR := $(shell mktemp -d)
TEST_PKGS := $(shell go list ./... | grep -v bpfassets | grep -v e2e)
SUDO?=sudo
SUDO_TEST_PKGS := $(shell go list ./... | grep bpfassets)

test-verbose: ginkgo-set tidy-vendor
@echo TAGS=$(GO_TEST_TAGS)
@echo GOENV=$(GOENV)
@$(GOENV) go test -tags $(GO_TEST_TAGS) \
-timeout=30m \
-covermode=atomic -coverprofile=coverage.out \
-v $$(go list ./... | grep pkg | grep -v bpfassets) \
--race --bench=. -cover --count=1 --vet=all
.PHONY: test
test: unit-test bpf-test bench ## Run all tests.

test-container-verbose: ginkgo-set tidy-vendor
.PHONY: unit-test
unit-test: ginkgo-set tidy-vendor ## Run unit tests.
@echo TAGS=$(GO_TEST_TAGS)
@echo GOENV=$(GOENV)
$(if $(VERBOSE),@echo GOENV=$(GOENV))
@$(GOENV) go test -tags $(GO_TEST_TAGS) \
-covermode=atomic -coverprofile=coverage.out \
-v $$(go list ./... | grep pkg | grep -v bpfassets) \
--race -cover --count=1 --vet=all
$(if $(VERBOSE),-v) \
-cover -covermode=atomic -coverprofile=coverage.out \
--race --count=1 \
$(TEST_PKGS)

.PHONY: bench
bench: ## Run benchmarks.
@echo TAGS=$(GO_TEST_TAGS)
$(GOENV) go test -tags $(GO_TEST_TAGS) \
$(if $(VERBOSE),-v) \
-test.run=dontrunanytests \
-bench=. --count=1 $(TEST_PKGS)

.PHONY: bpf-test
bpf-test: _build_ebpf_local ## Run BPF tests.
for pkg in $(SUDO_TEST_PKGS); do \
$(GOENV) go test -c $$pkg -tags $(GO_TEST_TAGS) -cover \
-covermode=atomic -coverprofile=coverage.bpf.out \
-o $(TMPDIR)/$$(basename $$pkg).test && \
$(SUDO) $(TMPDIR)/$$(basename $$pkg).test; \
done

.PHONY: test-mac-verbose
test-mac-verbose: ginkgo-set
@echo TAGS=$(GO_TEST_TAGS)
@go test $$(go list ./... | grep pkg | grep -v bpfassets) --race --bench=. -cover --count=1 --vet=all
@go test \
-covermode=atomic -coverprofile=coverage.out \
--race --count=1 \
$(TEST_PKGS)

escapes_detect: tidy-vendor
@$(GOENV) go build -tags $(GO_BUILD_TAGS) -gcflags="-m -l" ./... 2>&1 | grep "escapes to heap" || true
Expand Down
37 changes: 16 additions & 21 deletions cmd/exporter/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"runtime/debug"
"time"

bpfAttacher "github.com/sustainable-computing-io/kepler/pkg/bpfassets/attacher"
"github.com/sustainable-computing-io/kepler/pkg/collector/stats"
"github.com/sustainable-computing-io/kepler/pkg/config"
"github.com/sustainable-computing-io/kepler/pkg/manager"
Expand Down Expand Up @@ -53,7 +54,6 @@ var (
enabledEBPFCgroupID = flag.Bool("enable-cgroup-id", true, "whether enable eBPF to collect cgroup id (must have kernel version >= 4.18 and cGroup v2)")
exposeHardwareCounterMetrics = flag.Bool("expose-hardware-counter-metrics", true, "whether expose hardware counter as prometheus metrics")
enabledMSR = flag.Bool("enable-msr", false, "whether MSR is allowed to obtain energy data")
enabledBPFBatchDelete = flag.Bool("enable-bpf-batch-del", true, "bpf map batch deletion can be enabled for backported kernels older than 5.6")
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file, if empty we use the in-cluster configuration")
apiserverEnabled = flag.Bool("apiserver", true, "if apiserver is disabled, we collect pod information from kubelet")
redfishCredFilePath = flag.String("redfish-cred-file-path", "", "path to the redfish credential file")
Expand Down Expand Up @@ -94,14 +94,6 @@ func main() {
config.SetKubeConfig(*kubeconfig)
config.SetEnableAPIServer(*apiserverEnabled)

// the ebpf batch deletion operation was introduced in linux kernel 5.6, which provides better performance to delete keys.
// but the user can enable it if the kernel has backported this functionality.
config.EnabledBPFBatchDelete = *enabledBPFBatchDelete
if config.KernelVersion >= 5.6 {
config.EnabledBPFBatchDelete = true
}
klog.Infof("EnabledBPFBatchDelete: %v", config.EnabledBPFBatchDelete)

// set redfish credential file path
if *redfishCredFilePath != "" {
config.SetRedfishCredFilePath(*redfishCredFilePath)
Expand All @@ -112,11 +104,16 @@ func main() {
components.InitPowerImpl()
platform.InitPowerImpl()

stats.InitAvailableParamAndMetrics()
attacher, err := bpfAttacher.NewAttacher()
if err != nil {
klog.Fatalf("failed to create eBPF attacher: %v", err)
}
defer attacher.Detach()

stats.InitAvailableParamAndMetrics(attacher.GetEnabledBPFHWCounters(), attacher.GetEnabledBPFSWCounters())

if config.EnabledGPU {
klog.Infof("Initializing the GPU collector")
var err error
// the GPU operators typically takes longer time to initialize than kepler resulting in error to start the gpu driver
// therefore, we wait up to 1 min to allow the gpu operator initialize
for i := 0; i <= maxGPUInitRetry; i++ {
Expand All @@ -136,23 +133,21 @@ func main() {

if config.IsExposeQATMetricsEnabled() {
klog.Infof("Initializing the QAT collector")
err := qat.Init()
if err == nil {
if qatErr := qat.Init(); qatErr == nil {
defer qat.Shutdown()
} else {
klog.Infof("Failed to initialize the QAT collector: %v", err)
klog.Infof("Failed to initialize the QAT collector: %v", qatErr)
}
}

m := manager.New()
m := manager.New(attacher)
reg := m.PrometheusCollector.RegisterMetrics()
defer m.StatsCollector.Destroy()
defer components.StopPower()

// starting a new gorotine to collect data and report metrics
// BPF is attached here
if err := m.Start(); err != nil {
klog.Infof("%s", fmt.Sprintf("failed to start : %v", err))
if startErr := m.Start(); startErr != nil {
klog.Infof("%s", fmt.Sprintf("failed to start : %v", startErr))
}
metricPathConfig := config.GetMetricPath(*metricsPath)
bindAddressConfig := config.GetBindAddress(*address)
Expand All @@ -165,15 +160,15 @@ func main() {
))
http.HandleFunc("/healthz", healthProbe)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte(`<html>
_, httpErr := w.Write([]byte(`<html>
<head><title>Energy Stats Exporter</title></head>
<body>
<h1>Energy Stats Exporter</h1>
<p><a href="` + metricPathConfig + `">Metrics</a></p>
</body>
</html>`))
if err != nil {
klog.Fatalf("%s", fmt.Sprintf("failed to write response: %v", err))
klog.Fatalf("%s", fmt.Sprintf("failed to write response: %v", httpErr))
}
})

Expand All @@ -185,6 +180,6 @@ func main() {

klog.Infof(startedMsg, time.Since(start))
klog.Flush() // force flush to parse the start msg in the e2e test
err := <-ch
err = <-ch
klog.Fatalf("%s", fmt.Sprintf("failed to bind on %s: %v", bindAddressConfig, err))
}
12 changes: 6 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ require (
github.com/prometheus/common v0.48.0
github.com/prometheus/prometheus v0.48.1
github.com/sirupsen/logrus v1.9.0
golang.org/x/sys v0.19.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/sys v0.20.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.28.2
k8s.io/apimachinery v0.28.2
Expand Down Expand Up @@ -74,13 +75,12 @@ require (
github.com/prometheus/procfs v0.12.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.8.4 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/oauth2 v0.16.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/term v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.16.1 // indirect
golang.org/x/tools v0.21.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
Expand Down
24 changes: 12 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -173,17 +173,17 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand All @@ -196,23 +196,23 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down

0 comments on commit e0ec4fd

Please sign in to comment.