diff --git a/pkg/util/metrics/system/common.go b/pkg/util/metrics/system/common.go new file mode 100644 index 00000000..77a6125d --- /dev/null +++ b/pkg/util/metrics/system/common.go @@ -0,0 +1,38 @@ +/* +Copyright 2020 The Kubernetes Authors All rights reserved. +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 system + +import ( + "bufio" + "os" +) + +// ReadFile reads contents from a file and returns lines. +func ReadFileIntoLines(filename string) ([]string, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + defer file.Close() + + var result []string + s := bufio.NewScanner(file) + for s.Scan() { + result = append(result, s.Text()) + } + if s.Err() != nil { + return nil, err + } + return result, nil +} diff --git a/pkg/util/metrics/system/module_stats.go b/pkg/util/metrics/system/module_stats.go new file mode 100644 index 00000000..5b5da12a --- /dev/null +++ b/pkg/util/metrics/system/module_stats.go @@ -0,0 +1,81 @@ +/* +Copyright 2020 The Kubernetes Authors All rights reserved. +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 system + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" +) + +var modulesFilePath = "/proc/modules" + +type Module struct { + ModuleName string `json:"moduleName"` + Instances uint64 `json:"instances"` + Proprietary bool `json:"proprietary"` + OutOfTree bool `json:"outOfTree"` + Unsigned bool `json:"unsigned"` +} + +func (d Module) String() string { + s, _ := json.Marshal(d) + return string(s) +} + +// Module returns all the kernel modules and their +// usage. It is read from cat /proc/modules. +func Modules() ([]Module, error) { + lines, err := ReadFileIntoLines(modulesFilePath) + if err != nil { + return nil, fmt.Errorf("error reading the contents of %s: %s", modulesFilePath, err) + } + var result = make([]Module, 0, len(lines)) + + /* a line of /proc/modules has the following structure + nf_nat 61440 2 xt_MASQUERADE,iptable_nat, Live 0x0000000000000000 (O) + (1) (2) (3) (4) (5) (6) (7) + (1) name of the module + (2) memory size of the module, in bytes + (3) instances of the module are currently loaded + (4) module dependencies + (5) load state of the module: live, loading or unloading + (6) memory offset for the loaded module. + (7) return a string to represent the kernel taint state. (used here: "P" - Proprietary, "O" - out of tree kernel module, "E" - unsigned module + */ + for _, line := range lines { + fields := strings.Fields(line) + moduleName := fields[0] // name of the module + numberOfInstances, err := + strconv.ParseUint((fields[2]), 10, 64) // instances of the module are currently loaded + if err != nil { + numberOfInstances = 0 + } + + var module = Module{ + ModuleName: moduleName, + Instances: numberOfInstances, + } + // if the len of the fields is greater than 6, then the kernel taint state is available. + if len(fields) > 6 { + module.Proprietary = strings.Contains(fields[6], "P") + module.OutOfTree = strings.Contains(fields[6], "O") + module.Unsigned = strings.Contains(fields[6], "E") + } + + result = append(result, module) + } + return result, nil +} diff --git a/pkg/util/metrics/system/module_stats_test.go b/pkg/util/metrics/system/module_stats_test.go new file mode 100644 index 00000000..b80f0088 --- /dev/null +++ b/pkg/util/metrics/system/module_stats_test.go @@ -0,0 +1,126 @@ +/* +Copyright 2019 The Kubernetes Authors All rights reserved. +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 system + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestModules(t *testing.T) { + testcases := []struct { + name string + fakeModuleFilePath string + expectedModules []Module + }{ + { + name: "default_cos", + fakeModuleFilePath: "testdata/modules_cos.txt", + expectedModules: []Module{ + { + ModuleName: "crypto_simd", + Instances: 0x1, + Proprietary: false, + OutOfTree: false, + Unsigned: false, + }, + { + ModuleName: "virtio_balloon", + Instances: 0x0, + Proprietary: false, + OutOfTree: false, + Unsigned: false, + }, + { + ModuleName: "cryptd", + Instances: 0x1, + Proprietary: false, + OutOfTree: false, + Unsigned: false, + }, + { + ModuleName: "loadpin_trigger", + Instances: 0x0, + Proprietary: false, + OutOfTree: true, + Unsigned: false, + }, + }, + }, + { + name: "default_ubuntu", + fakeModuleFilePath: "testdata/modules_ubuntu.txt", + expectedModules: []Module{ + { + ModuleName: "drm", + Instances: 0x0, + Proprietary: false, + OutOfTree: false, + Unsigned: false, + }, + { + ModuleName: "virtio_rng", + Instances: 0x0, + Proprietary: false, + OutOfTree: false, + Unsigned: false, + }, + { + ModuleName: "x_tables", + Instances: 0x1, + Proprietary: false, + OutOfTree: false, + Unsigned: false, + }, + { + ModuleName: "autofs4", + Instances: 0x2, + Proprietary: false, + OutOfTree: false, + Unsigned: false, + }, + }, + }, + } + for _, test := range testcases { + t.Run(test.name, func(t *testing.T) { + originalModuleFilePath := modulesFilePath + defer func() { + modulesFilePath = originalModuleFilePath + }() + + modulesFilePath = test.fakeModuleFilePath + modules, err := Modules() + if err != nil { + t.Errorf("Unexpected error retrieving modules: %v\nModulesFilePath: %s\n", err, modulesFilePath) + } + assert.Equal(t, modules, test.expectedModules, "unpected modules retrieved: %v, expected: %v", modules, test.expectedModules) + }) + } +} + +func TestModuleStat_String(t *testing.T) { + v := Module{ + ModuleName: "test", + Instances: 2, + OutOfTree: false, + Unsigned: false, + } + e := `{"moduleName":"test","instances":2,"proprietary":false,"outOfTree":false,"unsigned":false}` + assert.Equal(t, + e, fmt.Sprintf("%v", v), "Module string is invalid: %v", v) + +} diff --git a/pkg/util/metrics/system/testdata/modules_cos.txt b/pkg/util/metrics/system/testdata/modules_cos.txt new file mode 100644 index 00000000..535ce478 --- /dev/null +++ b/pkg/util/metrics/system/testdata/modules_cos.txt @@ -0,0 +1,4 @@ +crypto_simd 16384 1 aesni_intel, Live 0x0000000000000000 +virtio_balloon 24576 0 - Live 0x0000000000000000 +cryptd 24576 1 crypto_simd, Live 0x0000000000000000 +loadpin_trigger 12288 0 [permanent], Live 0x0000000000000000 (O) \ No newline at end of file diff --git a/pkg/util/metrics/system/testdata/modules_ubuntu.txt b/pkg/util/metrics/system/testdata/modules_ubuntu.txt new file mode 100644 index 00000000..de05e3a7 --- /dev/null +++ b/pkg/util/metrics/system/testdata/modules_ubuntu.txt @@ -0,0 +1,4 @@ +drm 491520 0 - Live 0x0000000000000000 +virtio_rng 16384 0 - Live 0x0000000000000000 +x_tables 40960 1 ip_tables, Live 0x0000000000000000 +autofs4 45056 2 - Live 0x0000000000000000 \ No newline at end of file