using namespace android;
-#define CHECK_PERIOD 1 // in sec
-#define TRACING_CHECK_PERIOD 500000 // in micro sec
-#define MIN_BUFFER_SIZE 4
-#define MIN_BUFFER_SIZE_STR "4"
-#define MAX_BUFFER_SIZE 128
-#define MAX_BUFFER_SIZE_STR "128"
-#define CPU_STAT_ENTRIES 7 // number of cpu stat entries
-
#ifdef LOG_TAG
#undef LOG_TAG
#endif
#define LOG_TAG "anrdaemon"
+static const int check_period = 1; // in sec
+static const int tracing_check_period = 500000; // in micro sec
+static const int cpu_stat_entries = 7; // number of cpu stat entries
+static const int min_buffer_size = 16;
+static const int max_buffer_size = 2048;
+static const char *min_buffer_size_str = "16";
+static const char *max_buffer_size_str = "2048";
+
typedef struct cpu_stat {
unsigned long utime, ntime, stime, itime;
unsigned long iowtime, irqtime, sirqtime, steal;
unsigned long total;
} cpu_stat_t;
-/* Make the logging on/off threshold equal to 95% cpu usage. */
-static int idle_threshold = 5;
+/*
+ * Logging on/off threshold.
+ * Uint: 0.01%; default to 99.90% cpu.
+ */
+static int idle_threshold = 10;
static bool quit = false;
static bool suspend= false;
static char err_msg[100];
static bool tracing = false;
-static const char *buf_size_kb = "16";
+static const char *buf_size_kb = "2048";
+static const char *apps = "";
static uint64_t tag = 0;
-static const char* apps = "";
static cpu_stat_t new_cpu;
static cpu_stat_t old_cpu;
-static Vector<String16> targets;
/* Log certain kernel activity when enabled */
static bool log_sched = false;
} else {
if (fscanf(fp, params, &cpu->utime, &cpu->ntime,
&cpu->stime, &cpu->itime, &cpu->iowtime, &cpu->irqtime,
- &cpu->sirqtime) != CPU_STAT_ENTRIES) {
+ &cpu->sirqtime) != cpu_stat_entries) {
/*
* If failed in getting status, new_cpu won't be updated and
* is_heavy_loaded() will return false.
/*
* Calculate cpu usage in the past interval.
- * If tracing is on, increase the idle threshold by 1% so that we do not
+ * If tracing is on, increase the idle threshold by 1.00% so that we do not
* turn on and off tracing frequently whe the cpu load is right close to
* threshold.
*/
static bool is_heavy_load(void) {
unsigned long diff_idle, diff_total;
- int threshold = idle_threshold + (tracing?1:0);
+ int threshold = idle_threshold + (tracing?100:0);
get_cpu_stat(&new_cpu);
diff_idle = new_cpu.itime - old_cpu.itime;
diff_total = new_cpu.total - old_cpu.total;
old_cpu = new_cpu;
- return (diff_idle * 100 < diff_total * threshold);
+ return (diff_idle * 10000 < diff_total * threshold);
}
/*
* Force the userland processes to refresh their property for logging.
*/
-static void dfs_poke_binder(Vector<String16> services) {
+static void dfs_poke_binder(void) {
sp<IServiceManager> sm = defaultServiceManager();
- services = sm->listServices();
+ Vector<String16> services = sm->listServices();
for (size_t i = 0; i < services.size(); i++) {
sp<IBinder> obj = sm->checkService(services[i]);
if (obj != NULL) {
ssize_t len = strlen(control);
int max_try = 10; // Fail if write was interrupted for 10 times
while (write(fd, control, len) != len) {
- if (errno == EINTR && max_try-- > 0)
+ if (errno == EINTR && max_try-- > 0) {
+ usleep(100);
continue;
+ }
err = true;
sprintf(err_msg, "Error %d in writing to %s.", errno, path);
static void start_tracing(void) {
ALOGD("High cpu usage, start logging.");
- dfs_set_property(tag, apps, true);
- dfs_poke_binder(targets);
-
if (dfs_enable(true, dfs_control_path) != 0) {
ALOGE("Failed to start tracing.");
return;
/* Stop logging when cpu usage drops or the daemon is suspended.*/
do {
- usleep(TRACING_CHECK_PERIOD);
+ usleep(tracing_check_period);
} while (!suspend && is_heavy_load());
if (dfs_enable(false, dfs_control_path) != 0) {
ALOGE("Failed to stop tracing.");
}
- dfs_set_property(0, "", false);
- dfs_poke_binder(targets);
-
ALOGD("Usage back to low, stop logging.");
tracing = false;
}
static void start(void) {
if ((set_tracing_buffer_size()) != 0)
return;
+
+ dfs_set_property(tag, apps, true);
+ dfs_poke_binder();
+
get_cpu_stat(&old_cpu);
- sleep(CHECK_PERIOD);
+ sleep(check_period);
while (!quit && !err) {
if (!suspend && is_heavy_load()) {
start_tracing();
setpriority(PRIO_PROCESS, 0, 0);
}
- sleep(CHECK_PERIOD);
+ sleep(check_period);
}
return;
}
suspend = true;
while (tracing) {
ALOGI("Waiting logging to stop.");
- usleep(TRACING_CHECK_PERIOD);
+ usleep(tracing_check_period);
remain_attempts--;
if (remain_attempts == 0) {
ALOGE("Can't stop logging after 5 attempts. Dump aborted.");
}
/*
- * Create a dump file "dump_of_anrdaemon.<current_time>" under /data/anr/
+ * Create /sdcard/ANRdaemon/ if it doesn't exist
+ */
+ struct stat st;
+ if (stat("/sdcard/ANRdaemon", &st) == -1) {
+ ALOGI("Creating /sdcard/ANRdaemon/");
+ int err = mkdir("/sdcard/ANRdaemon", 0700);
+ if (err != 0)
+ ALOGI("Creating /sdcard/ANRdaemon/ failed with %s", strerror(err));
+ }
+
+ /*
+ * Create a dump file "dump_of_anrdaemon.<current_time>" under /sdcard/ANRdaemon/
*/
time_t now = time(0);
struct tm tstruct;
ssize_t header_len = strlen(header);
tstruct = *localtime(&now);
strftime(time_buf, sizeof(time_buf), "%Y-%m-%d.%X", &tstruct);
- sprintf(path_buf, "/data/anr/dump_of_anrdaemon.%s", time_buf);
+ sprintf(path_buf, "/sdcard/ANRdaemon/dump_of_anrdaemon.%s", time_buf);
int output_fd = creat(path_buf, S_IRWXU);
if (output_fd == -1) {
ALOGE("Failed to create %s. Dump aborted.", path_buf);
" -a appname enable app-level tracing for a comma "
"separated list of cmdlines\n"
" -t N cpu threshold for logging to start "
- "(min = 50, max = 100, default = 95)\n"
+ "(uint = 0.01%%, min = 5000, max = 9999, default = 9990)\n"
" -s N use a trace buffer size of N KB "
- "default to 16KB\n"
+ "default to 2048KB\n"
" -h show helps\n");
fprintf(stdout, "Categoris includes:\n"
" am - activity manager\n"
" sm - sync manager\n"
" input - input\n"
- " app - application\n"
" dalvik - dalvik VM\n"
+ " audio - Audio\n"
+ " gfx - Graphics\n"
+ " rs - RenderScript\n"
+ " hal - Hardware Modules\n"
" irq - kernel irq events\n"
" sched - kernel scheduler activity\n"
" stack - kernel stack\n"
apps = optarg;
break;
case 's':
- if (atoi(optarg) > MAX_BUFFER_SIZE)
- buf_size_kb = MAX_BUFFER_SIZE_STR;
- else if (atoi(optarg) < MIN_BUFFER_SIZE)
- buf_size_kb = MIN_BUFFER_SIZE_STR;
+ if (atoi(optarg) > max_buffer_size)
+ buf_size_kb = max_buffer_size_str;
+ else if (atoi(optarg) < min_buffer_size)
+ buf_size_kb = min_buffer_size_str;
else
buf_size_kb = optarg;
break;
case 't':
threshold = atoi(optarg);
- if (threshold > 100 || threshold < 50) {
- fprintf(stderr, "logging threshold should be 50-100\n");
+ if (threshold > 9999 || threshold < 5000) {
+ fprintf(stderr, "logging threshold should be 5000-9999\n");
return 1;
}
- idle_threshold = 100 - threshold;
+ idle_threshold = 10000 - threshold;
break;
case 'h':
show_help();
tag |= ATRACE_TAG_INPUT;
} else if (strcmp(argv[i], "sm") == 0) {
tag |= ATRACE_TAG_SYNC_MANAGER;
- } else if (strcmp(argv[i], "app") == 0) {
- tag |= ATRACE_TAG_APP;
} else if (strcmp(argv[i], "dalvik") == 0) {
tag |= ATRACE_TAG_DALVIK;
+ } else if (strcmp(argv[i], "gfx") == 0) {
+ tag |= ATRACE_TAG_GRAPHICS;
+ } else if (strcmp(argv[i], "audio") == 0) {
+ tag |= ATRACE_TAG_AUDIO;
+ } else if (strcmp(argv[i], "hal") == 0) {
+ tag |= ATRACE_TAG_HAL;
+ } else if (strcmp(argv[i], "rs") == 0) {
+ tag |= ATRACE_TAG_RS;
} else if (strcmp(argv[i], "sched") == 0) {
log_sched = true;
} else if (strcmp(argv[i], "stack") == 0) {
}
}
- bool kernel_log = log_sched || log_stack || log_workq || log_irq || log_sync;
- bool app_log = (tag == 0);
-
- /*
- * There are ~80 services. Too expensive to poke all of them. Just include
- * service that may help high CPU ANR analysis.
- */
- if (app_log) {
- targets.push_back(String16("activity"));
- targets.push_back(String16("alarm"));
- targets.push_back(String16("appops"));
- targets.push_back(String16("cpuinfo"));
- targets.push_back(String16("meminfo"));
- targets.push_back(String16("procstats"));
- targets.push_back(String16("input"));
- targets.push_back(String16("lancherapps"));
- targets.push_back(String16("bluetooth_manager"));
- targets.push_back(String16("SurfaceFlinger"));
- targets.push_back(String16("ClockworkProxyNativeService"));
- }
- if (!kernel_log && !app_log) {
- tag |= ATRACE_TAG_ACTIVITY_MANAGER;
- targets.push_back(String16("activity"));
+ /* If nothing is enabled, don't run */
+ if (!tag && !log_sched && !log_stack && !log_workq && !log_irq && !log_sync) {
+ ALOGE("Specify at least one category to trace.");
+ return 1;
}
return 0;
--- /dev/null
+#!/bin/bash
+
+TRACE_DIR=/sdcard/ANRdaemon
+
+if [ $# -eq 1 ]; then
+ DEVICE=$(echo "-s $1")
+else
+ DEVICE=""
+fi
+
+PID=$(adb $DEVICE shell "ps | grep anrd")
+
+if [ $? -ne 0 ]; then
+ echo "FAILED. ADB failed or Daemon is not running."
+ exit 1
+fi
+
+PID=$(echo "$PID" | awk '{ print $2 }')
+adb $DEVICE shell "kill -s SIGUSR1 $PID"
+
+TRACE_FILE=$(adb $DEVICE shell "ls $TRACE_DIR | tail -n1" | tr -d '\r')
+
+# Wiat the trace file generation to complete
+adb $DEVICE shell "lsof $PID" | grep $TRACE_FILE > /dev/null
+while [ $? -eq 0 ];
+do
+ sleep 1
+ adb $DEVICE shell "lsof $PID" | grep "$TRACE_FILE" > /dev/null
+done
+
+if [ -z "$TRACE_FILE" ]; then
+ echo "FAILED. Trace file not created"
+fi
+
+adb $DEVICE pull "${TRACE_DIR}/${TRACE_FILE}" ${TRACE_FILE}
+
+CURRENT_DIR=$(pwd)
+echo SUCCEED!
+echo Trace stored at ${CURRENT_DIR}/${TRACE_FILE}
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
+ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
-LOCAL_SRC_FILES:= ANRdaemon.cpp
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := ANRdaemon.cpp
LOCAL_C_INCLUDES += external/zlib
-
-LOCAL_MODULE:= anrdaemon
-
-LOCAL_MODULE_TAGS:= optional
-
+LOCAL_MODULE := anrd
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := debug
LOCAL_SHARED_LIBRARIES := \
liblog \
libbinder \
libcutils \
libutils \
- libz \
-
+ libz
include $(BUILD_EXECUTABLE)
+
+endif
--- /dev/null
+ANRdaemon is a daemon to help analyze ANR due to CPU starvation by logging system
+activity when CPU usage is very high. The daemon uses debugfs underlying for
+logging. Trace are configured ahead by setting different modules in /d/tracing.
+Depending on the CPU usage level, the trace is turn on/off by writting to the
+global control /d/trace/trace_on. The raw trace file is stored at
+/d/tracing/trace.
+
+The daemon will be started at boot time and will be running with the following
+settings:
+$ ANRdaemon -t 9990 sched gfx am
+This means tracing will be enabled above 99.90% CPU utilization and will trace
+sched, gfx and am modules (See -h for more info).
+
+Use ANRdaemon_get_trace.sh [device serial] to dump and fetch the compressed trace file.
+
+The compressed trace file can be parsed using systrace:
+$ systrace.py --from-file=<path to compressed trace file>
+
+Known issue: in the systrace output, anrdaemon will show up when the trace is
+not running. This is because the daemon process turns off tracing when CPU usage
+drops, the last entry it leaves in the raw trace file is the scheduler switched
+from some other process to the daemon. Then sometime later (say 20 secs later),
+when the CPU usage becomes high and the daemon process turn on tracing again,
+the first entry in /d/tracing/trace logged by sched is switching away from the
+daemon process to some other process. Due to this artifact, when the raw trace
+file is parsed by systrace.py, the daemon process is shown as running for the
+whole 20secs (because from systrace's view, the two 20 sec apart sched trace
+entries regarding the daemon process indicates the daemon process ran continuously
+for all 20sec). However, this will not affect the actual captured trace during
+high CPU usage case.
LOCAL_CLANG := true
LOCAL_MODULE := alloc-stress
LOCAL_CFLAGS += -g -Wall -Werror -std=gnu++11 -Wno-missing-field-initializers -Wno-sign-compare
+ifneq ($(ENABLE_MEM_CGROUPS),)
+ LOCAL_CFLAGS += -DENABLE_MEM_CGROUPS
+endif
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../include
LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
-LOCAL_SHARED_LIBRARIES := libhardware
+LOCAL_SHARED_LIBRARIES := libhardware libcutils
LOCAL_SRC_FILES := \
alloc-stress.cpp
include $(BUILD_EXECUTABLE)
+#include <arpa/inet.h>
#include <iostream>
#include <chrono>
+#include <cutils/sockets.h>
#include <hardware/gralloc.h>
#include <vector>
#include <tuple>
#include <fcntl.h>
#include <string>
#include <fstream>
+#include <sys/stat.h>
#include <sys/wait.h>
using namespace std;
char writeFdStr[16];
snprintf(readFdStr, sizeof(readFdStr), "%d", pipe.getReadFd());
snprintf(writeFdStr, sizeof(writeFdStr), "%d", pipe.getWriteFd());
-
execl(exName, exName, "--worker", arg, readFdStr, writeFdStr, nullptr);
ASSERT_TRUE(0);
}
}
-void writeToFile(const char * path, const char *data)
-{
- ofstream file(path);
- file << data;
- file.close();
+static void write_oomadj_to_lmkd(int oomadj) {
+ // Connect to lmkd and store our oom_adj
+ int lmk_procprio_cmd[4];
+ int sock;
+ int tries = 10;
+ while ((sock = socket_local_client("lmkd",
+ ANDROID_SOCKET_NAMESPACE_RESERVED,
+ SOCK_SEQPACKET)) < 0) {
+ usleep(100000);
+ if (tries-- < 0) break;
+ }
+ if (sock < 0) {
+ cout << "Failed to connect to lmkd, errno " << errno << endl;
+ exit(1);
+ }
+ lmk_procprio_cmd[0] = htonl(1);
+ lmk_procprio_cmd[1] = htonl(getpid());
+ lmk_procprio_cmd[2] = htonl(getuid());
+ lmk_procprio_cmd[3] = htonl(oomadj);
+
+ int written = write(sock, lmk_procprio_cmd, sizeof(lmk_procprio_cmd));
+ cout << "Wrote " << written << " bytes to lmkd control socket." << endl;
}
+#ifdef ENABLE_MEM_CGROUPS
+static void create_memcg() {
+ char buf[256];
+ pid_t pid = getpid();
+ snprintf(buf, sizeof(buf), "/dev/memctl/apps/%u", pid);
+
+ int tasks = mkdir(buf, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+ if (tasks < 0) {
+ cout << "Failed to create memory cgroup" << endl;
+ return;
+ }
+ snprintf(buf, sizeof(buf), "/dev/memctl/apps/%u/tasks", pid);
+ tasks = open(buf, O_WRONLY);
+ if (tasks < 0) {
+ cout << "Unable to add process to memory cgroup" << endl;
+ return;
+ }
+ snprintf(buf, sizeof(buf), "%u", pid);
+ write(tasks, buf, strlen(buf));
+ close(tasks);
+}
+#endif
+
size_t s = 4 * (1 << 20);
void *gptr;
int main(int argc, char *argv[])
{
if ((argc > 1) && (std::string(argv[1]) == "--worker")) {
+#ifdef ENABLE_MEM_CGROUPS
+ create_memcg();
+#endif
+ write_oomadj_to_lmkd(atoi(argv[2]));
Pipe p{atoi(argv[3]), atoi(argv[4])};
- writeToFile("/proc/self/oom_adj", argv[2]);
-
long long allocCount = 0;
while (1) {
p.wait();
allocCount += s;
}
} else {
- writeToFile("/proc/self/oom_adj", "-17");
cout << "parent:" << argc << endl;
- for (int i = 10; i >= 0; i--) {
+ write_oomadj_to_lmkd(-1000);
+ for (int i = 1000; i >= 0; i -= 100) {
auto pipes = Pipe::createPipePair();
char arg[16];
snprintf(arg, sizeof(arg), "%d", i);
sprintf(filename, "/sys/devices/system/cpu/cpu%d/cpufreq/stats/time_in_state", cpu);
file = fopen(filename, "r");
- if (!file) die("Could not open %s\n", filename);
for (i = 0; i < new_cpus[cpu].freq_count; i++) {
- fscanf(file, "%u %lu\n", &new_cpus[cpu].freqs[i].freq,
+ if (file) {
+ fscanf(file, "%u %lu\n", &new_cpus[cpu].freqs[i].freq,
&new_cpus[cpu].freqs[i].time);
+ } else {
+ /* The CPU has been off lined for some reason */
+ new_cpus[cpu].freqs[i].freq = old_cpus[cpu].freqs[i].freq;
+ new_cpus[cpu].freqs[i].time = old_cpus[cpu].freqs[i].time;
+ }
if (aggregate_freq_stats) {
new_total_cpu.freqs[i].freq = new_cpus[cpu].freqs[i].freq;
new_total_cpu.freqs[i].time += new_cpus[cpu].freqs[i].time;
}
}
- fclose(file);
+ if (file)
+ fclose(file);
}
/*
--- /dev/null
+LOCAL_PATH := $(call my-dir)
+ifeq ($(TARGET_ARCH),arm64)
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS := -O0 -march=armv8-a+crypto
+LOCAL_SRC_FILES := crypto.cpp
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE := crypto
+
+include $(BUILD_EXECUTABLE)
+endif
--- /dev/null
+
+ Copyright (c) 2012, 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
+
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sched.h>
+#include <sys/resource.h>
+#include <ctype.h>
+#define USEC_PER_SEC 1000000ULL
+#define MAX_COUNT 1000000000ULL
+#define NUM_INSTS_GARBAGE 18
+
+// Contains information about benchmark options.
+typedef struct {
+ int cpu_to_lock;
+ int locked_freq;
+} command_data_t;
+
+void usage() {
+ printf("--------------------------------------------------------------------------------\n");
+ printf("Usage:");
+ printf(" crypto [--cpu_to_lock CPU] [--locked_freq FREQ_IN_KHZ]\n\n");
+ printf("!!!!!!Lock the desired core to a desired frequency before invoking this benchmark.\n");
+ printf(
+ "Hint: Set scaling_max_freq=scaling_min_freq=FREQ_IN_KHZ. FREQ_IN_KHZ "
+ "can be obtained from scaling_available_freq\n");
+ printf("--------------------------------------------------------------------------------\n");
+}
+
+int processOptions(int argc, char **argv, command_data_t *cmd_data) {
+ // Initialize the command_flags.
+ cmd_data->cpu_to_lock = 0;
+ cmd_data->locked_freq = 1;
+ for (int i = 1; i < argc; i++) {
+ if (argv[i][0] == '-') {
+ int *save_value = NULL;
+ if (strcmp(argv[i], "--cpu_to_lock") == 0) {
+ save_value = &cmd_data->cpu_to_lock;
+ } else if (strcmp(argv[i], "--locked_freq") == 0) {
+ save_value = &cmd_data->locked_freq;
+ } else {
+ printf("Unknown option %s\n", argv[i]);
+ return -1;
+ }
+ if (save_value) {
+ // Checking both characters without a strlen() call should be
+ // safe since as long as the argument exists, one character will
+ // be present (\0). And if the first character is '-', then
+ // there will always be a second character (\0 again).
+ if (i == argc - 1 ||
+ (argv[i + 1][0] == '-' && !isdigit(argv[i + 1][1]))) {
+ printf("The option %s requires one argument.\n", argv[i]);
+ return -1;
+ }
+ *save_value = (int)strtol(argv[++i], NULL, 0);
+ }
+ }
+ }
+ return 0;
+}
+/* Performs encryption on garbage values. In Cortex-A57 r0p1 and later
+ * revisions, pairs of dependent AESE/AESMC and AESD/AESIMC instructions are
+ * higher performance when adjacent, and in the described order below. */
+void garbage_encrypt() {
+ __asm__ __volatile__(
+ "aese v0.16b, v4.16b ;"
+ "aesmc v0.16b, v0.16b ;"
+ "aese v1.16b, v4.16b ;"
+ "aesmc v1.16b, v1.16b ;"
+ "aese v2.16b, v4.16b ;"
+ "aesmc v2.16b, v2.16b ;"
+ "aese v0.16b, v5.16b ;"
+ "aesmc v0.16b, v0.16b ;"
+ "aese v1.16b, v5.16b ;"
+ "aesmc v1.16b, v1.16b ;"
+ "aese v2.16b, v5.16b ;"
+ "aesmc v2.16b, v2.16b ;"
+ "aese v0.16b, v6.16b ;"
+ "aesmc v0.16b, v0.16b ;"
+ "aese v1.16b, v6.16b ;"
+ "aesmc v1.16b, v1.16b ;"
+ "aese v2.16b, v6.16b ;"
+ "aesmc v2.16b, v2.16b ;");
+}
+
+void garbage_decrypt() {
+ __asm__ __volatile__(
+ "aesd v0.16b, v4.16b ;"
+ "aesimc v0.16b, v0.16b ;"
+ "aesd v1.16b, v4.16b ;"
+ "aesimc v1.16b, v1.16b ;"
+ "aesd v2.16b, v4.16b ;"
+ "aesimc v2.16b, v2.16b ;"
+ "aesd v0.16b, v5.16b ;"
+ "aesimc v0.16b, v0.16b ;"
+ "aesd v1.16b, v5.16b ;"
+ "aesimc v1.16b, v1.16b ;"
+ "aesd v2.16b, v5.16b ;"
+ "aesimc v2.16b, v2.16b ;"
+ "aesd v0.16b, v6.16b ;"
+ "aesimc v0.16b, v0.16b ;"
+ "aesd v1.16b, v6.16b ;"
+ "aesimc v1.16b, v1.16b ;"
+ "aesd v2.16b, v6.16b ;"
+ "aesimc v2.16b, v2.16b ;");
+}
+
+
+int main(int argc, char **argv) {
+ usage();
+ command_data_t cmd_data;
+
+ if(processOptions(argc, argv, &cmd_data) == -1) {
+ usage();
+ return -1;
+ }
+ unsigned long long count = 0;
+ struct timeval begin_time, end_time, elapsed_time;
+ cpu_set_t cpuset;
+ CPU_ZERO(&cpuset);
+ CPU_SET(cmd_data.cpu_to_lock, &cpuset);
+ if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) {
+ perror("sched_setaffinity failed");
+ return false;
+ }
+ gettimeofday(&begin_time, NULL);
+ while (count < MAX_COUNT) {
+ garbage_encrypt();
+ count++;
+ }
+ gettimeofday(&end_time, NULL);
+ timersub(&end_time, &begin_time, &elapsed_time);
+ fprintf(stderr, "encrypt: %llu us\n",
+ elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec);
+ fprintf(stderr, "encrypt instructions: %llu\n",
+ MAX_COUNT * NUM_INSTS_GARBAGE);
+ fprintf(stderr, "encrypt instructions per second: %f\n",
+ (float)(MAX_COUNT * NUM_INSTS_GARBAGE * USEC_PER_SEC) /
+ (elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec));
+ if (cmd_data.locked_freq != 0) {
+ fprintf(stderr, "encrypt instructions per cycle: %f\n",
+ (float)(MAX_COUNT * NUM_INSTS_GARBAGE * USEC_PER_SEC) /
+ ((elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec) *
+ 1000 * cmd_data.locked_freq));
+ }
+ printf("--------------------------------------------------------------------------------\n");
+
+ count = 0;
+ gettimeofday(&begin_time, NULL);
+ while (count < MAX_COUNT) {
+ garbage_decrypt();
+ count++;
+ }
+ gettimeofday(&end_time, NULL);
+ timersub(&end_time, &begin_time, &elapsed_time);
+ fprintf(stderr, "decrypt: %llu us\n",
+ elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec);
+ fprintf(stderr, "decrypt instructions: %llu\n",
+ MAX_COUNT * NUM_INSTS_GARBAGE);
+ fprintf(stderr, "decrypt instructions per second: %f\n",
+ (float)(MAX_COUNT * NUM_INSTS_GARBAGE * USEC_PER_SEC) /
+ (elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec));
+ if (cmd_data.locked_freq != 0) {
+ fprintf(stderr, "decrypt instructions per cycle: %f\n",
+ (float)(MAX_COUNT * NUM_INSTS_GARBAGE * USEC_PER_SEC) /
+ ((elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec) *
+ 1000 * cmd_data.locked_freq));
+ }
+ return 0;
+}
LOCAL_CFLAGS_linux := -DHOST
include $(BUILD_HOST_EXECUTABLE)
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := blk_alloc_to_base_fs.c
+LOCAL_MODULE := blk_alloc_to_base_fs
+LOCAL_SHARED_LIBRARIES += libcutils
+LOCAL_CFLAGS_darwin := -DHOST
+LOCAL_CFLAGS_linux := -DHOST
+include $(BUILD_HOST_EXECUTABLE)
#
# -- All host/targets excluding windows
libext4_utils_src_files += \
key_control.cpp \
- ext4_crypt.cpp \
- unencrypted_properties.cpp
+ ext4_crypt.cpp
ifneq ($(HOST_OS),windows)
# Various instances of dereferencing a type-punned pointer in extent.c
LOCAL_CFLAGS += -fno-strict-aliasing
LOCAL_SHARED_LIBRARIES := \
+ libbase \
libcutils \
libext2_uuid \
libselinux \
# Various instances of dereferencing a type-punned pointer in extent.c
LOCAL_CFLAGS += -fno-strict-aliasing
LOCAL_STATIC_LIBRARIES := \
+ libbase \
+ liblogwrap \
libsparse_static \
- libselinux
+ libselinux \
+ libbase
include $(BUILD_STATIC_LIBRARY)
* 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
+ * 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,
#include <stdio.h>
#include <stdlib.h>
-struct region {
- u32 block;
- u32 len;
- int bg;
- struct region *next;
- struct region *prev;
-};
-
-struct block_group_info {
- u32 first_block;
- int header_blocks;
- int data_blocks_used;
- int has_superblock;
- u8 *bitmaps;
- u8 *block_bitmap;
- u8 *inode_bitmap;
- u8 *inode_table;
- u32 free_blocks;
- u32 first_free_block;
- u32 free_inodes;
- u32 first_free_inode;
- u16 flags;
- u16 used_dirs;
-};
-
struct xattr_list_element {
struct ext4_inode *inode;
struct ext4_xattr_header *header;
reg->prev = NULL;
}
-static void region_list_append(struct region_list *list, struct region *reg)
+void region_list_append(struct region_list *list, struct region *reg)
{
if (list->first == NULL) {
list->first = reg;
reg->next = NULL;
}
+void region_list_merge(struct region_list *list1, struct region_list *list2)
+{
+ if (list1->first == NULL) {
+ list1->first = list2->first;
+ list1->last = list2->last;
+ list1->iter = list2->first;
+ list1->partial_iter = 0;
+ list1->first->prev = NULL;
+ } else {
+ list1->last->next = list2->first;
+ list2->first->prev = list1->last;
+ list1->last = list2->last;
+ }
+}
#if 0
static void dump_starting_from(struct region *reg)
{
}
#endif
-void print_blocks(FILE* f, struct block_allocation *alloc)
+void print_blocks(FILE* f, struct block_allocation *alloc, char separator)
{
struct region *reg;
+ fputc(' ', f);
for (reg = alloc->list.first; reg; reg = reg->next) {
if (reg->len == 1) {
- fprintf(f, " %d", reg->block);
+ fprintf(f, "%d", reg->block);
} else {
- fprintf(f, " %d-%d", reg->block, reg->block + reg->len - 1);
+ fprintf(f, "%d-%d", reg->block, reg->block + reg->len - 1);
}
+ fputc(separator, f);
}
fputc('\n', f);
}
/* Marks a the first num_blocks blocks in a block group as used, and accounts
for them in the block group free block info. */
-static int reserve_blocks(struct block_group_info *bg, u32 start, u32 num)
+static int reserve_blocks(struct block_group_info *bg, u32 bg_num, u32 start, u32 num)
{
unsigned int i = 0;
u32 block = start;
- if (num > bg->free_blocks)
- return -1;
-
for (i = 0; i < num && block % 8 != 0; i++, block++) {
if (bitmap_set_bit(bg->block_bitmap, block)) {
- error("attempted to reserve already reserved block");
+ error("attempted to reserve already reserved block %d in block group %d", block, bg_num);
return -1;
}
}
for (; i + 8 <= (num & ~7); i += 8, block += 8) {
if (bitmap_set_8_bits(bg->block_bitmap, block)) {
- error("attempted to reserve already reserved block");
+ error("attempted to reserve already reserved block %d in block group %d", block, bg_num);
return -1;
}
}
for (; i < num; i++, block++) {
if (bitmap_set_bit(bg->block_bitmap, block)) {
- error("attempted to reserve already reserved block");
+ error("attempted to reserve already reserved block %d in block group %d", block, bg_num);
return -1;
}
}
bg->free_blocks -= num;
- if (start == bg->first_free_block)
- bg->first_free_block = start + num;
return 0;
}
-static void free_blocks(struct block_group_info *bg, u32 num_blocks)
+static void free_blocks(struct block_group_info *bg, u32 block, u32 num_blocks)
{
unsigned int i;
- u32 block = bg->first_free_block - 1;
for (i = 0; i < num_blocks; i++, block--)
bg->block_bitmap[block / 8] &= ~(1 << (block % 8));
bg->free_blocks += num_blocks;
- bg->first_free_block -= num_blocks;
}
/* Reduces an existing allocation by len blocks by return the last blocks
{
while (len) {
struct region *last_reg = alloc->list.last;
+ struct block_group_info *bg = &aux_info.bgs[last_reg->bg];
if (last_reg->len > len) {
- free_blocks(&aux_info.bgs[last_reg->bg], len);
+ free_blocks(bg, last_reg->block + last_reg->len - bg->first_block - 1, len);
last_reg->len -= len;
len = 0;
} else {
struct region *reg = alloc->list.last->prev;
- free_blocks(&aux_info.bgs[last_reg->bg], last_reg->len);
+ free_blocks(bg, last_reg->block + last_reg->len - bg->first_block - 1, last_reg->len);
len -= last_reg->len;
if (reg) {
reg->next = NULL;
bg->data_blocks_used = 0;
bg->free_blocks = info.blocks_per_group;
- bg->first_free_block = 0;
bg->free_inodes = info.inodes_per_group;
bg->first_free_inode = 1;
bg->flags = EXT4_BG_INODE_UNINIT;
- if (reserve_blocks(bg, bg->first_free_block, bg->header_blocks) < 0)
+ bg->chunk_count = 0;
+ bg->max_chunk_count = 1;
+ bg->chunks = (struct region*) calloc(bg->max_chunk_count, sizeof(struct region));
+
+ if (reserve_blocks(bg, i, 0, bg->header_blocks) < 0)
error("failed to reserve %u blocks in block group %u\n", bg->header_blocks, i);
+ // Add empty starting delimiter chunk
+ reserve_bg_chunk(i, bg->header_blocks, 0);
if (bg->first_block + info.blocks_per_group > aux_info.len_blocks) {
u32 overrun = bg->first_block + info.blocks_per_group - aux_info.len_blocks;
- reserve_blocks(bg, info.blocks_per_group - overrun, overrun);
+ reserve_blocks(bg, i, info.blocks_per_group - overrun, overrun);
+ // Add empty ending delimiter chunk
+ reserve_bg_chunk(i, info.blocks_per_group - overrun, 0);
+ } else {
+ reserve_bg_chunk(i, info.blocks_per_group - 1, 0);
}
+
}
void block_allocator_init()
free(aux_info.bgs);
}
-static u32 ext4_allocate_blocks_from_block_group(u32 len, int bg_num)
-{
- if (get_free_blocks(bg_num) < len)
- return EXT4_ALLOCATE_FAILED;
-
- u32 block = aux_info.bgs[bg_num].first_free_block;
- struct block_group_info *bg = &aux_info.bgs[bg_num];
- if (reserve_blocks(bg, bg->first_free_block, len) < 0) {
- error("failed to reserve %u blocks in block group %u\n", len, bg_num);
- return EXT4_ALLOCATE_FAILED;
- }
-
- aux_info.bgs[bg_num].data_blocks_used += len;
-
- return bg->first_block + block;
-}
-
/* Allocate a single block and return its block number */
u32 allocate_block()
{
- unsigned int i;
- for (i = 0; i < aux_info.groups; i++) {
- u32 block = ext4_allocate_blocks_from_block_group(1, i);
-
- if (block != EXT4_ALLOCATE_FAILED)
- return block;
+ u32 block;
+ struct block_allocation *blk_alloc = allocate_blocks(1);
+ if (!blk_alloc) {
+ return EXT4_ALLOCATE_FAILED;
}
-
- return EXT4_ALLOCATE_FAILED;
+ block = blk_alloc->list.first->block;
+ free_alloc(blk_alloc);
+ return block;
}
static struct region *ext4_allocate_best_fit_partial(u32 len)
{
- unsigned int i;
- unsigned int found_bg = 0;
- u32 found_bg_len = 0;
+ unsigned int i, j;
+ unsigned int found_bg = 0, found_prev_chunk = 0, found_block = 0;
+ u32 found_allocate_len = 0;
+ bool minimize = false;
+ struct block_group_info *bgs = aux_info.bgs;
+ struct region *reg;
for (i = 0; i < aux_info.groups; i++) {
- u32 bg_len = aux_info.bgs[i].free_blocks;
-
- if ((len <= bg_len && (found_bg_len == 0 || bg_len < found_bg_len)) ||
- (len > found_bg_len && bg_len > found_bg_len)) {
- found_bg = i;
- found_bg_len = bg_len;
+ for (j = 1; j < bgs[i].chunk_count; j++) {
+ u32 hole_start, hole_size;
+ hole_start = bgs[i].chunks[j-1].block + bgs[i].chunks[j-1].len;
+ hole_size = bgs[i].chunks[j].block - hole_start;
+ if (hole_size == len) {
+ // Perfect fit i.e. right between 2 chunks no need to keep searching
+ found_bg = i;
+ found_prev_chunk = j - 1;
+ found_block = hole_start;
+ found_allocate_len = hole_size;
+ goto done;
+ } else if (hole_size > len && (found_allocate_len == 0 || (found_allocate_len > hole_size))) {
+ found_bg = i;
+ found_prev_chunk = j - 1;
+ found_block = hole_start;
+ found_allocate_len = hole_size;
+ minimize = true;
+ } else if (!minimize) {
+ if (found_allocate_len < hole_size) {
+ found_bg = i;
+ found_prev_chunk = j - 1;
+ found_block = hole_start;
+ found_allocate_len = hole_size;
+ }
+ }
}
}
- if (found_bg_len) {
- u32 allocate_len = min(len, found_bg_len);
- struct region *reg;
- u32 block = ext4_allocate_blocks_from_block_group(allocate_len, found_bg);
- if (block == EXT4_ALLOCATE_FAILED) {
- error("failed to allocate %d blocks in block group %d", allocate_len, found_bg);
- return NULL;
- }
- reg = malloc(sizeof(struct region));
- reg->block = block;
- reg->len = allocate_len;
- reg->next = NULL;
- reg->prev = NULL;
- reg->bg = found_bg;
- return reg;
- } else {
+ if (found_allocate_len == 0) {
error("failed to allocate %u blocks, out of space?", len);
+ return NULL;
}
-
- return NULL;
+ if (found_allocate_len > len) found_allocate_len = len;
+done:
+ // reclaim allocated space in chunk
+ bgs[found_bg].chunks[found_prev_chunk].len += found_allocate_len;
+ if (reserve_blocks(&bgs[found_bg],
+ found_bg,
+ found_block,
+ found_allocate_len) < 0) {
+ error("failed to reserve %u blocks in block group %u\n", found_allocate_len, found_bg);
+ return NULL;
+ }
+ bgs[found_bg].data_blocks_used += found_allocate_len;
+ reg = malloc(sizeof(struct region));
+ reg->block = found_block + bgs[found_bg].first_block;
+ reg->len = found_allocate_len;
+ reg->next = NULL;
+ reg->prev = NULL;
+ reg->bg = found_bg;
+ return reg;
}
static struct region *ext4_allocate_best_fit(u32 len)
/* Allocate len blocks. The blocks may be spread across multiple block groups,
and are returned in a linked list of the blocks in each block group. The
allocation algorithm is:
- 1. If the remaining allocation is larger than any available contiguous region,
- allocate the largest contiguous region and loop
- 2. Otherwise, allocate the smallest contiguous region that it fits in
+ 1. If the remaining allocation is larger than any available contiguous region,
+ allocate the largest contiguous region and loop
+ 2. Otherwise, allocate the smallest contiguous region that it fits in
*/
struct block_allocation *allocate_blocks(u32 len)
{
struct block_allocation *alloc = create_allocation();
alloc->list.first = reg;
+ while (reg->next != NULL)
+ reg = reg->next;
alloc->list.last = reg;
alloc->list.iter = alloc->list.first;
alloc->list.partial_iter = 0;
free(alloc);
}
+
+void reserve_bg_chunk(int bg, u32 start_block, u32 size) {
+ struct block_group_info *bgs = aux_info.bgs;
+ int chunk_count;
+ if (bgs[bg].chunk_count == bgs[bg].max_chunk_count) {
+ bgs[bg].max_chunk_count *= 2;
+ bgs[bg].chunks = realloc(bgs[bg].chunks, bgs[bg].max_chunk_count * sizeof(struct region));
+ if (!bgs[bg].chunks)
+ critical_error("realloc failed");
+ }
+ chunk_count = bgs[bg].chunk_count;
+ bgs[bg].chunks[chunk_count].block = start_block;
+ bgs[bg].chunks[chunk_count].len = size;
+ bgs[bg].chunks[chunk_count].bg = bg;
+ bgs[bg].chunk_count++;
+}
+
+int reserve_blocks_for_allocation(struct block_allocation *alloc) {
+ struct region *reg;
+ struct block_group_info *bgs = aux_info.bgs;
+
+ if (!alloc) return 0;
+ reg = alloc->list.first;
+ while (reg != NULL) {
+ if (reserve_blocks(&bgs[reg->bg], reg->bg, reg->block - bgs[reg->bg].first_block, reg->len) < 0) {
+ return -1;
+ }
+ reg = reg->next;
+ }
+ return 0;
+}
+
* 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
+ * 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,
#include "ext4_utils.h"
-struct region;
+struct region {
+ u32 block;
+ u32 len;
+ int bg;
+ struct region *next;
+ struct region *prev;
+};
struct region_list {
struct region *first;
struct block_allocation* next;
};
+struct block_group_info {
+ u32 first_block;
+ int header_blocks;
+ int data_blocks_used;
+ int has_superblock;
+ u8 *bitmaps;
+ u8 *block_bitmap;
+ u8 *inode_bitmap;
+ u8 *inode_table;
+ u32 free_blocks;
+ u32 free_inodes;
+ u32 first_free_inode;
+ u16 flags;
+ u16 used_dirs;
+ int chunk_count;
+ int max_chunk_count;
+ struct region *chunks;
+};
void block_allocator_init();
void block_allocator_free();
u32 block, u32 len, int bg);
struct block_allocation *create_allocation();
int append_oob_allocation(struct block_allocation *alloc, u32 len);
-void print_blocks(FILE* f, struct block_allocation *alloc);
-
+void region_list_append(struct region_list *list, struct region *reg);
+void region_list_merge(struct region_list *list1, struct region_list *list2);
+void print_blocks(FILE* f, struct block_allocation *alloc, char separator);
+void reserve_bg_chunk(int bg, u32 start_block, u32 size);
+int reserve_blocks_for_allocation(struct block_allocation *alloc);
#endif
--- /dev/null
+/*
+ * Copyright (C) 2016 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.
+ * 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.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define MAX_PATH 4096
+#define MAX_FILE_VERSION 100
+
+static void usage(char *filename)
+{
+ fprintf(stderr, "Usage: %s input_blk_alloc_file output_base_fs_file \n", filename);
+}
+
+int main(int argc, char **argv)
+{
+ FILE *blk_alloc_file = NULL, *base_fs_file = NULL;
+ char filename[MAX_PATH], file_version[MAX_FILE_VERSION], *spaced_allocs = NULL;
+ size_t spaced_allocs_len = 0;
+
+ if (argc != 3) {
+ usage(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ blk_alloc_file = fopen(argv[1], "r");
+ if (blk_alloc_file == NULL) {
+ fprintf(stderr, "failed to open %s: %s\n", argv[1], strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ base_fs_file = fopen(argv[2], "w");
+ if (base_fs_file == NULL) {
+ fprintf(stderr, "failed to open %s: %s\n", argv[2], strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ if (fscanf(blk_alloc_file, "Base EXT4 version %s", file_version) > 0) {
+ char c;
+ printf("%s is already in *.base_fs format, just copying into %s...\n", argv[1], argv[2]);
+ rewind(blk_alloc_file);
+ while ((c = fgetc(blk_alloc_file)) != EOF) {
+ fputc(c, base_fs_file);
+ }
+ return 0;
+ } else {
+ printf("Converting %s into *.base_fs format as %s...\n", argv[1], argv[2]);
+ rewind(blk_alloc_file);
+ }
+ fprintf(base_fs_file, "Base EXT4 version 1.0\n");
+ while(fscanf(blk_alloc_file, "%s ", filename) != EOF) {
+ int i;
+ fprintf(base_fs_file, "%s ", filename);
+ if (getline(&spaced_allocs, &spaced_allocs_len, blk_alloc_file) == -1) {
+ fprintf(stderr, "Bad blk_alloc format\n");
+ exit(EXIT_FAILURE);
+ }
+ for (i = 0; spaced_allocs[i]; i++) {
+ if (spaced_allocs[i] == ' ') {
+ if (!isspace(spaced_allocs[i + 1])) fputc(',', base_fs_file);
+ } else fputc(spaced_allocs[i], base_fs_file);
+ }
+ }
+ free(spaced_allocs);
+ fclose(blk_alloc_file);
+ fclose(base_fs_file);
+ return 0;
+}
/*
- * Copyright (c) 2015 Google, Inc.
+ * Copyright (C) 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.
+ * 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.
*/
-#define TAG "ext4_utils"
-
-#include "ext4_crypt_init_extensions.h"
+#include "ext4_crypt.h"
#include <dirent.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
-#include <cutils/klog.h>
-
-#include "unencrypted_properties.h"
+#include <android-base/logging.h>
+#include <cutils/properties.h>
#define XATTR_NAME_ENCRYPTION_POLICY "encryption.policy"
#define EXT4_KEYREF_DELIMITER ((char)'.')
// ext4enc:TODO Include structure from somewhere sensible
// MUST be in sync with ext4_crypto.c in kernel
#define EXT4_KEY_DESCRIPTOR_SIZE 8
+#define EXT4_KEY_DESCRIPTOR_SIZE_HEX 17
+
struct ext4_encryption_policy {
char version;
char contents_encryption_mode;
#define EXT4_ENCRYPTION_MODE_AES_256_CTS 4
// ext4enc:TODO Get value from somewhere sensible
-#define EXT4_IOC_SET_ENCRYPTION_POLICY \
- _IOR('f', 19, struct ext4_encryption_policy)
+#define EXT4_IOC_SET_ENCRYPTION_POLICY _IOR('f', 19, struct ext4_encryption_policy)
+#define EXT4_IOC_GET_ENCRYPTION_POLICY _IOW('f', 21, struct ext4_encryption_policy)
-/* Validate that all path items are available and accessible. */
-static int is_path_valid(const char *path)
-{
- if (access(path, W_OK)) {
- KLOG_ERROR(TAG, "Can't access %s: %s\n",strerror(errno), path);
- return 0;
- }
+#define HEX_LOOKUP "0123456789abcdef"
- return 1;
+bool e4crypt_is_native() {
+ char value[PROPERTY_VALUE_MAX];
+ property_get("ro.crypto.type", value, "none");
+ return !strcmp(value, "file");
}
-static int is_dir_empty(const char *dirname)
+static void policy_to_hex(const char* policy, char* hex) {
+ for (size_t i = 0, j = 0; i < EXT4_KEY_DESCRIPTOR_SIZE; i++) {
+ hex[j++] = HEX_LOOKUP[(policy[i] & 0xF0) >> 4];
+ hex[j++] = HEX_LOOKUP[policy[i] & 0x0F];
+ }
+ hex[EXT4_KEY_DESCRIPTOR_SIZE_HEX - 1] = '\0';
+}
+
+static bool is_dir_empty(const char *dirname, bool *is_empty)
{
int n = 0;
- struct dirent *d;
- DIR *dir;
-
- dir = opendir(dirname);
- while ((d = readdir(dir)) != NULL) {
- if (strcmp(d->d_name, "lost+found") == 0) {
- // Skip lost+found directory
- } else if (++n > 2) {
+ auto dirp = std::unique_ptr<DIR, int (*)(DIR*)>(opendir(dirname), closedir);
+ if (!dirp) {
+ PLOG(ERROR) << "Unable to read directory: " << dirname;
+ return false;
+ }
+ for (;;) {
+ errno = 0;
+ auto entry = readdir(dirp.get());
+ if (!entry) {
+ if (errno) {
+ PLOG(ERROR) << "Unable to read directory: " << dirname;
+ return false;
+ }
break;
}
+ if (strcmp(entry->d_name, "lost+found") != 0) { // Skip lost+found
+ ++n;
+ if (n > 2) {
+ *is_empty = false;
+ return true;
+ }
+ }
}
- closedir(dir);
- return n <= 2;
+ *is_empty = true;
+ return true;
}
-int do_policy_set(const char *directory, const char *policy, int policy_length)
-{
- struct stat st;
- ssize_t ret;
-
+static bool e4crypt_policy_set(const char *directory, const char *policy, size_t policy_length) {
if (policy_length != EXT4_KEY_DESCRIPTOR_SIZE) {
- KLOG_ERROR("Policy wrong length\n");
- return -EINVAL;
+ LOG(ERROR) << "Policy wrong length: " << policy_length;
+ return false;
}
-
- if (!is_path_valid(directory)) {
- return -EINVAL;
+ int fd = open(directory, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
+ if (fd == -1) {
+ PLOG(ERROR) << "Failed to open directory " << directory;
+ return false;
}
- stat(directory, &st);
- if (!S_ISDIR(st.st_mode)) {
- KLOG_ERROR(TAG, "Can only set policy on a directory (%s)\n", directory);
- return -EINVAL;
+ ext4_encryption_policy eep;
+ eep.version = 0;
+ eep.contents_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_XTS;
+ eep.filenames_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_CTS;
+ eep.flags = 0;
+ memcpy(eep.master_key_descriptor, policy, EXT4_KEY_DESCRIPTOR_SIZE);
+ if (ioctl(fd, EXT4_IOC_SET_ENCRYPTION_POLICY, &eep)) {
+ PLOG(ERROR) << "Failed to set encryption policy for " << directory;
+ close(fd);
+ return false;
}
+ close(fd);
+
+ char policy_hex[EXT4_KEY_DESCRIPTOR_SIZE_HEX];
+ policy_to_hex(policy, policy_hex);
+ LOG(INFO) << "Policy for " << directory << " set to " << policy_hex;
+ return true;
+}
- if (!is_dir_empty(directory)) {
- KLOG_ERROR(TAG, "Can only set policy on an empty directory (%s)\n",
- directory);
- return -EINVAL;
+static bool e4crypt_policy_get(const char *directory, char *policy, size_t policy_length) {
+ if (policy_length != EXT4_KEY_DESCRIPTOR_SIZE) {
+ LOG(ERROR) << "Policy wrong length: " << policy_length;
+ return false;
}
- int fd = open(directory, O_DIRECTORY);
+ int fd = open(directory, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
if (fd == -1) {
- KLOG_ERROR(TAG, "Failed to open directory (%s)\n", directory);
- return -EINVAL;
+ PLOG(ERROR) << "Failed to open directory " << directory;
+ return false;
}
ext4_encryption_policy eep;
- eep.version = 0;
- eep.contents_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_XTS;
- eep.filenames_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_CTS;
- eep.flags = 0;
- memcpy(eep.master_key_descriptor, policy, EXT4_KEY_DESCRIPTOR_SIZE);
- ret = ioctl(fd, EXT4_IOC_SET_ENCRYPTION_POLICY, &eep);
- auto preserve_errno = errno;
+ memset(&eep, 0, sizeof(ext4_encryption_policy));
+ if (ioctl(fd, EXT4_IOC_GET_ENCRYPTION_POLICY, &eep) != 0) {
+ PLOG(ERROR) << "Failed to get encryption policy for " << directory;
+ close(fd);
+ return -1;
+ }
close(fd);
- if (ret) {
- KLOG_ERROR(TAG, "Failed to set encryption policy for %s: %s\n",
- directory, strerror(preserve_errno));
- return -EINVAL;
+ if ((eep.version != 0)
+ || (eep.contents_encryption_mode != EXT4_ENCRYPTION_MODE_AES_256_XTS)
+ || (eep.filenames_encryption_mode != EXT4_ENCRYPTION_MODE_AES_256_CTS)
+ || (eep.flags != 0)) {
+ LOG(ERROR) << "Failed to find matching encryption policy for " << directory;
+ return false;
}
+ memcpy(policy, eep.master_key_descriptor, EXT4_KEY_DESCRIPTOR_SIZE);
- KLOG_INFO(TAG, "Encryption policy for %s is set to %02x%02x%02x%02x\n",
- directory, policy[0], policy[1], policy[2], policy[3]);
- return 0;
+ return true;
}
-bool e4crypt_non_default_key(const char* dir)
-{
- UnencryptedProperties props(dir);
- return props.Get<int>(properties::is_default, 1) != 1;
+static bool e4crypt_policy_check(const char *directory, const char *policy, size_t policy_length) {
+ if (policy_length != EXT4_KEY_DESCRIPTOR_SIZE) {
+ LOG(ERROR) << "Policy wrong length: " << policy_length;
+ return false;
+ }
+ char existing_policy[EXT4_KEY_DESCRIPTOR_SIZE];
+ if (!e4crypt_policy_get(directory, existing_policy, EXT4_KEY_DESCRIPTOR_SIZE)) return false;
+ char existing_policy_hex[EXT4_KEY_DESCRIPTOR_SIZE_HEX];
+
+ policy_to_hex(existing_policy, existing_policy_hex);
+
+ if (memcmp(policy, existing_policy, EXT4_KEY_DESCRIPTOR_SIZE) != 0) {
+ char policy_hex[EXT4_KEY_DESCRIPTOR_SIZE_HEX];
+ policy_to_hex(policy, policy_hex);
+ LOG(ERROR) << "Found policy " << existing_policy_hex << " at " << directory
+ << " which doesn't match expected value " << policy_hex;
+ return false;
+ }
+ LOG(INFO) << "Found policy " << existing_policy_hex << " at " << directory
+ << " which matches expected value";
+ return true;
+}
+
+int e4crypt_policy_ensure(const char *directory, const char *policy, size_t policy_length) {
+ bool is_empty;
+ if (!is_dir_empty(directory, &is_empty)) return -1;
+ if (is_empty) {
+ if (!e4crypt_policy_set(directory, policy, policy_length)) return -1;
+ } else {
+ if (!e4crypt_policy_check(directory, policy, policy_length)) return -1;
+ }
+ return 0;
}
--- /dev/null
+/*
+ * Copyright (C) 2016 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.
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+#include <stdbool.h>
+#include <cutils/multiuser.h>
+
+__BEGIN_DECLS
+
+bool e4crypt_is_native();
+
+int e4crypt_policy_ensure(const char *directory, const char* policy, size_t policy_length);
+
+static const char* e4crypt_unencrypted_folder = "/unencrypted";
+static const char* e4crypt_key_ref = "/unencrypted/ref";
+
+__END_DECLS
+/*
+ * Copyright (C) 2016 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.
+ * 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.
+ */
+
#define TAG "ext4_utils"
#include "ext4_crypt_init_extensions.h"
+#include "ext4_crypt.h"
+
+#include <android-base/logging.h>
#include <string>
+#include <vector>
#include <dirent.h>
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
+#include <android-base/file.h>
+
#include <cutils/klog.h>
#include <cutils/properties.h>
#include <cutils/sockets.h>
-#include <poll.h>
+#include <logwrap/logwrap.h>
#include "key_control.h"
-#include "unencrypted_properties.h"
static const std::string arbitrary_sequence_number = "42";
static const int vold_command_timeout_ms = 60 * 1000;
-static std::string vold_command(std::string const& command)
-{
- KLOG_INFO(TAG, "Running command %s\n", command.c_str());
- int sock = -1;
-
- while (true) {
- sock = socket_local_client("cryptd",
- ANDROID_SOCKET_NAMESPACE_RESERVED,
- SOCK_STREAM);
- if (sock >= 0) {
- break;
- }
- usleep(10000);
- }
-
- if (sock < 0) {
- KLOG_INFO(TAG, "Cannot open vold, failing command (%s)\n", strerror(errno));
- return "";
- }
-
- class CloseSocket
- {
- int sock_;
- public:
- explicit CloseSocket(int sock) : sock_(sock) {}
- ~CloseSocket() { close(sock_); }
- };
-
- CloseSocket cs(sock);
-
- // Use arbitrary sequence number. This should only be used when the
- // framework is down, so this is (mostly) OK.
- std::string actual_command = arbitrary_sequence_number + " " + command;
- if (write(sock, actual_command.c_str(), actual_command.size() + 1) < 0) {
- KLOG_ERROR(TAG, "Cannot write command (%s)\n", strerror(errno));
- return "";
- }
-
- struct pollfd poll_sock = {sock, POLLIN, 0};
-
- int rc = TEMP_FAILURE_RETRY(poll(&poll_sock, 1, vold_command_timeout_ms));
- if (rc < 0) {
- KLOG_ERROR(TAG, "Error in poll (%s)\n", strerror(errno));
- return "";
- }
-
- if (!(poll_sock.revents & POLLIN)) {
- KLOG_ERROR(TAG, "Timeout\n");
- return "";
- }
- char buffer[4096];
- memset(buffer, 0, sizeof(buffer));
- rc = TEMP_FAILURE_RETRY(read(sock, buffer, sizeof(buffer)));
- if (rc <= 0) {
- if (rc == 0) {
- KLOG_ERROR(TAG, "Lost connection to Vold - did it crash?\n");
- } else {
- KLOG_ERROR(TAG, "Error reading data (%s)\n", strerror(errno));
- }
- return "";
+static void kernel_logger(android::base::LogId, android::base::LogSeverity severity, const char*,
+ const char*, unsigned int, const char* message) {
+ if (severity == android::base::ERROR || severity == android::base::FATAL) {
+ KLOG_ERROR(TAG, "%s\n", message);
+ } else if (severity == android::base::WARNING) {
+ KLOG_WARNING(TAG, "%s\n", message);
+ } else {
+ KLOG_INFO(TAG, "%s\n", message);
}
+}
- // We don't truly know that this is the correct result. However,
- // since this will only be used when the framework is down,
- // it should be OK unless someone is running vdc at the same time.
- // Worst case we force a reboot in the very rare synchronization
- // error
- return std::string(buffer, rc);
+static void init_logging() {
+ android::base::SetLogger(kernel_logger);
}
int e4crypt_create_device_key(const char* dir,
int ensure_dir_exists(const char*))
{
- // Already encrypted with password? If so bail
- std::string temp_folder = std::string() + dir + "/tmp_mnt";
- DIR* temp_dir = opendir(temp_folder.c_str());
- if (temp_dir) {
- closedir(temp_dir);
- return 0;
- }
+ init_logging();
// Make sure folder exists. Use make_dir to set selinux permissions.
- if (ensure_dir_exists(UnencryptedProperties::GetPath(dir).c_str())) {
+ std::string unencrypted_dir = std::string(dir) + e4crypt_unencrypted_folder;
+ if (ensure_dir_exists(unencrypted_dir.c_str())) {
KLOG_ERROR(TAG, "Failed to create %s (%s)\n",
- UnencryptedProperties::GetPath(dir).c_str(),
+ unencrypted_dir.c_str(),
strerror(errno));
return -1;
}
- auto result = vold_command("cryptfs enablefilecrypto");
- // ext4enc:TODO proper error handling
- KLOG_INFO(TAG, "enablefilecrypto returned with result %s\n",
- result.c_str());
-
- return 0;
+ const char* argv[] = { "/system/bin/vdc", "--wait", "cryptfs", "enablefilecrypto" };
+ int rc = android_fork_execvp(4, (char**) argv, NULL, false, true);
+ LOG(INFO) << "enablefilecrypto result: " << rc;
+ return rc;
}
int e4crypt_install_keyring()
{
+ init_logging();
+
key_serial_t device_keyring = add_key("keyring", "e4crypt", 0, 0,
KEY_SPEC_SESSION_KEYRING);
return -1;
}
- KLOG_INFO(TAG, "Keyring created wth id %d in process %d\n",
+ KLOG_INFO(TAG, "Keyring created with id %d in process %d\n",
device_keyring, getpid());
return 0;
}
+int e4crypt_do_init_user0()
+{
+ init_logging();
+
+ const char* argv[] = { "/system/bin/vdc", "--wait", "cryptfs", "init_user0" };
+ int rc = android_fork_execvp(4, (char**) argv, NULL, false, true);
+ LOG(INFO) << "init_user0 result: " << rc;
+ return rc;
+}
+
int e4crypt_set_directory_policy(const char* dir)
{
+ init_logging();
+
// Only set policy on first level /data directories
// To make this less restrictive, consider using a policy file.
// However this is overkill for as long as the policy is simply
return 0;
}
- // Don't encrypt lost+found - ext4 doesn't like it
- if (!strcmp(dir, "/data/lost+found")) {
- return 0;
- }
-
- // ext4enc:TODO exclude /data/user with a horrible special case.
- if (!strcmp(dir, "/data/user")) {
- return 0;
+ // Special case various directories that must not be encrypted,
+ // often because their subdirectories must be encrypted.
+ // This isn't a nice way to do this, see b/26641735
+ std::vector<std::string> directories_to_exclude = {
+ "lost+found",
+ "system_ce", "system_de",
+ "misc_ce", "misc_de",
+ "media",
+ "data", "user", "user_de",
+ };
+ std::string prefix = "/data/";
+ for (auto d: directories_to_exclude) {
+ if ((prefix + d) == dir) {
+ KLOG_INFO(TAG, "Not setting policy on %s\n", dir);
+ return 0;
+ }
}
- UnencryptedProperties props("/data");
- std::string policy = props.Get<std::string>(properties::ref);
- if (policy.empty()) {
- // ext4enc:TODO why is this OK?
- return 0;
+ std::string ref_filename = std::string("/data") + e4crypt_key_ref;
+ std::string policy;
+ if (!android::base::ReadFileToString(ref_filename, &policy)) {
+ KLOG_ERROR(TAG, "Unable to read system policy to set on %s\n", dir);
+ return -1;
}
-
KLOG_INFO(TAG, "Setting policy on %s\n", dir);
- int result = do_policy_set(dir, policy.c_str(), policy.size());
+ int result = e4crypt_policy_ensure(dir, policy.c_str(), policy.size());
if (result) {
KLOG_ERROR(TAG, "Setting %02x%02x%02x%02x policy on %s failed!\n",
policy[0], policy[1], policy[2], policy[3], dir);
return 0;
}
-
-int e4crypt_set_user_crypto_policies(const char* dir)
-{
- auto command = std::string() + "cryptfs setusercryptopolicies " + dir;
- auto result = vold_command(command);
- // ext4enc:TODO proper error handling
- KLOG_INFO(TAG, "setusercryptopolicies returned with result %s\n",
- result.c_str());
- return 0;
-}
+/*
+ * Copyright (C) 2016 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.
+ * 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.
+ */
+
#include <sys/cdefs.h>
#include <stdbool.h>
+#include <cutils/multiuser.h>
__BEGIN_DECLS
int e4crypt_create_device_key(const char* path,
int ensure_dir_exists(const char* dir));
int e4crypt_set_directory_policy(const char* path);
-bool e4crypt_non_default_key(const char* path);
-int do_policy_set(const char *directory, const char *policy, int policy_length);
-int e4crypt_set_user_crypto_policies(const char* path);
+int e4crypt_do_init_user0();
__END_DECLS
struct fs_info info;
struct fs_aux_info aux_info;
struct sparse_file *ext4_sparse_file;
+struct block_allocation *base_fs_allocations = NULL;
jmp_buf setjmp_env;
extern struct fs_info info;
extern struct fs_aux_info aux_info;
extern struct sparse_file *ext4_sparse_file;
+extern struct block_allocation *base_fs_allocations;
extern jmp_buf setjmp_env;
const char *mountpoint, fs_config_func_t fs_config_func, int gzip,
int sparse, int crc, int wipe, int real_uuid,
struct selabel_handle *sehnd, int verbose, time_t fixed_time,
- FILE* block_list_file);
+ FILE* block_list_file, FILE* base_alloc_file_in, FILE* base_alloc_file_out);
int read_ext(int fd, int verbose);
* 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
+ * 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,
}
static struct block_allocation *do_inode_allocate_extents(
- struct ext4_inode *inode, u64 len)
+ struct ext4_inode *inode, u64 len, struct block_allocation *prealloc)
{
- u32 block_len = DIV_ROUND_UP(len, info.block_size);
- struct block_allocation *alloc = allocate_blocks(block_len + 1);
+ u32 block_len = DIV_ROUND_UP(len, info.block_size), prealloc_block_len;
+ struct block_allocation *alloc;
u32 extent_block = 0;
u32 file_block = 0;
struct ext4_extent *extent;
u64 blocks;
- if (alloc == NULL) {
- error("Failed to allocate %d blocks\n", block_len + 1);
- return NULL;
+ if (!prealloc) {
+ alloc = allocate_blocks(block_len + 1);
+ if (alloc == NULL) {
+ error("Failed to allocate %d blocks\n", block_len + 1);
+ return NULL;
+ }
+ } else {
+ prealloc_block_len = block_allocation_len(prealloc);
+ if (block_len + 1 > prealloc_block_len) {
+ alloc = allocate_blocks(block_len + 1 - prealloc_block_len);
+ if (alloc == NULL) {
+ error("Failed to allocate %d blocks\n",
+ block_len + 1 - prealloc_block_len);
+ return NULL;
+ }
+ region_list_merge(&prealloc->list, &alloc->list);
+ free(alloc);
+ }
+ alloc = prealloc;
}
int allocation_len = block_allocation_num_regions(alloc);
if (allocation_len <= 3) {
reduce_allocation(alloc, 1);
+ // IMPORTANT: reduce_allocation may have changed allocation
+ // length, otherwise file corruption happens when fs thinks
+ // a block is missing from extent header.
+ allocation_len = block_allocation_num_regions(alloc);
} else {
reserve_oob_blocks(alloc, 1);
extent_block = get_oob_block(alloc, 0);
struct block_allocation *alloc;
u8 *data = NULL;
- alloc = do_inode_allocate_extents(inode, len);
+ alloc = do_inode_allocate_extents(inode, len, NULL);
if (alloc == NULL) {
error("failed to allocate extents for %"PRIu64" bytes", len);
return NULL;
struct block_allocation* inode_allocate_file_extents(struct ext4_inode *inode, u64 len,
const char *filename)
{
- struct block_allocation *alloc;
+ struct block_allocation *alloc, *prealloc = base_fs_allocations, *prev_prealloc = NULL;
+ // TODO(mkayyash): base_fs_allocations is sorted by filename, consider
+ // storing it in an array and then binary searching for a filename match instead
+ while (prealloc && prealloc->filename != NULL) {
+ if (!strcmp(filename, prealloc->filename)) {
+ break;
+ }
+ prev_prealloc = prealloc;
+ prealloc = prealloc->next;
+ }
+ if (prealloc) {
+ if (!prev_prealloc) {
+ base_fs_allocations = base_fs_allocations->next;
+ } else {
+ prev_prealloc->next = prealloc->next;
+ }
+ prealloc->next = NULL;
+ }
- alloc = do_inode_allocate_extents(inode, len);
+ alloc = do_inode_allocate_extents(inode, len, prealloc);
if (alloc == NULL) {
error("failed to allocate extents for %"PRIu64" bytes", len);
return NULL;
{
struct block_allocation *alloc;
- alloc = do_inode_allocate_extents(inode, len);
+ alloc = do_inode_allocate_extents(inode, len, NULL);
if (alloc == NULL) {
error("failed to allocate extents for %"PRIu64" bytes", len);
return;
* 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
+ * 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,
#endif
+#define MAX_PATH 4096
+#define MAX_BLK_MAPPING_STR 1000
+
+const int blk_file_major_ver = 1;
+const int blk_file_minor_ver = 0;
+const char *blk_file_header_fmt = "Base EXT4 version %d.%d";
+
/* TODO: Not implemented:
Allocating blocks in the same block group as the file inode
Hash or binary tree directories
}
static u32 build_default_directory_structure(const char *dir_path,
- struct selabel_handle *sehnd)
+ struct selabel_handle *sehnd)
{
u32 inode;
u32 root_inode;
int make_ext4fs_sparse_fd(int fd, long long len,
const char *mountpoint, struct selabel_handle *sehnd)
{
+ return make_ext4fs_sparse_fd_directory(fd, len, mountpoint, sehnd, NULL);
+}
+
+int make_ext4fs_sparse_fd_directory(int fd, long long len,
+ const char *mountpoint, struct selabel_handle *sehnd,
+ const char *directory)
+{
reset_ext4fs_info();
info.len = len;
- return make_ext4fs_internal(fd, NULL, NULL, mountpoint, NULL, 0, 1, 0, 0, 0, sehnd, 0, -1, NULL);
+ return make_ext4fs_internal(fd, directory, NULL, mountpoint, NULL,
+ 0, 1, 0, 0, 0,
+ sehnd, 0, -1, NULL, NULL, NULL);
}
int make_ext4fs(const char *filename, long long len,
const char *mountpoint, struct selabel_handle *sehnd)
{
+ return make_ext4fs_directory(filename, len, mountpoint, sehnd, NULL);
+}
+
+int make_ext4fs_directory(const char *filename, long long len,
+ const char *mountpoint, struct selabel_handle *sehnd,
+ const char *directory)
+{
int fd;
int status;
return EXIT_FAILURE;
}
- status = make_ext4fs_internal(fd, NULL, NULL, mountpoint, NULL, 0, 0, 0, 1, 0, sehnd, 0, -1, NULL);
+ status = make_ext4fs_internal(fd, directory, NULL, mountpoint, NULL,
+ 0, 0, 0, 1, 0,
+ sehnd, 0, -1, NULL, NULL, NULL);
close(fd);
return status;
return canonicalize_slashes(str, false);
}
+static int compare_chunks(const void* chunk1, const void* chunk2) {
+ struct region* c1 = (struct region*) chunk1;
+ struct region* c2 = (struct region*) chunk2;
+ return c1->block - c2->block;
+}
+
+static int get_block_group(u32 block) {
+ int i, group = 0;
+ for(i = 0; i < aux_info.groups; i++) {
+ if (block >= aux_info.bgs[i].first_block)
+ group = i;
+ else
+ break;
+ }
+ return group;
+}
+
+static void extract_base_fs_allocations(const char *directory, const char *mountpoint,
+ FILE* base_alloc_file_in) {
+#define err_msg "base file badly formatted"
+#ifndef USE_MINGW
+ // FORMAT Version 1.0: filename blk_mapping
+ const char *base_alloc_file_in_format = "%s %s";
+ const int base_file_format_param_count = 2;
+
+ char stored_file_name[MAX_PATH], real_file_name[MAX_PATH], file_map[MAX_BLK_MAPPING_STR];
+ struct block_allocation *fs_alloc;
+ struct block_group_info *bgs = aux_info.bgs;
+ int i, major_version = 0, minor_version = 0;
+ char *base_file_line = NULL;
+ size_t base_file_line_len = 0;
+
+ printf("[v%d.%d] Generating an Incremental EXT4 image\n",
+ blk_file_major_ver, blk_file_minor_ver);
+ if (base_fs_allocations == NULL)
+ base_fs_allocations = create_allocation();
+ fs_alloc = base_fs_allocations;
+
+ fscanf(base_alloc_file_in, blk_file_header_fmt, &major_version, &minor_version);
+ if (major_version == 0) {
+ critical_error("Invalid base file");
+ }
+
+ if (major_version != blk_file_major_ver) {
+ critical_error("Incompatible base file: version required is %d.X",
+ blk_file_major_ver);
+ }
+
+ if (minor_version < blk_file_minor_ver) {
+ critical_error("Incompatible base file: version required is %d.%d or above",
+ blk_file_major_ver, blk_file_minor_ver);
+ }
+
+ while (getline(&base_file_line, &base_file_line_len, base_alloc_file_in) != -1) {
+ if (sscanf(base_file_line, base_alloc_file_in_format, &stored_file_name, &file_map)
+ != base_file_format_param_count) {
+ continue;
+ }
+ if (strlen(stored_file_name) < strlen(mountpoint)) {
+ continue;
+ }
+ snprintf(real_file_name, MAX_PATH, "%s%s", directory, stored_file_name + strlen(mountpoint));
+ if (!access(real_file_name, R_OK)) {
+ char *block_range, *end_string;
+ int real_file_fd;
+ u32 start_block, end_block, block_file_size;
+ u32 real_file_block_size;
+
+ real_file_fd = open(real_file_name, O_RDONLY);
+ if (real_file_fd == -1) {
+ critical_error(err_msg);
+ }
+ real_file_block_size = get_file_size(real_file_fd);
+ close(real_file_fd);
+ real_file_block_size = DIV_ROUND_UP(real_file_block_size, info.block_size);
+ fs_alloc->filename = strdup(real_file_name);
+ block_range = strtok_r(file_map, ",", &end_string);
+ while (block_range && real_file_block_size) {
+ int block_group;
+ char *range, *end_token = NULL;
+ range = strtok_r(block_range, "-", &end_token);
+ if (!range) {
+ critical_error(err_msg);
+ }
+ start_block = parse_num(range);
+ range = strtok_r(NULL, "-", &end_token);
+ if (!range) {
+ end_block = start_block;
+ } else {
+ end_block = parse_num(range);
+ }
+ // Assummption is that allocations are within the same block group
+ block_group = get_block_group(start_block);
+ if (block_group != get_block_group(end_block)) {
+ critical_error("base file allocation's end block is in a different "
+ "block group than start block. did you change fs params?");
+ }
+ block_range = strtok_r(NULL, ",", &end_string);
+ int bg_first_block = bgs[block_group].first_block;
+ int min_bg_bound = bgs[block_group].chunks[0].block + bgs[block_group].chunks[0].len;
+ int max_bg_bound = bgs[block_group].chunks[bgs[block_group].chunk_count - 1].block;
+
+ if (min_bg_bound >= start_block - bg_first_block ||
+ max_bg_bound <= end_block - bg_first_block) {
+ continue;
+ }
+ block_file_size = end_block - start_block + 1;
+ if (block_file_size > real_file_block_size) {
+ block_file_size = real_file_block_size;
+ }
+ append_region(fs_alloc, start_block, block_file_size, block_group);
+ reserve_bg_chunk(block_group, start_block - bgs[block_group].first_block, block_file_size);
+ real_file_block_size -= block_file_size;
+ }
+ if (reserve_blocks_for_allocation(fs_alloc) < 0)
+ critical_error("failed to reserve base fs allocation");
+ fs_alloc->next = create_allocation();
+ fs_alloc = fs_alloc->next;
+ }
+ }
+
+ for (i = 0; i < aux_info.groups; i++) {
+ qsort(bgs[i].chunks, bgs[i].chunk_count, sizeof(struct region), compare_chunks);
+ }
+
+ free(base_file_line);
+
+#else
+ return;
+#endif
+#undef err_msg
+}
+
+void generate_base_alloc_file_out(FILE* base_alloc_file_out, char* dir, char* mountpoint,
+ struct block_allocation* p)
+{
+ size_t dirlen = dir ? strlen(dir) : 0;
+ fprintf(base_alloc_file_out, blk_file_header_fmt, blk_file_major_ver, blk_file_minor_ver);
+ fputc('\n', base_alloc_file_out);
+ while (p) {
+ if (dir && strncmp(p->filename, dir, dirlen) == 0) {
+ // substitute mountpoint for the leading directory in the filename, in the output file
+ fprintf(base_alloc_file_out, "%s%s", mountpoint, p->filename + dirlen);
+ } else {
+ fprintf(base_alloc_file_out, "%s", p->filename);
+ }
+ print_blocks(base_alloc_file_out, p, ',');
+ struct block_allocation* pn = p->next;
+ p = pn;
+ }
+}
+
int make_ext4fs_internal(int fd, const char *_directory, const char *_target_out_directory,
const char *_mountpoint, fs_config_func_t fs_config_func, int gzip,
int sparse, int crc, int wipe, int real_uuid,
struct selabel_handle *sehnd, int verbose, time_t fixed_time,
- FILE* block_list_file)
+ FILE* block_list_file, FILE* base_alloc_file_in, FILE* base_alloc_file_out)
{
u32 root_inode_num;
u16 root_mode;
char *mountpoint;
char *directory = NULL;
char *target_out_directory = NULL;
+ struct block_allocation* p;
if (setjmp(setjmp_env))
return EXIT_FAILURE; /* Handle a call to longjmp() */
ext4_fill_in_sb(real_uuid);
+ if (base_alloc_file_in) {
+ extract_base_fs_allocations(directory, mountpoint, base_alloc_file_in);
+ }
if (reserve_inodes(0, 10) == EXT4_ALLOCATE_FAILED)
error("failed to reserve first 10 inodes");
ext4_update_free();
+ // TODO: Consider migrating the OTA tools to the new base alloc file format
+ // used for generating incremental images (see go/incremental-ext4)
if (block_list_file) {
size_t dirlen = directory ? strlen(directory) : 0;
struct block_allocation* p = get_saved_allocation_chain();
} else {
fprintf(block_list_file, "%s", p->filename);
}
- print_blocks(block_list_file, p);
+ print_blocks(block_list_file, p, ' ');
struct block_allocation* pn = p->next;
- free_alloc(p);
p = pn;
}
}
+ if (base_alloc_file_out) {
+ struct block_allocation* p = get_saved_allocation_chain();
+ generate_base_alloc_file_out(base_alloc_file_out, directory, mountpoint, p);
+ }
+
printf("Created filesystem with %d/%d inodes and %d/%d blocks\n",
aux_info.sb->s_inodes_count - aux_info.sb->s_free_inodes_count,
aux_info.sb->s_inodes_count,
sparse_file_destroy(ext4_sparse_file);
ext4_sparse_file = NULL;
+ p = get_saved_allocation_chain();
+ while (p) {
+ struct block_allocation* pn = p->next;
+ free_alloc(p);
+ p = pn;
+ }
+
free(mountpoint);
free(directory);
int make_ext4fs(const char *filename, long long len,
const char *mountpoint, struct selabel_handle *sehnd);
+int make_ext4fs_directory(const char *filename, long long len,
+ const char *mountpoint, struct selabel_handle *sehnd,
+ const char *directory);
int make_ext4fs_sparse_fd(int fd, long long len,
const char *mountpoint, struct selabel_handle *sehnd);
+int make_ext4fs_sparse_fd_directory(int fd, long long len,
+ const char *mountpoint, struct selabel_handle *sehnd,
+ const char *directory);
#ifdef __cplusplus
}
fprintf(stderr, " [ -L <label> ] [ -f ] [ -a <android mountpoint> ] [ -u ]\n");
fprintf(stderr, " [ -S file_contexts ] [ -C fs_config ] [ -T timestamp ]\n");
fprintf(stderr, " [ -z | -s ] [ -w ] [ -c ] [ -J ] [ -v ] [ -B <block_list_file> ]\n");
+ fprintf(stderr, " [ -d <base_alloc_file_in> ] [ -D <base_alloc_file_out> ]\n");
fprintf(stderr, " <filename> [[<directory>] <target_out_directory>]\n");
}
time_t fixed_time = -1;
struct selabel_handle *sehnd = NULL;
FILE* block_list_file = NULL;
+ FILE* base_alloc_file_in = NULL;
+ FILE* base_alloc_file_out = NULL;
#ifndef USE_MINGW
struct selinux_opt seopts[] = { { SELABEL_OPT_PATH, "" } };
#endif
- while ((opt = getopt(argc, argv, "l:j:b:g:i:I:L:a:S:T:C:B:fwzJsctvu")) != -1) {
+ while ((opt = getopt(argc, argv, "l:j:b:g:i:I:L:a:S:T:C:B:d:D:fwzJsctvu")) != -1) {
switch (opt) {
case 'l':
info.len = parse_num(optarg);
exit(EXIT_FAILURE);
}
break;
+ case 'd':
+ base_alloc_file_in = fopen(optarg, "r");
+ if (base_alloc_file_in == NULL) {
+ fprintf(stderr, "failed to open base_alloc_file_in: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'D':
+ base_alloc_file_out = fopen(optarg, "w");
+ if (base_alloc_file_out == NULL) {
+ fprintf(stderr, "failed to open base_alloc_file_out: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ break;
default: /* '?' */
usage(argv[0]);
exit(EXIT_FAILURE);
}
exitcode = make_ext4fs_internal(fd, directory, target_out_directory, mountpoint, fs_config_func, gzip,
- sparse, crc, wipe, real_uuid, sehnd, verbose, fixed_time, block_list_file);
+ sparse, crc, wipe, real_uuid, sehnd, verbose, fixed_time,
+ block_list_file, base_alloc_file_in, base_alloc_file_out);
close(fd);
if (block_list_file)
fclose(block_list_file);
+ if (base_alloc_file_out)
+ fclose(base_alloc_file_out);
+ if (base_alloc_file_in)
+ fclose(base_alloc_file_in);
if (exitcode && strcmp(filename, "-"))
unlink(filename);
return exitcode;
cat<<EOT
Usage:
mkuserimg.sh [-s] SRC_DIR OUTPUT_FILE EXT_VARIANT MOUNT_POINT SIZE [-j <journal_size>]
- [-T TIMESTAMP] [-C FS_CONFIG] [-D PRODUCT_OUT] [-B BLOCK_LIST_FILE] [-L LABEL] [FILE_CONTEXTS]
+ [-T TIMESTAMP] [-C FS_CONFIG] [-D PRODUCT_OUT] [-B BLOCK_LIST_FILE] [-d BASE_ALLOC_FILE_IN ] [-A BASE_ALLOC_FILE_OUT ] [-L LABEL] [FILE_CONTEXTS]
EOT
}
shift; shift
fi
+BASE_ALLOC_FILE_IN=
+if [[ "$1" == "-d" ]]; then
+ BASE_ALLOC_FILE_IN=$2
+ shift; shift
+fi
+
+BASE_ALLOC_FILE_OUT=
+if [[ "$1" == "-A" ]]; then
+ BASE_ALLOC_FILE_OUT=$2
+ shift; shift
+fi
+
LABEL=
if [[ "$1" == "-L" ]]; then
LABEL=$2
if [ -n "$BLOCK_LIST" ]; then
OPT="$OPT -B $BLOCK_LIST"
fi
+if [ -n "$BASE_ALLOC_FILE_IN" ]; then
+ OPT="$OPT -d $BASE_ALLOC_FILE_IN"
+fi
+if [ -n "$BASE_ALLOC_FILE_OUT" ]; then
+ OPT="$OPT -D $BASE_ALLOC_FILE_OUT"
+fi
if [ -n "$LABEL" ]; then
OPT="$OPT -L $LABEL"
fi
+++ /dev/null
-#include "unencrypted_properties.h"
-
-#include <sys/stat.h>
-#include <dirent.h>
-
-namespace properties {
- const char* key = "key";
- const char* ref = "ref";
- const char* props = "props";
- const char* is_default = "is_default";
-}
-
-namespace
-{
- const char* unencrypted_folder = "unencrypted";
-}
-
-std::string UnencryptedProperties::GetPath(const char* device)
-{
- return std::string() + device + "/" + unencrypted_folder;
-}
-
-UnencryptedProperties::UnencryptedProperties(const char* device)
- : folder_(GetPath(device))
-{
- DIR* dir = opendir(folder_.c_str());
- if (dir) {
- closedir(dir);
- } else {
- folder_.clear();
- }
-}
-
-UnencryptedProperties::UnencryptedProperties()
-{
-}
-
-template<> std::string UnencryptedProperties::Get(const char* name,
- std::string default_value) const
-{
- if (!OK()) return default_value;
- std::ifstream i(folder_ + "/" + name, std::ios::binary);
- if (!i) {
- return default_value;
- }
-
- i.seekg(0, std::ios::end);
- int length = i.tellg();
- i.seekg(0, std::ios::beg);
- if (length == -1) {
- return default_value;
- }
-
- std::string s(length, 0);
- i.read(&s[0], length);
- if (!i) {
- return default_value;
- }
-
- return s;
-}
-
-template<> bool UnencryptedProperties::Set(const char* name, std::string const& value)
-{
- if (!OK()) return false;
- std::ofstream o(folder_ + "/" + name, std::ios::binary);
- o << value;
- return !o.fail();
-}
-
-UnencryptedProperties UnencryptedProperties::GetChild(const char* name) const
-{
- UnencryptedProperties up;
- if (!OK()) return up;
-
- std::string directory(folder_ + "/" + name);
- if (mkdir(directory.c_str(), 700) == -1 && errno != EEXIST) {
- return up;
- }
-
- up.folder_ = directory;
- return up;
-}
-
-bool UnencryptedProperties::Remove(const char* name)
-{
- if (!OK()) return false;
- if (remove((folder_ + "/" + name).c_str())
- && errno != ENOENT) {
- return false;
- }
-
- return true;
-}
-
-bool UnencryptedProperties::OK() const
-{
- return !folder_.empty();
-}
+++ /dev/null
-#include <string>
-#include <fstream>
-
-// key names for properties we use
-namespace properties {
- extern const char* key;
- extern const char* ref;
- extern const char* props;
- extern const char* is_default;
-}
-
-/**
- * Class to store data on the unencrypted folder of a device.
- * Note that the folder must exist before this class is constructed.
- * All names must be valid single level (no '/') file or directory names
- * Data is organized hierarchically so we can get a child folder
- */
-class UnencryptedProperties
-{
-public:
- // Get path of folder. Must create before using any properties
- // This is to allow proper setting of SELinux policy
- static std::string GetPath(const char* device);
-
- // Opens properties folder on named device.
- // If folder does not exist, OK will return false, all
- // getters will return default properties and setters will fail.
- explicit UnencryptedProperties(const char* device);
-
- // Get named object. Return default if object does not exist or error.
- template<typename t> t Get(const char* name, t default_value = t()) const;
-
- // Set named object. Return true if success, false otherwise
- template<typename t> bool Set(const char* name, t const& value);
-
- // Get child properties
- UnencryptedProperties GetChild(const char* name) const;
-
- // Remove named object
- bool Remove(const char* name);
-
- // Does folder exist?
- bool OK() const;
-
-private:
- UnencryptedProperties();
- std::string folder_;
-};
-
-
-template<typename t> t UnencryptedProperties::Get(const char* name,
- t default_value) const
-{
- if (!OK()) return default_value;
- t value = default_value;
- std::ifstream(folder_ + "/" + name) >> value;
- return value;
-}
-
-template<typename t> bool UnencryptedProperties::Set(const char* name,
- t const& value)
-{
- if (!OK()) return false;
- std::ofstream o(folder_ + "/" + name);
- o << value;
- return !o.fail();
-}
-
-// Specialized getters/setters for strings
-template<> std::string UnencryptedProperties::Get(const char* name,
- std::string default_value) const;
-
-template<> bool UnencryptedProperties::Set(const char* name,
- std::string const& value);
#
LOCAL_PATH:= $(call my-dir)
-
include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := mmapPerf.cpp
LOCAL_MODULE := mmapPerf
-LOCAL_SRC_FILES_64 := mmapPerf.cpp
-LOCAL_SRC_FILES_32 := unsupported.cpp
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
LOCAL_CLANG := true
LOCAL_CFLAGS += -g -Wall -Werror -std=c++11 -Wno-missing-field-initializers -Wno-sign-compare -O3
LOCAL_FORCE_STATIC_EXECUTABLE := true
LOCAL_CXX_STL := libc++_static
LOCAL_STATIC_LIBRARIES := libc
-LOCAL_MODULE_TAGS := optional
-include $(BUILD_EXECUTABLE)
+include $(BUILD_NATIVE_BENCHMARK)
+#include "benchmark/benchmark_api.h"
#include <string>
#include <cstring>
#include <cstdlib>
#include <sys/mman.h>
using namespace std;
-static const size_t pageSize = 4096;
+static const size_t pageSize = PAGE_SIZE;
+static size_t fsize = 1024 * (1ull << 20);
+static size_t pagesTotal = fsize / pageSize;
class Fd {
int m_fd = -1;
FileMap(const string &name, size_t size, Hint hint = FILE_MAP_HINT_NONE) : m_name{name}, m_size{size} {
int fd = open(name.c_str(), O_CREAT | O_RDWR, S_IRWXU);
if (fd < 0) {
- cerr << "open failed: " << fd << endl;
- return;
+ cout << "Error: open failed for " << name << ": " << strerror(errno) << endl;
+ exit(1);
}
m_fileFd.set(fd);
fallocate(m_fileFd.get(), 0, 0, size);
unlink(name.c_str());
m_ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fileFd.get(), 0);
if ((int)(uintptr_t)m_ptr == -1) {
- cerr << "mmap failed: " << (int)(uintptr_t)m_ptr << endl;
- m_ptr = nullptr;
- return;
+ cout << "Error: mmap failed: " << (int)(uintptr_t)m_ptr << ": " << strerror(errno) << endl;
+ exit(1);
}
switch (hint) {
case FILE_MAP_HINT_NONE: break;
fillPageJunk(targetPtr);
}
}
- void benchRandom(bool write) {
- size_t pagesTotal = m_size / pageSize;
- size_t pagesToHit = pagesTotal / 128;
- uint64_t nsTotal = 0;
-
- chrono::time_point<chrono::high_resolution_clock> start, end;
- start = chrono::high_resolution_clock::now();
- for (int j = 0; j < pagesToHit; j++) {
- int targetPage = rand() % pagesTotal;
- uint8_t *targetPtr = (uint8_t*)m_ptr + 4096ull * targetPage;
- if (write) {
- *targetPtr = dummy;
- }
- else {
- dummy += *targetPtr;
- }
- }
- end = chrono::high_resolution_clock::now();
- nsTotal += chrono::duration_cast<chrono::nanoseconds>(end - start).count();
- //cout << "random: " << nsTotal / 1000.0 / (pagesToHit) << "us/page" << endl;
- cout << "random " << (write ? "write" : "read") << ": " << ((4096.0 * pagesToHit) / (1 << 20)) / (nsTotal / 1.0E9) << "MB/s" << endl;
+ void benchRandomRead(unsigned int targetPage) {
+ uint8_t *targetPtr = (uint8_t*)m_ptr + pageSize * targetPage;
+ dummy += *targetPtr;
}
- void benchLinear(bool write) {
- int pagesTotal = m_size / pageSize;
- int iterations = 4;
- uint64_t nsTotal = 0;
-
- chrono::time_point<chrono::high_resolution_clock> start, end;
- start = chrono::high_resolution_clock::now();
- for (int i = 0; i < iterations; i++) {
- for (int j = 0; j < pagesTotal; j++) {
- uint8_t *targetPtr = (uint8_t*)m_ptr + 4096ull * j;
- if (write) {
- *targetPtr = dummy;
- }
- else {
- dummy += *targetPtr;
- }
- }
- }
- end = chrono::high_resolution_clock::now();
- nsTotal += chrono::duration_cast<chrono::nanoseconds>(end - start).count();
- //cout << "linear: " << nsTotal / 1000.0 / (pagesTotal * iterations) << "us/page" << endl;
- cout << "linear " << (write ? "write" : "read") << ": " << ((4096.0 * pagesTotal * iterations) / (1 << 20)) / (nsTotal / 1.0E9 ) << "MB/s" << endl;
+ void benchRandomWrite(unsigned int targetPage) {
+ uint8_t *targetPtr = (uint8_t*)m_ptr + pageSize * targetPage;
+ *targetPtr = dummy;
+ }
+ void benchLinearRead(unsigned int j) {
+ uint8_t *targetPtr = (uint8_t*)m_ptr + pageSize * j;
+ dummy += *targetPtr;
+ }
+ void benchLinearWrite(unsigned int j) {
+ uint8_t *targetPtr = (uint8_t*)m_ptr + pageSize * j;
+ *targetPtr = dummy;
}
void dropCache() {
int ret1 = msync(m_ptr, m_size, MS_SYNC | MS_INVALIDATE);
};
-int main(int argc, char *argv[])
-{
- (void)argc;
- (void)argv;
- srand(0);
-
- {
- FileMap file{"/data/local/tmp/mmap_test", 16000 * (1ull << 20)};
- file.benchRandom(false);
- }
- {
- FileMap file{"/data/local/tmp/mmap_test", 16000 * (1ull << 20)};
- file.benchLinear(false);
+static void benchRandomRead(benchmark::State& state) {
+ FileMap file{"/data/local/tmp/mmap_test", fsize};
+ while (state.KeepRunning()) {
+ unsigned int targetPage = rand() % pagesTotal;
+ file.benchRandomRead(targetPage);
}
- {
- FileMap file{"/data/local/tmp/mmap_test", 16000 * (1ull << 20)};
- file.benchRandom(true);
- }
- {
- FileMap file{"/data/local/tmp/mmap_test", 16000 * (1ull << 20)};
- file.benchLinear(true);
+ state.SetBytesProcessed(state.iterations() * pageSize);
+}
+BENCHMARK(benchRandomRead);
+
+static void benchRandomWrite(benchmark::State& state) {
+ FileMap file{"/data/local/tmp/mmap_test", fsize};
+ while (state.KeepRunning()) {
+ unsigned int targetPage = rand() % pagesTotal;
+ file.benchRandomWrite(targetPage);
}
- return 0;
+ state.SetBytesProcessed(state.iterations() * pageSize);
+}
+BENCHMARK(benchRandomWrite);
+
+static void benchLinearRead(benchmark::State& state) {
+ FileMap file{"/data/local/tmp/mmap_test", fsize};
+ unsigned int j = 0;
+ while (state.KeepRunning()) {
+ file.benchLinearRead(j);
+ j = (j + 1) % pagesTotal;
+ }
+ state.SetBytesProcessed(state.iterations() * pageSize);
+}
+BENCHMARK(benchLinearRead);
+
+static void benchLinearWrite(benchmark::State& state) {
+ FileMap file{"/data/local/tmp/mmap_test", fsize};
+ unsigned int j = 0;
+ while (state.KeepRunning()) {
+ file.benchLinearWrite(j);
+ j = (j + 1) % pagesTotal;
+ }
+ state.SetBytesProcessed(state.iterations() * pageSize);
}
+BENCHMARK(benchLinearWrite);
+
+BENCHMARK_MAIN()
--- /dev/null
+LOCAL_PATH := $(call my-dir)
+
+# The PDK build does not have access to frameworks/native elements.
+ifneq ($(TARGET_BUILD_PDK), true)
+
+# Sample util binaries.
+include $(CLEAR_VARS)
+LOCAL_MODULE := dnschk
+
+LOCAL_C_INCLUDES += frameworks/native/include external/libcxx/include
+LOCAL_CPPFLAGS += -std=c++11
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := debug
+LOCAL_SHARED_LIBRARIES := libandroid libbase libc++
+LOCAL_SRC_FILES := dnschk.cpp common.cpp
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := httpurl
+
+LOCAL_C_INCLUDES += frameworks/native/include external/libcxx/include
+LOCAL_CPPFLAGS += -std=c++11
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := debug
+LOCAL_SHARED_LIBRARIES := libandroid libbase libc++
+LOCAL_SRC_FILES := httpurl.cpp common.cpp
+include $(BUILD_EXECUTABLE)
+
+endif # ifneq ($(TARGET_BUILD_PDK), true)
--- /dev/null
+/*
+ * Copyright (C) 2016 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.
+ * 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.
+ */
+
+
+#include "common.h"
+
+#include <android/api-level.h>
+#include <arpa/inet.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <iostream>
+
+
+namespace {
+
+bool strEqual(const char *a, const char *b) {
+ return strcmp(a, b) == 0;
+}
+
+// Allow specifying network handles in decimal and hexadecimal.
+bool parseNetworkHandle(const char *arg, net_handle_t *nethandle) {
+ if (arg == nullptr || !isdigit(arg[0]) || nethandle == nullptr) {
+ return false;
+ }
+
+ net_handle_t nh;
+ char *end = nullptr;
+
+ nh = strtoull(arg, &end, 0);
+ if (end != nullptr && *end == '\0') {
+ *nethandle = nh;
+ return true;
+ }
+ return false;
+}
+
+} // namespace
+
+
+void printUsage(const char *progname) {
+ std::cerr << "Usage: " << progname
+ << " [--nethandle <nethandle>]"
+ << " [--mode explicit|process]"
+ << " [--family unspec|ipv4|ipv6]"
+ << " <argument>"
+ << std::endl;
+ std::cerr << std::endl;
+ std::cerr << "Learn nethandle values from 'dumpsys connectivity --short' "
+ << "or 'dumpsys connectivity --diag'"
+ << std::endl;
+}
+
+Arguments::~Arguments() {}
+
+bool Arguments::parseArguments(int argc, const char* argv[]) {
+ if (argc < 1 || argv == nullptr) { return false; }
+
+ for (int i = 1; i < argc; i++) {
+ if (strEqual(argv[i], "--nethandle")) {
+ i++;
+ if (argc == i) break;
+ if (!parseNetworkHandle(argv[i], &nethandle)) {
+ std::cerr << "Failed to parse nethandle: '" << argv[i] << "'"
+ << std::endl;
+ break;
+ }
+ } else if (strEqual(argv[i], "--family")) {
+ i++;
+ if (argc == i) break;
+ if (strEqual(argv[i], "unspec")) {
+ family = AF_UNSPEC;
+ } else if (strEqual(argv[i], "ipv4")) {
+ family = AF_INET;
+ } else if (strEqual(argv[i], "ipv6")) {
+ family = AF_INET6;
+ } else {
+ break;
+ }
+ } else if (strEqual(argv[i], "--mode")) {
+ i++;
+ if (argc == i) break;
+ if (strEqual(argv[i], "explicit")) {
+ api_mode = ApiMode::EXPLICIT;
+ } else if (strEqual(argv[i], "process")) {
+ api_mode = ApiMode::PROCESS;
+ } else {
+ break;
+ }
+ } else if (arg1 == nullptr) {
+ arg1 = argv[i];
+ } else {
+ arg1 = nullptr;
+ break;
+ }
+ }
+
+ if (arg1 != nullptr) {
+ return true;
+ }
+
+ printUsage(argv[0]);
+ return false;
+}
+
+
+std::string inetSockaddrToString(const sockaddr* sa) {
+ const bool is_ipv6 = (sa->sa_family == AF_INET6);
+ char host[INET6_ADDRSTRLEN];
+ char port[sizeof("65535")];
+ getnameinfo(sa, is_ipv6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in),
+ host, sizeof(host),
+ port, sizeof(port),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+
+ if (port[0] == '0' || port[0] == '\0') {
+ return std::string(host);
+ }
+ return (is_ipv6 ? "[" : "") + std::string(host) + (is_ipv6 ? "]:" : ":") + std::string(port);
+}
--- /dev/null
+/*
+ * Copyright (C) 2016 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.
+ * 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.
+ */
+
+
+#ifndef SYSTEM_EXTRAS_MULTINETWORK_COMMON_H_
+#define SYSTEM_EXTRAS_MULTINETWORK_COMMON_H_
+
+#include <sys/cdefs.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <string>
+#include <android/multinetwork.h>
+
+enum class ApiMode {
+ EXPLICIT,
+ PROCESS,
+};
+
+
+struct Arguments {
+ Arguments() : nethandle(NETWORK_UNSPECIFIED),
+ api_mode(ApiMode::EXPLICIT),
+ family(AF_UNSPEC),
+ arg1(nullptr) {}
+ ~Arguments();
+
+ bool parseArguments(int argc, const char* argv[]);
+
+ net_handle_t nethandle;
+ ApiMode api_mode;
+ sa_family_t family;
+ const char* arg1;
+};
+
+
+void printUsage(const char *progname);
+
+// If port is non-zero returns strings of the form "192.0.2.1:port" or
+// "[2001:db8::1]:port", else it returns the bare IP string literal.
+std::string inetSockaddrToString(const sockaddr* sa);
+
+
+struct FdAutoCloser {
+ FdAutoCloser() : fd(-1) {}
+ /* not explicit */ FdAutoCloser(int fd) : fd(fd) {}
+ ~FdAutoCloser() {
+ if (fd > -1) {
+ close(fd);
+ }
+ fd = -1;
+ }
+
+ int fd;
+};
+
+#endif // SYSTEM_EXTRAS_MULTINETWORK_COMMON_H_
--- /dev/null
+/*
+ * Copyright (C) 2016 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.
+ * 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.
+ */
+
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <sys/socket.h>
+
+#include <iostream>
+#include <string>
+
+#include <android/multinetwork.h>
+#include "common.h"
+
+
+int main(int argc, const char* argv[]) {
+ int rval = -1;
+
+ struct Arguments args;
+ if (!args.parseArguments(argc, argv)) { return rval; }
+
+ const struct addrinfo hints = {
+ .ai_family = args.family,
+ .ai_socktype = SOCK_DGRAM,
+ };
+ struct addrinfo *result = nullptr;
+
+ std::cout << "# " << args.arg1
+ << " (via nethandle " << args.nethandle << "):"
+ << std::endl;
+
+ switch (args.api_mode) {
+ case ApiMode::EXPLICIT:
+ rval = android_getaddrinfofornetwork(args.nethandle,
+ args.arg1, nullptr, &hints, &result);
+ break;
+ case ApiMode::PROCESS:
+ if (args.nethandle != NETWORK_UNSPECIFIED) {
+ rval = android_setprocnetwork(args.nethandle);
+ if (rval != 0) {
+ std::cerr << "android_setprocnetwork returned " << rval
+ << std::endl;
+ return rval;
+ }
+ }
+ rval = getaddrinfo(args.arg1, nullptr, &hints, &result);
+ break;
+ default:
+ // Unreachable.
+ std::cerr << "Unknown api mode." << std::endl;
+ return -1;
+ }
+
+ if (rval != 0) {
+ std::cerr << "DNS resolution failure; gaierror=" << rval
+ << " [" << gai_strerror(rval) << "]"
+ << std::endl;
+ return rval;
+ }
+
+ for (struct addrinfo* rp = result; rp != nullptr; rp = rp->ai_next) {
+ std::cout << inetSockaddrToString(rp->ai_addr) << std::endl;
+ }
+
+ freeaddrinfo(result);
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) 2016 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.
+ * 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.
+ */
+
+
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <iostream>
+#include <string>
+
+#include <android/multinetwork.h>
+#include <android-base/stringprintf.h>
+#include "common.h"
+
+
+
+struct Parameters {
+ Parameters() : ss({}), port("80"), path("/") {}
+
+ struct sockaddr_storage ss;
+ std::string host;
+ std::string hostname;
+ std::string port;
+ std::string path;
+};
+
+
+bool parseUrl(const struct Arguments& args, struct Parameters* parameters) {
+ if (parameters == nullptr) { return false; }
+
+ static const char HTTP_PREFIX[] = "http://";
+ if (strncmp(args.arg1, HTTP_PREFIX, strlen(HTTP_PREFIX)) != 0) {
+ std::cerr << "Only " << HTTP_PREFIX << " URLs supported." << std::endl;
+ return false;
+ }
+
+ parameters->host = std::string(args.arg1).substr(strlen(HTTP_PREFIX));
+ const auto first_slash = parameters->host.find_first_of("/");
+ if (first_slash != std::string::npos) {
+ parameters->path = parameters->host.substr(first_slash);
+ parameters->host.erase(first_slash);
+ }
+
+ if (parameters->host.size() == 0) {
+ std::cerr << "Host portion cannot be empty." << std::endl;
+ return false;
+ }
+
+ if (parameters->host[0] == '[') {
+ const auto closing_bracket = parameters->host.find_first_of("]");
+ if (closing_bracket == std::string::npos) {
+ std::cerr << "Missing closing bracket." << std::endl;
+ return false;
+ }
+ parameters->hostname = parameters->host.substr(1, closing_bracket - 1);
+
+ const auto colon_port = closing_bracket + 1;
+ if (colon_port < parameters->host.size()) {
+ if (parameters->host[colon_port] != ':') {
+ std::cerr << "Malformed port portion." << std::endl;
+ return false;
+ }
+ parameters->port = parameters->host.substr(closing_bracket + 2);
+ }
+ } else {
+ const auto first_colon = parameters->host.find_first_of(":");
+ if (first_colon != std::string::npos) {
+ parameters->port = parameters->host.substr(first_colon + 1);
+ parameters->hostname = parameters->host.substr(0, first_colon);
+ } else {
+ parameters->hostname = parameters->host;
+ }
+ }
+
+ // TODO: find the request portion to send (before '#...').
+
+ std::cerr << "Resolving hostname=" << parameters->hostname
+ << ", port=" << parameters->port
+ << std::endl;
+
+ struct addrinfo hints = {
+ .ai_family = args.family,
+ .ai_socktype = SOCK_STREAM,
+ };
+ struct addrinfo *result = nullptr;
+
+ int rval = -1;
+ switch (args.api_mode) {
+ case ApiMode::EXPLICIT:
+ rval = android_getaddrinfofornetwork(args.nethandle,
+ parameters->hostname.c_str(),
+ parameters->port.c_str(),
+ &hints, &result);
+ break;
+ case ApiMode::PROCESS:
+ rval = getaddrinfo(parameters->hostname.c_str(),
+ parameters->port.c_str(),
+ &hints, &result);
+ break;
+ default:
+ // Unreachable.
+ std::cerr << "Unknown api mode." << std::endl;
+ return false;
+ }
+
+ if (rval != 0) {
+ std::cerr << "DNS resolution failure; gaierror=" << rval
+ << " [" << gai_strerror(rval) << "]"
+ << std::endl;
+ return rval;
+ }
+
+ memcpy(&(parameters->ss), result[0].ai_addr, result[0].ai_addrlen);
+ std::cerr << "Connecting to: "
+ << inetSockaddrToString(result[0].ai_addr)
+ << std::endl;
+
+ freeaddrinfo(result);
+ return true;
+}
+
+
+int makeTcpSocket(sa_family_t address_family, net_handle_t nethandle) {
+ int fd = socket(address_family, SOCK_STREAM, IPPROTO_TCP);
+ if (fd < 0) {
+ std::cerr << "failed to create TCP socket" << std::endl;
+ return -1;
+ }
+
+ // Don't let reads or writes block indefinitely. We cannot control
+ // connect() timeouts without nonblocking sockets and select/poll/epoll.
+ const struct timeval timeo = { 5, 0 }; // 5 seconds
+ setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo));
+ setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo));
+
+ if (nethandle != NETWORK_UNSPECIFIED) {
+ if (android_setsocknetwork(nethandle, fd) != 0) {
+ int errnum = errno;
+ std::cerr << "android_setsocknetwork() failed;"
+ << " errno: " << errnum << " [" << strerror(errnum) << "]"
+ << std::endl;
+ close(fd);
+ return -1;
+ }
+ }
+ return fd;
+}
+
+
+int doHttpQuery(int fd, const struct Parameters& parameters) {
+ int rval = -1;
+ if (connect(fd,
+ reinterpret_cast<const struct sockaddr *>(&(parameters.ss)),
+ (parameters.ss.ss_family == AF_INET6)
+ ? sizeof(struct sockaddr_in6)
+ : sizeof(struct sockaddr_in)) != 0) {
+ int errnum = errno;
+ std::cerr << "Failed to connect; errno=" << errnum
+ << " [" << strerror(errnum) << "]"
+ << std::endl;
+ return -1;
+ }
+
+ const std::string request(android::base::StringPrintf(
+ "GET %s HTTP/1.1\r\n"
+ "Host: %s\r\n"
+ "Accept: */*\r\n"
+ "Connection: close\r\n"
+ "User-Agent: httpurl/0.0\r\n"
+ "\r\n",
+ parameters.path.c_str(), parameters.host.c_str()));
+ const ssize_t sent = write(fd, request.c_str(), request.size());
+ if (sent != static_cast<ssize_t>(request.size())) {
+ std::cerr << "Sent only " << sent << "/" << request.size() << " bytes"
+ << std::endl;
+ return -1;
+ }
+
+ char buf[4*1024];
+ do {
+ rval = recv(fd, buf, sizeof(buf), 0);
+
+ if (rval < 0) {
+ const int saved_errno = errno;
+ std::cerr << "Failed to recv; errno=" << saved_errno
+ << " [" << strerror(saved_errno) << "]"
+ << std::endl;
+ } else if (rval > 0) {
+ std::cout.write(buf, rval);
+ std::cout.flush();
+ }
+ } while (rval > 0);
+ std::cout << std::endl;
+
+ return 0;
+}
+
+
+int main(int argc, const char* argv[]) {
+ int rval = -1;
+
+ struct Arguments args;
+ if (!args.parseArguments(argc, argv)) { return rval; }
+
+ if (args.api_mode == ApiMode::PROCESS) {
+ rval = android_setprocnetwork(args.nethandle);
+ if (rval != 0) {
+ int errnum = errno;
+ std::cerr << "android_setprocnetwork(" << args.nethandle << ") failed;"
+ << " errno: " << errnum << " [" << strerror(errnum) << "]"
+ << std::endl;
+ return rval;
+ }
+ }
+
+ struct Parameters parameters;
+ if (!parseUrl(args, ¶meters)) { return -1; }
+
+ // TODO: Fall back from IPv6 to IPv4 if ss.ss_family is AF_UNSPEC.
+ // This will involve changes to parseUrl() as well.
+ struct FdAutoCloser closer = makeTcpSocket(
+ parameters.ss.ss_family,
+ (args.api_mode == ApiMode::EXPLICIT) ? args.nethandle
+ : NETWORK_UNSPECIFIED);
+ if (closer.fd < 0) { return closer.fd; }
+
+ return doHttpQuery(closer.fd, parameters);
+}
--- /dev/null
+#!/bin/bash
+
+nethandle=0
+
+readonly TEST_HOST="connectivitycheck.gstatic.com"
+readonly TEST_PATH="/generate_204"
+readonly PREFIX=">>>"
+
+function getUrls() {
+ if [ ! -z $(echo "$1" | sed -e 's/[^:]//g') ]; then
+ echo "http://[$1]$TEST_PATH"
+ echo "http://[$1]:80$TEST_PATH"
+ else
+ echo "http://$1$TEST_PATH"
+ echo "http://$1:80$TEST_PATH"
+ fi
+}
+
+function toHex() {
+ readonly local hexValue=$(bc -q 2>/dev/null << EOT
+obase=16
+$1
+EOT
+)
+ if [ ! -z "$hexValue" ]; then
+ echo "0x$hexValue"
+ fi
+}
+
+
+if [ ! -z "$1" ]; then
+ nethandle="$1"
+fi
+echo "$PREFIX Using nethandle $nethandle ($(toHex $nethandle))"
+echo ""
+
+readonly IPADDRESSES=$(
+ adb shell /system/xbin/dnschk --nethandle $nethandle $TEST_HOST |
+ sed -e 's/#.*//' -e '/^$/d')
+
+
+for host in $TEST_HOST $IPADDRESSES; do
+ urls=$(getUrls $host)
+ for url in $urls; do
+ echo "$PREFIX Checking $url" >&2
+ adb shell /system/xbin/httpurl --nethandle $nethandle "$url"
+ done
+done
--- /dev/null
+# 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)
+
--- /dev/null
+
+ 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
+
--- /dev/null
+Pagecache tools.
+
+dumpcache.c: dumps complete pagecache of device.
+pagecache.py: shows live info on files going in/out of pagecache.
--- /dev/null
+#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()
+{
+ size_t i;
+ 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 (i = 0; i < g_num_files; i++) {
+ struct file_info *info = g_files[i];
+ fprintf(stdout, "%s: %zu cached pages (%.2f MB, %zu%% 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;
+}
--- /dev/null
+#!/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 = {}
+ 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:
+ self._file_pages[filename] = [1, 0]
+ else:
+ self._file_pages[filename][0] += 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:
+ self._file_pages[filename] = [0, 1]
+ else:
+ self._file_pages[filename][1] += 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.clear()
+ self._total_pages_added = 0;
+ self._total_pages_removed = 0;
+
+ def print_stats(self):
+ # Create new merged dict
+ sorted_added = sorted(self._file_pages.items(), key=operator.itemgetter(1), reverse=True)
+ row_format = "{:<70}{:<12}{:<14}{:<9}"
+ print row_format.format('NAME', 'ADDED (MB)', 'REMOVED (MB)', 'SIZE (MB)')
+ for filename, added in sorted_added:
+ filesize = self._file_size[filename]
+ added = self._file_pages[filename][0]
+ removed = self._file_pages[filename][1]
+ if (filename > 64):
+ filename = filename[-64:]
+ print row_format.format(filename, self.pages_to_mb(added), self.pages_to_mb(removed), self.bytes_to_mb(filesize))
+
+ print row_format.format('TOTAL', self.pages_to_mb(self._total_pages_added), self.pages_to_mb(self._total_pages_removed), '')
+
+ def print_stats_curses(self, pad):
+ sorted_added = sorted(self._file_pages.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_removed in sorted_added:
+ filesize = self._file_size[filename]
+ added = self._file_pages[filename][0]
+ removed = self._file_pages[filename][1]
+ 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, app_name):
+ # 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 app_name != None and not (app_name in m.group(0)):
+ return
+ 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]+)d? ([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_file(trace_file, pagecache_stats, app_name):
+ for line in trace_file:
+ parse_atrace_line(line, pagecache_stats, app_name)
+ pagecache_stats.print_stats();
+
+def read_and_parse_trace_data_live(stdout, stderr, pagecache_stats, app_name):
+ # Start reading trace data
+ stdout_queue = Queue.Queue(maxsize=128)
+ stderr_queue = Queue.Queue()
+
+ stdout_thread = FileReaderThread(stdout, stdout_queue,
+ text_file=True, chunk_size=64)
+ stderr_thread = FileReaderThread(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, app_name)
+ except Queue.Empty:
+ break
+
+ key = ''
+ try:
+ key = stdscr.getkey()
+ except:
+ pass
+
+ if key == 'r':
+ pagecache_stats.reset_stats()
+
+ pagecache_stats.print_stats_curses(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()
+
+ stdout.close()
+ 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')
+ parser.add_option('-f', dest='trace_file', metavar='FILE',
+ help='Show stats from a trace file, instead of running live.')
+ parser.add_option('-a', dest='app_name', type='string',
+ help='filter a particular app')
+
+ 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)
+
+ if options.trace_file is not None:
+ if not os.path.isfile(options.trace_file):
+ print >> sys.stderr, ('Couldn\'t load trace file.')
+ sys.exit(1)
+ trace_file = open(options.trace_file, 'r')
+ read_and_parse_trace_file(trace_file, pagecache_stats, options.app_name)
+ else:
+ # 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_live(atrace.stdout, atrace.stderr, pagecache_stats, options.app_name)
+
+if __name__ == "__main__":
+ main()
}
}
+static uint64_t get_zram_mem_used() {
+#define ZRAM_SYSFS "/sys/block/zram0/"
+ FILE *f = fopen(ZRAM_SYSFS "mm_stat", "r");
+ if (f) {
+ uint64_t mem_used_total = 0;
+
+ int matched = fscanf(f, "%*d %*d %" SCNu64 " %*d %*d %*d %*d", &mem_used_total);
+ if (matched != 1)
+ fprintf(stderr, "warning: failed to parse " ZRAM_SYSFS "mm_stat\n");
+
+ fclose(f);
+ return mem_used_total;
+ }
+
+ f = fopen(ZRAM_SYSFS "mem_used_total", "r");
+ if (f) {
+ uint64_t mem_used_total = 0;
+
+ int matched = fscanf(f, "%" SCNu64, &mem_used_total);
+ if (matched != 1)
+ fprintf(stderr, "warning: failed to parse " ZRAM_SYSFS "mem_used_total\n");
+
+ fclose(f);
+ return mem_used_total;
+ }
+
+ return 0;
+}
+
int main(int argc, char *argv[]) {
pm_kernel_t *ker;
pm_process_t *proc;
uint64_t mem[MEMINFO_COUNT] = { };
pm_proportional_swap_t *p_swap;
- int fd, len;
- char buffer[1024];
float zram_cr = 0.0;
signal(SIGPIPE, SIG_IGN);
qsort(procs.data(), procs.size(), sizeof(procs[0]), compfn);
if (has_swap) {
- fd = open("/sys/block/zram0/mem_used_total", O_RDONLY);
- if (fd >= 0) {
- len = read(fd, buffer, sizeof(buffer)-1);
- close(fd);
- if (len > 0) {
- buffer[len] = 0;
- mem[MEMINFO_ZRAM_TOTAL] = atoll(buffer)/1024;
- zram_cr = (float) mem[MEMINFO_ZRAM_TOTAL] /
- (mem[MEMINFO_SWAP_TOTAL] - mem[MEMINFO_SWAP_FREE]);
- has_zram = true;
- }
+ uint64_t zram_mem_used = get_zram_mem_used();
+ if (zram_mem_used) {
+ mem[MEMINFO_ZRAM_TOTAL] = zram_mem_used/1024;
+ zram_cr = (float) mem[MEMINFO_ZRAM_TOTAL] /
+ (mem[MEMINFO_SWAP_TOTAL] - mem[MEMINFO_SWAP_FREE]);
+ has_zram = true;
}
}
char name[1];
};
+static bool verbose = false;
+static bool terse = false;
+static bool addresses = false;
+static bool quiet = false;
+
static int is_library(const char *name) {
int len = strlen(name);
return len >= 4 && name[0] == '/'
snprintf(fn, sizeof(fn), "/proc/%d/smaps", pid);
fp = fopen(fn, "r");
if (fp == 0) {
- fprintf(stderr, "cannot open /proc/%d/smaps: %s\n", pid, strerror(errno));
+ if (!quiet) fprintf(stderr, "cannot open /proc/%d/smaps: %s\n", pid, strerror(errno));
return NULL;
}
fclose(fp);
if (!head) {
- fprintf(stderr, "could not read /proc/%d/smaps\n", pid);
+ if (!quiet) fprintf(stderr, "could not read /proc/%d/smaps\n", pid);
return NULL;
}
return head;
}
-static bool verbose = false;
-static bool terse = false;
-static bool addresses = false;
-
static void print_header()
{
const char *addr1 = addresses ? " start end " : "";
mapinfo *milist = load_maps(pid, addresses, !verbose && !addresses);
if (milist == NULL) {
- return 1;
+ return quiet ? 0 : 1;
}
print_header();
total.pss += mi->pss;
total.size += mi->size;
total.count += mi->count;
-
+
if (terse && !mi->private_dirty) {
goto out;
}
addresses = true;
continue;
}
+ if (!strcmp(arg,"-q")) {
+ quiet = true;
+ continue;
+ }
if (argc != 1) {
fprintf(stderr, "too many arguments\n");
break;
if (usage) {
fprintf(stderr,
- "showmap [-t] [-v] [-c] <pid>\n"
+ "showmap [-t] [-v] [-c] [-q] <pid>\n"
" -t = terse (show only items with private pages)\n"
" -v = verbose (don't coalesce maps with the same name)\n"
" -a = addresses (show virtual memory map)\n"
+ " -q = quiet (don't show error if map could not be read)\n"
);
result = 1;
}
CreateProcesses(2, &workloads);
std::string pid_list = android::base::StringPrintf(
"%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
- TemporaryFile tmpfile;
- ASSERT_TRUE(RecordCmd()->Run({"-p", pid_list, "-o", tmpfile.path}));
+ ASSERT_TRUE(RunRecordCmd({"-p", pid_list}));
}
TEST(record_cmd, existing_threads) {
// Process id can also be used as thread id in linux.
std::string tid_list = android::base::StringPrintf(
"%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
- TemporaryFile tmpfile;
- ASSERT_TRUE(RecordCmd()->Run({"-t", tid_list, "-o", tmpfile.path}));
+ ASSERT_TRUE(RunRecordCmd({"-t", tid_list}));
}
TEST(record_cmd, no_monitored_threads) { ASSERT_FALSE(RecordCmd()->Run({""})); }
std::vector<std::unique_ptr<Workload>>* workloads) {
workloads->clear();
for (size_t i = 0; i < count; ++i) {
- auto workload = Workload::CreateWorkload({"sleep", "1"});
+ // Create a workload runs longer than profiling time.
+ auto workload = Workload::CreateWorkload({"sleep", "1000"});
ASSERT_TRUE(workload != nullptr);
ASSERT_TRUE(workload->Start());
workloads->push_back(std::move(workload));
CreateProcesses(2, &workloads);
std::string pid_list = android::base::StringPrintf(
"%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
- ASSERT_TRUE(StatCmd()->Run({"-p", pid_list}));
+ ASSERT_TRUE(StatCmd()->Run({"-p", pid_list, "sleep", "1"}));
}
TEST(stat_cmd, existing_threads) {
// Process id can be used as thread id in linux.
std::string tid_list = android::base::StringPrintf(
"%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
- ASSERT_TRUE(StatCmd()->Run({"-t", tid_list}));
+ ASSERT_TRUE(StatCmd()->Run({"-t", tid_list, "sleep", "1"}));
}
TEST(stat_cmd, no_monitored_threads) { ASSERT_FALSE(StatCmd()->Run({""})); }
function usage() {
cat<<EOT
Usage:
-${0##*/} SRC_DIR OUTPUT_FILE [-s] [-m MOUNT_POINT] [-d PRODUCT_OUT] [-C FS_CONFIG ] [-c FILE_CONTEXTS] [-b BLOCK_SIZE] [-z COMPRESSOR] [-zo COMPRESSOR_OPT]
+${0##*/} SRC_DIR OUTPUT_FILE [-s] [-m MOUNT_POINT] [-d PRODUCT_OUT] [-C FS_CONFIG ] [-c FILE_CONTEXTS] [-B BLOCK_MAP_FILE] [-b BLOCK_SIZE] [-z COMPRESSOR] [-zo COMPRESSOR_OPT] [-a ]
EOT
}
shift; shift
fi
+BLOCK_MAP_FILE=
+if [[ "$1" == "-B" ]]; then
+ BLOCK_MAP_FILE=$2
+ shift; shift
+fi
+
BLOCK_SIZE=131072
if [[ "$1" == "-b" ]]; then
BLOCK_SIZE=$2
shift; shift
fi
+DISABLE_4K_ALIGN=false
+if [[ "$1" == "-a" ]]; then
+ DISABLE_4K_ALIGN=true
+ shift;
+fi
+
OPT=""
if [ -n "$MOUNT_POINT" ]; then
OPT="$OPT -mount-point $MOUNT_POINT"
if [ -n "$FILE_CONTEXTS" ]; then
OPT="$OPT -context-file $FILE_CONTEXTS"
fi
+if [ -n "$BLOCK_MAP_FILE" ]; then
+ OPT="$OPT -block-map $BLOCK_MAP_FILE"
+fi
if [ -n "$BLOCK_SIZE" ]; then
OPT="$OPT -b $BLOCK_SIZE"
fi
+if [ "$DISABLE_4K_ALIGN" = true ]; then
+ OPT="$OPT -disable-4k-align"
+fi
-MAKE_SQUASHFS_CMD="mksquashfs $SRC_DIR/ $OUTPUT_FILE -no-progress -comp $COMPRESSOR $COMPRESSOR_OPT -no-exports -noappend -no-recovery -android-fs-config $OPT"
+MAKE_SQUASHFS_CMD="mksquashfs $SRC_DIR/ $OUTPUT_FILE -no-progress -comp $COMPRESSOR $COMPRESSOR_OPT -no-exports -noappend -no-recovery -no-fragments -no-duplicates -android-fs-config $OPT"
echo $MAKE_SQUASHFS_CMD
$MAKE_SQUASHFS_CMD
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
LOCAL_MODULE_TAGS := eng tests
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativebenchmark
LOCAL_STATIC_LIBRARIES += \
libtestUtil
LOCAL_MODULE := binderAddInts
LOCAL_SRC_FILES := binderAddInts.cpp
-include $(BUILD_EXECUTABLE)
+include $(BUILD_NATIVE_BENCHMARK)
*/
/*
- * Binder add integers benchmark
+ * Binder add integers benchmark (Using google-benchmark library)
*
- * Measures the rate at which a short binder IPC operation can be
- * performed. The operation consists of the client sending a parcel
- * that contains two integers. For each parcel that the server
- * receives, it adds the two integers and sends the sum back to
- * the client.
- *
- * This benchmark supports the following command-line options:
- *
- * -c cpu - bind client to specified cpu (default: unbound)
- * -s cpu - bind server to specified cpu (default: unbound)
- * -n num - perform IPC operation num times (default: 1000)
- * -d time - delay specified amount of seconds after each
- * IPC operation. (default 1e-3)
*/
#include <cerrno>
#include <grp.h>
#include <iostream>
+#include <iomanip>
#include <libgen.h>
#include <time.h>
#include <unistd.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
+
+#include <benchmark/benchmark.h>
+
#include <utils/Log.h>
#include <testUtil.h>
struct options {
int serverCPU;
int clientCPU;
- unsigned int iterations;
float iterDelay; // End of iteration delay in seconds
} options = { // Set defaults
unbound, // Server CPU
unbound, // Client CPU
- 1000, // Iterations
- 1e-3, // End of iteration delay
+ 0.0, // End of iteration delay
};
class AddIntsService : public BBinder
};
// File scope function prototypes
-static void server(void);
-static void client(void);
+static bool server(void);
+static void BM_client(benchmark::State& state);
static void bindCPU(unsigned int cpu);
static ostream &operator<<(ostream &stream, const String16& str);
static ostream &operator<<(ostream &stream, const cpu_set_t& set);
-int main(int argc, char *argv[])
-{
- int rv;
-
- // Determine CPUs available for use.
- // This testcase limits its self to using CPUs that were
- // available at the start of the benchmark.
- cpu_set_t availCPUs;
- if ((rv = sched_getaffinity(0, sizeof(availCPUs), &availCPUs)) != 0) {
- cerr << "sched_getaffinity failure, rv: " << rv
- << " errno: " << errno << endl;
- exit(1);
- }
-
- // Parse command line arguments
- int opt;
- while ((opt = getopt(argc, argv, "s:c:n:d:?")) != -1) {
- char *chptr; // character pointer for command-line parsing
-
- switch (opt) {
- case 'c': // client CPU
- case 's': { // server CPU
- // Parse the CPU number
- int cpu = strtoul(optarg, &chptr, 10);
- if (*chptr != '\0') {
- cerr << "Invalid cpu specified for -" << (char) opt
- << " option of: " << optarg << endl;
- exit(2);
- }
-
- // Is the CPU available?
- if (!CPU_ISSET(cpu, &availCPUs)) {
- cerr << "CPU " << optarg << " not currently available" << endl;
- cerr << " Available CPUs: " << availCPUs << endl;
- exit(3);
- }
-
- // Record the choice
- *((opt == 'c') ? &options.clientCPU : &options.serverCPU) = cpu;
- break;
- }
-
- case 'n': // iterations
- options.iterations = strtoul(optarg, &chptr, 10);
- if (*chptr != '\0') {
- cerr << "Invalid iterations specified of: " << optarg << endl;
- exit(4);
- }
- if (options.iterations < 1) {
- cerr << "Less than 1 iteration specified by: "
- << optarg << endl;
- exit(5);
- }
- break;
-
- case 'd': // Delay between each iteration
- options.iterDelay = strtod(optarg, &chptr);
- if ((*chptr != '\0') || (options.iterDelay < 0.0)) {
- cerr << "Invalid delay specified of: " << optarg << endl;
- exit(6);
- }
- break;
-
- case '?':
- default:
- cerr << basename(argv[0]) << " [options]" << endl;
- cerr << " options:" << endl;
- cerr << " -s cpu - server CPU number" << endl;
- cerr << " -c cpu - client CPU number" << endl;
- cerr << " -n num - iterations" << endl;
- cerr << " -d time - delay after operation in seconds" << endl;
- exit(((optopt == 0) || (optopt == '?')) ? 0 : 7);
- }
- }
-
- // Display selected options
- cout << "serverCPU: ";
- if (options.serverCPU == unbound) {
- cout << " unbound";
- } else {
- cout << options.serverCPU;
- }
- cout << endl;
- cout << "clientCPU: ";
- if (options.clientCPU == unbound) {
- cout << " unbound";
- } else {
- cout << options.clientCPU;
- }
- cout << endl;
- cout << "iterations: " << options.iterations << endl;
- cout << "iterDelay: " << options.iterDelay << endl;
-
- // Fork client, use this process as server
- fflush(stdout);
- switch (pid_t pid = fork()) {
- case 0: // Child
- client();
- return 0;
-
- default: // Parent
- server();
-
- // Wait for all children to end
- do {
- int stat;
- rv = wait(&stat);
- if ((rv == -1) && (errno == ECHILD)) { break; }
- if (rv == -1) {
- cerr << "wait failed, rv: " << rv << " errno: "
- << errno << endl;
- perror(NULL);
- exit(8);
- }
- } while (1);
- return 0;
-
- case -1: // Error
- exit(9);
- }
-
- return 0;
-}
-
-static void server(void)
+static bool server(void)
{
int rv;
new AddIntsService(options.serverCPU))) != 0) {
cerr << "addService " << serviceName << " failed, rv: " << rv
<< " errno: " << errno << endl;
+ return false;
}
// Start threads to handle server work
proc->startThreadPool();
+ return true;
}
-static void client(void)
+static void BM_client(benchmark::State& state)
{
+ server();
int rv;
sp<IServiceManager> sm = defaultServiceManager();
- double min = FLT_MAX, max = 0.0, total = 0.0; // Time in seconds for all
- // the IPC calls.
// If needed bind to client CPU
if (options.clientCPU != unbound) { bindCPU(options.clientCPU); }
// Attach to service
sp<IBinder> binder;
- do {
+ for (int i = 0; i < 3; i++) {
binder = sm->getService(serviceName);
if (binder != 0) break;
cout << serviceName << " not published, waiting..." << endl;
usleep(500000); // 0.5 s
- } while(true);
+ }
- // Perform the IPC operations
- for (unsigned int iter = 0; iter < options.iterations; iter++) {
+ if (binder == 0) {
+ cout << serviceName << " failed to publish, aborting" << endl;
+ return;
+ }
+
+ unsigned int iter = 0;
+ // Perform the IPC operations in the benchmark
+ while (state.KeepRunning()) {
Parcel send, reply;
// Create parcel to be sent. Will use the iteration cound
// and the iteration count + 3 as the two integer values
// to be sent.
+ state.PauseTiming();
int val1 = iter;
int val2 = iter + 3;
int expected = val1 + val2; // Expect to get the sum back
send.writeInt32(val1);
send.writeInt32(val2);
-
+ state.ResumeTiming();
// Send the parcel, while timing how long it takes for
// the answer to return.
- struct timespec start;
- clock_gettime(CLOCK_MONOTONIC, &start);
if ((rv = binder->transact(AddIntsService::ADD_INTS,
send, &reply)) != 0) {
cerr << "binder->transact failed, rv: " << rv
<< " errno: " << errno << endl;
exit(10);
}
- struct timespec current;
- clock_gettime(CLOCK_MONOTONIC, ¤t);
-
- // Calculate how long this operation took and update the stats
- struct timespec deltaTimespec = tsDelta(&start, ¤t);
- double delta = ts2double(&deltaTimespec);
- min = (delta < min) ? delta : min;
- max = (delta > max) ? delta : max;
- total += delta;
+
+ state.PauseTiming();
int result = reply.readInt32();
if (result != (int) (iter + iter + 3)) {
cerr << "Unexpected result for iteration " << iter << endl;
}
if (options.iterDelay > 0.0) { testDelaySpin(options.iterDelay); }
+ state.ResumeTiming();
}
-
- // Display the results
- cout << "Time per iteration min: " << min
- << " avg: " << (total / options.iterations)
- << " max: " << max
- << endl;
}
+BENCHMARK(BM_client);
+
AddIntsService::AddIntsService(int cpu): cpu_(cpu) {
if (cpu != unbound) { bindCPU(cpu); }
return stream;
}
+
+int main(int argc, char *argv[])
+{
+ int rv;
+ ::benchmark::Initialize(&argc, argv);
+ // Determine CPUs available for use.
+ // This testcase limits its self to using CPUs that were
+ // available at the start of the benchmark.
+ cpu_set_t availCPUs;
+ if ((rv = sched_getaffinity(0, sizeof(availCPUs), &availCPUs)) != 0) {
+ cerr << "sched_getaffinity failure, rv: " << rv
+ << " errno: " << errno << endl;
+ exit(1);
+ }
+
+ // Parse command line arguments
+ int opt;
+ while ((opt = getopt(argc, argv, "s:c:d:?")) != -1) {
+ char *chptr; // character pointer for command-line parsing
+
+ switch (opt) {
+ case 'c': // client CPU
+ case 's': { // server CPU
+ // Parse the CPU number
+ int cpu = strtoul(optarg, &chptr, 10);
+ if (*chptr != '\0') {
+ cerr << "Invalid cpu specified for -" << (char) opt
+ << " option of: " << optarg << endl;
+ exit(2);
+ }
+
+ // Is the CPU available?
+ if (!CPU_ISSET(cpu, &availCPUs)) {
+ cerr << "CPU " << optarg << " not currently available" << endl;
+ cerr << " Available CPUs: " << availCPUs << endl;
+ exit(3);
+ }
+
+ // Record the choice
+ *((opt == 'c') ? &options.clientCPU : &options.serverCPU) = cpu;
+ break;
+ }
+
+ case 'd': // delay between each iteration
+ options.iterDelay = strtod(optarg, &chptr);
+ if ((*chptr != '\0') || (options.iterDelay < 0.0)) {
+ cerr << "Invalid delay specified of: " << optarg << endl;
+ exit(6);
+ }
+ break;
+
+ case '?':
+ default:
+ cerr << basename(argv[0]) << " [options]" << endl;
+ cerr << " options:" << endl;
+ cerr << " -s cpu - server CPU number" << endl;
+ cerr << " -c cpu - client CPU number" << endl;
+ cerr << " -d time - delay after operation in seconds" << endl;
+ exit(((optopt == 0) || (optopt == '?')) ? 0 : 7);
+ }
+ }
+
+ ::benchmark::RunSpecifiedBenchmarks();
+}
+
case $DEVICE in
-(shamu|hammerhead|bullhead)
+(shamu|hammerhead|bullhead|ariel)
# no scaling necessary
xmax=0
ymax=0;;
--- /dev/null
+#
+# Script to start 3 chrome tabs, fling each of them, repeat
+# For each iteration, Total frames and janky frames are reported.
+#
+# Options are described below.
+#
+iterations=10
+startapps=1
+capturesystrace=0
+waittime=4
+app=chrome
+
+function processLocalOption {
+ ret=0
+ case "$1" in
+ (-N) startapps=0;;
+ (-A) unset appList;;
+ (-L) appList=$2; shift; ret=1;;
+ (-T) capturesystrace=1;;
+ (-W) waittime=$2; shift; ret=1;;
+ (*)
+ echo "$0: unrecognized option: $1"
+ echo; echo "Usage: $0 [options]"
+ echo "-A : use all known applications"
+ echo "-L applist : list of applications"
+ echo " default: $appList"
+ echo "-N : no app startups, just fling"
+ echo "-g : generate activity strings"
+ echo "-i iterations"
+ echo "-T : capture systrace on each iteration"
+ echo "-d device : device type (shamu, volantis, bullhead,...)"
+ exit 1;;
+ esac
+ return $ret
+}
+
+CMDDIR=$(dirname $0 2>/dev/null)
+CMDDIR=${CMDDIR:=.}
+. $CMDDIR/defs.sh
+
+case $DEVICE in
+(hammerhead)
+ flingtime=300
+ downCount=2
+ upCount=6
+ UP="70 400 70 100 $flingtime"
+ DOWN="70 100 70 400 $flingtime";;
+(shamu)
+ flingtime=100
+ downCount=2
+ upCount=2
+ UP="700 1847 700 400 $flingtime"
+ DOWN="700 400 700 1847 $flingtime";;
+(angler)
+ flingtime=150
+ downCount=4
+ upCount=3
+ UP="500 1200 500 550 $flingtime"
+ DOWN="500 550 500 1200 $flingtime";;
+(bullhead|volantis)
+ flingtime=200
+ downCount=5
+ upCount=5
+ UP="500 1400 500 400 $flingtime"
+ DOWN="500 400 500 1400 $flingtime";;
+(ariel)
+ flingtime=200
+ downCount=5
+ upCount=5
+ UP="500 1560 500 530 $flingtime"
+ DOWN="500 530 500 1560 $flingtime";;
+(*)
+ echo "Error: No display information available for $DEVICE"
+ exit 1;;
+esac
+
+function swipe {
+ count=0
+ while [ $count -lt $2 ]
+ do
+ doSwipe $1
+ ((count=count+1))
+ done
+ sleep 1
+}
+
+cur=1
+frameSum=0
+jankSum=0
+latency90Sum=0
+latency95Sum=0
+latency99Sum=0
+
+doKeyevent HOME
+sleep 0.5
+resetJankyFrames $(getPackageName $app)
+
+while [ $cur -le $iterations ]
+do
+ if [ $capturesystrace -gt 0 ]; then
+ ${ADB}atrace --async_start -z -c -b 16000 freq gfx view idle sched
+ fi
+ t=$(startActivity $app)
+ sleep $waittime
+ swipe "$UP" $upCount
+
+ sleep $waittime
+ swipe "$DOWN" $downCount
+
+ doKeyevent BACK
+ sleep 0.5
+
+ if [ $capturesystrace -gt 0 ]; then
+ ${ADB}atrace --async_dump -z -c -b 16000 freq gfx view idle sched > trace.${cur}.out
+ fi
+ doKeyevent HOME
+ sleep 0.5
+
+ set -- $(getJankyFrames $(getPackageName $app))
+ totalDiff=$1
+ jankyDiff=$2
+ latency90=$3
+ latency95=$4
+ latency99=$5
+ if [ ${totalDiff:=0} -eq 0 ]; then
+ echo Error: could not read frame info with \"dumpsys gfxinfo\"
+ exit 1
+ fi
+
+ ((frameSum=frameSum+totalDiff))
+ ((jankSum=jankSum+jankyDiff))
+ ((latency90Sum=latency90Sum+latency90))
+ ((latency95Sum=latency95Sum+latency95))
+ ((latency99Sum=latency99Sum+latency99))
+ if [ "$totalDiff" -eq 0 ]; then
+ echo Error: no frames detected. Is the display off?
+ exit 1
+ fi
+ ((jankPct=jankyDiff*100/totalDiff))
+ resetJankyFrames $(getPackageName $app)
+
+
+ echo Frames: $totalDiff latency: $latency90/$latency95/$latency99 Janks: $jankyDiff\(${jankPct}%\)
+ ((cur=cur+1))
+done
+doKeyevent HOME
+((aveJankPct=jankSum*100/frameSum))
+((aveJanks=jankSum/iterations))
+((aveFrames=frameSum/iterations))
+((aveLatency90=latency90Sum/iterations))
+((aveLatency95=latency95Sum/iterations))
+((aveLatency99=latency99Sum/iterations))
+echo AVE: Frames: $aveFrames latency: $aveLatency90/$aveLatency95/$aveLatency99 Janks: $aveJanks\(${aveJankPct}%\)
# default activities. Can dynamically generate with -g.
gmailActivity='com.google.android.gm/com.google.android.gm.ConversationListActivityGmail'
+clockActivity='com.google.android.deskclock/com.android.deskclock.DeskClock'
hangoutsActivity='com.google.android.talk/com.google.android.talk.SigningInActivity'
chromeActivity='com.android.chrome/_not_used'
+contactsActivity='com.google.android.contacts/com.android.contacts.activities.PeopleActivity'
youtubeActivity='com.google.android.youtube/com.google.android.apps.youtube.app.WatchWhileActivity'
cameraActivity='com.google.android.GoogleCamera/com.android.camera.CameraActivity'
playActivity='com.android.vending/com.google.android.finsky.activities.MainActivity'
feedlyActivity='com.devhd.feedly/com.devhd.feedly.Main'
-photosActivity='com.google.android.apps.plus/com.google.android.apps.photos.phone.PhotosHomeActivity'
+photosActivity='com.google.android.apps.photos/com.google.android.apps.photos.home.HomeActivity'
mapsActivity='com.google.android.apps.maps/com.google.android.maps.MapsActivity'
calendarActivity='com.google.android.calendar/com.android.calendar.AllInOneActivity'
earthActivity='com.google.earth/com.google.earth.EarthActivity'
-calculatorActivity='com.android.calculator2/com.android.calculator2.Calculator'
+calculatorActivity='com.google.android.calculator/com.android.calculator2.Calculator'
+calculatorLActivity='com.android.calculator2/com.android.calculator2.Calculator'
sheetsActivity='com.google.android.apps.docs.editors.sheets/com.google.android.apps.docs.app.NewMainProxyActivity'
docsActivity='com.google.android.apps.docs.editors.docs/com.google.android.apps.docs.app.NewMainProxyActivity'
operaActivity='com.opera.mini.native/com.opera.mini.android.Browser'
firefoxActivity='org.mozilla.firefox/org.mozilla.firefox.App'
+suntempleActivity='com.BrueComputing.SunTemple/com.epicgames.ue4.GameActivity'
homeActivity='com.google.android.googlequicksearchbox/com.google.android.launcher.GEL'
function showUsage {
fi
deviceName=$1
ADB="adb -s $deviceName shell "
- DEVICE=$(echo $4 | sed 's/product://')
+ if [ "$DEVICE" = "" -o "$DEVICE" = unknown ]; then
+ DEVICE=$(echo $4 | sed 's/product://')
+ fi
isOnDevice=0
fi
+if [ $isOnDevice -gt 0 ]; then
+ case "$DEVICE" in
+ (bullhead|angler)
+ if ! echo $$ > /dev/cpuset/background/tasks; then
+ echo Could not put PID $$ in background
+ fi
+ ;;
+ (*)
+ ;;
+ esac
+fi
+
# default values if not set by options or calling script
appList=${appList:=$dfltAppList}
savetmpfiles=${savetmpfiles:=0}
output=${output:="./out"}
# clear the output file
-> $output
+if [ -f $output ]; then
+ > $output
+fi
# ADB commands
AM_FORCE_START="${ADB}am start -W -S"
in=$1
in=${in:=0.0}
set -- $(echo $in | tr . " ")
+
# shell addition via (( )) doesn't like leading zeroes in msecs
# field so remove leading zeroes
msecfield=$(expr 0 + $2)
function resetJankyFrames {
_gfxapp=$1
- _gfxapp=${app:="com.android.systemui"}
+ _gfxapp=${_gfxapp:="com.android.systemui"}
${ADB}dumpsys gfxinfo $_gfxapp reset 2>&1 >/dev/null
}
}
function startInstramentation {
+ _iter=$1
+ _iter=${_iter:=0}
+ enableAtrace=$2
+ enableAtrace=${enableAtrace:=1}
# Called at beginning of loop. Turn on instramentation like atrace
vout start instramentation $(date)
echo =============================== >> $output
- echo Before iteration >> $output
+ echo Before iteration $_iter >> $output
echo =============================== >> $output
${ADB}cat /proc/meminfo 2>&1 >> $output
${ADB}dumpsys meminfo 2>&1 >> $output
- if [ "$user" = root ]; then
+ if [ "$DEVICE" = volantis ]; then
+ ${ADB}cat /d/nvmap/iovmm/procrank 2>&1 >> $output
+ fi
+ if [ "$user" = root -a $enableAtrace -gt 0 ]; then
vout ${ADB}atrace -b 32768 --async_start $tracecategories
${ADB}atrace -b 32768 --async_start $tracecategories >> $output
echo >> $output
}
function stopInstramentation {
- if [ "$user" = root ]; then
+ enableAtrace=$1
+ enableAtrace=${enableAtrace:=1}
+ if [ "$user" = root -a $enableAtrace -gt 0 ]; then
vout ${ADB}atrace --async_stop
${ADB}atrace --async_stop > /dev/null
fi
}
function stopAndDumpInstramentation {
- # Called at beginning of loop. Turn on instramentation like atrace
vout stop instramentation $(date)
echo =============================== >> $output
echo After iteration >> $output
python $UNCOMPRESS $tmpTrace >> $traceout
rm -f $tmpTrace
else
- ${ADB}atrace $zarg -b 32768 --async_dump >> $traceout
+ ${ADB}atrace -b 32768 --async_dump > $traceout
fi
- vout ${ADB}atrace $zarg --async_dump
+ vout ${ADB}atrace $zarg -b 32768 --async_dump
vout ${ADB}atrace --async_stop
${ADB}atrace --async_stop > /dev/null
fi
echo 0
return 0
elif [ "$1" = chrome ]; then
- if [ "$DEVICE" = volantis ]; then
+ if [ "$DEVICE" = volantis -o "$DEVICE" = ariel ]; then
vout $AM_START_NOWAIT -p "$(getPackageName $1)" http://www.theverge.com
$AM_START_NOWAIT -p "$(getPackageName $1)" http://www.theverge.com > /dev/null
set -- 0 0
function doSwipe {
vout ${ADB}input swipe $*
- ${ADB}input swipe $*
+ ${ADB}nice input swipe $*
+}
+
+function doText {
+ echo $* > ./tmpOutput
+ vout ${ADB}input text \"$*\"
+ ${ADB}input text "$(cat ./tmpOutput)"
+ rm -f ./tmpOutput
}
function doTap {
--- /dev/null
+#!/usr/bin/python
+
+import sys
+import getopt
+
+def usage():
+ print "powersum.py [OPTIONS] HZ VOLTAGE [FILE]"
+ print "OPTIONS: "
+ print "-o OFFSET: subtract OFFSET from all data points"
+ print "\nHZ: samples per second in FILE or stdin"
+ sys.exit(0)
+
+offset = 0.0
+voltage = 4.3
+
+parsedargv,argvrem = getopt.getopt(sys.argv[1:], "vo:w:l:h", ["help"])
+for o,a in parsedargv:
+ if o == '-o': offset = float(a)
+ if o == '-h' or o == '--help': usage()
+
+hz = float(argvrem[0])
+voltage = float(argvrem[1])
+if len(argvrem) > 1:
+ f = open(argvrem[2], "r")
+else:
+ f = sys.stdin
+
+totalpower = 0.0
+samplectr = 0
+
+for line in f:
+ try:
+ val = float(line.split(" ")[1]) # xxx take 2nd arg in line
+ val -= offset
+ except:
+ print "Can't parse data line, did you remember the timestamp?"
+ print "data was: %s" % line
+ sys.exit(1)
+
+ samplectr+=1
+ totalpower += val/hz
+
+avecurrent = totalpower * hz *1000 / samplectr
+avepower = avecurrent * voltage
+
+print "%.3f %.3f" % (avecurrent, avepower)
--- /dev/null
+# print summary of output generated by pwrtest.sh
+#
+# default results directories are <device>-<date>[-experiment]. By default
+# match any device and the year 201*.
+#
+# Examples:
+#
+# - show output for all bullhead tests in july 2015:
+# ./pwrsummary.sh -r "bh-201507*"
+#
+# - generate CSV file for import into spreadsheet:
+# ./pwrsummary.sh -o csv
+#
+
+CMDDIR=$(dirname $0 2>/dev/null)
+CMDDIR=${CMDDIR:=.}
+cd $CMDDIR
+CMDDIR=$(pwd)
+cd -
+POWERAVE="python $CMDDIR/powerave.py"
+
+defaultPattern="*-201*"
+defaultVoltage=4.3
+defaultFrequency=5
+
+function Usage {
+ echo "$0 [-o format] [-v voltage] [-h freq] [-f resultsDirectories]"
+}
+
+while [ $# -gt 0 ]
+do
+ case "$1" in
+ (-o) format=$2; shift;;
+ (-v) voltage=$2; shift;;
+ (-h) hz=$2; shift;;
+ (-r) testResults="$2"; shift;;
+ (--help) Usage; exit 0;;
+ (--) shift; break;;
+ (*)
+ echo Unknown option: $1
+ Usage
+ exit 1;;
+ esac
+ shift
+done
+
+testResults=${testResults:=$defaultPattern}
+voltage=${voltage:=$defaultVoltage}
+hz=${hz:=$defaultFrequency}
+
+function printHeader {
+ workload=$1
+ units="unknown"
+ case $workload in
+ (suntemple|shadowgrid2)
+ units="FPS";;
+ (recentfling|youtube|chrome)
+ units="FPS from app point of view: 1/(90th percentile render time)";;
+ (sysapps)
+ units="App start/switch per second";;
+ esac
+
+ echo "Performance unit for $workload is: $units"
+ if [ "$format" = csv ]; then
+ printf "%s,%s,%s,%s,%s,%s,%s,%s,%s\n" " " build min ave max net-mA@${voltage}v base-mW net-mW perf/W
+ else
+ printf "%-30s %-8s %12.12s %12.12s %12.12s %12.12s %12.12s %12.12s %12.12s\n" " " build min ave max net-mA@${voltage}v base-mW net-mW perf/W
+ fi
+}
+
+function average {
+ awk 'BEGIN { count=0; sum=0; max=-1000000000; min=1000000000; }
+ {
+ cur = $1;
+ sum = sum + cur;
+ if (cur > max) max = cur;
+ if (cur < min) min = cur;
+ count++;
+ }
+
+ END {
+ if (count > 0) {
+ ave = sum / count;
+ printf "%.2f %.2f %.2f\n", min, ave, max;
+ }
+ }'
+}
+
+function hwuiOutputParser {
+ # Stats since: 60659316905953ns
+ # Total frames rendered: 150
+ # Janky frames: 89 (59.33%)
+ # 90th percentile: 23ms
+ # 95th percentile: 27ms
+ # 99th percentile: 32ms
+ # Number Missed Vsync: 0
+ # Number High input latency: 0
+ # Number Slow UI thread: 0
+ # Number Slow bitmap uploads: 12
+ # Number Slow draw: 89
+ # use with "stdbuf -o0 " to disable pipe buffering
+ # stdbuf -o0 adb shell /data/hwuitest shadowgrid2 400 | stdbuf -o0 ./hwuitestfilter.sh | tee t.csv
+ sed -e 's/ns//' -e 's/[\(\)%]/ /g' | awk '
+ BEGIN { startTime=0; lastTime=0; }
+ /^Stats since:/ {
+ curTime = $3;
+ if (startTime == 0) {
+ startTime = curTime;
+ }
+ if (lastTime) {
+ interval = curTime - lastTime;
+ fps = totalFrames*1000000000 / interval;
+ diffTime = curTime - startTime;
+ printf "%.2f, %.2f, ",diffTime/1000000, fps;
+ }
+ }
+ /^Total frames/ { totalFrames=$4; }
+ /^Janky frames:/ {
+ if (lastTime) {
+ printf "%.2f\n",$4; lastTime=curTime;
+ }
+ lastTime = curTime;
+ }'
+}
+
+function sysappOutputParser {
+ awk '
+ BEGIN { fmt=0; count=0; sum=0; }
+ /^App/ {
+ if (count != 0) {
+ if (fmt > 2) printf "Ave: %0.2fms\n", sum/count;
+ else printf " %0.2f\n", sum/count;
+ count = 0;
+ sum = 0;
+ }
+ }
+ /^[a-z]/ { val=$2; if (val != 0) { count++; sum+=val; } }
+ /^Iteration/ { if (fmt > 2) printf "%s : ", $0; else if (fmt) printf "%d ", $2; }
+ '
+}
+
+function calcPerfData {
+ testdir=$1
+ workload=$2
+ baselineCurrent=$3
+ baselinePower=$4
+
+ file=${workload}.out
+ powerfile=${workload}-power.out
+ build="$(cat build 2>/dev/null)"
+ build=${build:="Unknown"}
+
+ lines=$(wc -l $file 2>/dev/null | cut -f1 -d\ )
+
+ if [ ${lines:=0} -eq -0 ]; then
+ # No performance data captured
+ if [ "$format" = csv ]; then
+ printf "%s,%s,%s\n" $testdir "$build" "no data"
+ else
+ printf "%-30s %-8s %12.12s\n" $testdir "$build" "no data"
+ fi
+ return 1
+ fi
+
+ set -- $($POWERAVE $hz $voltage $powerfile)
+ current=$(echo $1 $baselineCurrent | awk '{ printf "%.2f", $1-$2; }')
+ power=$(echo $2 $baselinePower | awk '{ printf "%.2f", $1-$2; }')
+
+ case $workload in
+ (idle)
+ set -- 0 0 0
+ ;;
+ (suntemple)
+ # units are fps
+ set -- $(grep "FPS average" $file | sed 's/^.*seconds for a //' | awk '{ print $1; }' | average)
+ ;;
+ (recentfling|youtube|chrome)
+ # units are ms, so need to convert to app/ms
+ set -- $(grep ^Frames: $file | tr "/" " " | awk '{ print $4; }' | average | awk '{ printf "%.3f %.3f %.3f\n", 1000/$3, 1000/$2, 1000/$1;}' )
+ ;;
+ (sysapps)
+ # units are ms, so need to convert to app/ms
+ set -- $(cat $file | sysappOutputParser | average | awk '{ printf "%.3f %.3f %.3f\n", 1000/$3, 1000/$2, 1000/$1;}' )
+ ;;
+ (shadowgrid2)
+ # units are fps
+ set -- $(cat $file | hwuiOutputParser | tr ',' ' ' | awk '{print $2;}' | average)
+ ;;
+ esac
+
+ minperf=$1
+ aveperf=$2
+ maxperf=$3
+ perfPerWatt=$(echo $aveperf $power | awk '{ if ($2) { val=$1*1000/$2; printf "%.3f\n", val; } else print "unknown"; }')
+ if [ "$format" = csv ]; then
+ printf "%s,%s,%f,%f,%f,%f,%f,%f," $testdir "$build" $minperf $aveperf $maxperf $current $baselinePower $power
+ printf "%s\n" $perfPerWatt
+ else
+ printf "%-30s %-8s %12.2f %12.2f %12.2f %12.2f %12.2f %12.2f " $testdir "$build" $minperf $aveperf $maxperf $current $baselinePower $power
+ printf "%12s\n" $perfPerWatt
+ fi
+}
+
+function calcBaselinePower {
+ workload=$1
+ defaultPowerFile="idle-display-power.out"
+ powerFile=$defaultPowerFile
+ case $workload in
+ (shadowgrid2|suntemple|recentfling)
+ powerFile="idle-airplane-display-power.out"
+ if [ ! -f $powerFile ]; then
+ powerFile=$defaultPowerFile
+ fi;;
+ esac
+ if [ -f $powerFile ]; then
+ $POWERAVE 5 4.3 $powerFile
+ fi
+}
+
+for t in $(cat tests)
+do
+ echo .======================= $t ================================
+ printHeader $t
+ for i in $testResults
+ do
+ cd $i
+ baseline="$(calcBaselinePower $t)"
+ if [ "$baseline" != "" ]; then
+ calcPerfData $i $t $baseline
+ else
+ echo "$i : no baseline current"
+ fi
+ cd - > /dev/null
+ done
+done
--- /dev/null
+# Script to gather perf and perf/watt data for several workloads
+#
+# Setup:
+#
+# - device connected to monsoon with USB passthrough enabled
+# - network enabled (baseline will be measured and subtracted
+# from results) (network needed for chrome, youtube tests)
+# - the device is rebooted after each test (can be inhibited
+# with "-r 0")
+#
+# Default behavior is to run each of the known workloads for
+# 30 minutes gathering both performance and power data.
+#
+# The default time can be overridden with the -t option. To
+# change individual test times, a config file can be specifed
+# via -f with times for individual tests. Example file contents:
+#
+# idleTime=60
+# recentflingTime=60
+# chromeTime=60
+# youtubeTime=0
+# sysappsTime=60
+# suntempleTime=5
+#
+# Output goes to the current directory.
+#
+# Examples:
+#
+# - Run all tests for 15 minutes (default is 30): ./pwrtest.sh -t 15 -R MDA20
+#
+# - Use a config file for test times: ./pwrtest.sh -f ./myconfig -R MDA20
+#
+# - Use a init file to setup device tuneables after each restart (this is
+# a bash script which should include adb commands to set up device):
+# ./pwrtest.sh -F devtunables
+#
+
+defaultTime=30
+garbageminutes=8
+
+function Usage {
+ echo "Usage: $0 [OPTIONS]"
+ echo "-d device : device type (shamu, bullhead, ...)"
+ echo "-f configFile : config file to override individual test times"
+ echo "-g garbageMinutes : time to skip power measurement at beginning of test"
+ echo " default=$garbagetime minutes"
+ echo "-r restart : 0=no reboot between tests, 1=reboot (default)"
+ echo "-t defaultTimeMin : default time to run each test"
+ echo " default=$defaultTime minutes"
+ echo "-D cmddir : directory to find defs.sh"
+ echo "-F restartHookFile : file of commands to set device tunables after restart (optional)"
+ echo "-R release : release running on device (MDA20, 2054728, etc)"
+}
+
+restart=1
+hz=5
+shadowgrid2TimeMax=25
+
+CMDDIR=$(dirname $0 2>/dev/null)
+CMDDIR=${CMDDIR:=.}
+
+MONSOON=monsoon.par
+
+while [ $# -gt 0 ]
+do
+ case "$1" in
+ (-D) CMDDIR=$2; shift;;
+ (-r) restart=$2; shift;;
+ (-t) defaultTime=$2; shift;;
+ (-F) restartfile=$2; shift;;
+ (-g) garbageminutes=$2; shift;;
+ (-f)
+ configFile=$2;
+ echo "Reading configs from $configFile..."
+ . ./$configFile
+ shift;;
+ (-R) echo $2 > ./build; shift;;
+ (--) ;;
+ (--help)
+ Usage
+ exit 0;;
+ (*)
+ echo "Unknown option: $1"
+ Usage
+ exit 1;;
+ esac
+ shift
+done
+
+. $CMDDIR/defs.sh --
+
+devdir="/data/local/tmp"
+suntempledir=${CMDDIR}/suntemple
+
+case $DEVICE in
+(shamu|hammerhead)
+ HWUITEST=hwuitest
+ onSwipe="700 1847 700 400 50"
+ ;;
+(*)
+ HWUITEST=hwuitest64
+ onSwipe="500 1200 500 550 150"
+ ;;
+esac
+
+scripts="defs.sh systemapps.sh recentfling.sh youtube.sh chromefling.sh $HWUITEST"
+
+if ! $MONSOON >/dev/null 2>&1; then
+ echo $MONSOON must be in your PATH >&2
+ exit 1
+fi
+
+function usbpassthru {
+ if [ "$1" = off ]; then
+ state=off
+ else
+ state=on
+ fi
+ echo Setting usb pass-thru to $state
+ monsoon.par --usbpassthrough=$state
+}
+
+function pwrcollect {
+ collectmin=$1
+ collectmin=${collectmin:=60}
+ # samples = hz * 60 * minutes
+ ((samples=5*60*collectmin))
+ monsoon.par --timestamp --samples $samples --hz 5
+}
+
+function copy_files {
+ adb shell mkdir -p $devdir
+ for file in $scripts
+ do
+ adb push $CMDDIR/$file $devdir
+ done
+}
+
+function install_suntemple {
+ echo Checking for suntemple installation...
+ #stdest=/storage/sdcard0/obb/com.BrueComputing.SunTemple
+ stdest=/storage/emulated/0/obb/com.BrueComputing.SunTemple
+ dircontents=$(adb ls $stdest 2>/dev/null)
+ if [ "$dircontents" = "" ]; then
+ echo Installing suntemple...
+ adb install $suntempledir/*.apk
+ adb shell mkdir -p $stdest
+ adb push $suntempledir/main*obb $stdest
+ else
+ echo dircontents=$dircontents
+ echo Suntemple already installed.
+ fi
+}
+
+function run_test {
+ testName=$1
+ collectMinutes=$2
+ collectOutput=${testName}-power-raw.out
+ powerOutput=${testName}-power.out
+ echo -----------------------------------------------------
+ echo TEST: $testName
+ echo enabled Cores $(adb shell "cat /sys/devices/system/cpu/online")
+ date
+ echo -----------------------------------------------------
+ usbpassthru off
+ pwrcollect $collectMinutes > $collectOutput 2>/dev/null
+ # take off the first 2min of samples
+ totalSamples=$(cat $collectOutput | wc -l)
+ # we throw away the first "garbageminutes" of the data
+ # since it is volatile
+ ((garbage=hz*60*garbageminutes))
+ ((remaining=totalSamples-garbage))
+ if [ $remaining -gt 0 ]; then
+ tail -$remaining $collectOutput > $powerOutput
+ else
+ cp $collectOutput $powerOutput
+ fi
+ echo power data for $testName copied to $collectOutput
+ usbpassthru on
+ sleep 10
+ adb devices
+ sleep 10
+}
+
+function start_job {
+ cmdline="$1"
+ echo Running $cmdline
+ (adb shell "cd $devdir && nohup $cmdline > test.out") &
+ sleep 5
+ kill %1 2>/dev/null
+}
+
+function cleanup_job {
+ testName=$1
+ processName=$2
+ processName=${processName:=" sh "}
+ set -- $(adb shell ps | tr "\r" " " | grep "$processName")
+ echo killing PID=$2...
+ adb shell kill $2
+ sleep 1
+ echo copying test output to $testName...
+ adb pull $devdir/test.out
+ mv test.out ${testName}.out
+ if [ $restart -gt 0 ]; then
+ restart_device
+ else
+ doKeyevent HOME
+ fi
+}
+
+function airplane_mode {
+ if [ "$1" = "on" ]; then
+ mode=true
+ setting=1
+ else
+ mode=false
+ setting=0
+ fi
+ adb shell settings put global airplane_mode_on $setting
+ adb shell am broadcast -a android.intent.action.AIRPLANE_MODE --ez state $mode
+ echo Set airplane mode to $mode
+}
+
+function restart_device {
+ adb reboot
+ echo Wait 60s for device to restart...
+ sleep 60
+ while ! adb root
+ do
+ echo Waiting for device to come up...
+ sleep 10
+ done
+ echo Wait 30s to complete boot activities...
+ sleep 30
+ echo Restart complete.
+ doTap 897 1075
+ sleep 2
+ doSwipe $onSwipe
+ restartfile=${restartfile:="./restarthook"}
+ if [ -f $restartfile ]; then
+ # hook to change tunables after a restart
+ . $restartfile
+ fi
+}
+
+usbpassthru on
+adb devices 2>/dev/null
+
+airplane_mode off
+if [ $restart -gt 0 ]; then
+ restart_device
+fi
+
+echo Copying $scripts to device $devdir...
+copy_files
+tests=""
+
+# measure background power
+idleTime=${idleTime:=$defaultTime}
+if [ $idleTime -gt 0 ]; then
+ echo Test 1 : measure idle power for $idleTime minutes
+ run_test idle $idleTime
+ airplane_mode on
+ echo Restarting for power baseline in airplane mode...
+ restart_device
+ run_test idle-airplane $idleTime
+ airplane_mode off
+ # the screen blanks after 30 minutes. The first 2 minutes of the test
+ # have already been filtered off. For our power baseline, keep the first
+ # 20 minutes of the results
+ ((twentyminutes=hz*20*60))
+ powerOutput="idle-power.out"
+ displayPowerOutput="idle-display-power.out"
+ airplanePowerOutput="idle-airplane-power.out"
+ airplaneDisplayPowerOutput="idle-airplane-display-power.out"
+ totalSamples=$(cat $powerOutput | wc -l)
+ if [ $twentyminutes -lt $totalSamples ]; then
+ head -$twentyminutes $powerOutput > $displayPowerOutput
+ head -$twentyminutes $airplanePowerOutput > $airplaneDisplayPowerOutput
+ else
+ cp $powerOutput $displayPowerOutput
+ cp $airplanePowerOutput $airplaneDisplayPowerOutput
+ fi
+ tests="$tests idle"
+fi
+
+recentflingTime=${recentflingTime:=$defaultTime}
+if [ $recentflingTime -gt 0 ]; then
+ echo $(date) Test 2 : recents fling for $recentflingTime minutes
+ airplane_mode on
+ adb shell "cd $devdir && ./systemapps.sh -A -T -i 1"
+ start_job "./recentfling.sh -N -i 1000 -d $DEVICE"
+ run_test recentfling $recentflingTime
+ cleanup_job recentfling
+ airplane_mode off
+ date
+ tests="$tests recentfling"
+fi
+
+suntempleTime=${suntempleTime:=$defaultTime}
+if [ $suntempleTime -gt 0 ]; then
+ echo $(date) Test 2 : run Sun Temple $suntempleTime minutes
+ airplane_mode on
+ install_suntemple
+ adb shell "am start $suntempleActivity"
+ run_test suntemple $suntempleTime
+ adb pull /sdcard/SunTemple/SunTemple/Saved/Logs/SunTemple.log
+ cleanup_job suntemple BrueComp
+ airplane_mode off
+ mv SunTemple.log suntemple.out
+ # grab the suntemple log
+ date
+ tests="$tests suntemple"
+fi
+
+chromeTime=${chromeTime:=$defaultTime}
+if [ $chromeTime -gt 0 ]; then
+ echo $(date) Test 3 : chrome fling for $chromeTime minutes
+ start_job "./chromefling.sh -i 1000 -d $DEVICE"
+ run_test chrome $chromeTime
+ cleanup_job chrome
+ date
+ tests="$tests chrome"
+fi
+
+shadowgrid2Time=${shadowgrid2Time:=$defaultTime}
+if [ $shadowgrid2Time -gt $shadowgrid2TimeMax ]; then
+ # we cap shadowgrid2 time since the display goes
+ # off after 30 minutes
+ $shadowgrid2Time=$shadowgrid2TimeMax
+fi
+if [ $shadowgrid2Time -gt 0 ]; then
+ airplane_mode on
+ echo $(date) Test 4 : shadowgrid2 for $shadowgrid2Time minutes
+ start_job "./$HWUITEST shadowgrid2 100000"
+ run_test shadowgrid2 $shadowgrid2Time
+ cleanup_job shadowgrid2 $HWUITEST
+ airplane_mode off
+ date
+ tests="$tests shadowgrid2"
+fi
+
+youtubeTime=${youtubeTime:=$defaultTime}
+if [ $youtubeTime -gt 0 ]; then
+ echo $(date) Test 5 : youtube for $youtubeTime minutes
+ start_job "./youtube.sh -i 1000 -d $DEVICE"
+ run_test youtube $youtubeTime
+ cleanup_job youtube
+ date
+ tests="$tests youtube"
+fi
+
+sysappsTime=${sysappsTime:=$defaultTime}
+if [ $sysappsTime -gt 0 ]; then
+ echo $(date) Test 6 : app switching for $sysappsTime minutes
+ start_job "./systemapps.sh -T -i 1000 -d $DEVICE"
+ run_test sysapps $sysappsTime
+ cleanup_job sysapps
+ date
+ tests="$tests sysapps"
+fi
+
+echo Ran tests: $tests
+echo $tests > tests
+
(-A) unset appList;;
(-L) appList=$2; shift; ret=1;;
(-T) capturesystrace=1;;
+ (-B) echo $$ > /dev/cpuset/background/tasks;;
(*)
echo "$0: unrecognized option: $1"
echo; echo "Usage: $0 [options]"
echo "-A : use all known applications"
+ echo "-B : run in background cpuset"
echo "-L applist : list of applications"
echo " default: $appList"
echo "-N : no app startups, just fling"
upCount=6
UP="70 400 70 100 $flingtime"
DOWN="70 100 70 400 $flingtime";;
+(angler|ariel|mtp8996)
+ flingtime=150
+ downCount=4
+ upCount=3
+ UP="500 1200 500 550 $flingtime"
+ DOWN="500 550 500 1200 $flingtime";;
(bullhead)
flingtime=200
downCount=5
latency99=$5
if [ ${totalDiff:=0} -eq 0 ]; then
echo Error: could not read frame info with \"dumpsys gfxinfo\"
- exit 1
fi
((frameSum=frameSum+totalDiff))
((latency99Sum=latency99Sum+latency99))
if [ "$totalDiff" -eq 0 ]; then
echo Error: no frames detected. Is the display off?
- exit 1
fi
((jankPct=jankyDiff*100/totalDiff))
resetJankyFrames
# Other options are described below.
#
iterations=1
-tracecategories="gfx view am input memreclaim"
+tracecategories="gfx am memreclaim"
totaltimetest=0
forcecoldstart=0
waitTime=3.0
+memstats=0
-appList="gmail hangouts chrome youtube play home"
+appList="gmail maps chrome youtube play home"
function processLocalOption {
ret=0
(-L) appList=$2; shift; ret=1;;
(-T) totaltimetest=1;;
(-W) waitTime=$2; shift; ret=1;;
+ (-M) memstats=1;;
(*)
echo "$0: unrecognized option: $1"
echo; echo "Usage: $0 [options]"
if [ $iterations -gt 1 ]; then
echo =========================================
echo Iteration $cur of $iterations
+ date
echo =========================================
fi
if [ $iterations -gt 1 -o $cur -eq 1 ]; then
if [ $totaltimetest -eq 0 ]; then
tmpTraceOut="$tmpTraceOutBase-$app.out"
>$tmpTraceOut
- startInstramentation
+ startInstramentation "$app-$cur"
else
+ if [ "$memstats" -gt 0 ]; then
+ startInstramentation "$app-$cur" 0
+ fi
if [ $appnum -eq 0 ]; then
printf "%-8s %5s(ms) %3s(ms) %s %s\n" App Start Iter Jank Latency
fi
printf "%-10s %5.0f %5.0f\n" TOTAL $totaltime $diffTime
fi
+overallSum=0
+appCount=0
if [ $iterations -gt 1 -a $totaltimetest -eq 0 ]; then
echo
echo =========================================
((ave90=l90/iterations))
((ave95=l95/iterations))
((ave99=l99/iterations))
- ((jankPct=100*janks/frames))
+ if [ $frames -gt 0 ]; then
+ ((jankPct=100*janks/frames))
+ fi
printf "%-12s %5d %5d %5d %5d %5d %5d(%d%%) %d/%d/%d\n" $app $1 $ave $2 $4 $5 $janks $jankPct $ave90 $ave95 $ave99
+ ((overallSum=overallSum+ave))
+ ((appCount=appCount+1))
done
+ if [ $appCount -gt 0 ]; then
+ printf "Average Start Time: %.2f\n", $(echo $overallSum $appCount | awk '{ printf "%.2f\n", $1/$2 }')
+ fi
fi
--- /dev/null
+#
+# Script to play a john oliver youtube video N times.
+# For each iteration, Total frames and janky frames are reported.
+#
+# Options are described below.
+#
+iterations=10
+app=youtube
+searchText="last week tonight with john oliver: online harassment"
+vidMinutes=15
+
+function processLocalOption {
+ ret=0
+ case "$1" in
+ (-S) searchText="$2"; shift;;
+ (-t) vidMinutes="$2"; shift;;
+ (*)
+ echo "$0: unrecognized option: $1"
+ echo; echo "Usage: $0 [options]"
+ echo "-i iterations"
+ echo "-S youtube search text"
+ echo "-d device"
+ echo "-t vidMinutes"
+ exit 1;;
+ esac
+ return $ret
+}
+
+CMDDIR=$(dirname $0 2>/dev/null)
+CMDDIR=${CMDDIR:=.}
+. $CMDDIR/defs.sh
+
+case $DEVICE in
+(angler)
+ searchButton="860 177"
+ selectFirstVideo="225 400"
+ enableControls="1000 610"
+ fullScreen="1011 632"
+ ;;
+(shamu)
+ searchButton="1200 160"
+ selectFirstVideo="480 653"
+ enableControls="1377 812"
+ fullScreen="1377 812"
+ ;;
+(bullhead|hammerhead)
+ searchButton="860 177"
+ selectFirstVideo="225 400"
+ enableControls="1000 610"
+ fullScreen="1011 632"
+ ;;
+(volantis)
+ searchButton="1356 93"
+ selectFirstVideo="378 264"
+ enableControls="1464 812"
+ fullScreen="1480 835"
+ ;;
+(ariel)
+ searchButton="1440 70"
+ selectFirstVideo="228 224"
+ enableControls="1528 880"
+ fullScreen="1528 880"
+ ;;
+
+(*)
+ echo "Error: No display information available for $DEVICE"
+ exit 1;;
+esac
+
+function swipe {
+ count=0
+ while [ $count -lt $2 ]
+ do
+ echo doSwipe...
+ doSwipe $1
+ ((count=count+1))
+ done
+ sleep 1
+}
+
+cur=1
+frameSum=0
+jankSum=0
+latency90Sum=0
+latency95Sum=0
+latency99Sum=0
+
+doKeyevent HOME
+sleep 0.5
+resetJankyFrames $(getPackageName $app)
+
+while [ $cur -le $iterations ]
+do
+ t=$(startActivity $app)
+ sleep 4.0
+ doTap $searchButton
+ sleep 1.0
+ doText "$searchText"
+ sleep 1.0
+ doKeyevent ENTER
+ sleep 5.0
+ doTap $selectFirstVideo
+ sleep 10.0
+ doTap $fullScreen
+ sleep 0.5
+ doTap $fullScreen
+ # 15 minutes
+ ((vidTime=60*vidMinutes))
+ sleep $vidTime
+ doKeyevent BACK
+ sleep 0.5
+ doKeyevent BACK
+ sleep 0.5
+ doKeyevent BACK
+ sleep 0.5
+
+ set -- $(getJankyFrames $(getPackageName $app))
+ totalDiff=$1
+ jankyDiff=$2
+ latency90=$3
+ latency95=$4
+ latency99=$5
+ if [ ${totalDiff:=0} -eq 0 ]; then
+ echo Error: could not read frame info with \"dumpsys gfxinfo\"
+ fi
+
+ ((frameSum=frameSum+totalDiff))
+ ((jankSum=jankSum+jankyDiff))
+ ((latency90Sum=latency90Sum+latency90))
+ ((latency95Sum=latency95Sum+latency95))
+ ((latency99Sum=latency99Sum+latency99))
+ if [ "$totalDiff" -eq 0 ]; then
+ echo Error: no frames detected. Is the display off?
+ fi
+ ((jankPct=jankyDiff*100/totalDiff))
+ resetJankyFrames $(getPackageName $app)
+
+
+ echo Frames: $totalDiff latency: $latency90/$latency95/$latency99 Janks: $jankyDiff\(${jankPct}%\)
+ ((cur=cur+1))
+done
+doKeyevent HOME
+((aveJankPct=jankSum*100/frameSum))
+((aveJanks=jankSum/iterations))
+((aveFrames=frameSum/iterations))
+((aveLatency90=latency90Sum/iterations))
+((aveLatency95=latency95Sum/iterations))
+((aveLatency99=latency99Sum/iterations))
+echo AVE: Frames: $aveFrames latency: $aveLatency90/$aveLatency95/$aveLatency99 Janks: $aveJanks\(${aveJankPct}%\)