diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -264,6 +264,8 @@ 'ArcanistHgProxyClient' => 'hgdaemon/ArcanistHgProxyClient.php', 'ArcanistHgProxyServer' => 'hgdaemon/ArcanistHgProxyServer.php', 'ArcanistHgServerChannel' => 'hgdaemon/ArcanistHgServerChannel.php', + 'ArcanistHostMemorySnapshot' => 'filesystem/memory/ArcanistHostMemorySnapshot.php', + 'ArcanistHostMemorySnapshotTestCase' => 'filesystem/memory/__tests__/ArcanistHostMemorySnapshotTestCase.php', 'ArcanistImplicitConstructorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistImplicitConstructorXHPASTLinterRule.php', 'ArcanistImplicitConstructorXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistImplicitConstructorXHPASTLinterRuleTestCase.php', 'ArcanistImplicitFallthroughXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistImplicitFallthroughXHPASTLinterRule.php', @@ -1323,6 +1325,8 @@ 'ArcanistHgProxyClient' => 'Phobject', 'ArcanistHgProxyServer' => 'Phobject', 'ArcanistHgServerChannel' => 'PhutilProtocolChannel', + 'ArcanistHostMemorySnapshot' => 'Phobject', + 'ArcanistHostMemorySnapshotTestCase' => 'PhutilTestCase', 'ArcanistImplicitConstructorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistImplicitConstructorXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistImplicitFallthroughXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', diff --git a/src/filesystem/memory/ArcanistHostMemorySnapshot.php b/src/filesystem/memory/ArcanistHostMemorySnapshot.php new file mode 100644 --- /dev/null +++ b/src/filesystem/memory/ArcanistHostMemorySnapshot.php @@ -0,0 +1,156 @@ +memorySnapshot = $snapshot->readMeminfoSnapshot( + $meminfo_source, + $meminfo_raw); + + return $snapshot; + } + + public function getTotalSwapBytes() { + $info = $this->getMemorySnapshot(); + return $info['swap.total']; + } + + private function getMemorySnapshot() { + if ($this->memorySnapshot === null) { + $this->memorySnapshot = $this->newMemorySnapshot(); + } + + return $this->memorySnapshot; + } + + private function newMemorySnapshot() { + $meminfo_source = '/proc/meminfo'; + list($meminfo_raw) = execx('cat %s', $meminfo_source); + return $this->readMeminfoSnapshot($meminfo_source, $meminfo_raw); + } + + private function readMeminfoSnapshot($meminfo_source, $meminfo_raw) { + $meminfo_pattern = '/^([^:]+):\s+(\S+)(?:\s+(kB))?\z/'; + + $meminfo_map = array(); + + $meminfo_lines = phutil_split_lines($meminfo_raw, false); + foreach ($meminfo_lines as $meminfo_line) { + $meminfo_parts = phutil_preg_match($meminfo_pattern, $meminfo_line); + + if (!$meminfo_parts) { + throw new Exception( + pht( + 'Unable to parse line in meminfo source "%s": "%s".', + $meminfo_source, + $meminfo_line)); + } + + $meminfo_key = $meminfo_parts[1]; + $meminfo_value = $meminfo_parts[2]; + $meminfo_unit = idx($meminfo_parts, 3); + + if (isset($meminfo_map[$meminfo_key])) { + throw new Exception( + pht( + 'Encountered duplicate meminfo key "%s" in meminfo source "%s".', + $meminfo_key, + $meminfo_source)); + } + + $meminfo_map[$meminfo_key] = array( + 'value' => $meminfo_value, + 'unit' => $meminfo_unit, + ); + } + + $swap_total_bytes = $this->readMeminfoBytes( + $meminfo_source, + $meminfo_map, + 'SwapTotal'); + + return array( + 'swap.total' => $swap_total_bytes, + ); + } + + private function readMeminfoBytes( + $meminfo_source, + $meminfo_map, + $meminfo_key) { + + $meminfo_integer = $this->readMeminfoIntegerValue( + $meminfo_source, + $meminfo_map, + $meminfo_key); + + $meminfo_unit = $meminfo_map[$meminfo_key]['unit']; + if ($meminfo_unit === null) { + throw new Exception( + pht( + 'Expected to find a byte unit for meminfo key "%s" in meminfo '. + 'source "%s", found no unit.', + $meminfo_key, + $meminfo_source)); + } + + if ($meminfo_unit !== 'kB') { + throw new Exception( + pht( + 'Expected unit for meminfo key "%s" in meminfo source "%s" '. + 'to be "kB", found "%s".', + $meminfo_key, + $meminfo_source, + $meminfo_unit)); + } + + $meminfo_bytes = ($meminfo_integer * 1024); + + return $meminfo_bytes; + } + + private function readMeminfoIntegerValue( + $meminfo_source, + $meminfo_map, + $meminfo_key) { + + $meminfo_value = $this->readMeminfoValue( + $meminfo_source, + $meminfo_map, + $meminfo_key); + + if (!phutil_preg_match('/^\d+\z/', $meminfo_value)) { + throw new Exception( + pht( + 'Expected to find an integer value for meminfo key "%s" in '. + 'meminfo source "%s", found "%s".', + $meminfo_key, + $meminfo_source, + $meminfo_value)); + } + + return (int)$meminfo_value; + } + + private function readMeminfoValue( + $meminfo_source, + $meminfo_map, + $meminfo_key) { + + if (!isset($meminfo_map[$meminfo_key])) { + throw new Exception( + pht( + 'Expected to find meminfo key "%s" in meminfo source "%s".', + $meminfo_key, + $meminfo_source)); + } + + return $meminfo_map[$meminfo_key]['value']; + } + +} diff --git a/src/filesystem/memory/__tests__/ArcanistHostMemorySnapshotTestCase.php b/src/filesystem/memory/__tests__/ArcanistHostMemorySnapshotTestCase.php new file mode 100644 --- /dev/null +++ b/src/filesystem/memory/__tests__/ArcanistHostMemorySnapshotTestCase.php @@ -0,0 +1,51 @@ + 4294963200, + 'meminfo_swap_zero.txt' => 0, + 'meminfo_swap_missing.txt' => false, + 'meminfo_swap_invalid.txt' => false, + 'meminfo_swap_badunits.txt' => false, + 'meminfo_swap_duplicate.txt' => false, + ); + + $test_dir = dirname(__FILE__).'/data/'; + + foreach ($test_cases as $test_file => $expect) { + $test_data = Filesystem::readFile($test_dir.$test_file); + + $caught = null; + $actual = null; + try { + $snapshot = ArcanistHostMemorySnapshot::newFromRawMeminfo( + $test_file, + $test_data); + $actual = $snapshot->getTotalSwapBytes(); + } catch (Exception $ex) { + if ($expect === false) { + $caught = $ex; + } else { + throw $ex; + } + } catch (Throwable $ex) { + throw $ex; + } + + if ($expect === false) { + $this->assertTrue( + ($caught instanceof Exception), + pht('Expected exception for "%s".', $test_file)); + } else { + $this->assertEqual( + $expect, + $actual, + pht('Result for "%s".', $test_file)); + } + } + } + +} diff --git a/src/filesystem/memory/__tests__/data/meminfo_swap_badunits.txt b/src/filesystem/memory/__tests__/data/meminfo_swap_badunits.txt new file mode 100644 --- /dev/null +++ b/src/filesystem/memory/__tests__/data/meminfo_swap_badunits.txt @@ -0,0 +1,2 @@ +MemTotal: 8140924 kB +SwapTotal: 4194 mB diff --git a/src/filesystem/memory/__tests__/data/meminfo_swap_duplicate.txt b/src/filesystem/memory/__tests__/data/meminfo_swap_duplicate.txt new file mode 100644 --- /dev/null +++ b/src/filesystem/memory/__tests__/data/meminfo_swap_duplicate.txt @@ -0,0 +1,3 @@ +MemTotal: 8140924 kB +SwapTotal: 4194300 kB +SwapTotal: 4194300 kB diff --git a/src/filesystem/memory/__tests__/data/meminfo_swap_invalid.txt b/src/filesystem/memory/__tests__/data/meminfo_swap_invalid.txt new file mode 100644 --- /dev/null +++ b/src/filesystem/memory/__tests__/data/meminfo_swap_invalid.txt @@ -0,0 +1,2 @@ +MemTotal: 8140924 kB +SwapTotal: aardvark kB diff --git a/src/filesystem/memory/__tests__/data/meminfo_swap_missing.txt b/src/filesystem/memory/__tests__/data/meminfo_swap_missing.txt new file mode 100644 --- /dev/null +++ b/src/filesystem/memory/__tests__/data/meminfo_swap_missing.txt @@ -0,0 +1 @@ +MemTotal: 8140924 kB diff --git a/src/filesystem/memory/__tests__/data/meminfo_swap_normal.txt b/src/filesystem/memory/__tests__/data/meminfo_swap_normal.txt new file mode 100644 --- /dev/null +++ b/src/filesystem/memory/__tests__/data/meminfo_swap_normal.txt @@ -0,0 +1,51 @@ +MemTotal: 8140924 kB +MemFree: 1760456 kB +MemAvailable: 4264888 kB +Buffers: 36784 kB +Cached: 2654788 kB +SwapCached: 2132 kB +Active: 331128 kB +Inactive: 5677400 kB +Active(anon): 22692 kB +Inactive(anon): 3325560 kB +Active(file): 308436 kB +Inactive(file): 2351840 kB +Unevictable: 23012 kB +Mlocked: 18476 kB +SwapTotal: 4194300 kB +SwapFree: 2440444 kB +Dirty: 44 kB +Writeback: 0 kB +AnonPages: 3337944 kB +Mapped: 117424 kB +Shmem: 19872 kB +KReclaimable: 124728 kB +Slab: 240640 kB +SReclaimable: 124728 kB +SUnreclaim: 115912 kB +KernelStack: 6736 kB +PageTables: 42044 kB +NFS_Unstable: 0 kB +Bounce: 0 kB +WritebackTmp: 0 kB +CommitLimit: 8264760 kB +Committed_AS: 6994880 kB +VmallocTotal: 34359738367 kB +VmallocUsed: 16656 kB +VmallocChunk: 0 kB +Percpu: 9856 kB +HardwareCorrupted: 0 kB +AnonHugePages: 0 kB +ShmemHugePages: 0 kB +ShmemPmdMapped: 0 kB +FileHugePages: 0 kB +FilePmdMapped: 0 kB +HugePages_Total: 0 +HugePages_Free: 0 +HugePages_Rsvd: 0 +HugePages_Surp: 0 +Hugepagesize: 2048 kB +Hugetlb: 0 kB +DirectMap4k: 270336 kB +DirectMap2M: 8118272 kB +DirectMap1G: 1048576 kB diff --git a/src/filesystem/memory/__tests__/data/meminfo_swap_zero.txt b/src/filesystem/memory/__tests__/data/meminfo_swap_zero.txt new file mode 100644 --- /dev/null +++ b/src/filesystem/memory/__tests__/data/meminfo_swap_zero.txt @@ -0,0 +1,51 @@ +MemTotal: 8140924 kB +MemFree: 1760456 kB +MemAvailable: 4264888 kB +Buffers: 36784 kB +Cached: 2654788 kB +SwapCached: 2132 kB +Active: 331128 kB +Inactive: 5677400 kB +Active(anon): 22692 kB +Inactive(anon): 3325560 kB +Active(file): 308436 kB +Inactive(file): 2351840 kB +Unevictable: 23012 kB +Mlocked: 18476 kB +SwapTotal: 0 kB +SwapFree: 2440444 kB +Dirty: 44 kB +Writeback: 0 kB +AnonPages: 3337944 kB +Mapped: 117424 kB +Shmem: 19872 kB +KReclaimable: 124728 kB +Slab: 240640 kB +SReclaimable: 124728 kB +SUnreclaim: 115912 kB +KernelStack: 6736 kB +PageTables: 42044 kB +NFS_Unstable: 0 kB +Bounce: 0 kB +WritebackTmp: 0 kB +CommitLimit: 8264760 kB +Committed_AS: 6994880 kB +VmallocTotal: 34359738367 kB +VmallocUsed: 16656 kB +VmallocChunk: 0 kB +Percpu: 9856 kB +HardwareCorrupted: 0 kB +AnonHugePages: 0 kB +ShmemHugePages: 0 kB +ShmemPmdMapped: 0 kB +FileHugePages: 0 kB +FilePmdMapped: 0 kB +HugePages_Total: 0 +HugePages_Free: 0 +HugePages_Rsvd: 0 +HugePages_Surp: 0 +Hugepagesize: 2048 kB +Hugetlb: 0 kB +DirectMap4k: 270336 kB +DirectMap2M: 8118272 kB +DirectMap1G: 1048576 kB