diff --git a/ui/metrics/proc/Android.bp b/ui/metrics/proc/Android.bp new file mode 100644 index 000000000..32d821750 --- /dev/null +++ b/ui/metrics/proc/Android.bp @@ -0,0 +1,37 @@ +// Copyright 2020 Google Inc. 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. + +bootstrap_go_package { + name: "soong-ui-metrics-proc", + pkgPath: "android/soong/ui/metrics/proc", + deps: [ + "soong-finder-fs", + ], + srcs: [ + "status.go", + ], + linux: { + srcs: [ + "status_linux.go", + ], + testSrcs: [ + "status_linux_test.go", + ], + }, + darwin: { + srcs: [ + "status_darwin.go", + ], + }, +} diff --git a/ui/metrics/proc/status.go b/ui/metrics/proc/status.go new file mode 100644 index 000000000..f8b734c02 --- /dev/null +++ b/ui/metrics/proc/status.go @@ -0,0 +1,128 @@ +// package proc contains functionality to read proc status files. +package proc + +import ( + "strconv" + "strings" +) + +// ProcStatus holds information regarding the memory usage of +// an executing process. The memory sizes in each of the field +// is in bytes. +type ProcStatus struct { + // Process PID. + pid int + + // Peak virtual memory size. + VmPeak uint64 + + // Virtual memory size. + VmSize uint64 + + // Locked Memory size. + VmLck uint64 + + // Pinned memory size. + VmPin uint64 + + // Peak resident set size. + VmHWM uint64 + + // Resident set size (sum of RssAnon, RssFile and RssShmem). + VmRss uint64 + + // Size of resident anonymous memory. + RssAnon uint64 + + // Size of resident shared memory. + RssShmem uint64 + + // Size of data segments. + VmData uint64 + + // Size of stack segments. + VmStk uint64 + + //Size of text segments. + VmExe uint64 + + //Shared library code size. + VmLib uint64 + + // Page table entries size. + VmPTE uint64 + + // Size of second-level page tables. + VmPMD uint64 + + // Swapped-out virtual memory size by anonymous private. + VmSwap uint64 + + // Size of hugetlb memory page size. + HugetlbPages uint64 +} + +// fillProcStatus takes the key and value, converts the value +// to the proper size unit and is stored in the ProcStatus. +func fillProcStatus(s *ProcStatus, key, value string) { + v := strToUint64(value) + switch key { + case "VmPeak": + s.VmPeak = v + case "VmSize": + s.VmSize = v + case "VmLck": + s.VmLck = v + case "VmPin": + s.VmPin = v + case "VmHWM": + s.VmHWM = v + case "VmRSS": + s.VmRss = v + case "RssAnon": + s.RssAnon = v + case "RssShmem": + s.RssShmem = v + case "VmData": + s.VmData = v + case "VmStk": + s.VmStk = v + case "VmExe": + s.VmExe = v + case "VmLib": + s.VmLib = v + case "VmPTE": + s.VmPTE = v + case "VmPMD": + s.VmPMD = v + case "VmSwap": + s.VmSwap = v + case "HugetlbPages": + s.HugetlbPages = v + } +} + +// strToUint64 takes the string and converts to unsigned 64-bit integer. +// If the string contains a memory unit such as kB and is converted to +// bytes. +func strToUint64(v string) uint64 { + // v could be "1024 kB" so scan for the empty space and + // split between the value and the unit. + var separatorIndex int + if separatorIndex = strings.IndexAny(v, " "); separatorIndex < 0 { + separatorIndex = len(v) + } + value, err := strconv.ParseUint(v[:separatorIndex], 10, 64) + if err != nil { + return 0 + } + + var scale uint64 = 1 + switch strings.TrimSpace(v[separatorIndex:]) { + case "kB", "KB": + scale = 1024 + case "mB", "MB": + scale = 1024 * 1024 + } + return value * scale +} diff --git a/ui/metrics/proc/status_darwin.go b/ui/metrics/proc/status_darwin.go new file mode 100644 index 000000000..5c788a5a0 --- /dev/null +++ b/ui/metrics/proc/status_darwin.go @@ -0,0 +1,11 @@ +package proc + +import ( + "android/soong/finder/fs" +) + +// NewProcStatus returns a zero filled value of ProcStatus as it +// is not supported for darwin distribution based. +func NewProcStatus(pid int, _ fs.FileSystem) (*ProcStatus, error) { + return &ProcStatus{}, nil +} diff --git a/ui/metrics/proc/status_linux.go b/ui/metrics/proc/status_linux.go new file mode 100644 index 000000000..dc0f943dd --- /dev/null +++ b/ui/metrics/proc/status_linux.go @@ -0,0 +1,46 @@ +package proc + +import ( + "io/ioutil" + "path/filepath" + "strconv" + "strings" + + "android/soong/finder/fs" +) + +// NewProcStatus returns an instance of the ProcStatus that contains memory +// information of the process. The memory information is extracted from the +// "/proc//status" text file. This is only available for Linux +// distribution that supports /proc. +func NewProcStatus(pid int, fileSystem fs.FileSystem) (*ProcStatus, error) { + statusFname := filepath.Join("/proc", strconv.Itoa(pid), "status") + r, err := fileSystem.Open(statusFname) + if err != nil { + return &ProcStatus{}, err + } + defer r.Close() + + data, err := ioutil.ReadAll(r) + if err != nil { + return &ProcStatus{}, err + } + + s := &ProcStatus{ + pid: pid, + } + + for _, l := range strings.Split(string(data), "\n") { + // If the status file does not contain "key: values", just skip the line + // as the information we are looking for is not needed. + if !strings.Contains(l, ":") { + continue + } + + // At this point, we're only considering entries that has key, single value pairs. + kv := strings.SplitN(l, ":", 2) + fillProcStatus(s, strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1])) + } + + return s, nil +} diff --git a/ui/metrics/proc/status_linux_test.go b/ui/metrics/proc/status_linux_test.go new file mode 100644 index 000000000..67098502b --- /dev/null +++ b/ui/metrics/proc/status_linux_test.go @@ -0,0 +1,112 @@ +package proc + +import ( + "fmt" + "path/filepath" + "reflect" + "strconv" + "testing" + + "android/soong/finder/fs" +) + +func TestNewProcStatus(t *testing.T) { + fs := fs.NewMockFs(nil) + + pid := 4032827 + procDir := filepath.Join("/proc", strconv.Itoa(pid)) + if err := fs.MkDirs(procDir); err != nil { + t.Fatalf("failed to create proc pid dir %s: %v", procDir, err) + } + statusFilename := filepath.Join(procDir, "status") + + if err := fs.WriteFile(statusFilename, statusData, 0644); err != nil { + t.Fatalf("failed to write proc file %s: %v", statusFilename, err) + } + + status, err := NewProcStatus(pid, fs) + if err != nil { + t.Fatalf("got %v, want nil for error", err) + } + + fmt.Printf("%d %d\b", status.VmPeak, expectedStatus.VmPeak) + if !reflect.DeepEqual(status, expectedStatus) { + t.Errorf("got %v, expecting %v for ProcStatus", status, expectedStatus) + } +} + +var statusData = []byte(`Name: fake_process +Umask: 0022 +State: S (sleeping) +Tgid: 4032827 +Ngid: 0 +Pid: 4032827 +PPid: 1 +TracerPid: 0 +Uid: 0 0 0 0 +Gid: 0 0 0 0 +FDSize: 512 +Groups: +NStgid: 4032827 +NSpid: 4032827 +NSpgid: 4032827 +NSsid: 4032827 +VmPeak: 733232 kB +VmSize: 733232 kB +VmLck: 132 kB +VmPin: 130 kB +VmHWM: 69156 kB +VmRSS: 69156 kB +RssAnon: 50896 kB +RssFile: 18260 kB +RssShmem: 122 kB +VmData: 112388 kB +VmStk: 132 kB +VmExe: 9304 kB +VmLib: 8 kB +VmPTE: 228 kB +VmSwap: 10 kB +HugetlbPages: 22 kB +CoreDumping: 0 +THP_enabled: 1 +Threads: 46 +SigQ: 2/767780 +SigPnd: 0000000000000000 +ShdPnd: 0000000000000000 +SigBlk: fffffffe3bfa3a00 +SigIgn: 0000000000000000 +SigCgt: fffffffe7fc1feff +CapInh: 0000000000000000 +CapPrm: 0000003fffffffff +CapEff: 0000003fffffffff +CapBnd: 0000003fffffffff +CapAmb: 0000000000000000 +NoNewPrivs: 0 +Seccomp: 0 +Speculation_Store_Bypass: thread vulnerable +Cpus_allowed: ff,ffffffff,ffffffff +Cpus_allowed_list: 0-71 +Mems_allowed: 00000000,00000003 +Mems_allowed_list: 0-1 +voluntary_ctxt_switches: 1635 +nonvoluntary_ctxt_switches: 32 +`) + +var expectedStatus = &ProcStatus{ + pid: 4032827, + VmPeak: 750829568, + VmSize: 750829568, + VmLck: 135168, + VmPin: 133120, + VmHWM: 70815744, + VmRss: 70815744, + RssAnon: 52117504, + RssShmem: 124928, + VmData: 115085312, + VmStk: 135168, + VmExe: 9527296, + VmLib: 8192, + VmPTE: 233472, + VmSwap: 10240, + HugetlbPages: 22528, +}