-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: naoki-take <[email protected]>
- Loading branch information
Showing
18 changed files
with
714 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
* | ||
!src |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# bpf-map-pressure-exporter container | ||
|
||
# Stage1: build from source | ||
FROM quay.io/cybozu/golang:1.21-jammy AS build | ||
COPY src /work/src | ||
WORKDIR /work/src | ||
RUN CGO_ENABLED=0 go build -ldflags="-w -s" -o bpf-map-pressure-exporter | ||
|
||
# Stage2: setup runtime container | ||
FROM scratch | ||
COPY --from=build /work/src/bpf-map-pressure-exporter / | ||
EXPOSE 8080/tcp | ||
ENTRYPOINT ["/bpf-map-pressure-exporter"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
bpf-map-pressure-exporter | ||
=================== | ||
|
||
`bpf-map-pressure-exporter` exposes BPF map pressure. | ||
|
||
## Config | ||
Default config file is `/etc/bpf-map-pressure-exporter/config.yaml`. | ||
The target BPF maps should be specified under `mapNames`. | ||
``` | ||
mapNames: | ||
- cilium_ct | ||
- ... | ||
``` | ||
|
||
`mapNames` are interpreted as substrings of the map names and the pressure metrics for all maps including the substring are exposed. | ||
If multiple `mapNames` are specified and some of them match the same map, only 1 metric is exposed. | ||
Note that BPF map names are truncated to 15 characters. | ||
|
||
Command-line options are: | ||
|
||
| Option | Default value | Description | | ||
| -------------- | ----------------------------------------------- | ------------------------------------ | | ||
| `port` | `8080` | port number to export metrics | | ||
| `config` | `/etc/bpf-map-pressure-exporter/config.yaml` | config file path | | ||
|
||
API endpoints are: | ||
|
||
| Path | Description | | ||
| -------- | --------------------------- | | ||
| /health | the path for liveness probe | | ||
| /metrics | exporting metrics | | ||
|
||
## Prometheus metrics | ||
|
||
`bpf-map-pressure-exporter` exposes the following metrics. | ||
|
||
### `bpf_map_pressure` | ||
|
||
`bpf_map_pressure` is a gauge that indicates the BPF map pressure. | ||
|
||
| Label | Description | | ||
| ---------- | -------------------------- | | ||
| `map_id` | ID of the BPF map | | ||
| `map_name` | name of the BPF map | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
0.1.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
SUDO = sudo | ||
|
||
.PHONY: all | ||
all: check-generate test | ||
|
||
.PHONY: check-generate | ||
check-generate: | ||
go mod tidy | ||
git diff --exit-code --name-only | ||
|
||
.PHONY: test | ||
test: | ||
test -z "$$(gofmt -s -l . | tee /dev/stderr)" | ||
staticcheck ./... | ||
test -z "$$(custom-checker -restrictpkg.packages=html/template,log ./... 2>&1 | tee /dev/stderr)" | ||
go vet ./... | ||
go test -c ./... | ||
$(SUDO) ./bpf-map-pressure-exporter.test -test.v | ||
rm -f ./bpf-map-pressure-exporter.test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package main | ||
|
||
import ( | ||
"strconv" | ||
|
||
"github.com/prometheus/client_golang/prometheus" | ||
) | ||
|
||
type bpfMapPressureCollector struct { | ||
describe *prometheus.Desc | ||
fetcher IBpfMapPressureFetcher | ||
} | ||
|
||
func newCollector(fetcher IBpfMapPressureFetcher) *bpfMapPressureCollector { | ||
return &bpfMapPressureCollector{ | ||
describe: prometheus.NewDesc( | ||
"bpf_map_pressure", | ||
"bpf map pressure", | ||
[]string{"map_id", "map_name"}, | ||
nil, | ||
), | ||
fetcher: fetcher, | ||
} | ||
} | ||
|
||
func (c *bpfMapPressureCollector) Describe(ch chan<- *prometheus.Desc) { | ||
ch <- c.describe | ||
} | ||
|
||
func (c *bpfMapPressureCollector) Collect(ch chan<- prometheus.Metric) { | ||
results := c.fetcher.Fetch() | ||
for _, result := range results { | ||
ch <- prometheus.MustNewConstMetric( | ||
c.describe, | ||
prometheus.GaugeValue, | ||
result.mapPressure, | ||
strconv.FormatUint(uint64(result.mapId), 10), result.mapName, | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package main | ||
|
||
import ( | ||
"strings" | ||
"testing" | ||
|
||
"github.com/prometheus/client_golang/prometheus/testutil" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
type mockBpfMapPressureFetcher struct { | ||
fetchFunc func() []bpfMapPressure | ||
} | ||
|
||
func (f *mockBpfMapPressureFetcher) Fetch() []bpfMapPressure { | ||
return f.fetchFunc() | ||
} | ||
|
||
func TestBpfMapPressureCollector(t *testing.T) { | ||
cases := []struct { | ||
name string | ||
fetcher IBpfMapPressureFetcher | ||
expect string | ||
}{ | ||
{ | ||
name: "success", | ||
fetcher: &mockBpfMapPressureFetcher{ | ||
fetchFunc: func() []bpfMapPressure { | ||
return []bpfMapPressure{ | ||
{ | ||
mapId: 1, | ||
mapName: "cilium_test_1", | ||
mapPressure: 0.1, | ||
}, | ||
{ | ||
mapId: 2, | ||
mapName: "cilium_test_2", | ||
mapPressure: 0.2, | ||
}, | ||
} | ||
}, | ||
}, | ||
expect: `# HELP bpf_map_pressure bpf map pressure | ||
# TYPE bpf_map_pressure gauge | ||
bpf_map_pressure{map_id="1",map_name="cilium_test_1"} 0.1 | ||
bpf_map_pressure{map_id="2",map_name="cilium_test_2"} 0.2 | ||
`, | ||
}, | ||
{ | ||
name: "duplicate maps", | ||
fetcher: &mockBpfMapPressureFetcher{ | ||
fetchFunc: func() []bpfMapPressure { | ||
return []bpfMapPressure{ | ||
{ | ||
mapId: 1, | ||
mapName: "cilium_test_1", | ||
mapPressure: 0.1, | ||
}, | ||
{ | ||
mapId: 1, | ||
mapName: "cilium_test_2", | ||
mapPressure: 0.1, | ||
}, | ||
} | ||
}, | ||
}, | ||
expect: `# HELP bpf_map_pressure bpf map pressure | ||
# TYPE bpf_map_pressure gauge | ||
bpf_map_pressure{map_id="1",map_name="cilium_test_1"} 0.1 | ||
bpf_map_pressure{map_id="1",map_name="cilium_test_1"} 0.1 | ||
`, | ||
}, | ||
{ | ||
name: "no maps", | ||
fetcher: &mockBpfMapPressureFetcher{ | ||
fetchFunc: func() []bpfMapPressure { | ||
return []bpfMapPressure{} | ||
}, | ||
}, | ||
expect: `# HELP bpf_map_pressure bpf map pressure | ||
# TYPE bpf_map_pressure gauge | ||
`, | ||
}, | ||
} | ||
|
||
for _, tc := range cases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
err := testutil.CollectAndCompare(newCollector(tc.fetcher), strings.NewReader(tc.expect), tc.name) | ||
assert.NoError(t, err) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package main | ||
|
||
import ( | ||
"os" | ||
|
||
"github.com/go-yaml/yaml" | ||
) | ||
|
||
const ( | ||
DefaultConfigPath = "/etc/bpf-map-pressure-exporter/config.yaml" | ||
) | ||
|
||
type Config struct { | ||
MapNames []string `yaml:"mapNames"` | ||
} | ||
|
||
func loadConfig(path string) (*Config, error) { | ||
if path == "" { | ||
path = DefaultConfigPath | ||
} | ||
f, err := os.Open(path) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer f.Close() | ||
var cfg Config | ||
if err := yaml.NewDecoder(f).Decode(&cfg); err != nil { | ||
return nil, err | ||
} | ||
return &cfg, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package main | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestLoadConfig(t *testing.T) { | ||
cases := []struct { | ||
name string | ||
path string | ||
expect *Config | ||
expectError bool | ||
}{ | ||
{ | ||
name: "success", | ||
path: "testdata/config.yaml", | ||
expect: &Config{ | ||
MapNames: []string{"hoge", "fuga", "piyo"}, | ||
}, | ||
}, | ||
{ | ||
name: "file not found", | ||
path: "testdata/notfound.yaml", | ||
expectError: true, | ||
}, | ||
{ | ||
name: "invalid yaml", | ||
path: "testdata/invalid.yaml", | ||
expectError: true, | ||
}, | ||
} | ||
|
||
for _, tc := range cases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
cfg, err := loadConfig(tc.path) | ||
if tc.expectError { | ||
assert.Error(t, err) | ||
return | ||
} | ||
assert.NoError(t, err) | ||
assert.Equal(t, tc.expect.MapNames, cfg.MapNames) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"os" | ||
"strings" | ||
|
||
"github.com/cilium/ebpf" | ||
"github.com/cybozu-go/log" | ||
) | ||
|
||
type IBpfMapPressureFetcher interface { | ||
Fetch() []bpfMapPressure | ||
} | ||
|
||
type bpfMapPressureFetcher struct { | ||
mapNameStrings []string | ||
} | ||
|
||
type bpfMapPressure struct { | ||
mapId uint32 | ||
mapName string | ||
mapPressure float64 | ||
} | ||
|
||
func newFetcher(mapNameStrings []string) *bpfMapPressureFetcher { | ||
return &bpfMapPressureFetcher{ | ||
mapNameStrings: mapNameStrings, | ||
} | ||
} | ||
|
||
func (f *bpfMapPressureFetcher) Fetch() []bpfMapPressure { | ||
results := []bpfMapPressure{} | ||
var id ebpf.MapID = 0 | ||
var err error | ||
for { | ||
id, err = ebpf.MapGetNextID(id) | ||
if errors.Is(err, os.ErrNotExist) { | ||
break | ||
} | ||
if err != nil { | ||
_ = logger.Warn("failed to get next map id", map[string]interface{}{ | ||
"id": id, | ||
log.FnError: err, | ||
}) | ||
return nil | ||
} | ||
m, err := ebpf.NewMapFromID(id) | ||
if err != nil { | ||
_ = logger.Warn("failed to get map", map[string]interface{}{ | ||
"id": id, | ||
log.FnError: err, | ||
}) | ||
return nil | ||
} | ||
info, err := m.Info() | ||
if err != nil { | ||
_ = logger.Warn("failed to get map info", map[string]interface{}{ | ||
"id": id, | ||
log.FnError: err, | ||
}) | ||
return nil | ||
} | ||
for _, str := range f.mapNameStrings { | ||
if strings.Contains(info.Name, str) { | ||
itr := m.Iterate() | ||
var key, value []byte | ||
cnt := 0 | ||
for itr.Next(&key, &value) { | ||
cnt++ | ||
} | ||
results = append(results, bpfMapPressure{ | ||
mapId: uint32(id), | ||
mapName: info.Name, | ||
mapPressure: float64(cnt) / float64(info.MaxEntries), | ||
}) | ||
break | ||
} | ||
} | ||
} | ||
return results | ||
} |
Oops, something went wrong.