summaryrefslogtreecommitdiff
path: root/pagecache
diff options
context:
space:
mode:
authorMartijn Coenen <maco@google.com>2015-11-30 14:43:10 +0100
committerMartijn Coenen <maco@google.com>2015-12-04 16:59:36 +0100
commit0a1b0183f6a4a9c73dd91333b5dc3d5b72c62e6e (patch)
treed9ca1db02553090175f4d55a1c0a037f832c7494 /pagecache
parentf40118215093b21a4eb021bbe19201bd743c120b (diff)
downloadextras-0a1b0183f6a4a9c73dd91333b5dc3d5b72c62e6e.tar.gz
Pagecache tools.
Dumpcache: dumps complete pagecache of device. pagecache.py: shows live info on files going in/out of pagecache Change-Id: Ieb2960d9e5daea8a7d9dcf23d2c31986182bc359
Diffstat (limited to 'pagecache')
-rw-r--r--pagecache/Android.mk13
-rw-r--r--pagecache/MODULE_LICENSE_APACHE20
-rw-r--r--pagecache/NOTICE190
-rw-r--r--pagecache/README4
-rw-r--r--pagecache/dumpcache.c152
-rwxr-xr-xpagecache/pagecache.py372
6 files changed, 731 insertions, 0 deletions
diff --git a/pagecache/Android.mk b/pagecache/Android.mk
new file mode 100644
index 00000000..fe064100
--- /dev/null
+++ b/pagecache/Android.mk
@@ -0,0 +1,13 @@
+# Copyright 2015 The Android Open Source Project
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= dumpcache.c
+LOCAL_SHARED_LIBRARIES := libcutils
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE:= dumpcache
+
+include $(BUILD_EXECUTABLE)
+
diff --git a/pagecache/MODULE_LICENSE_APACHE2 b/pagecache/MODULE_LICENSE_APACHE2
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/pagecache/MODULE_LICENSE_APACHE2
diff --git a/pagecache/NOTICE b/pagecache/NOTICE
new file mode 100644
index 00000000..34bdaf1d
--- /dev/null
+++ b/pagecache/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2015, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/pagecache/README b/pagecache/README
new file mode 100644
index 00000000..08f4b536
--- /dev/null
+++ b/pagecache/README
@@ -0,0 +1,4 @@
+Pagecache tools.
+
+dumpcache.c: dumps complete pagecache of device.
+pagecache.py: shows live info on files going in/out of pagecache.
diff --git a/pagecache/dumpcache.c b/pagecache/dumpcache.c
new file mode 100644
index 00000000..fc06bf24
--- /dev/null
+++ b/pagecache/dumpcache.c
@@ -0,0 +1,152 @@
+#include <ftw.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <ctype.h>
+#include <stddef.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+// Initial size of the array holding struct file_info
+#define INITIAL_NUM_FILES 512
+
+// Max number of file descriptors to use for ntfw
+#define MAX_NUM_FD 1
+
+struct file_info {
+ char *name;
+ size_t file_size;
+ size_t num_cached_pages;
+};
+
+// Size of pages on this system
+static int g_page_size;
+
+// Total number of cached pages found so far
+static size_t g_total_cached = 0;
+
+// Total number of files scanned so far
+static size_t g_num_files = 0;
+
+// Scanned files and their associated cached page counts
+static struct file_info **g_files;
+
+// Current size of files array
+size_t g_files_size;
+
+static struct file_info *get_file_info(const char* fpath, size_t file_size) {
+ struct file_info *info;
+ if (g_num_files >= g_files_size) {
+ g_files = realloc(g_files, 2 * g_files_size * sizeof(struct file_info*));
+ if (!g_files) {
+ fprintf(stderr, "Couldn't allocate space for files array: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ g_files_size = 2 * g_files_size;
+ }
+
+ info = calloc(1, sizeof(*info));
+ if (!info) {
+ fprintf(stderr, "Couldn't allocate space for file struct: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ info->name = malloc(strlen(fpath) + 1);
+ if (!info->name) {
+ fprintf(stderr, "Couldn't allocate space for file struct: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ strcpy(info->name, fpath);
+
+ info->num_cached_pages = 0;
+ info->file_size = file_size;
+
+ g_files[g_num_files++] = info;
+
+ return info;
+}
+
+static int store_num_cached(const char* fpath, const struct stat *sb) {
+ int fd;
+ fd = open (fpath, O_RDONLY);
+
+ if (fd == -1) {
+ printf("Could not open file.");
+ return -1;
+ }
+
+ void* mapped_addr = mmap(NULL, sb->st_size, PROT_NONE, MAP_SHARED, fd, 0);
+
+ if (mapped_addr != MAP_FAILED) {
+ // Calculate bit-vector size
+ size_t num_file_pages = (sb->st_size + g_page_size - 1) / g_page_size;
+ unsigned char* mincore_data = calloc(1, num_file_pages);
+ int ret = mincore(mapped_addr, sb->st_size, mincore_data);
+ int num_cached = 0;
+ unsigned int page = 0;
+ for (page = 0; page < num_file_pages; page++) {
+ if (mincore_data[page]) num_cached++;
+ }
+ if (num_cached > 0) {
+ struct file_info *info = get_file_info(fpath, sb->st_size);
+ info->num_cached_pages += num_cached;
+ g_total_cached += num_cached;
+ }
+ munmap(mapped_addr, sb->st_size);
+ }
+
+ close(fd);
+ return 0;
+}
+
+static int scan_entry(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) {
+ if (typeflag == FTW_F) {
+ store_num_cached(fpath, sb);
+ }
+ return 0;
+}
+
+static int cmpsize(size_t a, size_t b) {
+ if (a < b) return -1;
+ if (a > b) return 1;
+ return 0;
+}
+
+static int cmpfiles(const void *a, const void *b) {
+ return cmpsize((*((struct file_info**)a))->num_cached_pages,
+ (*((struct file_info**)b))->num_cached_pages);
+}
+
+int main()
+{
+ g_page_size = getpagesize();
+
+ g_files = malloc(INITIAL_NUM_FILES * sizeof(struct file_info*));
+ g_files_size = INITIAL_NUM_FILES;
+
+ // Walk filesystem trees
+ nftw("/system/", &scan_entry, MAX_NUM_FD, 0);
+ nftw("/vendor/", &scan_entry, MAX_NUM_FD, 0);
+ nftw("/data/", &scan_entry, MAX_NUM_FD, 0);
+
+ // Sort entries
+ qsort(g_files, g_num_files, sizeof(g_files[0]), &cmpfiles);
+
+ // Dump entries
+ for (size_t i = 0; i < g_num_files; i++) {
+ struct file_info *info = g_files[i];
+ fprintf(stdout, "%s: %zu cached pages (%.2f MB, %lu%% of total file size.)\n", info->name,
+ info->num_cached_pages,
+ (float) (info->num_cached_pages * g_page_size) / 1024 / 1024,
+ (100 * info->num_cached_pages * g_page_size) / info->file_size);
+ }
+
+ fprintf(stdout, "TOTAL CACHED: %zu pages (%f MB)\n", g_total_cached,
+ (float) (g_total_cached * 4096) / 1024 / 1024);
+ return 0;
+}
diff --git a/pagecache/pagecache.py b/pagecache/pagecache.py
new file mode 100755
index 00000000..30d9fc67
--- /dev/null
+++ b/pagecache/pagecache.py
@@ -0,0 +1,372 @@
+#!/usr/bin/env python
+
+import curses
+import operator
+import optparse
+import os
+import re
+import subprocess
+import sys
+import threading
+import Queue
+
+STATS_UPDATE_INTERVAL = 0.2
+PAGE_SIZE = 4096
+
+class PagecacheStats():
+ """Holds pagecache stats by accounting for pages added and removed.
+
+ """
+ def __init__(self, inode_to_filename):
+ self._inode_to_filename = inode_to_filename
+ self._file_size = {}
+ self._file_pages_added = {}
+ self._file_pages_removed = {}
+ self._total_pages_added = 0
+ self._total_pages_removed = 0
+
+ def add_page(self, device_number, inode, offset):
+ # See if we can find the page in our lookup table
+ if (device_number, inode) in self._inode_to_filename:
+ filename, filesize = self._inode_to_filename[(device_number, inode)]
+ if filename not in self._file_pages_added:
+ self._file_pages_added[filename] = 1
+ else:
+ self._file_pages_added[filename] += 1
+ self._total_pages_added += 1
+
+ if filename not in self._file_size:
+ self._file_size[filename] = filesize
+
+ def remove_page(self, device_number, inode, offset):
+ if (device_number, inode) in self._inode_to_filename:
+ filename, filesize = self._inode_to_filename[(device_number, inode)]
+ if filename not in self._file_pages_removed:
+ self._file_pages_removed[filename] = 1
+ else:
+ self._file_pages_removed[filename] += 1
+ self._total_pages_removed += 1
+
+ if filename not in self._file_size:
+ self._file_size[filename] = filesize
+
+ def pages_to_mb(self, num_pages):
+ return "%.2f" % round(num_pages * PAGE_SIZE / 1024.0 / 1024.0, 2)
+
+ def bytes_to_mb(self, num_bytes):
+ return "%.2f" % round(int(num_bytes) / 1024.0 / 1024.0, 2)
+
+ def print_pages_and_mb(self, num_pages):
+ pages_string = str(num_pages) + ' (' + str(self.pages_to_mb(num_pages)) + ' MB)'
+ return pages_string
+
+ def reset_stats(self):
+ self._file_pages_removed.clear()
+ self._file_pages_added.clear()
+ self._total_pages_added = 0;
+ self._total_pages_removed = 0;
+
+ def print_stats(self, pad):
+ sorted_added = sorted(self._file_pages_added.items(), key=operator.itemgetter(1), reverse=True)
+ height, width = pad.getmaxyx()
+ pad.clear()
+ pad.addstr(0, 2, 'NAME'.ljust(68), curses.A_REVERSE)
+ pad.addstr(0, 70, 'ADDED (MB)'.ljust(12), curses.A_REVERSE)
+ pad.addstr(0, 82, 'REMOVED (MB)'.ljust(14), curses.A_REVERSE)
+ pad.addstr(0, 96, 'SIZE (MB)'.ljust(9), curses.A_REVERSE)
+ y = 1
+ for filename, added in sorted_added:
+ filesize = self._file_size[filename]
+ removed = 0
+ if filename in self._file_pages_removed:
+ removed = self._file_pages_removed[filename]
+ if (filename > 64):
+ filename = filename[-64:]
+ pad.addstr(y, 2, filename)
+ pad.addstr(y, 70, self.pages_to_mb(added).rjust(10))
+ pad.addstr(y, 80, self.pages_to_mb(removed).rjust(14))
+ pad.addstr(y, 96, self.bytes_to_mb(filesize).rjust(9))
+ y += 1
+ if y == height - 2:
+ pad.addstr(y, 4, "<more...>")
+ break
+ y += 1
+ pad.addstr(y, 2, 'TOTAL'.ljust(74), curses.A_REVERSE)
+ pad.addstr(y, 70, str(self.pages_to_mb(self._total_pages_added)).rjust(10), curses.A_REVERSE)
+ pad.addstr(y, 80, str(self.pages_to_mb(self._total_pages_removed)).rjust(14), curses.A_REVERSE)
+ pad.refresh(0,0, 0,0, height,width)
+
+class FileReaderThread(threading.Thread):
+ """Reads data from a file/pipe on a worker thread.
+
+ Use the standard threading. Thread object API to start and interact with the
+ thread (start(), join(), etc.).
+ """
+
+ def __init__(self, file_object, output_queue, text_file, chunk_size=-1):
+ """Initializes a FileReaderThread.
+
+ Args:
+ file_object: The file or pipe to read from.
+ output_queue: A Queue.Queue object that will receive the data
+ text_file: If True, the file will be read one line at a time, and
+ chunk_size will be ignored. If False, line breaks are ignored and
+ chunk_size must be set to a positive integer.
+ chunk_size: When processing a non-text file (text_file = False),
+ chunk_size is the amount of data to copy into the queue with each
+ read operation. For text files, this parameter is ignored.
+ """
+ threading.Thread.__init__(self)
+ self._file_object = file_object
+ self._output_queue = output_queue
+ self._text_file = text_file
+ self._chunk_size = chunk_size
+ assert text_file or chunk_size > 0
+
+ def run(self):
+ """Overrides Thread's run() function.
+
+ Returns when an EOF is encountered.
+ """
+ if self._text_file:
+ # Read a text file one line at a time.
+ for line in self._file_object:
+ self._output_queue.put(line)
+ else:
+ # Read binary or text data until we get to EOF.
+ while True:
+ chunk = self._file_object.read(self._chunk_size)
+ if not chunk:
+ break
+ self._output_queue.put(chunk)
+
+ def set_chunk_size(self, chunk_size):
+ """Change the read chunk size.
+
+ This function can only be called if the FileReaderThread object was
+ created with an initial chunk_size > 0.
+ Args:
+ chunk_size: the new chunk size for this file. Must be > 0.
+ """
+ # The chunk size can be changed asynchronously while a file is being read
+ # in a worker thread. However, type of file can not be changed after the
+ # the FileReaderThread has been created. These asserts verify that we are
+ # only changing the chunk size, and not the type of file.
+ assert not self._text_file
+ assert chunk_size > 0
+ self._chunk_size = chunk_size
+
+class AdbUtils():
+ @staticmethod
+ def add_adb_serial(adb_command, device_serial):
+ if device_serial is not None:
+ adb_command.insert(1, device_serial)
+ adb_command.insert(1, '-s')
+
+ @staticmethod
+ def construct_adb_shell_command(shell_args, device_serial):
+ adb_command = ['adb', 'shell', ' '.join(shell_args)]
+ AdbUtils.add_adb_serial(adb_command, device_serial)
+ return adb_command
+
+ @staticmethod
+ def run_adb_shell(shell_args, device_serial):
+ """Runs "adb shell" with the given arguments.
+
+ Args:
+ shell_args: array of arguments to pass to adb shell.
+ device_serial: if not empty, will add the appropriate command-line
+ parameters so that adb targets the given device.
+ Returns:
+ A tuple containing the adb output (stdout & stderr) and the return code
+ from adb. Will exit if adb fails to start.
+ """
+ adb_command = AdbUtils.construct_adb_shell_command(shell_args, device_serial)
+
+ adb_output = []
+ adb_return_code = 0
+ try:
+ adb_output = subprocess.check_output(adb_command, stderr=subprocess.STDOUT,
+ shell=False, universal_newlines=True)
+ except OSError as error:
+ # This usually means that the adb executable was not found in the path.
+ print >> sys.stderr, ('\nThe command "%s" failed with the following error:'
+ % ' '.join(adb_command))
+ print >> sys.stderr, ' %s' % str(error)
+ print >> sys.stderr, 'Is adb in your path?'
+ adb_return_code = error.errno
+ adb_output = error
+ except subprocess.CalledProcessError as error:
+ # The process exited with an error.
+ adb_return_code = error.returncode
+ adb_output = error.output
+
+ return (adb_output, adb_return_code)
+
+ @staticmethod
+ def do_preprocess_adb_cmd(command, serial):
+ args = [command]
+ dump, ret_code = AdbUtils.run_adb_shell(args, serial)
+ if ret_code != 0:
+ return None
+
+ dump = ''.join(dump)
+ return dump
+
+def parse_atrace_line(line, pagecache_stats):
+ # Find a mm_filemap_add_to_page_cache entry
+ m = re.match('.* (mm_filemap_add_to_page_cache|mm_filemap_delete_from_page_cache): dev (\d+):(\d+) ino ([0-9a-z]+) page=([0-9a-z]+) pfn=\d+ ofs=(\d+).*', line)
+ if m != None:
+ # Get filename
+ device_number = int(m.group(2)) << 8 | int(m.group(3))
+ if device_number == 0:
+ return
+ inode = int(m.group(4), 16)
+ if m.group(1) == 'mm_filemap_add_to_page_cache':
+ pagecache_stats.add_page(device_number, inode, m.group(4))
+ elif m.group(1) == 'mm_filemap_delete_from_page_cache':
+ pagecache_stats.remove_page(device_number, inode, m.group(4))
+
+def build_inode_lookup_table(inode_dump):
+ inode2filename = {}
+ text = inode_dump.splitlines()
+ for line in text:
+ result = re.match('([0-9]+) ([0-9]+) ([0-9]+) (.*)', line)
+ if result:
+ inode2filename[(int(result.group(1)), int(result.group(2)))] = (result.group(4), result.group(3))
+
+ return inode2filename;
+
+def get_inode_data(datafile, dumpfile, adb_serial):
+ if datafile is not None and os.path.isfile(datafile):
+ print('Using cached inode data from ' + datafile)
+ f = open(datafile, 'r')
+ stat_dump = f.read();
+ else:
+ # Build inode maps if we were tracing page cache
+ print('Downloading inode data from device')
+ stat_dump = AdbUtils.do_preprocess_adb_cmd('find /system /data /vendor ' +
+ '-exec stat -c "%d %i %s %n" {} \;', adb_serial)
+ if stat_dump is None:
+ print 'Could not retrieve inode data from device.'
+ sys.exit(1)
+
+ if dumpfile is not None:
+ print 'Storing inode data in ' + dumpfile
+ f = open(dumpfile, 'w')
+ f.write(stat_dump)
+ f.close()
+
+ sys.stdout.write('Done.\n')
+
+ return stat_dump
+
+def read_and_parse_trace_data(atrace, pagecache_stats):
+ # Start reading trace data
+ stdout_queue = Queue.Queue(maxsize=128)
+ stderr_queue = Queue.Queue()
+
+ stdout_thread = FileReaderThread(atrace.stdout, stdout_queue,
+ text_file=True, chunk_size=64)
+ stderr_thread = FileReaderThread(atrace.stderr, stderr_queue,
+ text_file=True)
+ stdout_thread.start()
+ stderr_thread.start()
+
+ stdscr = curses.initscr()
+
+ try:
+ height, width = stdscr.getmaxyx()
+ curses.noecho()
+ curses.cbreak()
+ stdscr.keypad(True)
+ stdscr.nodelay(True)
+ stdscr.refresh()
+ # We need at least a 30x100 window
+ used_width = max(width, 100)
+ used_height = max(height, 30)
+
+ # Create a pad for pagecache stats
+ pagecache_pad = curses.newpad(used_height - 2, used_width)
+
+ stdscr.addstr(used_height - 1, 0, 'KEY SHORTCUTS: (r)eset stats, CTRL-c to quit')
+ while (stdout_thread.isAlive() or stderr_thread.isAlive() or
+ not stdout_queue.empty() or not stderr_queue.empty()):
+ while not stderr_queue.empty():
+ # Pass along errors from adb.
+ line = stderr_queue.get()
+ sys.stderr.write(line)
+ while True:
+ try:
+ line = stdout_queue.get(True, STATS_UPDATE_INTERVAL)
+ parse_atrace_line(line, pagecache_stats)
+ except Queue.Empty:
+ break
+
+ key = ''
+ try:
+ key = stdscr.getkey()
+ except:
+ pass
+
+ if key == 'r':
+ pagecache_stats.reset_stats()
+
+ pagecache_stats.print_stats(pagecache_pad)
+ except Exception, e:
+ curses.endwin()
+ print e
+ finally:
+ curses.endwin()
+ # The threads should already have stopped, so this is just for cleanup.
+ stdout_thread.join()
+ stderr_thread.join()
+
+ atrace.stdout.close()
+ atrace.stderr.close()
+
+
+def parse_options(argv):
+ usage = 'Usage: %prog [options]'
+ desc = 'Example: %prog'
+ parser = optparse.OptionParser(usage=usage, description=desc)
+ parser.add_option('-d', dest='inode_dump_file', metavar='FILE',
+ help='Dump the inode data read from a device to a file.'
+ ' This file can then be reused with the -i option to speed'
+ ' up future invocations of this script.')
+ parser.add_option('-i', dest='inode_data_file', metavar='FILE',
+ help='Read cached inode data from a file saved arlier with the'
+ ' -d option.')
+ parser.add_option('-s', '--serial', dest='device_serial', type='string',
+ help='adb device serial number')
+ options, categories = parser.parse_args(argv[1:])
+ if options.inode_dump_file and options.inode_data_file:
+ parser.error('options -d and -i can\'t be used at the same time')
+ return (options, categories)
+
+def main():
+ options, categories = parse_options(sys.argv)
+
+ # Load inode data for this device
+ inode_data = get_inode_data(options.inode_data_file, options.inode_dump_file,
+ options.device_serial)
+ # Build (dev, inode) -> filename hash
+ inode_lookup_table = build_inode_lookup_table(inode_data)
+ # Init pagecache stats
+ pagecache_stats = PagecacheStats(inode_lookup_table)
+
+ # Construct and execute trace command
+ trace_cmd = AdbUtils.construct_adb_shell_command(['atrace', '--stream', 'pagecache'],
+ options.device_serial)
+
+ try:
+ atrace = subprocess.Popen(trace_cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ except OSError as error:
+ print >> sys.stderr, ('The command failed')
+ sys.exit(1)
+
+ read_and_parse_trace_data(atrace, pagecache_stats)
+
+if __name__ == "__main__":
+ main()