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
@@ -317,6 +317,8 @@
     'PhutilSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilSyntaxHighlighter.php',
     'PhutilSyntaxHighlighterEngine' => 'markup/syntax/engine/PhutilSyntaxHighlighterEngine.php',
     'PhutilSyntaxHighlighterException' => 'markup/syntax/highlighter/PhutilSyntaxHighlighterException.php',
+    'PhutilSystem' => 'utils/PhutilSystem.php',
+    'PhutilSystemTestCase' => 'utils/__tests__/PhutilSystemTestCase.php',
     'PhutilTestCase' => 'infrastructure/testing/PhutilTestCase.php',
     'PhutilTestPhobject' => 'object/__tests__/PhutilTestPhobject.php',
     'PhutilTortureTestDaemon' => 'daemon/torture/PhutilTortureTestDaemon.php',
@@ -715,6 +717,8 @@
     'PhutilSimpleOptionsTestCase' => 'PhutilTestCase',
     'PhutilSocketChannel' => 'PhutilChannel',
     'PhutilSyntaxHighlighterException' => 'Exception',
+    'PhutilSystem' => 'Phobject',
+    'PhutilSystemTestCase' => 'PhutilTestCase',
     'PhutilTestCase' => 'ArcanistPhutilTestCase',
     'PhutilTestPhobject' => 'Phobject',
     'PhutilTortureTestDaemon' => 'PhutilDaemon',
diff --git a/src/utils/PhutilSystem.php b/src/utils/PhutilSystem.php
new file mode 100644
--- /dev/null
+++ b/src/utils/PhutilSystem.php
@@ -0,0 +1,158 @@
+<?php
+
+/**
+ * Interact with the operating system.
+ *
+ * @task memory Interacting with System Memory
+ */
+final class PhutilSystem extends Phobject {
+
+
+  /**
+   * Get information about total and free memory on the system.
+   *
+   * Because "free memory" is a murky concept, the interpretation of the values
+   * returned from this method will vary from system to system and the numbers
+   * themselves may be only roughly accurate.
+   *
+   * @return map<string, wild> Dictionary of memory information.
+   * @task memory
+   */
+  public static function getSystemMemoryInformation() {
+    $meminfo_path = '/proc/meminfo';
+    if (Filesystem::pathExists($meminfo_path)) {
+      $meminfo_data = Filesystem::readFile($meminfo_path);
+      return self::parseMemInfo($meminfo_data);
+    } else if (Filesystem::binaryExists('vm_stat')) {
+      list($vm_stat_stdout) = execx('vm_stat');
+      return self::parseVMStat($vm_stat_stdout);
+    } else {
+      throw new Exception(
+        pht(
+          'Unable to access /proc/meminfo or `vm_stat` on this system to '.
+          'get system memory information.'));
+    }
+  }
+
+
+  /**
+   * Parse the output of `/proc/meminfo`.
+   *
+   * See @{method:getSystemMemoryInformation}. This method is used to get memory
+   * information on Linux.
+   *
+   * @param string Raw `/proc/meminfo`.
+   * @return map<string, wild> Parsed memory information.
+   * @task memory
+   */
+  public static function parseMemInfo($data) {
+    $data = phutil_split_lines($data);
+
+    $map = array();
+    foreach ($data as $line) {
+      list($key, $value) = explode(':', $line, 2);
+      $key = trim($key);
+      $value = trim($value);
+
+      $matches = null;
+      if (preg_match('/^(\d+) kB\z/', $value, $matches)) {
+        $value = (int)$matches[1] * 1024;
+      }
+
+      $map[$key] = $value;
+    }
+
+    $expect = array(
+      'MemTotal',
+      'MemFree',
+      'Buffers',
+      'Cached',
+    );
+    foreach ($expect as $key) {
+      if (!array_key_exists($key, $map)) {
+        throw new Exception(
+          pht(
+            'Expected to find "%s" in "/proc/meminfo" output, but did not.',
+            $key));
+      }
+    }
+
+    $total = $map['MemTotal'];
+    $free = $map['MemFree'] + $map['Buffers'] + $map['Cached'];
+
+    return array(
+      'total' => $total,
+      'free' => $free,
+    );
+  }
+
+
+  /**
+   * Parse the output of `vm_stat`.
+   *
+   * See @{method:getSystemMemoryInformation}. This method is used to get memory
+   * information on Mac OS X.
+   *
+   * @param string Raw `vm_stat` output.
+   * @return map<string, wild> Parsed memory information.
+   * @task memory
+   */
+  public static function parseVMStat($data) {
+    $data = phutil_split_lines($data);
+
+    $page_size = null;
+    $map = array();
+
+    foreach ($data as $line) {
+      list($key, $value) = explode(':', $line, 2);
+      $key = trim($key);
+      $value = trim($value);
+
+      $matches = null;
+      if (preg_match('/page size of (\d+) bytes/', $value, $matches)) {
+        $page_size = (int)$matches[1];
+        continue;
+      }
+
+      $value = trim($value, '.');
+      $map[$key] = $value;
+    }
+
+    if (!$page_size) {
+      throw new Exception(
+        pht(
+          'Expected to find "page size" in `vm_stat` output, but did not.'));
+    }
+
+    $expect = array(
+      'Pages free',
+      'Pages active',
+      'Pages inactive',
+      'Pages wired down',
+    );
+    foreach ($expect as $key) {
+      if (!array_key_exists($key, $map)) {
+        throw new Exception(
+          pht(
+            'Expected to find "%s" in `vm_stat` output, but did not.',
+            $key));
+      }
+    }
+
+    // NOTE: This calculation probably isn't quite right. In particular,
+    // the numbers don't exactly add up, and "Pages inactive" includes a
+    // bunch of disk cache. So these numbers aren't totally reliable and they
+    // aren't directly comparable to the /proc/meminfo numbers.
+
+    $free = $map['Pages free'];
+    $active = $map['Pages active'];
+    $inactive = $map['Pages inactive'];
+    $wired = $map['Pages wired down'];
+
+    return array(
+      'total' => ($free + $active + $inactive + $wired) * $page_size,
+      'free' => ($free) * $page_size,
+    );
+  }
+
+}
diff --git a/src/utils/__tests__/PhutilSystemTestCase.php b/src/utils/__tests__/PhutilSystemTestCase.php
new file mode 100644
--- /dev/null
+++ b/src/utils/__tests__/PhutilSystemTestCase.php
@@ -0,0 +1,43 @@
+<?php
+
+final class PhutilSystemTestCase extends PhutilTestCase {
+
+  public function testParseVMStat() {
+    $tests = array(
+      'vmstat.yosemite.txt' => array(
+        'total' => 16503578624,
+        'free' => 1732366336,
+      ),
+    );
+
+    $dir = dirname(__FILE__).'/memory';
+    foreach ($tests as $input => $expect) {
+      $raw = Filesystem::readFile($dir.'/'.$input);
+      $actual = PhutilSystem::parseVMStat($raw);
+      $this->assertEqual(
+        $expect,
+        $actual,
+        pht('Parse of "%s".', $input));
+    }
+  }
+
+  public function testParseMeminfo() {
+    $tests = array(
+      'meminfo.ubuntu14.txt' => array(
+        'total' => 7843336192,
+        'free' => 3758297088,
+      ),
+    );
+
+    $dir = dirname(__FILE__).'/memory';
+    foreach ($tests as $input => $expect) {
+      $raw = Filesystem::readFile($dir.'/'.$input);
+      $actual = PhutilSystem::parseMemInfo($raw);
+      $this->assertEqual(
+        $expect,
+        $actual,
+        pht('Parse of "%s".', $input));
+    }
+  }
+
+}
diff --git a/src/utils/__tests__/memory/meminfo.ubuntu14.txt b/src/utils/__tests__/memory/meminfo.ubuntu14.txt
new file mode 100644
--- /dev/null
+++ b/src/utils/__tests__/memory/meminfo.ubuntu14.txt
@@ -0,0 +1,42 @@
+MemTotal:        7659508 kB
+MemFree:          246684 kB
+Buffers:          126580 kB
+Cached:          3296948 kB
+SwapCached:            0 kB
+Active:          4916076 kB
+Inactive:        1732880 kB
+Active(anon):    3225504 kB
+Inactive(anon):    20576 kB
+Active(file):    1690572 kB
+Inactive(file):  1712304 kB
+Unevictable:           0 kB
+Mlocked:               0 kB
+SwapTotal:             0 kB
+SwapFree:              0 kB
+Dirty:                32 kB
+Writeback:             0 kB
+AnonPages:       3225428 kB
+Mapped:            37500 kB
+Shmem:             20652 kB
+Slab:             633908 kB
+SReclaimable:     467472 kB
+SUnreclaim:       166436 kB
+KernelStack:        2416 kB
+PageTables:        64744 kB
+NFS_Unstable:          0 kB
+Bounce:                0 kB
+WritebackTmp:          0 kB
+CommitLimit:     3829752 kB
+Committed_AS:    4935116 kB
+VmallocTotal:   34359738367 kB
+VmallocUsed:       17244 kB
+VmallocChunk:   34359712740 kB
+HardwareCorrupted:     0 kB
+AnonHugePages:    954368 kB
+HugePages_Total:       0
+HugePages_Free:        0
+HugePages_Rsvd:        0
+HugePages_Surp:        0
+Hugepagesize:       2048 kB
+DirectMap4k:       45056 kB
+DirectMap2M:     7950336 kB
diff --git a/src/utils/__tests__/memory/vmstat.yosemite.txt b/src/utils/__tests__/memory/vmstat.yosemite.txt
new file mode 100644
--- /dev/null
+++ b/src/utils/__tests__/memory/vmstat.yosemite.txt
@@ -0,0 +1,23 @@
+Mach Virtual Memory Statistics: (page size of 4096 bytes)
+Pages free:                              422941.
+Pages active:                           2348641.
+Pages inactive:                          830440.
+Pages speculative:                       110635.
+Pages throttled:                              0.
+Pages wired down:                        427172.
+Pages purgeable:                          33368.
+"Translation faults":                 931955891.
+Pages copy-on-write:                   59498342.
+Pages zero filled:                    411628732.
+Pages reactivated:                       175636.
+Pages purged:                            569552.
+File-backed pages:                       926777.
+Anonymous pages:                        2362939.
+Pages stored in compressor:              125673.
+Pages occupied by compressor:             51938.
+Decompressions:                           32945.
+Compressions:                            197789.
+Pageins:                               13750115.
+Pageouts:                                 39562.
+Swapins:                                      0.
+Swapouts:                                  2290.