diff --git a/.gitignore b/.gitignore index 99537de4..4b94779d 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ cadvisor # Files generated by JetBrains IDEs, e.g. IntelliJ IDEA .idea/ *.iml +*.swp diff --git a/go.mod b/go.mod index f536d991..0e592377 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( github.com/influxdb/influxdb v0.9.6-0.20151125225445-9eab56311373 github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 // indirect github.com/karrick/godirwalk v1.7.5 - github.com/kevinburke/go-bindata v3.17.0+incompatible // indirect + github.com/kevinburke/go-bindata v3.18.0+incompatible // indirect github.com/klauspost/crc32 v0.0.0-20151223135126-a3b15ae34567 // indirect github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/kr/pretty v0.0.0-20140723054909-088c856450c0 diff --git a/go.sum b/go.sum index 9bd005ac..9c1aa5e2 100644 --- a/go.sum +++ b/go.sum @@ -167,6 +167,8 @@ github.com/kevinburke/go-bindata v3.16.0+incompatible h1:TFzFZop2KxGhqNwsyjgmIh5 github.com/kevinburke/go-bindata v3.16.0+incompatible/go.mod h1:/pEEZ72flUW2p0yi30bslSp9YqD9pysLxunQDdb2CPM= github.com/kevinburke/go-bindata v3.17.0+incompatible h1:eXG58bFD2FVMYilP6PfjjSwKy4YTHmifsI8TURmlVVg= github.com/kevinburke/go-bindata v3.17.0+incompatible/go.mod h1:/pEEZ72flUW2p0yi30bslSp9YqD9pysLxunQDdb2CPM= +github.com/kevinburke/go-bindata v3.18.0+incompatible h1:NfOP49jFW7KyBl7UwTg0xkhSfHjESEwe2VMrcnSHG20= +github.com/kevinburke/go-bindata v3.18.0+incompatible/go.mod h1:/pEEZ72flUW2p0yi30bslSp9YqD9pysLxunQDdb2CPM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= diff --git a/info/v1/machine.go b/info/v1/machine.go index cef4c423..3af5d15f 100644 --- a/info/v1/machine.go +++ b/info/v1/machine.go @@ -173,6 +173,9 @@ type MachineInfo struct { // The amount of memory (in bytes) in this machine MemoryCapacity uint64 `json:"memory_capacity"` + // Memory capacity and number of DIMMs by memory type + MemoryByType map[string]*MemoryInfo `json:"memory_by_type"` + // HugePages on this machine. HugePages []HugePagesInfo `json:"hugepages"` @@ -208,6 +211,14 @@ type MachineInfo struct { InstanceID InstanceID `json:"instance_id"` } +type MemoryInfo struct { + // The amount of memory (in bytes). + Capacity uint64 + + // Number of memory DIMMs. + DimmCount uint +} + type VersionInfo struct { // Kernel version. KernelVersion string `json:"kernel_version"` diff --git a/machine/info.go b/machine/info.go index d5350f97..23234939 100644 --- a/machine/info.go +++ b/machine/info.go @@ -34,6 +34,7 @@ import ( ) const hugepagesDirectory = "/sys/kernel/mm/hugepages/" +const memoryControllerPath = "/sys/devices/system/edac/mc/" var machineIdFilePath = flag.String("machine_id_file", "/etc/machine-id,/var/lib/dbus/machine-id", "Comma-separated list of files to check for machine-id. Use the first one that exists.") var bootIdFilePath = flag.String("boot_id_file", "/proc/sys/kernel/random/boot_id", "Comma-separated list of files to check for boot-id. Use the first one that exists.") @@ -72,6 +73,11 @@ func Info(sysFs sysfs.SysFs, fsInfo fs.FsInfo, inHostNamespace bool) (*info.Mach return nil, err } + memoryByType, err := GetMachineMemoryByType(memoryControllerPath) + if err != nil { + return nil, err + } + hugePagesInfo, err := GetHugePagesInfo(hugepagesDirectory) if err != nil { return nil, err @@ -113,6 +119,7 @@ func Info(sysFs sysfs.SysFs, fsInfo fs.FsInfo, inHostNamespace bool) (*info.Mach NumSockets: GetSockets(cpuinfo), CpuFrequency: clockSpeed, MemoryCapacity: memoryCapacity, + MemoryByType: memoryByType, HugePages: hugePagesInfo, DiskMap: diskMap, NetworkDevices: netDevices, diff --git a/machine/machine.go b/machine/machine.go index 2478499d..40daf763 100644 --- a/machine/machine.go +++ b/machine/machine.go @@ -19,6 +19,8 @@ import ( "bytes" "fmt" "io/ioutil" + "os" + "path" "path/filepath" "regexp" "strconv" @@ -47,7 +49,9 @@ var ( memoryCapacityRegexp = regexp.MustCompile(`MemTotal:\s*([0-9]+) kB`) swapCapacityRegexp = regexp.MustCompile(`SwapTotal:\s*([0-9]+) kB`) - cpuBusPath = "/sys/bus/cpu/devices/" + cpuBusPath = "/sys/bus/cpu/devices/" + isMemoryController = regexp.MustCompile("mc[0-9]+") + isDimm = regexp.MustCompile("dimm[0-9]+") ) const maxFreqFile = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq" @@ -55,6 +59,8 @@ const nodePath = "/sys/devices/system/node" const sysFsCPUCoreID = "core_id" const sysFsCPUPhysicalPackageID = "physical_package_id" const sysFsCPUTopology = "topology" +const memTypeFileName = "dimm_mem_type" +const sizeFileName = "size" // GetPhysicalCores returns number of CPU cores reading /proc/cpuinfo file or if needed information from sysfs cpu path func GetPhysicalCores(procInfo []byte) int { @@ -133,6 +139,65 @@ func GetMachineMemoryCapacity() (uint64, error) { return memoryCapacity, err } +// GetMachineMemoryByType returns information about memory capcity and number of DIMMs. +// Information is retrieved from sysfs edac per-DIMM API (/sys/devices/system/edac/mc/) +// introduced in kernel 3.6. Documentation can be found at +// https://www.kernel.org/doc/Documentation/admin-guide/ras.rst. +// Full list of memory types can be found in edac_mc.c +// (https://github.com/torvalds/linux/blob/v5.5/drivers/edac/edac_mc.c#L198) +func GetMachineMemoryByType(edacPath string) (map[string]*info.MemoryInfo, error) { + memory := map[string]*info.MemoryInfo{} + names, err := ioutil.ReadDir(edacPath) + // On some architectures (such as ARM) memory controller device may not exist. + // If this is the case then we ignore error and return empty slice. + _, ok := err.(*os.PathError) + if err != nil && ok { + return memory, nil + } else if err != nil { + return memory, err + } + for _, controllerDir := range names { + controller := controllerDir.Name() + if !isMemoryController.MatchString(controller) { + continue + } + dimms, err := ioutil.ReadDir(path.Join(edacPath, controllerDir.Name())) + if err != nil { + return map[string]*info.MemoryInfo{}, err + } + for _, dimmDir := range dimms { + dimm := dimmDir.Name() + if !isDimm.MatchString(dimm) { + continue + } + memType, err := ioutil.ReadFile(path.Join(edacPath, controller, dimm, memTypeFileName)) + readableMemType := strings.TrimSpace(string(memType)) + if err != nil { + return map[string]*info.MemoryInfo{}, err + } + if _, exists := memory[readableMemType]; !exists { + memory[readableMemType] = &info.MemoryInfo{} + } + size, err := ioutil.ReadFile(path.Join(edacPath, controller, dimm, sizeFileName)) + if err != nil { + return map[string]*info.MemoryInfo{}, err + } + capacity, err := strconv.Atoi(strings.TrimSpace(string(size))) + if err != nil { + return map[string]*info.MemoryInfo{}, err + } + memory[readableMemType].Capacity += uint64(mbToBytes(capacity)) + memory[readableMemType].DimmCount++ + } + } + + return memory, nil +} + +func mbToBytes(megabytes int) int { + return megabytes * 1024 * 1024 +} + // GetMachineSwapCapacity returns the machine's total swap from /proc/meminfo. // Returns the total swap capacity as an uint64 (number of bytes). func GetMachineSwapCapacity() (uint64, error) { diff --git a/machine/testdata/edac/mc/mc0/dimm0/dimm_mem_type b/machine/testdata/edac/mc/mc0/dimm0/dimm_mem_type new file mode 100644 index 00000000..c13a3cae --- /dev/null +++ b/machine/testdata/edac/mc/mc0/dimm0/dimm_mem_type @@ -0,0 +1 @@ +Unbuffered-DDR4 diff --git a/machine/testdata/edac/mc/mc0/dimm0/size b/machine/testdata/edac/mc/mc0/dimm0/size new file mode 100644 index 00000000..0c2b7810 --- /dev/null +++ b/machine/testdata/edac/mc/mc0/dimm0/size @@ -0,0 +1 @@ +789 diff --git a/machine/testdata/edac/mc/mc0/dimm1/dimm_mem_type b/machine/testdata/edac/mc/mc0/dimm1/dimm_mem_type new file mode 100644 index 00000000..da2d3363 --- /dev/null +++ b/machine/testdata/edac/mc/mc0/dimm1/dimm_mem_type @@ -0,0 +1 @@ +Non-volatile-RAM diff --git a/machine/testdata/edac/mc/mc0/dimm1/size b/machine/testdata/edac/mc/mc0/dimm1/size new file mode 100644 index 00000000..8d38505c --- /dev/null +++ b/machine/testdata/edac/mc/mc0/dimm1/size @@ -0,0 +1 @@ +456 diff --git a/machine/testdata/edac/mc/mc0/dimm_is_fake/dimm_mem_type b/machine/testdata/edac/mc/mc0/dimm_is_fake/dimm_mem_type new file mode 100644 index 00000000..35c62132 --- /dev/null +++ b/machine/testdata/edac/mc/mc0/dimm_is_fake/dimm_mem_type @@ -0,0 +1 @@ +Unbuffered-DDR4 \ No newline at end of file diff --git a/machine/testdata/edac/mc/mc0/dimm_is_fake/size b/machine/testdata/edac/mc/mc0/dimm_is_fake/size new file mode 100644 index 00000000..9d239ff8 --- /dev/null +++ b/machine/testdata/edac/mc/mc0/dimm_is_fake/size @@ -0,0 +1 @@ +321 \ No newline at end of file diff --git a/machine/testdata/edac/mc/mc1/dimm0/dimm_mem_type b/machine/testdata/edac/mc/mc1/dimm0/dimm_mem_type new file mode 100644 index 00000000..6224b304 --- /dev/null +++ b/machine/testdata/edac/mc/mc1/dimm0/dimm_mem_type @@ -0,0 +1 @@ +Non-volatile-RAM \ No newline at end of file diff --git a/machine/testdata/edac/mc/mc1/dimm0/size b/machine/testdata/edac/mc/mc1/dimm0/size new file mode 100644 index 00000000..d800886d --- /dev/null +++ b/machine/testdata/edac/mc/mc1/dimm0/size @@ -0,0 +1 @@ +123 \ No newline at end of file diff --git a/machine/testdata/edac/mc/mc_fake/dimm0/dimm0/dimm_mem_type b/machine/testdata/edac/mc/mc_fake/dimm0/dimm0/dimm_mem_type new file mode 100644 index 00000000..35c62132 --- /dev/null +++ b/machine/testdata/edac/mc/mc_fake/dimm0/dimm0/dimm_mem_type @@ -0,0 +1 @@ +Unbuffered-DDR4 \ No newline at end of file diff --git a/machine/testdata/edac/mc/mc_fake/dimm0/dimm0/size b/machine/testdata/edac/mc/mc_fake/dimm0/dimm0/size new file mode 100644 index 00000000..be2fb0a3 --- /dev/null +++ b/machine/testdata/edac/mc/mc_fake/dimm0/dimm0/size @@ -0,0 +1 @@ +789 \ No newline at end of file diff --git a/machine/testdata/edac/mc/mc_fake/dimm0/dimm1/dimm_mem_type b/machine/testdata/edac/mc/mc_fake/dimm0/dimm1/dimm_mem_type new file mode 100644 index 00000000..6224b304 --- /dev/null +++ b/machine/testdata/edac/mc/mc_fake/dimm0/dimm1/dimm_mem_type @@ -0,0 +1 @@ +Non-volatile-RAM \ No newline at end of file diff --git a/machine/testdata/edac/mc/mc_fake/dimm0/dimm1/size b/machine/testdata/edac/mc/mc_fake/dimm0/dimm1/size new file mode 100644 index 00000000..ee2b8364 --- /dev/null +++ b/machine/testdata/edac/mc/mc_fake/dimm0/dimm1/size @@ -0,0 +1 @@ +456 \ No newline at end of file diff --git a/machine/testdata/edac/mc/mc_fake/dimm0/dimm_mem_type b/machine/testdata/edac/mc/mc_fake/dimm0/dimm_mem_type new file mode 100644 index 00000000..35c62132 --- /dev/null +++ b/machine/testdata/edac/mc/mc_fake/dimm0/dimm_mem_type @@ -0,0 +1 @@ +Unbuffered-DDR4 \ No newline at end of file diff --git a/machine/testdata/edac/mc/mc_fake/dimm0/size b/machine/testdata/edac/mc/mc_fake/dimm0/size new file mode 100644 index 00000000..be2fb0a3 --- /dev/null +++ b/machine/testdata/edac/mc/mc_fake/dimm0/size @@ -0,0 +1 @@ +789 \ No newline at end of file diff --git a/machine/testdata/edac/mc/mc_fake/dimm1/dimm_mem_type b/machine/testdata/edac/mc/mc_fake/dimm1/dimm_mem_type new file mode 100644 index 00000000..6224b304 --- /dev/null +++ b/machine/testdata/edac/mc/mc_fake/dimm1/dimm_mem_type @@ -0,0 +1 @@ +Non-volatile-RAM \ No newline at end of file diff --git a/machine/testdata/edac/mc/mc_fake/dimm1/size b/machine/testdata/edac/mc/mc_fake/dimm1/size new file mode 100644 index 00000000..ee2b8364 --- /dev/null +++ b/machine/testdata/edac/mc/mc_fake/dimm1/size @@ -0,0 +1 @@ +456 \ No newline at end of file diff --git a/machine/topology_test.go b/machine/topology_test.go index c85852c1..65c1d0fb 100644 --- a/machine/topology_test.go +++ b/machine/topology_test.go @@ -247,3 +247,23 @@ func TestGetHugePagesInfo(t *testing.T) { t.Errorf("Expected HugePagesInfo %+v, got %+v", expected, val) } } + +func TestMemoryInfo(t *testing.T) { + testPath := "./testdata/edac/mc" + memory, err := GetMachineMemoryByType(testPath) + + assert.Nil(t, err) + assert.Len(t, memory, 2) + assert.Equal(t, uint64(789*1024*1024), memory["Unbuffered-DDR4"].Capacity) + assert.Equal(t, uint64(579*1024*1024), memory["Non-volatile-RAM"].Capacity) + assert.Equal(t, uint(1), memory["Unbuffered-DDR4"].DimmCount) + assert.Equal(t, uint(2), memory["Non-volatile-RAM"].DimmCount) +} + +func TestMemoryInfoOnArchThatDoNotExposeMemoryController(t *testing.T) { + testPath := "./there/is/no/spoon" + memory, err := GetMachineMemoryByType(testPath) + + assert.Nil(t, err) + assert.Len(t, memory, 0) +}