Read the proc status file when PID is given for metrics purpose.

To measure the MaxRSS memory correctly, read the proc status file
under /proc/<pid>/status and extract the MaxRSS value from it. The
implementation is only available for Linux based distributions.

Bug: b/169453825
Test: go test
Change-Id: I32e3068fee7447f9ef5dfb5a8d8dcb6934e0af23
This commit is contained in:
Patrice Arruda 2020-12-16 11:21:37 -08:00
parent bb42c44e71
commit 04157e186f
5 changed files with 334 additions and 0 deletions

View File

@ -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",
],
},
}

128
ui/metrics/proc/status.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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/<pid>/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
}

View File

@ -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,
}