selinux collector: Added AVC statistics collection.

Signed-off-by: Jonathan Davies <jpds@protonmail.com>
This commit is contained in:
Jonathan Davies 2022-06-30 19:50:38 +01:00
parent 69a3f73a9b
commit 03d9c9fe27
4 changed files with 363 additions and 4 deletions

139
collector/avcstat_linux.go Normal file
View file

@ -0,0 +1,139 @@
// Copyright 2022 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux && !noselinux
// +build linux,!noselinux
package collector
import (
"bufio"
"fmt"
"io"
"os"
"regexp"
"strconv"
"strings"
)
func getAVCStats(path string) (avcStats map[string]uint64, err error) {
file, err := os.Open(sysFilePath(path))
if err != nil {
return nil, err
}
defer file.Close()
return parseAVCStats(file)
}
func getAVCHashStats(path string) (avcStats map[string]uint64, err error) {
file, err := os.Open(sysFilePath(path))
if err != nil {
return nil, err
}
defer file.Close()
return parseAVCHashStats(file)
}
func parseAVCStats(r io.Reader) (stats map[string]uint64, err error) {
avcValuesRE := regexp.MustCompile(`\d+`)
stats = make(map[string]uint64)
scanner := bufio.NewScanner(r)
scanner.Scan() // Skip header
for scanner.Scan() {
avcValues := avcValuesRE.FindAllString(scanner.Text(), -1)
if len(avcValues) != 6 { //
return nil, fmt.Errorf("invalid AVC stat line: %s",
scanner.Text())
}
lookups, err := strconv.ParseUint(avcValues[0], 0, 64)
if err != nil {
return nil, fmt.Errorf("could not parse expected integer value for lookups")
}
hits, err := strconv.ParseUint(avcValues[1], 0, 64)
if err != nil {
return nil, fmt.Errorf("could not parse expected integer value for hits")
}
misses, err := strconv.ParseUint(avcValues[2], 0, 64)
if err != nil {
return nil, fmt.Errorf("could not parse expected integer value for misses")
}
allocations, err := strconv.ParseUint(avcValues[3], 0, 64)
if err != nil {
return nil, fmt.Errorf("could not parse expected integer value for allocations")
}
reclaims, err := strconv.ParseUint(avcValues[4], 0, 64)
if err != nil {
return nil, fmt.Errorf("could not parse expected integer value for reclaims")
}
frees, err := strconv.ParseUint(avcValues[5], 0, 64)
if err != nil {
return nil, fmt.Errorf("could not parse expected integer value for frees")
}
stats["lookups"] += lookups
stats["hits"] += hits
stats["misses"] += misses
stats["allocations"] += allocations
stats["reclaims"] += reclaims
stats["frees"] += frees
}
return stats, err
}
func parseAVCHashStats(r io.Reader) (stats map[string]uint64, err error) {
stats = make(map[string]uint64)
scanner := bufio.NewScanner(r)
scanner.Scan()
entriesValue := strings.TrimPrefix(scanner.Text(), "entries: ")
scanner.Scan()
bucketsValues := strings.Split(scanner.Text(), "buckets used: ")
bucketsValuesTuple := strings.Split(bucketsValues[1], "/")
scanner.Scan()
longestChainValue := strings.TrimPrefix(scanner.Text(), "longest chain: ")
stats["entries"], err = strconv.ParseUint(entriesValue, 0, 64)
if err != nil {
return nil, fmt.Errorf("could not parse expected integer value for hash entries")
}
stats["buckets_used"], err = strconv.ParseUint(bucketsValuesTuple[0], 0, 64)
if err != nil {
return nil, fmt.Errorf("could not parse expected integer value for hash buckets used")
}
stats["buckets_available"], err = strconv.ParseUint(bucketsValuesTuple[1], 0, 64)
if err != nil {
return nil, fmt.Errorf("could not parse expected integer value for hash buckets available")
}
stats["longest_chain"], err = strconv.ParseUint(longestChainValue, 0, 64)
if err != nil {
return nil, fmt.Errorf("could not parse expected integer value for hash longest chain")
}
return stats, err
}

View file

@ -0,0 +1,72 @@
// Copyright 2022 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package collector
import (
"testing"
)
func TestAVCStat(t *testing.T) {
avcStats, err := getAVCStats("fixtures/sys/fs/selinux/avc/cache_stats")
if err != nil {
t.Fatal(err)
}
if want, got := uint64(91590784), avcStats["lookups"]; want != got {
t.Errorf("want avcstat lookups %v, got %v", want, got)
}
if want, got := uint64(91569452), avcStats["hits"]; want != got {
t.Errorf("want avcstat hits %v, got %v", want, got)
}
if want, got := uint64(21332), avcStats["misses"]; want != got {
t.Errorf("want avcstat misses %v, got %v", want, got)
}
if want, got := uint64(21332), avcStats["allocations"]; want != got {
t.Errorf("want avcstat allocations %v, got %v", want, got)
}
if want, got := uint64(20400), avcStats["reclaims"]; want != got {
t.Errorf("want avcstat reclaims %v, got %v", want, got)
}
if want, got := uint64(20826), avcStats["frees"]; want != got {
t.Errorf("want avcstat frees %v, got %v", want, got)
}
}
func TestAVCHashStat(t *testing.T) {
avcHashStats, err := getAVCHashStats("fixtures/sys/fs/selinux/avc/hash_stats")
if err != nil {
t.Fatal(err)
}
if want, got := uint64(503), avcHashStats["entries"]; want != got {
t.Errorf("want avc hash stat entries %v, got %v", want, got)
}
if want, got := uint64(512), avcHashStats["buckets_available"]; want != got {
t.Errorf("want avc hash stat buckets available %v, got %v", want, got)
}
if want, got := uint64(257), avcHashStats["buckets_used"]; want != got {
t.Errorf("want avc hash stat buckets used %v, got %v", want, got)
}
if want, got := uint64(8), avcHashStats["longest_chain"]; want != got {
t.Errorf("want avc hash stat longest chain %v, got %v", want, got)
}
}

View file

@ -4627,6 +4627,37 @@ Lines: 1
4096
Mode: 444
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: sys/fs/selinux/avc
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: sys/fs/selinux/avc/cache_stats
Lines: 17
lookups hits misses allocations reclaims frees
6603240 6601784 1456 1456 1424 1479
5110559 5109586 973 973 1152 1162
6720426 6719001 1425 1425 1488 1515
4713015 4711743 1272 1272 1200 1211
6949114 6947453 1661 1661 1504 1555
5624578 5622931 1647 1647 1328 1347
5884601 5882982 1619 1619 1488 1525
3170931 3170061 870 870 832 858
7336127 7334467 1660 1660 1632 1644
2853424 2852652 772 772 704 712
7596237 7594408 1829 1829 1936 1960
4491113 4490186 927 927 832 854
7319238 7317738 1500 1500 1504 1538
4945119 4944151 968 968 912 943
7610170 7608283 1887 1887 1648 1695
4662892 4662026 866 866 816 828
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: sys/fs/selinux/avc/hash_stats
Lines: 3
entries: 503
buckets used: 257/512
longest chain: 8
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: sys/fs/xfs
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View file

@ -23,10 +23,21 @@ import (
)
type selinuxCollector struct {
configMode *prometheus.Desc
currentMode *prometheus.Desc
enabled *prometheus.Desc
logger log.Logger
avcAllocations *prometheus.Desc
avcFrees *prometheus.Desc
avcHashBucketsAvailable *prometheus.Desc
avcHashBucketsUsed *prometheus.Desc
avcHashEntries *prometheus.Desc
avcHashLongestChain *prometheus.Desc
avcHits *prometheus.Desc
avcMisses *prometheus.Desc
avcLookups *prometheus.Desc
avcReclaims *prometheus.Desc
avcThreshold *prometheus.Desc
configMode *prometheus.Desc
currentMode *prometheus.Desc
enabled *prometheus.Desc
logger log.Logger
}
func init() {
@ -53,6 +64,61 @@ func NewSelinuxCollector(logger log.Logger) (Collector, error) {
"SELinux is enabled, 1 is true, 0 is false",
nil, nil,
),
avcAllocations: prometheus.NewDesc(
prometheus.BuildFQName(namespace, subsystem, "avc_allocations_total"),
"SELinux AVC allocations",
nil, nil,
),
avcFrees: prometheus.NewDesc(
prometheus.BuildFQName(namespace, subsystem, "avc_frees_total"),
"SELinux AVC frees",
nil, nil,
),
avcHashBucketsAvailable: prometheus.NewDesc(
prometheus.BuildFQName(namespace, subsystem, "avc_hash_buckets_available"),
"SELinux AVC hash buckets available",
nil, nil,
),
avcHashBucketsUsed: prometheus.NewDesc(
prometheus.BuildFQName(namespace, subsystem, "avc_hash_buckets_used"),
"SELinux AVC hash buckets used",
nil, nil,
),
avcHashEntries: prometheus.NewDesc(
prometheus.BuildFQName(namespace, subsystem, "avc_hash_entries"),
"SELinux AVC hash entries",
nil, nil,
),
avcHashLongestChain: prometheus.NewDesc(
prometheus.BuildFQName(namespace, subsystem, "avc_hash_longest_chain"),
"SELinux AVC hash longest chain",
nil, nil,
),
avcHits: prometheus.NewDesc(
prometheus.BuildFQName(namespace, subsystem, "avc_hits_total"),
"SELinux AVC hits",
nil, nil,
),
avcMisses: prometheus.NewDesc(
prometheus.BuildFQName(namespace, subsystem, "avc_misses_total"),
"SELinux AVC misses",
nil, nil,
),
avcLookups: prometheus.NewDesc(
prometheus.BuildFQName(namespace, subsystem, "avc_lookups_total"),
"SELinux AVC lookups",
nil, nil,
),
avcReclaims: prometheus.NewDesc(
prometheus.BuildFQName(namespace, subsystem, "avc_reclaims_total"),
"SELinux AVC reclaims",
nil, nil,
),
avcThreshold: prometheus.NewDesc(
prometheus.BuildFQName(namespace, subsystem, "avc_threshold"),
"SELinux AVC threshold",
nil, nil,
),
logger: logger,
}, nil
}
@ -74,5 +140,56 @@ func (c *selinuxCollector) Update(ch chan<- prometheus.Metric) error {
ch <- prometheus.MustNewConstMetric(
c.currentMode, prometheus.GaugeValue, float64(selinux.EnforceMode()))
avcStats, err := getAVCStats("fs/selinux/avc/cache_stats")
if err != nil {
return err
}
ch <- prometheus.MustNewConstMetric(
c.avcLookups, prometheus.CounterValue, float64(avcStats["lookups"]))
ch <- prometheus.MustNewConstMetric(
c.avcHits, prometheus.CounterValue, float64(avcStats["hits"]))
ch <- prometheus.MustNewConstMetric(
c.avcMisses, prometheus.CounterValue, float64(avcStats["misses"]))
ch <- prometheus.MustNewConstMetric(
c.avcAllocations, prometheus.CounterValue, float64(avcStats["allocations"]))
ch <- prometheus.MustNewConstMetric(
c.avcReclaims, prometheus.CounterValue, float64(avcStats["reclaims"]))
ch <- prometheus.MustNewConstMetric(
c.avcFrees, prometheus.CounterValue, float64(avcStats["frees"]))
avcHashStats, err := getAVCHashStats("fs/selinux/avc/hash_stats")
if err != nil {
return err
}
ch <- prometheus.MustNewConstMetric(
c.avcHashEntries, prometheus.GaugeValue, float64(avcHashStats["entries"]))
ch <- prometheus.MustNewConstMetric(
c.avcHashBucketsAvailable, prometheus.GaugeValue, float64(avcHashStats["buckets_available"]))
ch <- prometheus.MustNewConstMetric(
c.avcHashBucketsUsed, prometheus.GaugeValue, float64(avcHashStats["buckets_used"]))
ch <- prometheus.MustNewConstMetric(
c.avcHashLongestChain, prometheus.GaugeValue, float64(avcHashStats["longest_chain"]))
avcThreshold, err := readUintFromFile(sysFilePath("fs/selinux/avc/cache_threshold"))
if err != nil {
return err
}
ch <- prometheus.MustNewConstMetric(
c.avcThreshold, prometheus.GaugeValue, float64(avcThreshold))
return nil
}