Merge "ext4_utils: Fix a looping bug and memory leak"
diff --git a/ANRdaemon/ANRdaemon.cpp b/ANRdaemon/ANRdaemon.cpp
index 35f2ecb..bd433b3 100644
--- a/ANRdaemon/ANRdaemon.cpp
+++ b/ANRdaemon/ANRdaemon.cpp
@@ -36,7 +36,6 @@
#include <ctime>
#include <cutils/properties.h>
#include <signal.h>
-#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -134,7 +133,7 @@
if ((fp = fopen("/proc/stat", "r")) == NULL) {
err = true;
- sprintf(err_msg, "can't read from /proc/stat with errno %d", errno);
+ snprintf(err_msg, sizeof(err_msg), "can't read from /proc/stat with errno %d", errno);
} else {
if (fscanf(fp, params, &cpu->utime, &cpu->ntime,
&cpu->stime, &cpu->itime, &cpu->iowtime, &cpu->irqtime,
@@ -144,6 +143,7 @@
* is_heavy_loaded() will return false.
*/
ALOGE("Error in getting cpu status. Skipping this check.");
+ fclose(fp);
return;
}
@@ -157,7 +157,7 @@
/*
* Calculate cpu usage in the past interval.
* 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
+ * turn on and off tracing frequently when the cpu load is right close to
* threshold.
*/
static bool is_heavy_load(void) {
@@ -192,7 +192,7 @@
int fd = open(path, O_WRONLY);
if (fd == -1) {
err = true;
- sprintf(err_msg, "Can't open %s. Error: %d", path, errno);
+ snprintf(err_msg, sizeof(err_msg), "Can't open %s. Error: %d", path, errno);
return -1;
}
const char* control = (enable?"1":"0");
@@ -205,7 +205,7 @@
}
err = true;
- sprintf(err_msg, "Error %d in writing to %s.", errno, path);
+ snprintf(err_msg, sizeof(err_msg), "Error %d in writing to %s.", errno, path);
}
close(fd);
return (err?-1:0);
@@ -216,16 +216,16 @@
*/
static void dfs_set_property(uint64_t mtag, const char* mapp, bool enable) {
char buf[64];
- snprintf(buf, 64, "%#" PRIx64, mtag);
+ snprintf(buf, sizeof(buf), "%#" PRIx64, mtag);
if (property_set(dfs_tags_property, buf) < 0) {
err = true;
- sprintf(err_msg, "Failed to set debug tags system properties.");
+ snprintf(err_msg, sizeof(err_msg), "Failed to set debug tags system properties.");
}
if (strlen(mapp) > 0
&& property_set(dfs_apps_property, mapp) < 0) {
err = true;
- sprintf(err_msg, "Failed to set debug applications.");
+ snprintf(err_msg, sizeof(err_msg), "Failed to set debug applications.");
}
if (log_sched) {
@@ -403,13 +403,13 @@
int fd = open(dfs_buffer_size_path, O_WRONLY);
if (fd == -1) {
err = true;
- sprintf(err_msg, "Can't open atrace buffer size file under /d/tracing.");
+ snprintf(err_msg, sizeof(err_msg), "Can't open atrace buffer size file under /d/tracing.");
return -1;
}
ssize_t len = strlen(buf_size_kb);
if (write(fd, buf_size_kb, len) != len) {
err = true;
- sprintf(err_msg, "Error in writing to atrace buffer size file.");
+ snprintf(err_msg, sizeof(err_msg), "Error in writing to atrace buffer size file.");
}
close(fd);
return (err?-1:0);
diff --git a/alloc-stress/Android.mk b/alloc-stress/Android.mk
index ec19d79..ad46f26 100644
--- a/alloc-stress/Android.mk
+++ b/alloc-stress/Android.mk
@@ -12,3 +12,13 @@
LOCAL_SRC_FILES := \
alloc-stress.cpp
include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := mem-pressure
+LOCAL_CFLAGS += -g -Wall -Werror -Wno-missing-field-initializers -Wno-sign-compare
+ifneq ($(ENABLE_MEM_CGROUPS),)
+ LOCAL_CFLAGS += -DENABLE_MEM_CGROUPS
+endif
+LOCAL_SRC_FILES := \
+ mem-pressure.cpp
+include $(BUILD_EXECUTABLE)
diff --git a/alloc-stress/mem-pressure.cpp b/alloc-stress/mem-pressure.cpp
new file mode 100644
index 0000000..777015d
--- /dev/null
+++ b/alloc-stress/mem-pressure.cpp
@@ -0,0 +1,107 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <getopt.h>
+
+void *alloc_set(size_t size) {
+ void *addr = NULL;
+
+ addr = malloc(size);
+ if (!addr) {
+ printf("Allocating %zd MB failed\n", size / 1024 / 1024);
+ } else {
+ memset(addr, 0, size);
+ }
+ return addr;
+}
+
+void add_pressure(size_t *shared, size_t size, size_t step_size,
+ size_t duration, const char *oom_score) {
+ int fd, ret;
+
+ fd = open("/proc/self/oom_score_adj", O_WRONLY);
+ ret = write(fd, oom_score, strlen(oom_score));
+ if (ret < 0) {
+ printf("Writing oom_score_adj failed with err %s\n",
+ strerror(errno));
+ }
+ close(fd);
+
+ if (alloc_set(size)) {
+ *shared = size;
+ }
+
+ while (alloc_set(step_size)) {
+ size += step_size;
+ *shared = size;
+ usleep(duration);
+ }
+}
+
+void usage()
+{
+ printf("Usage: [OPTIONS]\n\n"
+ " -d N: Duration in microsecond to sleep between each allocation.\n"
+ " -i N: Number of iterations to run the alloc process.\n"
+ " -o N: The oom_score to set the child process to before alloc.\n"
+ " -s N: Number of bytes to allocate in an alloc process loop.\n"
+ );
+}
+
+int main(int argc, char *argv[])
+{
+ pid_t pid;
+ size_t *shared;
+ int c, i = 0;
+
+ size_t duration = 1000;
+ int iterations = 0;
+ const char *oom_score = "899";
+ size_t step_size = 2 * 1024 * 1024; // 2 MB
+ size_t size = step_size;
+
+ while ((c = getopt(argc, argv, "hi:d:o:s:")) != -1) {
+ switch (c)
+ {
+ case 'i':
+ iterations = atoi(optarg);
+ break;
+ case 'd':
+ duration = atoi(optarg);
+ break;
+ case 'o':
+ oom_score = optarg;
+ break;
+ case 's':
+ step_size = atoi(optarg);
+ break;
+ case 'h':
+ usage();
+ abort();
+ default:
+ abort();
+ }
+ }
+
+ shared = (size_t *)mmap(NULL, sizeof(size_t), PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_SHARED, 0, 0);
+
+ while (iterations == 0 || i < iterations) {
+ *shared = 0;
+ pid = fork();
+ if (!pid) {
+ /* Child */
+ add_pressure(shared, size, step_size, duration, oom_score);
+ /* Shoud not get here */
+ exit(0);
+ } else {
+ wait(NULL);
+ printf("Child %d allocated %zd MB\n", i,
+ *shared / 1024 / 1024);
+ size = *shared / 2;
+ }
+ i++;
+ }
+}
diff --git a/cppreopts/cppreopts.sh b/cppreopts/cppreopts.sh
index 8798206..8a0d466 100644
--- a/cppreopts/cppreopts.sh
+++ b/cppreopts/cppreopts.sh
@@ -19,20 +19,21 @@
# Helper function to copy files
function do_copy() {
- odex_file=$1
+ source_file=$1
dest_name=$2
# Move to a temporary file so we can do a rename and have the preopted file
# appear atomically in the filesystem.
temp_dest_name=${dest_name}.tmp
- if ! cp ${odex_file} ${temp_dest_name} ; then
- log -p w -t cppreopts "Unable to copy odex file ${odex_file} to ${temp_dest_name}!"
+ if ! cp ${source_file} ${temp_dest_name} ; then
+ log -p w -t cppreopts "Unable to copy file ${source_file} to ${temp_dest_name}!"
else
- log -p i -t cppreopts "Copied odex file from ${odex_file} to ${temp_dest_name}"
+ log -p i -t cppreopts "Copied file from ${source_file} to ${temp_dest_name}"
sync
if ! mv ${temp_dest_name} ${dest_name} ; then
- log -p w -t cppreopts "Unable to rename temporary odex file from ${temp_dest_name} to ${dest_name}"
+ log -p w -t cppreopts "Unable to rename temporary file from ${temp_dest_name} to ${dest_name}"
+ rm ${temp_dest_name} || log -p w -t cppreopts "Unable to remove temporary file ${temp_dest_name}"
else
- log -p i -t cppreopts "Renamed temporary odex file from ${temp_dest_name} to ${dest_name}"
+ log -p i -t cppreopts "Renamed temporary file from ${temp_dest_name} to ${dest_name}"
fi
fi
}
@@ -42,23 +43,23 @@
mountpoint=$1
if ! test -f ${mountpoint}/system-other-odex-marker ; then
- log -p i -t cppreopts "system_other partition does not appear have been built to contain preopted files."
+ log -p i -t cppreopts "system_other partition does not appear to have been built to contain preopted files."
exit 1
fi
log -p i -t cppreopts "cppreopts from ${mountpoint}"
- # For each odex file do the copy task
+ # For each odex and vdex file do the copy task
# NOTE: this implementation will break in any path with spaces to favor
# background copy tasks
- for odex_file in $(find ${mountpoint} -type f -name "*.odex"); do
- real_odex_name=${odex_file/${mountpoint}/\/system}
- dest_name=$(preopt2cachename ${real_odex_name})
+ for file in $(find ${mountpoint} -type f -name "*.odex" -o -type f -name "*.vdex"); do
+ real_name=${file/${mountpoint}/\/system}
+ dest_name=$(preopt2cachename ${real_name})
if ! test $? -eq 0 ; then
- log -p i -t cppreopts "Unable to figure out destination for ${odex_file}"
+ log -p i -t cppreopts "Unable to figure out destination for ${file}"
continue
fi
# Copy files in background to speed things up
- do_copy ${odex_file} ${dest_name} &
+ do_copy ${file} ${dest_name} &
done
# Wait for jobs to finish
wait
diff --git a/ext4_utils/allocate.c b/ext4_utils/allocate.c
index 00f2203..e0f662c 100644
--- a/ext4_utils/allocate.c
+++ b/ext4_utils/allocate.c
@@ -234,6 +234,18 @@
for (i = 0; i < num_blocks; i++, block--)
bg->block_bitmap[block / 8] &= ~(1 << (block % 8));
bg->free_blocks += num_blocks;
+ for (i = bg->chunk_count; i > 0 ;) {
+ --i;
+ if (bg->chunks[i].len >= num_blocks && bg->chunks[i].block <= block) {
+ if (bg->chunks[i].block == block) {
+ bg->chunks[i].block += num_blocks;
+ bg->chunks[i].len -= num_blocks;
+ } else if (bg->chunks[i].block + bg->chunks[i].len - 1 == block + num_blocks) {
+ bg->chunks[i].len -= num_blocks;
+ }
+ break;
+ }
+ }
}
/* Reduces an existing allocation by len blocks by return the last blocks
@@ -255,6 +267,7 @@
len -= last_reg->len;
if (reg) {
reg->next = NULL;
+ alloc->list.last = reg;
} else {
alloc->list.first = NULL;
alloc->list.last = NULL;
diff --git a/ext4_utils/ext4_crypt.cpp b/ext4_utils/ext4_crypt.cpp
index d594a48..de85f7e 100644
--- a/ext4_utils/ext4_crypt.cpp
+++ b/ext4_utils/ext4_crypt.cpp
@@ -39,17 +39,25 @@
#define EXT4_KEY_DESCRIPTOR_SIZE_HEX 17
struct ext4_encryption_policy {
- char version;
- char contents_encryption_mode;
- char filenames_encryption_mode;
- char flags;
- char master_key_descriptor[EXT4_KEY_DESCRIPTOR_SIZE];
+ uint8_t version;
+ uint8_t contents_encryption_mode;
+ uint8_t filenames_encryption_mode;
+ uint8_t flags;
+ uint8_t master_key_descriptor[EXT4_KEY_DESCRIPTOR_SIZE];
} __attribute__((__packed__));
#define EXT4_ENCRYPTION_MODE_AES_256_XTS 1
#define EXT4_ENCRYPTION_MODE_AES_256_CTS 4
+#define EXT4_ENCRYPTION_MODE_AES_256_HEH 126
#define EXT4_ENCRYPTION_MODE_PRIVATE 127
+#define EXT4_POLICY_FLAGS_PAD_4 0x00
+#define EXT4_POLICY_FLAGS_PAD_8 0x01
+#define EXT4_POLICY_FLAGS_PAD_16 0x02
+#define EXT4_POLICY_FLAGS_PAD_32 0x03
+#define EXT4_POLICY_FLAGS_PAD_MASK 0x03
+#define EXT4_POLICY_FLAGS_VALID 0x03
+
// ext4enc:TODO Get value from somewhere sensible
#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)
@@ -100,8 +108,25 @@
return true;
}
+static uint8_t e4crypt_get_policy_flags(int filenames_encryption_mode) {
+
+ // With HEH, pad filenames with zeroes to the next 16-byte boundary. This
+ // is not required, but it's more secure (helps hide the length of
+ // filenames), makes the inputs evenly divisible into blocks which is more
+ // efficient for encryption and decryption, and we had the opportunity to
+ // make a breaking change when introducing a new mode anyway.
+ if (filenames_encryption_mode == EXT4_ENCRYPTION_MODE_AES_256_HEH) {
+ return EXT4_POLICY_FLAGS_PAD_16;
+ }
+
+ // Default flags (4-byte padding) for CTS
+ return EXT4_POLICY_FLAGS_PAD_4;
+}
+
static bool e4crypt_policy_set(const char *directory, const char *policy,
- size_t policy_length, int contents_encryption_mode) {
+ size_t policy_length,
+ int contents_encryption_mode,
+ int filenames_encryption_mode) {
if (policy_length != EXT4_KEY_DESCRIPTOR_SIZE) {
LOG(ERROR) << "Policy wrong length: " << policy_length;
return false;
@@ -115,8 +140,8 @@
ext4_encryption_policy eep;
eep.version = 0;
eep.contents_encryption_mode = contents_encryption_mode;
- eep.filenames_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_CTS;
- eep.flags = 0;
+ eep.filenames_encryption_mode = filenames_encryption_mode;
+ eep.flags = e4crypt_get_policy_flags(filenames_encryption_mode);
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;
@@ -132,7 +157,9 @@
}
static bool e4crypt_policy_get(const char *directory, char *policy,
- size_t policy_length, int contents_encryption_mode) {
+ size_t policy_length,
+ int contents_encryption_mode,
+ int filenames_encryption_mode) {
if (policy_length != EXT4_KEY_DESCRIPTOR_SIZE) {
LOG(ERROR) << "Policy wrong length: " << policy_length;
return false;
@@ -155,8 +182,9 @@
if ((eep.version != 0)
|| (eep.contents_encryption_mode != contents_encryption_mode)
- || (eep.filenames_encryption_mode != EXT4_ENCRYPTION_MODE_AES_256_CTS)
- || (eep.flags != 0)) {
+ || (eep.filenames_encryption_mode != filenames_encryption_mode)
+ || (eep.flags !=
+ e4crypt_get_policy_flags(filenames_encryption_mode))) {
LOG(ERROR) << "Failed to find matching encryption policy for " << directory;
return false;
}
@@ -166,14 +194,17 @@
}
static bool e4crypt_policy_check(const char *directory, const char *policy,
- size_t policy_length, int contents_encryption_mode) {
+ size_t policy_length,
+ int contents_encryption_mode,
+ int filenames_encryption_mode) {
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,
- contents_encryption_mode)) return false;
+ contents_encryption_mode,
+ filenames_encryption_mode)) return false;
char existing_policy_hex[EXT4_KEY_DESCRIPTOR_SIZE_HEX];
policy_to_hex(existing_policy, existing_policy_hex);
@@ -191,14 +222,30 @@
}
int e4crypt_policy_ensure(const char *directory, const char *policy,
- size_t policy_length, const char* contents_encryption_mode) {
- int mode = 0;
- if (!strcmp(contents_encryption_mode, "software")) {
- mode = EXT4_ENCRYPTION_MODE_AES_256_XTS;
+ size_t policy_length,
+ const char *contents_encryption_mode,
+ const char *filenames_encryption_mode) {
+ int contents_mode = 0;
+ int filenames_mode = 0;
+
+ if (!strcmp(contents_encryption_mode, "software") ||
+ !strcmp(contents_encryption_mode, "aes-256-xts")) {
+ contents_mode = EXT4_ENCRYPTION_MODE_AES_256_XTS;
} else if (!strcmp(contents_encryption_mode, "ice")) {
- mode = EXT4_ENCRYPTION_MODE_PRIVATE;
+ contents_mode = EXT4_ENCRYPTION_MODE_PRIVATE;
} else {
- LOG(ERROR) << "Invalid encryption mode";
+ LOG(ERROR) << "Invalid file contents encryption mode: "
+ << contents_encryption_mode;
+ return -1;
+ }
+
+ if (!strcmp(filenames_encryption_mode, "aes-256-cts")) {
+ filenames_mode = EXT4_ENCRYPTION_MODE_AES_256_CTS;
+ } else if (!strcmp(filenames_encryption_mode, "aes-256-heh")) {
+ filenames_mode = EXT4_ENCRYPTION_MODE_AES_256_HEH;
+ } else {
+ LOG(ERROR) << "Invalid file names encryption mode: "
+ << filenames_encryption_mode;
return -1;
}
@@ -206,10 +253,10 @@
if (!is_dir_empty(directory, &is_empty)) return -1;
if (is_empty) {
if (!e4crypt_policy_set(directory, policy, policy_length,
- mode)) return -1;
+ contents_mode, filenames_mode)) return -1;
} else {
if (!e4crypt_policy_check(directory, policy, policy_length,
- mode)) return -1;
+ contents_mode, filenames_mode)) return -1;
}
return 0;
}
diff --git a/ext4_utils/ext4_crypt_init_extensions.cpp b/ext4_utils/ext4_crypt_init_extensions.cpp
index 04d7119..2bf8801 100644
--- a/ext4_utils/ext4_crypt_init_extensions.cpp
+++ b/ext4_utils/ext4_crypt_init_extensions.cpp
@@ -28,6 +28,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
#include <cutils/properties.h>
#include <cutils/sockets.h>
#include <keyutils.h>
@@ -91,14 +92,23 @@
}
auto type_filename = std::string("/data") + e4crypt_key_mode;
- std::string contents_encryption_mode;
- if (!android::base::ReadFileToString(type_filename, &contents_encryption_mode)) {
+ std::string modestring;
+ if (!android::base::ReadFileToString(type_filename, &modestring)) {
LOG(ERROR) << "Cannot read mode";
}
+ std::vector<std::string> modes = android::base::Split(modestring, ":");
+
+ if (modes.size() < 1 || modes.size() > 2) {
+ LOG(ERROR) << "Invalid encryption mode string: " << modestring;
+ return -1;
+ }
+
LOG(INFO) << "Setting policy on " << dir;
int result = e4crypt_policy_ensure(dir, policy.c_str(), policy.length(),
- contents_encryption_mode.c_str());
+ modes[0].c_str(),
+ modes.size() >= 2 ?
+ modes[1].c_str() : "aes-256-cts");
if (result) {
LOG(ERROR) << android::base::StringPrintf(
"Setting %02x%02x%02x%02x policy on %s failed!",
diff --git a/ext4_utils/include/ext4_utils/ext4_crypt.h b/ext4_utils/include/ext4_utils/ext4_crypt.h
index 2be0bec..d410ccf 100644
--- a/ext4_utils/include/ext4_utils/ext4_crypt.h
+++ b/ext4_utils/include/ext4_utils/ext4_crypt.h
@@ -25,9 +25,10 @@
bool e4crypt_is_native();
-int e4crypt_policy_ensure(const char *directory,
- const char* policy, size_t policy_length,
- const char* contents_encryption_mode);
+int e4crypt_policy_ensure(const char *directory, const char *policy,
+ size_t policy_length,
+ const char *contents_encryption_mode,
+ const char *filenames_encryption_mode);
static const char* e4crypt_unencrypted_folder = "/unencrypted";
static const char* e4crypt_key_ref = "/unencrypted/ref";
diff --git a/ext4_utils/mkuserimg_mke2fs.sh b/ext4_utils/mkuserimg_mke2fs.sh
index 8cb9202..64b1fe3 100755
--- a/ext4_utils/mkuserimg_mke2fs.sh
+++ b/ext4_utils/mkuserimg_mke2fs.sh
@@ -21,6 +21,8 @@
if [ "$1" = "-s" ]; then
MKE2FS_EXTENDED_OPTS+="android_sparse"
shift
+else
+ E2FSDROID_OPTS+="-e"
fi
if [ $# -lt 5 ]; then
@@ -90,7 +92,7 @@
fi
if [[ "$1" == "-e" ]]; then
- if [[ MKE2FS_EXTENDED_OPTS ]]; then
+ if [[ $MKE2FS_EXTENDED_OPTS ]]; then
MKE2FS_EXTENDED_OPTS+=","
fi
MKE2FS_EXTENDED_OPTS+="stripe_width=$(($2/BLOCKSIZE))"
@@ -98,7 +100,7 @@
fi
if [[ "$1" == "-o" ]]; then
- if [[ MKE2FS_EXTENDED_OPTS ]]; then
+ if [[ $MKE2FS_EXTENDED_OPTS ]]; then
MKE2FS_EXTENDED_OPTS+=","
fi
# stride should be the max of 8kb and the logical block size
@@ -106,11 +108,13 @@
shift; shift
fi
-if [[ MKE2FS_EXTENDED_OPTS ]]; then
+if [[ $MKE2FS_EXTENDED_OPTS ]]; then
MKE2FS_OPTS+=" -E $MKE2FS_EXTENDED_OPTS"
fi
-E2FSDROID_OPTS+=" -S $1"
+if [[ $1 ]]; then
+ E2FSDROID_OPTS+=" -S $1"
+fi
case $EXT_VARIANT in
ext4) ;;
@@ -122,6 +126,10 @@
exit 2
fi
+if [[ ${MOUNT_POINT:0:1} != "/" ]]; then
+ MOUNT_POINT="/"$MOUNT_POINT
+fi
+
if [ -z $SIZE ]; then
echo "Need size of filesystem"
exit 2
@@ -130,6 +138,9 @@
# Round down the filesystem length to be a multiple of the block size
SIZE=$((SIZE / BLOCKSIZE))
+# truncate output file since mke2fs will keep verity section in existing file
+cat /dev/null >$OUTPUT_FILE
+
MAKE_EXT4FS_CMD="mke2fs $MKE2FS_OPTS -t $EXT_VARIANT -b $BLOCKSIZE $OUTPUT_FILE $SIZE"
echo $MAKE_EXT4FS_CMD
MKE2FS_CONFIG=./system/extras/ext4_utils/mke2fs.conf $MAKE_EXT4FS_CMD
@@ -137,7 +148,7 @@
exit 4
fi
-E2FSDROID_CMD="e2fsdroid $E2FSDROID_OPTS -f $SRC_DIR -a /$MOUNT_POINT $OUTPUT_FILE"
+E2FSDROID_CMD="e2fsdroid $E2FSDROID_OPTS -f $SRC_DIR -a $MOUNT_POINT $OUTPUT_FILE"
echo $E2FSDROID_CMD
$E2FSDROID_CMD
if [ $? -ne 0 ]; then
diff --git a/ioshark/Android.mk b/ioshark/Android.mk
new file mode 100644
index 0000000..a647d75
--- /dev/null
+++ b/ioshark/Android.mk
@@ -0,0 +1,44 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+#LOCAL_32_BIT_ONLY = true
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_SRC_FILES := ioshark_bench.c ioshark_bench_subr.c ioshark_bench_mmap.c
+LOCAL_CFLAGS := -g -O2 -Wall -Werror
+LOCAL_MODULE := ioshark_bench
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+#LOCAL_32_BIT_ONLY = true
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_SRC_FILES := compile_ioshark.c compile_ioshark_subr.c
+LOCAL_CFLAGS := -g -O2 -Wall -Werror -D_GNU_SOURCE
+LOCAL_MODULE := compile_ioshark
+LOCAL_MODULE_TAGS := debug
+include $(BUILD_HOST_EXECUTABLE)
+
+include $(CLEAR_VARS)
+#LOCAL_32_BIT_ONLY = true
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_SRC_FILES := dump_ioshark_filenames.c
+LOCAL_CFLAGS := -g -O2 -Wall -Werror -D_GNU_SOURCE
+LOCAL_MODULE := dump_ioshark_filenames
+LOCAL_MODULE_TAGS := debug
+include $(BUILD_HOST_EXECUTABLE)
+
diff --git a/ioshark/README b/ioshark/README
new file mode 100644
index 0000000..0c50fec
--- /dev/null
+++ b/ioshark/README
@@ -0,0 +1,30 @@
+IOshark is a repeatable application workload storage benchmark. You
+can find more documentation on IOshark at :
+https://docs.google.com/a/google.com/document/d/1Bhq7iNPVc_JzwRrkmZqcPjMvWgpHX0r3Ncq-ZsRNOBA/edit?usp=sharing
+
+The short summary of what IOshark is : IOshark has 2 components, one
+is a strace+ftrace compiler that takes straces and select ftraces fed
+into it and compiles this into bytecodes (stored in *.wl files). The
+compiler runs on a Linux host. The second component (which runs on the
+device) is the tester that takes as input the bytecode files (*.wl
+files) and executes them on the device.
+
+How to Run :
+----------
+- First collect straces and compile these into bytecodes. The wrapper
+script provided (collect-straces.sh) collects straces, ships them to
+the host where the script runs, compiles and packages up the bytecode
+files into a wl.tar file.
+- Ship the wl.tar file and the iostark_bench binaries to the target
+device (on /data/local/tmp say). Explode the tarfile.
+- Run the tester. "ioshark_bench *.wl" runs the test with default
+options. Supported ioshark_bench options :
+-d : Preserve the delays between successive filesystem syscalls as
+seen in the original straces.
+-n <N> : Run for N iterations
+-t <N> : Limit to N threads. By default (without this option), IOshark
+will launch as many threads as there are input files, so 1 thread/file.
+-v : verbose. Chatty mode.
+-s : One line summary.
+-q : Don't create the files in read-only partitions like /system and
+/vendor. Instead do reads on those files.
diff --git a/ioshark/collect-straces-ftraces.sh b/ioshark/collect-straces-ftraces.sh
new file mode 100644
index 0000000..6e83a24
--- /dev/null
+++ b/ioshark/collect-straces-ftraces.sh
@@ -0,0 +1,202 @@
+#!/bin/sh
+
+# This function just re-writes the timestamp of the strace entries to be
+# seconds.usecs since boot. To match the timestamping of ftrace (so we can
+# merge them later).
+process_strace()
+{
+ strace=$1
+ # parse in data/system/vendor and parse out /sys/devices/system/...
+ egrep '\/system\/|\/data\/|\/vendor\/' $strace | egrep -v '\/sys\/devices\/system\/' > bar
+ fgrep -v '= -1' bar > foo
+ mv foo bar
+ # begin_time is seconds since epoch
+ begin_time=`cat trace.begin`
+ # replace seconds since epoch with SECONDS SINCE BOOT in the
+ # strace files
+ awk -v begin="$begin_time" '{ printf "%f strace ", $1 - begin; $1=""; print $0}' bar > $2
+ rm bar
+}
+
+#
+# This function processes the ftrace file, removing the fields that we don't care
+# about, breaks up the ftrace file into one file per pid.
+# Input : One single fstrace file.
+# Output : Multiple fstrace.pid files.
+prep_fstrace()
+{
+ # Remove leading junk
+ fgrep android_fs_data $1 | sed 's/^.* \[.*\] //' | sed s/://g | sed s/,//g > foo
+ # Sanitize the filenames, removing spaces within the filename etc
+ sed 's/android_fs_dataread_start/read/' foo > bar1
+ mv bar1 bar
+ # First column is timestamp SECONDS SINCE BOOT
+ awk '{ print $2, "ftrace", $3, $5, $7, $9, $13 }' bar > foo
+ #awk '{ s ="" ; for (i=2; i <= NF ; i++) s = s $i " "; print s}' bar > foo
+ rm bar
+ # Get all the uniq pids
+ awk '{print $7}' foo | sort | uniq > pidlist
+ for i in `cat pidlist`
+ do
+ awk -v pid=$i '{ if (pid == $7) print $0}' foo > fstrace.$i
+ done
+ rm pidlist
+ rm foo
+}
+
+# Merge straces and ftraces.
+# The goal here is to catch mmap'ed IO (reads) that won't be in the
+# strace file. The algorithm is to look for mmaps in the strace file,
+# use the files tha are mmap'ed to search in the ftraces to pick up
+# tracepoints from there, and merge those with the straces.
+# The output of this function is a set of parsed_input_trace.<pid>
+# files, that can then be compiled into .wl files
+merge_compile()
+{
+ for stracefile in trace.*
+ do
+ if [ $stracefile == trace.begin ] || [ $stracefile == trace.tar ];
+ then
+ continue
+ fi
+ # Get the pid from the strace filename (pid is the extension)
+ pid=${stracefile##*.}
+ process_strace $stracefile foo.$pid
+ if ! [ -s foo.$pid ]; then
+ rm foo.$pid
+ continue
+ fi
+ #
+ # If we have matching strace and ftrace files, then look for mmaps in
+ # the strace pluck the corresponding entries for the mmap (mmaped IO)
+ # from the ftrace and merge them into the strace
+ #
+ if [ -f fstrace.$pid ]; then
+ fgrep mmap foo.$pid > bar
+ if [ -s bar ]; then
+ # Get all the unique mmap'ed filenames from the strace
+ awk '{ print $7 }' bar | sed 's/^[^<]*<//g' | sed 's/>,//g' > mapped_files
+ # Pluck all the lines from the ftrace corresponding to the mmaps
+ cat /dev/null > footemp
+ for j in `sort mapped_files | uniq`
+ do
+ # Merge the readpage(s) traces from the ftrace into strace
+ # for this mmaped file.
+ grep -w $j fstrace.$pid > foobar
+ if [ $? == 0 ]; then
+ sort foo.$pid foobar >> footemp
+ fi
+ rm foobar
+ done
+ rm mapped_files
+ if [ -s footemp ]; then
+ mv footemp parsed_input_trace.$pid
+ else
+ mv foo.$pid parsed_input_trace.$pid
+ fi
+ else
+ mv foo.$pid parsed_input_trace.$pid
+ fi
+ rm bar
+ else
+ mv foo.$pid parsed_input_trace.$pid
+ fi
+ echo compiling parsed_input_trace.$pid
+ compile_ioshark parsed_input_trace.$pid $pid.wl
+ rm parsed_input_trace.$pid
+ rm -f foo.$pid
+ done
+}
+
+catch_sigint()
+{
+ echo "signal INT received, killing streaming trace capture"
+ ps_line=`ps -ef | grep trace_pipe | grep adb `
+ if [ $? == 0 ]; then
+ echo Killing `echo $ps_line | awk '{s = ""; for (i=8; i <= NF ; i++) s = s $i " "; print s}' `
+ kill `echo $ps_line | awk '{print $2}' `
+ fi
+ ps_line=`ps -ef | grep strace | grep adb `
+ if [ $? == 0 ]; then
+ echo Killing `echo $ps_line | awk '{s = ""; for (i=8; i <= NF ; i++) s = s $i " "; print s}' `
+ kill `echo $ps_line | awk '{print $2}' `
+ fi
+}
+
+enable_tracepoints()
+{
+ adb shell "echo 1 > /sys/kernel/debug/tracing/events/android_fs/android_fs_dataread_start/enable"
+ adb shell "echo 1 > /sys/kernel/debug/tracing/tracing_on"
+}
+
+disable_tracepoints()
+{
+ adb shell "echo 0 > /sys/kernel/debug/tracing/events/android_fs/android_fs_dataread_start/enable"
+ adb shell "echo 0 > /sys/kernel/debug/tracing/tracing_on"
+}
+
+kill_traces()
+{
+ ps_line=`ps -ef | grep trace_pipe | grep adb `
+ if [ $? == 0 ]; then
+ echo Killing `echo $ps_line | awk '{s = ""; for (i=8; i <= NF ; i++) s = s $i " "; print s}' `
+ kill `echo $ps_line | awk '{print $2}' `
+ fi
+ ps_line=`ps -ef | grep strace | grep adb `
+ if [ $? == 0 ]; then
+ echo Killing `echo $ps_line | awk '{s = ""; for (i=8; i <= NF ; i++) s = s $i " "; print s}' `
+ kill `echo $ps_line | awk '{print $2}' `
+ fi
+}
+
+catch_sigint()
+{
+ echo "signal INT received, killing streaming trace capture"
+ kill_traces
+}
+
+# main() starts here
+
+adb root && adb wait-for-device
+
+enable_tracepoints
+
+trap 'catch_sigint' INT
+
+adb shell 'ps' | grep zygote > zygote_pids
+fgrep -v grep zygote_pids > bar
+mv bar zygote_pids
+pid1=`grep -w zygote zygote_pids | awk '{print $2}' `
+pid2=`grep -w zygote64 zygote_pids | awk '{print $2}' `
+rm -f zygote_pids
+
+adb shell "date +%s > /data/local/tmp/trace.begin ; strace -p $pid1,$pid2 -o /data/local/tmp/trace -q -qq -f -ff -y -ttt -e trace=mmap2,read,write,pread64,pwrite64,fsync,fdatasync,openat,close,lseek,_llseek" &
+adb shell "cat /sys/kernel/debug/tracing/trace_pipe" > fstrace &
+
+echo "^C this when done with the test"
+
+wait
+
+adb shell 'monkey -p com.android.alarmclock -p com.android.chrome -p com.android.calculator -p com.android.calendar -p com.google.android.calendar -p com.google.android.camera -p com.android.contacts -p com.google.android.gm -p com.android.im -p com.android.launcher -p com.google.android.apps.maps -p com.android.mms -p com.google.android.music -p com.android.phone -p com.google.android.youtube -p com.android.email -p com.google.android.voicesearch -c android.intent.category.LAUNCHER --throttle 200 --ignore-security-exceptions --ignore-crashes --ignore-timeouts -v -v -v 25000'
+
+kill_traces
+
+disable_tracepoints
+
+rm -f trace.*
+rm -f fstrace.*
+rm -f *.wl
+rm -f parsed*
+
+# Get the tracefiles from the device
+adb shell 'cd /data/local/tmp ; tar cvf trace.tar trace.*'
+adb pull /data/local/tmp/trace.tar
+tar xf trace.tar
+
+# Pre-process the ftrace file
+prep_fstrace fstrace
+# Merge the ftrace file(s) with the strace files
+merge_compile
+
+# tar up the .wl files just created
+tar cf wl.tar ioshark_filenames *.wl
diff --git a/ioshark/collect-straces.sh b/ioshark/collect-straces.sh
new file mode 100755
index 0000000..21a3939
--- /dev/null
+++ b/ioshark/collect-straces.sh
@@ -0,0 +1,97 @@
+# 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.
+
+#!/bin/sh
+
+# When signal is received, the stracer will get killed
+# Call this (just to make sure anyway)
+kill_strace() {
+ ps_line=`ps -ef | grep strace | grep adb `
+ if [ $? == 0 ]; then
+ echo Killing `echo $ps_line | awk '{s = ""; for (i=8; i <= NF ; i++) s = s \
+$i " "; print s}' `
+ kill `echo $ps_line | awk '{print $2}' `
+ fi
+}
+
+catch_sigint()
+{
+ echo "signal INT received, killing streaming trace capture"
+ kill_strace
+}
+
+compile_tracefiles()
+{
+ for i in trace.*
+ do
+ if [ $i != trace.begin ] && [ $i != trace.tar ];
+ then
+ egrep '\/system\/|\/data\/|\/vendor\/' $i > bar
+# parse out /sys/devices/system/...
+ egrep -v '\/sys\/devices\/system\/' bar > bar0
+ mv bar0 bar
+ fgrep -v '= -1' bar > foo
+ rm bar
+ # begin_time is seconds since epoch
+ begin_time=`cat trace.begin`
+ # replace seconds since epoch with SECONDS SINCE BOOT in the
+ # strace files
+ awk -v begin="$begin_time" '{ printf "%f strace ", $1 - begin; $1=""; print $0}' foo > bar
+ if [ -s bar ]
+ then
+ echo parsing $i
+ pid=${i##*.}
+ compile_ioshark bar $pid.wl
+ rm -f bar
+ else
+ rm -f $i bar
+ fi
+ fi
+ done
+}
+
+# main() starts here
+
+adb root && adb wait-for-device
+
+adb shell 'ps' | grep zygote > zygote_pids
+
+fgrep -v grep zygote_pids > bar
+mv bar zygote_pids
+pid1=`grep -w zygote zygote_pids | awk '{print $2}' `
+pid2=`grep -w zygote64 zygote_pids | awk '{print $2}' `
+rm -f zygote_pids
+
+trap 'catch_sigint' INT
+
+echo "^C this script once you finish running your test"
+
+adb shell "date +%s > /data/local/tmp/trace.begin ; strace -p $pid1,$pid2 -o /data/local/tmp/trace -q -qq -f -ff -y -ttt -e trace=mmap2,read,write,pread64,pwrite64,fsync,fdatasync,openat,close,lseek,_llseek"
+
+# Remove any remnant tracefiles first
+rm -f trace.*
+
+# Get the tracefiles from the device
+adb shell 'cd /data/local/tmp ; tar cvf trace.tar trace.*'
+adb pull /data/local/tmp/trace.tar
+
+# Extract the tracefiles from the device
+rm -f *.wl
+tar xf trace.tar
+
+# Compile the tracefiles
+compile_tracefiles
+
+# tar up the .wl files just created
+rm -f wl.tar
+tar cf wl.tar ioshark_filenames *.wl
diff --git a/ioshark/compile-only.sh b/ioshark/compile-only.sh
new file mode 100644
index 0000000..634b474
--- /dev/null
+++ b/ioshark/compile-only.sh
@@ -0,0 +1,121 @@
+#!/bin/sh
+
+# This function just re-writes the timestamp of the strace entries to be
+# seconds.usecs since boot. To match the timestamping of ftrace (so we can
+# merge them later).
+process_strace()
+{
+ strace=$1
+ # parse in data/system/vendor and parse out /sys/devices/system/...
+ egrep '\/system\/|\/data\/|\/vendor\/' $strace | egrep -v '\/sys\/devices\/system\/' > bar
+ fgrep -v '= -1' bar > foo
+ mv foo bar
+ # begin_time is seconds since epoch
+ begin_time=`cat trace.begin`
+ # replace seconds since epoch with SECONDS SINCE BOOT in the
+ # strace files
+ awk -v begin="$begin_time" '{ printf "%f strace ", $1 - begin; $1=""; print $0}' bar > $2
+ rm bar
+}
+
+#
+# This function processes the ftrace file, removing the fields that we don't care
+# about, breaks up the ftrace file into one file per pid.
+# Input : One single fstrace file.
+# Output : Multiple fstrace.pid files.
+prep_fstrace()
+{
+ # Remove leading junk
+ fgrep android_fs_data $1 | sed 's/^.* \[.*\] //' | sed s/://g | sed s/,//g > foo
+ sed 's/android_fs_dataread_start/read/' foo > bar1
+ mv bar1 bar
+ # First column is timestamp SECONDS SINCE BOOT
+ awk '{ print $2, "ftrace", $3, $5, $7, $9, $13 }' bar > foo
+ rm bar
+ # Get all the uniq pids
+ awk '{print $7}' foo | sort | uniq > pidlist
+ for i in `cat pidlist`
+ do
+ awk -v pid=$i '{ if (pid == $7) print $0}' foo > fstrace.$i
+ done
+ rm pidlist
+ rm foo
+}
+
+# Merge straces and ftraces.
+# The goal here is to catch mmap'ed IO (reads) that won't be in the
+# strace file. The algorithm is to look for mmaps in the strace file,
+# use the files tha are mmap'ed to search in the ftraces to pick up
+# tracepoints from there, and merge those with the straces.
+# The output of this function is a set of parsed_input_trace.<pid>
+# files, that can then be compiled into .wl files
+merge_compile()
+{
+ for stracefile in trace.*
+ do
+ if [ $stracefile == trace.begin ] || [ $stracefile == trace.tar ];
+ then
+ continue
+ fi
+ # Get the pid from the strace filename (pid is the extension)
+ pid=${stracefile##*.}
+ process_strace $stracefile foo.$pid
+ if ! [ -s foo.$pid ]; then
+ rm foo.$pid
+ continue
+ fi
+ #
+ # If we have matching strace and ftrace files, then look for mmaps in
+ # the strace pluck the corresponding entries for the mmap (mmaped IO)
+ # from the ftrace and merge them into the strace
+ #
+ if [ -f fstrace.$pid ]; then
+ fgrep mmap foo.$pid > bar
+ if [ -s bar ]; then
+ # Get all the unique mmap'ed filenames from the strace
+ awk '{ print $7 }' bar | sed 's/^[^<]*<//g' | sed 's/>,//g' > mapped_files
+ # Pluck all the lines from the ftrace corresponding to the mmaps
+ cat /dev/null > footemp
+ for j in `sort mapped_files | uniq`
+ do
+ # Merge the readpage(s) traces from the ftrace into strace
+ # for this mmaped file.
+ grep -w $j fstrace.$pid > foobar
+ if [ $? == 0 ]; then
+ sort foo.$pid foobar >> footemp
+ fi
+ rm foobar
+ done
+ rm mapped_files
+ if [ -s footemp ]; then
+ mv footemp parsed_input_trace.$pid
+ else
+ mv foo.$pid parsed_input_trace.$pid
+ fi
+ else
+ mv foo.$pid parsed_input_trace.$pid
+ fi
+ rm bar
+ else
+ mv foo.$pid parsed_input_trace.$pid
+ fi
+ echo compiling parsed_input_trace.$pid
+ compile_ioshark parsed_input_trace.$pid $pid.wl
+ rm parsed_input_trace.$pid
+ rm -f foo.$pid
+ done
+}
+
+# main() starts here
+
+rm -f *.wl
+rm -f parsed*
+rm ioshark_filenames
+
+# Pre-process the ftrace file
+prep_fstrace fstrace
+# Merge the ftrace file(s) with the strace files
+merge_compile
+
+# tar up the .wl files just created
+tar cf wl-test.tar ioshark_filenames *.wl
diff --git a/ioshark/compile_ioshark.c b/ioshark/compile_ioshark.c
new file mode 100644
index 0000000..f35d595
--- /dev/null
+++ b/ioshark/compile_ioshark.c
@@ -0,0 +1,687 @@
+/*
+ * 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 <stdio.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/errno.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include "ioshark.h"
+#include "compile_ioshark.h"
+
+char *progname;
+
+char in_buf[2048];
+
+struct flags_map_s {
+ char *flag_str;
+ int flag;
+};
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+
+struct flags_map_s open_flags_map[] = {
+ { "O_RDONLY", O_RDONLY },
+ { "O_WRONLY", O_WRONLY },
+ { "O_RDWR", O_RDWR },
+ { "O_CREAT", O_CREAT },
+ { "O_SYNC", O_SYNC },
+ { "O_TRUNC", O_TRUNC },
+ { "O_EXCL", O_EXCL },
+ { "O_APPEND", O_APPEND },
+ { "O_NOATIME", O_NOATIME },
+ { "O_ASYNC", O_ASYNC },
+ { "O_CLOEXEC", O_CLOEXEC },
+ { "O_DIRECT", O_DIRECT },
+ { "O_DIRECTORY", O_DIRECTORY },
+ { "O_LARGEFILE", O_LARGEFILE },
+ { "O_NOCTTY", O_NOCTTY },
+ { "O_NOFOLLOW", O_NOFOLLOW },
+ { "O_NONBLOCK", O_NONBLOCK },
+ { "O_NDELAY", O_NDELAY },
+ { "O_PATH", O_PATH }
+};
+
+struct flags_map_s lseek_action_map[] = {
+ { "SEEK_SET", SEEK_SET },
+ { "SEEK_CUR", SEEK_CUR },
+ { "SEEK_END", SEEK_END }
+};
+
+struct flags_map_s fileop_map[] = {
+ { "lseek", IOSHARK_LSEEK },
+ { "_llseek", IOSHARK_LLSEEK },
+ { "pread64", IOSHARK_PREAD64 },
+ { "pwrite64", IOSHARK_PWRITE64 },
+ { "read", IOSHARK_READ },
+ { "write", IOSHARK_WRITE },
+ { "mmap", IOSHARK_MMAP },
+ { "mmap2", IOSHARK_MMAP2 },
+ { "openat", IOSHARK_OPEN },
+ { "fsync", IOSHARK_FSYNC },
+ { "fdatasync", IOSHARK_FDATASYNC },
+ { "close", IOSHARK_CLOSE },
+ { "ftrace", IOSHARK_MAPPED_PREAD }
+};
+
+struct in_mem_file_op {
+ struct ioshark_file_operation disk_file_op;
+ struct in_mem_file_op *next;
+};
+
+struct in_mem_file_op *in_mem_file_op_head = NULL, *in_mem_file_op_tail = NULL;
+
+void usage(void)
+{
+ fprintf(stderr, "%s in_file out_file\n", progname);
+}
+
+void
+init_prev_time(struct timeval *tv)
+{
+ tv->tv_sec = tv->tv_usec = 0;
+}
+
+/*
+ * delta ts is the time delta from the previous IO in this tracefile.
+ */
+static u_int64_t
+get_delta_ts(char *buf, struct timeval *prev)
+{
+ struct timeval op_tv, tv_res;
+
+ sscanf(buf, "%lu.%lu", &op_tv.tv_sec, &op_tv.tv_usec);
+ /* First item */
+ if (prev->tv_sec == 0 && prev->tv_usec == 0)
+ tv_res.tv_sec = tv_res.tv_usec = 0;
+ else
+ timersub(&op_tv, prev, &tv_res);
+ *prev = op_tv;
+ return (tv_res.tv_usec + (tv_res.tv_sec * 1000000));
+}
+
+void
+get_tracetype(char *buf, char *trace_type)
+{
+ char *s, *s2;
+
+ *trace_type = '\0';
+ s = strchr(buf, ' ');
+ if (s == NULL) {
+ fprintf(stderr,
+ "%s Malformed Trace Type ? %s\n",
+ progname, __func__);
+ exit(EXIT_FAILURE);
+ }
+ while (*s == ' ')
+ s++;
+ if (sscanf(s, "%s", trace_type) != 1) {
+ fprintf(stderr,
+ "%s Malformed Trace Type ? %s\n",
+ progname, __func__);
+ exit(EXIT_FAILURE);
+ }
+ if (strcmp(trace_type, "strace") != 0 &&
+ strcmp(trace_type, "ftrace") != 0) {
+ fprintf(stderr,
+ "%s Unknown/Missing Trace Type (has to be strace|ftrace) %s\n",
+ progname, __func__);
+ exit(EXIT_FAILURE);
+ }
+ /*
+ * Remove the keyword "strace"/"ftrace" from the buffer
+ */
+ s2 = strchr(s, ' ');
+ if (s2 == NULL) {
+ fprintf(stderr,
+ "%s Malformed Trace Type ? %s\n",
+ progname, __func__);
+ exit(EXIT_FAILURE);
+ }
+ while (*s2 == ' ')
+ s2++;
+ if (*s2 == '\0') {
+ /*
+ * Premature end of input record
+ */
+ fprintf(stderr,
+ "%s Mal-formed strace/ftrace record %s:%s\n",
+ progname, __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ /* strcpy() expects non-overlapping buffers, but bcopy doesn't */
+ bcopy(s2, s, strlen(s2) + 1);
+}
+
+void
+get_pathname(char *buf, char *pathname, enum file_op file_op)
+{
+ char *s, *s2, save;
+
+ if (file_op == IOSHARK_MAPPED_PREAD) {
+ s = strchr(buf, '/');
+ if (s == NULL) {
+ fprintf(stderr, "%s: Malformed line: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ s2 = strchr(s, ' ');
+ if (s2 == NULL) {
+ fprintf(stderr, "%s: Malformed line: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ } else {
+ if (file_op == IOSHARK_OPEN)
+ s = strchr(buf, '"');
+ else
+ s = strchr(buf, '<');
+ if (s == NULL) {
+ fprintf(stderr, "%s: Malformed line: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ s += 1;
+ if (file_op == IOSHARK_OPEN)
+ s2 = strchr(s, '"');
+ else
+ s2 = strchr(s, '>');
+ if (s2 == NULL) {
+ fprintf(stderr, "%s: Malformed line: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ }
+ save = *s2;
+ *s2 = '\0';
+ strcpy(pathname, s);
+ *s2 = save;
+}
+
+void
+get_syscall(char *buf, char *syscall)
+{
+ char *s, *s2;
+
+ s = strchr(buf, ' ');
+ if (s == NULL) {
+ fprintf(stderr, "%s: Malformed line: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ s += 1;
+ s2 = strchr(s, '(');
+ if (s2 == NULL) {
+ fprintf(stderr, "%s: Malformed line: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ *s2 = '\0';
+ strcpy(syscall, s);
+ *s2 = '(';
+}
+
+void
+get_mmap_offset_len_prot(char *buf, int *prot, off_t *offset, u_int64_t *len)
+{
+ char *s, *s1;
+ int i;
+ char protstr[128];
+
+ s = strchr(buf, ',');
+ if (s == NULL) {
+ fprintf(stderr, "%s: Malformed line: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ s += 2;
+ sscanf(s, "%ju", len);
+ s1 = strchr(s, ',');
+ if (s1 == NULL) {
+ fprintf(stderr, "%s: Malformed line: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ s1 += 2;
+ s = strchr(s1, ',');
+ if (s == NULL) {
+ fprintf(stderr, "%s: Malformed line: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ *s = '\0';
+ strcpy(protstr, s1);
+ *prot = 0;
+ if (strstr(protstr, "PROT_READ"))
+ *prot |= IOSHARK_PROT_READ;
+ if (strstr(protstr, "PROT_WRITE"))
+ *prot |= IOSHARK_PROT_WRITE;
+ *s = ',';
+ s += 2;
+ for (i = 0 ; i < 2 ; i++) {
+ s = strchr(s, ',');
+ if (s == NULL) {
+ fprintf(stderr, "%s: Malformed line: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ s += 2;
+ }
+ sscanf(s, "%jx", offset);
+}
+
+void
+get_lseek_offset_action(char *buf, enum file_op op,
+ off_t *offset, char *action)
+{
+ char *s, *s2;
+
+ s = strchr(buf, ',');
+ if (s == NULL) {
+ fprintf(stderr, "%s: Malformed line: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ s += 2;
+ sscanf(s, "%ju", offset);
+ s = strchr(s, ',');
+ if (s == NULL) {
+ fprintf(stderr, "%s: Malformed line: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ s += 2;
+ if (op == IOSHARK_LLSEEK) {
+ s = strchr(s, ',');
+ if (s == NULL) {
+ fprintf(stderr, "%s: Malformed line: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ s += 2;
+ }
+ s2 = strchr(s, ')');
+ if (s2 == NULL) {
+ fprintf(stderr, "%s: Malformed line: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ *s2 = '\0';
+ strcpy(action, s);
+ *s2 = ')';
+}
+
+void
+get_rw_len(char *buf,
+ u_int64_t *len)
+{
+ char *s_len;
+
+ s_len = strrchr(buf, ',');
+ if (s_len == NULL) {
+ fprintf(stderr, "%s: Malformed line: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ sscanf(s_len + 2, "%ju", len);
+}
+
+void
+get_prw64_offset_len(char *buf,
+ off_t *offset,
+ u_int64_t *len)
+{
+ char *s_offset, *s_len;
+
+ s_offset = strrchr(buf, ',');
+ if (s_offset == NULL) {
+ fprintf(stderr, "%s: Malformed line 1: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ *s_offset = '\0';
+ s_len = strrchr(buf, ',');
+ if (s_len == NULL) {
+ fprintf(stderr, "%s: Malformed line 2: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ *s_offset = ',';
+ sscanf(s_len + 2, "%ju", len);
+ sscanf(s_offset + 2, "%ju", offset);
+}
+
+
+void
+get_ftrace_offset_len(char *buf,
+ off_t *offset,
+ u_int64_t *len)
+{
+ char *s_offset;
+
+ s_offset = strchr(buf, '/');
+ if (s_offset == NULL) {
+ fprintf(stderr, "%s: Malformed line 1: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ s_offset = strchr(s_offset, ' ');
+ if (s_offset == NULL) {
+ fprintf(stderr, "%s: Malformed line 2: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ while (*s_offset == ' ')
+ s_offset++;
+ if (sscanf(s_offset, "%ju %ju", offset, len) != 2) {
+ fprintf(stderr, "%s: Malformed line 3: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+}
+
+void
+get_openat_flags_mode(char *buf, char *flags, mode_t *mode)
+{
+ char *s, *s2, lookfor;
+
+ s = strchr(buf, ',');
+ if (s == NULL) {
+ fprintf(stderr, "%s: Malformed line: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ s += 2;
+ s = strchr(s, ',');
+ if (s == NULL) {
+ fprintf(stderr, "%s: Malformed line: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ s += 2;
+ if (strstr(s, "O_CREAT") == NULL)
+ lookfor = ')';
+ else
+ lookfor = ',';
+ s2 = strchr(s, lookfor);
+ if (s2 == NULL) {
+ fprintf(stderr, "%s: Malformed line: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ *s2 = '\0';
+ strcpy(flags, s);
+ *s2 = lookfor;
+ if (strstr(s, "O_CREAT") != NULL) {
+ s = s2 + 2;
+ s2 = strchr(s, ')');
+ if (s2 == NULL) {
+ fprintf(stderr, "%s: Malformed line: %s\n",
+ __func__, buf);
+ exit(EXIT_FAILURE);
+ }
+ *s2 = '\0';
+ sscanf(s, "%o", mode);
+ *s2 = ')';
+ }
+}
+
+int
+lookup_map(char *s, struct flags_map_s *flags_map, int maplen)
+{
+ int found = 0, flag = 0;
+ int i;
+
+ while (isspace(*s))
+ s++;
+ for (i = 0 ; i < maplen ; i++) {
+ if (strcmp(flags_map[i].flag_str, s) == 0) {
+ flag = flags_map[i].flag;
+ found = 1;
+ break;
+ }
+ }
+ if (found == 0) {
+ fprintf(stderr, "%s: Unknown syscall %s\n",
+ __func__, s);
+ exit(1);
+ }
+ return flag;
+}
+
+int
+map_open_flags(char *s)
+{
+ int flags = 0;
+ char *s1;
+
+ while ((s1 = strchr(s, '|'))) {
+ *s1 = '\0';
+ flags |= lookup_map(s, open_flags_map,
+ ARRAY_SIZE(open_flags_map));
+ *s1 = '|';
+ s = s1 + 1;
+ }
+ /* Last option */
+ flags |= lookup_map(s, open_flags_map,
+ ARRAY_SIZE(open_flags_map));
+ return flags;
+}
+
+int
+map_lseek_action(char *s)
+{
+ int flags = 0;
+ char *s1;
+
+ while ((s1 = strchr(s, '|'))) {
+ *s1 = '\0';
+ flags |= lookup_map(s, lseek_action_map,
+ ARRAY_SIZE(lseek_action_map));
+ *s1 = '|';
+ s = s1 + 1;
+ }
+ /* Last option */
+ flags |= lookup_map(s, lseek_action_map,
+ ARRAY_SIZE(lseek_action_map));
+ return flags;
+}
+
+enum file_op
+map_syscall(char *syscall)
+{
+ return lookup_map(syscall, fileop_map,
+ ARRAY_SIZE(fileop_map));
+}
+
+/*
+ * For each tracefile, we first create in-memory structures, then once
+ * we've processed each tracefile completely, we write out the in-memory
+ * structures and free them.
+ */
+int main(int argc, char **argv)
+{
+ FILE *fp;
+ char path[512];
+ char syscall[512];
+ char lseek_action_str[512];
+ char *s;
+ char open_flags_str[64];
+ void *db_node;
+ int num_io_operations = 0;
+ struct ioshark_header header;
+ struct ioshark_file_operation *disk_file_op;
+ struct in_mem_file_op *in_mem_fop;
+ struct stat st;
+ char *infile, *outfile;
+ struct timeval prev_time;
+ char trace_type[64];
+
+ progname = argv[0];
+ if (argc != 3) {
+ usage();
+ exit(EXIT_FAILURE);
+ }
+ infile = argv[1];
+ outfile = argv[2];
+ if (stat(infile, &st) < 0) {
+ fprintf(stderr, "%s Can't stat %s\n",
+ progname, infile);
+ exit(EXIT_FAILURE);
+ }
+ if (st.st_size == 0) {
+ fprintf(stderr, "%s Empty file %s\n",
+ progname, infile);
+ exit(EXIT_FAILURE);
+ }
+ init_prev_time(&prev_time);
+ init_filename_cache();
+ fp = fopen(infile, "r");
+ if (fp == NULL) {
+ fprintf(stderr, "%s Can't open %s\n",
+ progname, infile);
+ exit(EXIT_FAILURE);
+ }
+ while (fgets(in_buf, 2048, fp)) {
+ s = in_buf;
+ while (isspace(*s))
+ s++;
+ in_mem_fop = malloc(sizeof(struct in_mem_file_op));
+ disk_file_op = &in_mem_fop->disk_file_op;
+ disk_file_op->delta_us = get_delta_ts(s, &prev_time);
+ get_tracetype(s, trace_type);
+ if (strcmp(trace_type, "strace") == 0) {
+ get_syscall(s, syscall);
+ disk_file_op->file_op = map_syscall(syscall);
+ } else
+ disk_file_op->file_op = map_syscall("ftrace");
+ get_pathname(s, path, disk_file_op->file_op);
+ db_node = files_db_add(path);
+ disk_file_op->fileno = files_db_get_fileno(db_node);
+ switch (disk_file_op->file_op) {
+ case IOSHARK_LLSEEK:
+ case IOSHARK_LSEEK:
+ get_lseek_offset_action(s,
+ disk_file_op->file_op,
+ &disk_file_op->lseek_offset,
+ lseek_action_str);
+ disk_file_op->lseek_action =
+ map_lseek_action(lseek_action_str);
+ if (disk_file_op->lseek_action == SEEK_SET)
+ files_db_update_size(db_node,
+ disk_file_op->lseek_offset);
+ break;
+ case IOSHARK_PREAD64:
+ case IOSHARK_PWRITE64:
+ get_prw64_offset_len(s,
+ &disk_file_op->prw_offset,
+ (u_int64_t *)&disk_file_op->prw_len);
+ files_db_update_size(db_node,
+ disk_file_op->prw_offset +
+ disk_file_op->prw_len);
+ break;
+ case IOSHARK_READ:
+ case IOSHARK_WRITE:
+ get_rw_len(s, (u_int64_t *)&disk_file_op->rw_len);
+ files_db_add_to_size(db_node,
+ disk_file_op->rw_len);
+ break;
+ case IOSHARK_MMAP:
+ case IOSHARK_MMAP2:
+ get_mmap_offset_len_prot(s,
+ &disk_file_op->mmap_prot,
+ &disk_file_op->mmap_offset,
+ (u_int64_t *)&disk_file_op->mmap_len);
+ files_db_update_size(db_node,
+ disk_file_op->mmap_offset +
+ disk_file_op->mmap_len);
+ break;
+ case IOSHARK_OPEN:
+ disk_file_op->open_mode = 0;
+ get_openat_flags_mode(s, open_flags_str,
+ &disk_file_op->open_mode);
+ disk_file_op->open_flags =
+ map_open_flags(open_flags_str);
+ break;
+ case IOSHARK_FSYNC:
+ case IOSHARK_FDATASYNC:
+ break;
+ case IOSHARK_CLOSE:
+ break;
+ case IOSHARK_MAPPED_PREAD:
+ /* Convert a mmap'ed read into a PREAD64 */
+ disk_file_op->file_op = IOSHARK_PREAD64;
+ get_ftrace_offset_len(s,
+ &disk_file_op->prw_offset,
+ (u_int64_t *)&disk_file_op->prw_len);
+ files_db_update_size(db_node,
+ disk_file_op->prw_offset +
+ disk_file_op->prw_len);
+ break;
+ default:
+ break;
+ }
+ /* Chain at the end */
+ num_io_operations++;
+ in_mem_fop->next = NULL;
+ if (in_mem_file_op_head == NULL) {
+ in_mem_file_op_tail = in_mem_fop;
+ in_mem_file_op_head = in_mem_fop;
+ } else {
+ in_mem_file_op_tail->next = in_mem_fop;
+ in_mem_file_op_tail = in_mem_fop;
+ }
+ }
+ fclose(fp);
+ /*
+ * Now we can write everything out to the output tracefile.
+ */
+ fp = fopen(outfile, "w+");
+ if (fp == NULL) {
+ fprintf(stderr, "%s Can't open trace.outfile\n",
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ header.num_io_operations = num_io_operations;
+ header.num_files = files_db_get_total_obj();
+ if (fwrite(&header, sizeof(struct ioshark_header), 1, fp) != 1) {
+ fprintf(stderr, "%s Write error trace.outfile\n",
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ files_db_write_objects(fp);
+ while (in_mem_file_op_head != NULL) {
+ struct in_mem_file_op *temp;
+
+ disk_file_op = &in_mem_file_op_head->disk_file_op;
+ if (fwrite(disk_file_op,
+ sizeof(struct ioshark_file_operation), 1, fp) != 1) {
+ fprintf(stderr, "%s Write error trace.outfile\n",
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ temp = in_mem_file_op_head;
+ in_mem_file_op_head = in_mem_file_op_head->next;
+ free(temp);
+ }
+ store_filename_cache();
+}
diff --git a/ioshark/compile_ioshark.h b/ioshark/compile_ioshark.h
new file mode 100644
index 0000000..9e11bd2
--- /dev/null
+++ b/ioshark/compile_ioshark.h
@@ -0,0 +1,81 @@
+/*
+ * 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 FILE_DB_HASHSIZE 8192
+
+struct files_db_s {
+ char *filename;
+ int fileno;
+ struct files_db_s *next;
+ size_t size;
+ int global_filename_ix;
+};
+
+/* Lifted from Wikipedia Jenkins Hash function page */
+static inline u_int32_t
+jenkins_one_at_a_time_hash(char *key, size_t len)
+{
+ u_int32_t hash, i;
+
+ for(hash = i = 0; i < len; ++i) {
+ hash += key[i];
+ hash += (hash << 10);
+ hash ^= (hash >> 6);
+ }
+ hash += (hash << 3);
+ hash ^= (hash >> 11);
+ hash += (hash << 15);
+ return hash;
+}
+
+static inline void
+files_db_update_size(void *node, u_int64_t new_size)
+{
+ struct files_db_s *db_node = (struct files_db_s *)node;
+
+ if (db_node->size < new_size)
+ db_node->size = new_size;
+}
+
+static inline void
+files_db_add_to_size(void *node, u_int64_t size_incr)
+{
+ ((struct files_db_s *)node)->size += size_incr;
+}
+
+static inline int
+files_db_get_fileno(void *node)
+{
+ return (((struct files_db_s *)node)->fileno);
+}
+
+static inline char *
+files_db_get_filename(void *node)
+{
+ return (((struct files_db_s *)node)->filename);
+}
+
+
+void *files_db_create_handle(void);
+void files_db_write_objects(FILE *fp);
+void *files_db_add(char *filename);
+void *files_db_lookup(char *filename);
+int files_db_get_total_obj(void);
+void init_filename_cache(void);
+void store_filename_cache(void);
+
+
+
diff --git a/ioshark/compile_ioshark_subr.c b/ioshark/compile_ioshark_subr.c
new file mode 100644
index 0000000..5cc07c6
--- /dev/null
+++ b/ioshark/compile_ioshark_subr.c
@@ -0,0 +1,217 @@
+/*
+ * 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 <fcntl.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/errno.h>
+#include "ioshark.h"
+#include "compile_ioshark.h"
+
+extern char *progname;
+
+static struct files_db_s *files_db_buckets[FILE_DB_HASHSIZE];
+static int current_fileno = 1;
+static int num_objects = 0;
+
+static int filename_cache_lookup(char *filename);;
+
+void
+files_db_write_objects(FILE *fp)
+{
+ int i;
+ struct ioshark_file_state st;
+
+ for (i = 0 ; i < FILE_DB_HASHSIZE ; i++) {
+ struct files_db_s *db_node, *s;
+
+ db_node = files_db_buckets[i];
+ while (db_node != NULL) {
+ st.fileno = db_node->fileno;
+ st.size = db_node->size;
+ st.global_filename_ix =
+ db_node->global_filename_ix;
+ if (fwrite(&st, sizeof(st), 1, fp) != 1) {
+ fprintf(stderr,
+ "%s Write error trace.outfile\n",
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ s = db_node;
+ db_node = db_node->next;
+ free(s->filename);
+ free(s);
+ }
+ }
+}
+
+void *files_db_lookup(char *pathname)
+{
+ u_int32_t hash;
+ struct files_db_s *db_node;
+
+ hash = jenkins_one_at_a_time_hash(pathname, strlen(pathname));
+ hash %= FILE_DB_HASHSIZE;
+ db_node = files_db_buckets[hash];
+ while (db_node != NULL) {
+ if (strcmp(db_node->filename, pathname) == 0)
+ break;
+ db_node = db_node->next;
+ }
+ return db_node;
+}
+
+void *files_db_add(char *filename)
+{
+ u_int32_t hash;
+ struct files_db_s *db_node;
+
+ if ((db_node = files_db_lookup(filename)))
+ return db_node;
+ hash = jenkins_one_at_a_time_hash(filename, strlen(filename));
+ hash %= FILE_DB_HASHSIZE;
+ db_node = malloc(sizeof(struct files_db_s));
+ db_node->filename = strdup(filename);
+ db_node->global_filename_ix =
+ filename_cache_lookup(filename);
+ db_node->fileno = current_fileno++;
+ db_node->next = files_db_buckets[hash];
+ db_node->size = 0;
+ files_db_buckets[hash] = db_node;
+ num_objects++;
+ return db_node;
+}
+
+int
+files_db_get_total_obj(void)
+{
+ return num_objects;
+}
+
+static struct ioshark_filename_struct *filename_cache;
+static int filename_cache_num_entries;
+static int filename_cache_size;
+
+void
+init_filename_cache(void)
+{
+ static FILE *filename_cache_fp;
+ struct stat st;
+ int file_exists = 1;
+
+ if (stat("ioshark_filenames", &st) < 0) {
+ if (errno != ENOENT) {
+ fprintf(stderr, "%s Can't stat ioshark_filenames file\n",
+ progname);
+ exit(EXIT_FAILURE);
+ } else {
+ file_exists = 0;
+ filename_cache_num_entries = 0;
+ }
+ } else {
+ filename_cache_num_entries = st.st_size /
+ sizeof(struct ioshark_filename_struct);
+ }
+ if (file_exists) {
+ filename_cache_fp = fopen("ioshark_filenames", "r");
+ if (filename_cache_fp == NULL) {
+ fprintf(stderr, "%s Cannot open ioshark_filenames file\n",
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ }
+ /* Preallocate a fixed size of entries */
+ filename_cache_size = filename_cache_num_entries + 1024;
+ filename_cache = calloc(filename_cache_size,
+ sizeof(struct ioshark_filename_struct));
+ if (filename_cache == NULL) {
+ fprintf(stderr, "%s Can't allocate memory - this is fatal\n",
+ __func__);
+ exit(EXIT_FAILURE);
+ }
+ if (fread(filename_cache,
+ sizeof(struct ioshark_filename_struct),
+ filename_cache_num_entries,
+ filename_cache_fp) != (size_t)filename_cache_num_entries) {
+ fprintf(stderr, "%s Can't read ioshark_filenames file\n",
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ if (file_exists)
+ fclose(filename_cache_fp);
+}
+
+static int
+filename_cache_lookup(char *filename)
+{
+ int ret;
+ int i;
+
+ for (i = 0 ; i < filename_cache_num_entries ; i++) {
+ if (strcmp(filename_cache[i].path, filename) == 0)
+ return i;
+ }
+ if (filename_cache_num_entries >= filename_cache_size) {
+ int newsize;
+
+ /* reallocate the filename cache up first */
+ filename_cache_size += 1024;
+ newsize = filename_cache_size *
+ sizeof(struct ioshark_filename_struct);
+ filename_cache = realloc(filename_cache, newsize);
+ if (filename_cache == NULL) {
+ fprintf(stderr,
+ "%s Can't allocate memory - this is fatal\n",
+ __func__);
+ exit(EXIT_FAILURE);
+ }
+ }
+ strcpy(filename_cache[filename_cache_num_entries].path,
+ filename);
+ ret = filename_cache_num_entries;
+ filename_cache_num_entries++;
+ return ret;
+}
+
+void
+store_filename_cache(void)
+{
+ static FILE *filename_cache_fp;
+
+ filename_cache_fp = fopen("ioshark_filenames", "w+");
+ if (filename_cache_fp == NULL) {
+ fprintf(stderr, "%s Cannot open ioshark_filenames file\n",
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ if (fwrite(filename_cache,
+ sizeof(struct ioshark_filename_struct),
+ filename_cache_num_entries,
+ filename_cache_fp) != (size_t)filename_cache_num_entries) {
+ fprintf(stderr, "%s Can't read ioshark_filenames file\n",
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ fclose(filename_cache_fp);
+ free(filename_cache);
+}
+
+
+
diff --git a/ioshark/dump_ioshark_filenames.c b/ioshark/dump_ioshark_filenames.c
new file mode 100644
index 0000000..c082c27
--- /dev/null
+++ b/ioshark/dump_ioshark_filenames.c
@@ -0,0 +1,80 @@
+/*
+ * 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 <stdio.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/errno.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include "ioshark.h"
+
+/*
+ * Real simple utility that just extracts and dumps the IOshark filenames
+ * one per line from the ioshark_filenames file. Useful in debugging.
+ */
+int
+main(int argc __attribute__((unused)), char **argv)
+{
+ char *progname;
+ static FILE *filename_cache_fp;
+ struct stat st;
+ struct ioshark_filename_struct *filename_cache;
+ int filename_cache_num_entries;
+ size_t filename_cache_size;
+ int i;
+
+ progname = argv[0];
+ if (stat("ioshark_filenames", &st) < 0) {
+ fprintf(stderr, "%s Can't stat ioshark_filenames file\n",
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ filename_cache_num_entries = st.st_size /
+ sizeof(struct ioshark_filename_struct);
+ filename_cache_fp = fopen("ioshark_filenames", "r");
+ if (filename_cache_fp == NULL) {
+ fprintf(stderr, "%s Cannot open ioshark_filenames file\n",
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ /* Preallocate a fixed size of entries */
+ filename_cache_size = filename_cache_num_entries + 1024;
+ filename_cache = calloc(filename_cache_size,
+ sizeof(struct ioshark_filename_struct));
+ if (filename_cache == NULL) {
+ fprintf(stderr, "%s Can't allocate memory - this is fatal\n",
+ __func__);
+ exit(EXIT_FAILURE);
+ }
+ if (fread(filename_cache,
+ sizeof(struct ioshark_filename_struct),
+ filename_cache_num_entries,
+ filename_cache_fp) != (size_t)filename_cache_num_entries) {
+ fprintf(stderr, "%s Can't read ioshark_filenames file\n",
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ for (i = 0 ; i < filename_cache_num_entries ; i++) {
+ printf("%s\n", filename_cache[i].path);
+ }
+ free(filename_cache);
+ fclose(filename_cache_fp);
+}
diff --git a/ioshark/ioshark.h b/ioshark/ioshark.h
new file mode 100644
index 0000000..fd9caca
--- /dev/null
+++ b/ioshark/ioshark.h
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+
+/*
+ * Format of the parsed workload files.
+ * 1) Header
+ * 2) Table of the entries, each entry describes 1 file
+ * 3) Table of IO operations to perform on the files
+ */
+
+/*
+ * The parsed workload file starts off with the header, which
+ * contains the count of the total # of files that are operated on.
+ * and the total number of IO operations.
+ */
+struct ioshark_header {
+ int num_files;
+ int num_io_operations;
+};
+
+/*
+ * After the header, we have a table of #files entries. Each entry
+ * in this table describes 1 file, indexed by fileno and with the
+ * specified size.
+ * Before the tests starts, these files are pre-created.
+ */
+struct ioshark_file_state {
+ int fileno; /* 1..num_files, with files name ioshark.<fileno> */
+ size_t size;
+ int global_filename_ix;
+};
+
+enum file_op {
+ IOSHARK_LSEEK = 0,
+ IOSHARK_LLSEEK,
+ IOSHARK_PREAD64,
+ IOSHARK_PWRITE64,
+ IOSHARK_READ,
+ IOSHARK_WRITE,
+ IOSHARK_MMAP,
+ IOSHARK_MMAP2,
+ IOSHARK_OPEN,
+ IOSHARK_FSYNC,
+ IOSHARK_FDATASYNC,
+ IOSHARK_CLOSE,
+ IOSHARK_MAPPED_PREAD,
+ IOSHARK_MAPPED_PWRITE,
+ IOSHARK_MAX_FILE_OP
+};
+
+/* mmap prot flags */
+#define IOSHARK_PROT_READ 0x1
+#define IOSHARK_PROT_WRITE 0x2
+
+/*
+ * Next we have the table of IO operatiosn to perform. Each
+ * IO operation is described by this entry.
+ */
+struct ioshark_file_operation {
+ /* delta us between previous file op and this */
+ u_int64_t delta_us;
+ enum file_op file_op;
+ int fileno;
+ union {
+ struct lseek_args {
+#define lseek_offset u.lseek_a.offset
+#define lseek_action u.lseek_a.action
+ off_t offset;
+ int action;
+ } lseek_a;
+ struct prw_args {
+#define prw_offset u.prw_a.offset
+#define prw_len u.prw_a.len
+ off_t offset;
+ size_t len;
+ } prw_a;
+#define rw_len u.rw_a.len
+ struct rw_args {
+ size_t len;
+ } rw_a;
+#define mmap_offset u.mmap_a.offset
+#define mmap_len u.mmap_a.len
+#define mmap_prot u.mmap_a.prot
+ struct mmap_args {
+ off_t offset;
+ size_t len;
+ int prot;
+ } mmap_a;
+#define open_flags u.open_a.flags
+#define open_mode u.open_a.mode
+ struct open_args {
+ int flags;
+ mode_t mode;
+ } open_a;
+ } u;
+};
+
+#define MAX_IOSHARK_PATHLEN 512
+
+/*
+ * Global table of all fileames
+ */
+struct ioshark_filename_struct
+{
+ char path[MAX_IOSHARK_PATHLEN];
+};
diff --git a/ioshark/ioshark_bench.c b/ioshark/ioshark_bench.c
new file mode 100644
index 0000000..f44e73e
--- /dev/null
+++ b/ioshark/ioshark_bench.c
@@ -0,0 +1,862 @@
+/*
+ * 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 <stdio.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <assert.h>
+#include <pthread.h>
+#include <sys/statfs.h>
+#include <sys/resource.h>
+#include "ioshark.h"
+#define IOSHARK_MAIN
+#include "ioshark_bench.h"
+
+/*
+ * Note on "quick" mode where we do reads on existing /system,
+ * /vendor and other files in ro partitions, instead of creating
+ * them. The ioshark compiler builds up a table of all the files
+ * in /system, /vendor and other ro partitions. For files in this
+ * list, the benchmark skips the pre-creation of these files and
+ * reads them directly.
+ * The code relevant to this is in *filename_cache*.
+ */
+
+char *progname;
+
+#define MAX_INPUT_FILES 8192
+#define MAX_THREADS 8192
+
+struct thread_state_s {
+ char *filename;
+ FILE *fp;
+ int num_files;
+ void *db_handle;
+};
+
+struct thread_state_s thread_state[MAX_INPUT_FILES];
+int num_input_files = 0;
+int next_input_file;
+
+pthread_t tid[MAX_THREADS];
+
+/*
+ * Global options
+ */
+int do_delay = 0;
+int verbose = 0;
+int summary_mode = 0;
+int quick_mode = 0;
+
+#if 0
+static long gettid()
+{
+ return syscall(__NR_gettid);
+}
+#endif
+
+void usage()
+{
+ fprintf(stderr, "%s [-d preserve_delays] [-n num_iterations] [-t num_threads] -q -v | -s <list of parsed input files>\n",
+ progname);
+ fprintf(stderr, "%s -s, -v are mutually exclusive\n",
+ progname);
+ exit(EXIT_FAILURE);
+}
+
+pthread_mutex_t time_mutex = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t stats_mutex = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t work_mutex = PTHREAD_MUTEX_INITIALIZER;
+struct timeval aggregate_file_create_time;
+struct timeval debug_file_create_time;
+struct timeval aggregate_file_remove_time;
+struct timeval aggregate_IO_time;
+struct timeval aggregate_delay_time;
+
+u_int64_t aggr_op_counts[IOSHARK_MAX_FILE_OP];
+struct rw_bytes_s aggr_io_rw_bytes;
+struct rw_bytes_s aggr_create_rw_bytes;
+
+/*
+ * Locking needed here because aggregate_delay_time is updated
+ * from multiple threads concurrently.
+ */
+static void
+update_time(struct timeval *aggr_time,
+ struct timeval *delta_time)
+{
+ struct timeval tmp;
+
+ pthread_mutex_lock(&time_mutex);
+ timeradd(aggr_time, delta_time, &tmp);
+ *aggr_time = tmp;
+ pthread_mutex_unlock(&time_mutex);
+}
+
+static void
+update_op_counts(u_int64_t *op_counts)
+{
+ int i;
+
+ pthread_mutex_lock(&stats_mutex);
+ for (i = IOSHARK_LSEEK ; i < IOSHARK_MAX_FILE_OP ; i++)
+ aggr_op_counts[i] += op_counts[i];
+ pthread_mutex_unlock(&stats_mutex);
+}
+
+static void
+update_byte_counts(struct rw_bytes_s *dest, struct rw_bytes_s *delta)
+{
+ pthread_mutex_lock(&stats_mutex);
+ dest->bytes_read += delta->bytes_read;
+ dest->bytes_written += delta->bytes_written;
+ pthread_mutex_unlock(&stats_mutex);
+}
+
+static int work_next_file;
+static int work_num_files;
+
+void
+init_work(int next_file, int num_files)
+{
+ pthread_mutex_lock(&work_mutex);
+ work_next_file = next_file;
+ work_num_files = work_next_file + num_files;
+ pthread_mutex_unlock(&work_mutex);
+}
+
+/* Dole out the next file to work on to the thread */
+static struct thread_state_s *
+get_work()
+{
+ struct thread_state_s *work = NULL;
+
+ pthread_mutex_lock(&work_mutex);
+ if (work_next_file < work_num_files)
+ work = &thread_state[work_next_file++];
+ pthread_mutex_unlock(&work_mutex);
+ return work;
+}
+
+static void
+create_files(struct thread_state_s *state)
+{
+ int i;
+ struct ioshark_file_state file_state;
+ char path[MAX_IOSHARK_PATHLEN];
+ void *db_node;
+ struct rw_bytes_s rw_bytes;
+ char *filename;
+ int readonly;
+
+ memset(&rw_bytes, 0, sizeof(struct rw_bytes_s));
+ for (i = 0 ; i < state->num_files ; i++) {
+ if (fread(&file_state, sizeof(struct ioshark_file_state),
+ 1, state->fp) != 1) {
+ fprintf(stderr, "%s read error tracefile\n",
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ /*
+ * Check to see if the file is in a readonly partition,
+ * in which case, we don't have to pre-create the file
+ * we can just read the existing file.
+ */
+ filename =
+ get_ro_filename(file_state.global_filename_ix);
+ if (quick_mode)
+ assert(filename != NULL);
+ if (quick_mode == 0 ||
+ is_readonly_mount(filename, file_state.size) == 0) {
+ sprintf(path, "file.%d.%d",
+ (int)(state - thread_state),
+ file_state.fileno);
+ create_file(path, file_state.size,
+ &rw_bytes);
+ filename = path;
+ readonly = 0;
+ } else {
+ readonly = 1;
+ }
+ db_node = files_db_add_byfileno(state->db_handle,
+ file_state.fileno,
+ readonly);
+ files_db_update_size(db_node, file_state.size);
+ files_db_update_filename(db_node, filename);
+ }
+ update_byte_counts(&aggr_create_rw_bytes, &rw_bytes);
+}
+
+static void
+do_one_io(void *db_node,
+ struct ioshark_file_operation *file_op,
+ u_int64_t *op_counts,
+ struct rw_bytes_s *rw_bytes,
+ char **bufp, int *buflen)
+{
+ assert(file_op->file_op < IOSHARK_MAX_FILE_OP);
+ op_counts[file_op->file_op]++;
+ switch (file_op->file_op) {
+ int ret;
+ char *p;
+ int fd;
+
+ case IOSHARK_LSEEK:
+ case IOSHARK_LLSEEK:
+ ret = lseek(files_db_get_fd(db_node),
+ file_op->lseek_offset,
+ file_op->lseek_action);
+ if (ret < 0) {
+ fprintf(stderr,
+ "%s: lseek(%s %lu %d) returned error %d\n",
+ progname, files_db_get_filename(db_node),
+ file_op->lseek_offset,
+ file_op->lseek_action, errno);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case IOSHARK_PREAD64:
+ p = get_buf(bufp, buflen, file_op->prw_len, 0);
+ ret = pread(files_db_get_fd(db_node), p,
+ file_op->prw_len, file_op->prw_offset);
+ rw_bytes->bytes_read += file_op->prw_len;
+ if (ret < 0) {
+ fprintf(stderr,
+ "%s: pread(%s %zu %lu) error %d\n",
+ progname,
+ files_db_get_filename(db_node),
+ file_op->prw_len,
+ file_op->prw_offset, errno);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case IOSHARK_PWRITE64:
+ p = get_buf(bufp, buflen, file_op->prw_len, 1);
+ ret = pwrite(files_db_get_fd(db_node), p,
+ file_op->prw_len, file_op->prw_offset);
+ rw_bytes->bytes_written += file_op->prw_len;
+ if (ret < 0) {
+ fprintf(stderr,
+ "%s: pwrite(%s %zu %lu) error %d\n",
+ progname,
+ files_db_get_filename(db_node),
+ file_op->prw_len,
+ file_op->prw_offset, errno);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case IOSHARK_READ:
+ p = get_buf(bufp, buflen, file_op->rw_len, 0);
+ ret = read(files_db_get_fd(db_node), p,
+ file_op->rw_len);
+ rw_bytes->bytes_read += file_op->rw_len;
+ if (ret < 0) {
+ fprintf(stderr,
+ "%s: read(%s %zu) error %d\n",
+ progname,
+ files_db_get_filename(db_node),
+ file_op->rw_len,
+ errno);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case IOSHARK_WRITE:
+ p = get_buf(bufp, buflen, file_op->rw_len, 1);
+ ret = write(files_db_get_fd(db_node), p,
+ file_op->rw_len);
+ rw_bytes->bytes_written += file_op->rw_len;
+ if (ret < 0) {
+ fprintf(stderr,
+ "%s: write(%s %zu) error %d\n",
+ progname,
+ files_db_get_filename(db_node),
+ file_op->rw_len,
+ errno);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case IOSHARK_MMAP:
+ case IOSHARK_MMAP2:
+ ioshark_handle_mmap(db_node, file_op,
+ bufp, buflen, op_counts,
+ rw_bytes);
+ break;
+ case IOSHARK_OPEN:
+ if (file_op->open_flags & O_CREAT) {
+ fd = open(files_db_get_filename(db_node),
+ file_op->open_flags,
+ file_op->open_mode);
+ if (fd < 0) {
+ /*
+ * EEXIST error acceptable, others are fatal.
+ * Although we failed to O_CREAT the file (O_EXCL)
+ * We will force an open of the file before any
+ * IO.
+ */
+ if (errno == EEXIST) {
+ return;
+ } else {
+ fprintf(stderr,
+ "%s: O_CREAT open(%s %x %o) error %d\n",
+ progname,
+ files_db_get_filename(db_node),
+ file_op->open_flags,
+ file_op->open_mode, errno);
+ exit(EXIT_FAILURE);
+ }
+ }
+ } else {
+ fd = open(files_db_get_filename(db_node),
+ file_op->open_flags);
+ if (fd < 0) {
+ if (file_op->open_flags & O_DIRECTORY) {
+ /* O_DIRECTORY open()s should fail */
+ return;
+ } else {
+ fprintf(stderr,
+ "%s: open(%s %x) error %d\n",
+ progname,
+ files_db_get_filename(db_node),
+ file_op->open_flags,
+ errno);
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+ files_db_close_fd(db_node);
+ files_db_update_fd(db_node, fd);
+ break;
+ case IOSHARK_FSYNC:
+ case IOSHARK_FDATASYNC:
+ if (file_op->file_op == IOSHARK_FSYNC) {
+ ret = fsync(files_db_get_fd(db_node));
+ if (ret < 0) {
+ fprintf(stderr,
+ "%s: fsync(%s) error %d\n",
+ progname,
+ files_db_get_filename(db_node),
+ errno);
+ exit(EXIT_FAILURE);
+ }
+ } else {
+ ret = fdatasync(files_db_get_fd(db_node));
+ if (ret < 0) {
+ fprintf(stderr,
+ "%s: fdatasync(%s) error %d\n",
+ progname,
+ files_db_get_filename(db_node),
+ errno);
+ exit(EXIT_FAILURE);
+ }
+ }
+ break;
+ case IOSHARK_CLOSE:
+ ret = close(files_db_get_fd(db_node));
+ if (ret < 0) {
+ fprintf(stderr,
+ "%s: close(%s) error %d\n",
+ progname,
+ files_db_get_filename(db_node), errno);
+ exit(EXIT_FAILURE);
+ }
+ files_db_update_fd(db_node, -1);
+ break;
+ default:
+ fprintf(stderr, "%s: unknown FILE_OP %d\n",
+ progname, file_op->file_op);
+ exit(EXIT_FAILURE);
+ break;
+ }
+}
+
+static void
+do_io(struct thread_state_s *state)
+{
+ void *db_node;
+ struct ioshark_header header;
+ struct ioshark_file_operation file_op;
+ int fd;
+ int i;
+ char *buf = NULL;
+ int buflen = 0;
+ struct timeval total_delay_time;
+ u_int64_t op_counts[IOSHARK_MAX_FILE_OP];
+ struct rw_bytes_s rw_bytes;
+
+ rewind(state->fp);
+ if (fread(&header, sizeof(struct ioshark_header), 1, state->fp) != 1) {
+ fprintf(stderr, "%s read error %s\n",
+ progname, state->filename);
+ exit(EXIT_FAILURE);
+ }
+ /*
+ * First open and pre-create all the files. Indexed by fileno.
+ */
+ timerclear(&total_delay_time);
+ memset(&rw_bytes, 0, sizeof(struct rw_bytes_s));
+ memset(op_counts, 0, sizeof(op_counts));
+ fseek(state->fp,
+ sizeof(struct ioshark_header) +
+ header.num_files * sizeof(struct ioshark_file_state),
+ SEEK_SET);
+ /*
+ * Loop over all the IOs, and launch each
+ */
+ for (i = 0 ; i < header.num_io_operations ; i++) {
+ if (fread(&file_op, sizeof(struct ioshark_file_operation),
+ 1, state->fp) != 1) {
+ fprintf(stderr, "%s read error trace.outfile\n",
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ if (do_delay) {
+ struct timeval start;
+
+ (void)gettimeofday(&start, (struct timezone *)NULL);
+ usleep(file_op.delta_us);
+ update_delta_time(&start, &total_delay_time);
+ }
+ db_node = files_db_lookup_byfileno(state->db_handle,
+ file_op.fileno);
+ if (db_node == NULL) {
+ fprintf(stderr,
+ "%s Can't lookup fileno %d, fatal error\n",
+ progname, file_op.fileno);
+ exit(EXIT_FAILURE);
+ }
+ if (file_op.file_op != IOSHARK_OPEN &&
+ files_db_get_fd(db_node) == -1) {
+ int openflags;
+
+ /*
+ * This is a hack to workaround the fact that we did not
+ * see an open() for this file until now. open() the
+ * file O_RDWR, so that we can perform the IO.
+ */
+ if (files_db_readonly(db_node))
+ openflags = O_RDONLY;
+ else
+ openflags = O_RDWR;
+ fd = open(files_db_get_filename(db_node),
+ openflags);
+ if (fd < 0) {
+ fprintf(stderr, "%s: open(%s %x) error %d\n",
+ progname,
+ files_db_get_filename(db_node),
+ openflags,
+ errno);
+ exit(EXIT_FAILURE);
+ }
+ files_db_update_fd(db_node, fd);
+ }
+ do_one_io(db_node, &file_op,
+ op_counts, &rw_bytes, &buf, &buflen);
+ }
+ files_db_fsync_discard_files(state->db_handle);
+ files_db_close_files(state->db_handle);
+ update_time(&aggregate_delay_time, &total_delay_time);
+ update_op_counts(op_counts);
+ update_byte_counts(&aggr_io_rw_bytes, &rw_bytes);
+}
+
+void *
+io_thread(void *unused __attribute__((unused)))
+{
+ struct thread_state_s *state;
+
+ srand(gettid());
+ while ((state = get_work()))
+ do_io(state);
+ pthread_exit(NULL);
+ return(NULL);
+}
+
+static void
+do_create(struct thread_state_s *state)
+{
+ struct ioshark_header header;
+
+ if (fread(&header, sizeof(struct ioshark_header), 1, state->fp) != 1) {
+ fprintf(stderr, "%s read error %s\n",
+ progname, state->filename);
+ exit(EXIT_FAILURE);
+ }
+ state->num_files = header.num_files;
+ state->db_handle = files_db_create_handle();
+ create_files(state);
+}
+
+void *
+create_files_thread(void *unused __attribute__((unused)))
+{
+ struct thread_state_s *state;
+
+ while ((state = get_work()))
+ do_create(state);
+ pthread_exit(NULL);
+ return(NULL);
+}
+
+int
+get_start_end(int *start_ix)
+{
+ int i, j, ret_numfiles;
+ u_int64_t free_fs_bytes;
+ char *infile;
+ FILE *fp;
+ struct ioshark_header header;
+ struct ioshark_file_state file_state;
+ struct statfs fsstat;
+ static int fssize_clamp_next_index = 0;
+ static int chunk = 0;
+
+ if (fssize_clamp_next_index == num_input_files)
+ return 0;
+ if (statfs("/data/local/tmp", &fsstat) < 0) {
+ fprintf(stderr, "%s: Can't statfs /data/local/tmp\n",
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ free_fs_bytes = (fsstat.f_bavail * fsstat.f_bsize) * 9 /10;
+ for (i = fssize_clamp_next_index; i < num_input_files; i++) {
+ infile = thread_state[i].filename;
+ fp = fopen(infile, "r");
+ if (fp == NULL) {
+ fprintf(stderr, "%s: Can't open %s\n",
+ progname, infile);
+ exit(EXIT_FAILURE);
+ }
+ if (fread(&header, sizeof(struct ioshark_header),
+ 1, fp) != 1) {
+ fprintf(stderr, "%s read error %s\n",
+ progname, infile);
+ exit(EXIT_FAILURE);
+ }
+ for (j = 0 ; j < header.num_files ; j++) {
+ if (fread(&file_state, sizeof(struct ioshark_file_state),
+ 1, fp) != 1) {
+ fprintf(stderr, "%s read error tracefile\n",
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ if (quick_mode == 0 ||
+ !is_readonly_mount(
+ get_ro_filename(file_state.global_filename_ix),
+ file_state.size)) {
+ if (file_state.size > free_fs_bytes) {
+ fclose(fp);
+ goto out;
+ }
+ free_fs_bytes -= file_state.size;
+ }
+ }
+ fclose(fp);
+ }
+out:
+ if (verbose) {
+ if (chunk > 0 || i < num_input_files) {
+ printf("Breaking up input files, Chunk %d: %d to %d\n",
+ chunk++, fssize_clamp_next_index, i - 1);
+ } else {
+ printf("Entire Dataset fits start = %d to %d, free_bytes = %ju\n",
+ fssize_clamp_next_index,
+ i - fssize_clamp_next_index,
+ free_fs_bytes);
+ }
+ }
+ *start_ix = fssize_clamp_next_index;
+ ret_numfiles = i - fssize_clamp_next_index;
+ fssize_clamp_next_index = i;
+ return ret_numfiles;
+}
+
+int
+ioshark_pthread_create(pthread_t *tidp, void *(*start_routine)(void *))
+{
+ pthread_attr_t attr;
+
+ pthread_attr_init(&attr);
+ pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
+ pthread_attr_setstacksize(&attr, (size_t)(1024*1024));
+ return pthread_create(tidp, &attr, start_routine, (void *)NULL);
+}
+
+void
+wait_for_threads(int num_threads)
+{
+ int i;
+
+ for (i = 0; i < num_threads; i++) {
+ pthread_join(tid[i], NULL);
+ tid[i] = 0;
+ }
+}
+
+#define IOSHARK_FD_LIM 8192
+
+static void
+sizeup_fd_limits(void)
+{
+ struct rlimit r;
+
+ getrlimit(RLIMIT_NOFILE, &r);
+ if (r.rlim_cur >= IOSHARK_FD_LIM)
+ /* cur limit already at what we want */
+ return;
+ /*
+ * Size up both the Max and Cur to IOSHARK_FD_LIM.
+ * If we are not running as root, this will fail,
+ * catch that below and exit.
+ */
+ if (r.rlim_max < IOSHARK_FD_LIM)
+ r.rlim_max = IOSHARK_FD_LIM;
+ r.rlim_cur = IOSHARK_FD_LIM;
+ if (setrlimit(RLIMIT_NOFILE, &r) < 0) {
+ fprintf(stderr, "%s: Can't setrlimit (RLIMIT_NOFILE, 8192)\n",
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ getrlimit(RLIMIT_NOFILE, &r);
+ if (r.rlim_cur < IOSHARK_FD_LIM) {
+ fprintf(stderr, "%s: Can't setrlimit up to 8192\n",
+ progname);
+ fprintf(stderr, "%s: Running as root ?\n",
+ progname);
+ exit(EXIT_FAILURE);
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ int i;
+ FILE *fp;
+ struct stat st;
+ char *infile;
+ int num_threads = 0;
+ int num_iterations = 1;
+ int c;
+ int num_files, start_file;
+ struct thread_state_s *state;
+
+ progname = argv[0];
+ while ((c = getopt(argc, argv, "dn:st:qv")) != EOF) {
+ switch (c) {
+ case 'd':
+ do_delay = 1;
+ break;
+ case 'n':
+ num_iterations = atoi(optarg);
+ break;
+ case 's':
+ /* Non-verbose summary mode for nightly runs */
+ summary_mode = 1;
+ break;
+ case 't':
+ num_threads = atoi(optarg);
+ break;
+ case 'q':
+ /*
+ * If quick mode is enabled, then we won't
+ * pre-create files that we are doing IO on that
+ * live in readonly partitions (/system, /vendor etc)
+ */
+ quick_mode = 1;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ if ((verbose + summary_mode) == 2)
+ usage();
+
+ if (num_threads > MAX_THREADS)
+ usage();
+
+ if (optind == argc)
+ usage();
+
+ sizeup_fd_limits();
+
+ for (i = optind; i < argc; i++) {
+ infile = argv[i];
+ if (stat(infile, &st) < 0) {
+ fprintf(stderr, "%s: Can't stat %s\n",
+ progname, infile);
+ exit(EXIT_FAILURE);
+ }
+ if (st.st_size == 0) {
+ fprintf(stderr, "%s: Empty file %s\n",
+ progname, infile);
+ continue;
+ }
+ fp = fopen(infile, "r");
+ if (fp == NULL) {
+ fprintf(stderr, "%s: Can't open %s\n",
+ progname, infile);
+ continue;
+ }
+ thread_state[num_input_files].filename = infile;
+ thread_state[num_input_files].fp = fp;
+ num_input_files++;
+ }
+
+ if (num_input_files == 0) {
+ exit(EXIT_SUCCESS);
+ }
+ if (verbose) {
+ printf("Total Input Files = %d\n", num_input_files);
+ printf("Num Iterations = %d\n", num_iterations);
+ }
+ timerclear(&aggregate_file_create_time);
+ timerclear(&aggregate_file_remove_time);
+ timerclear(&aggregate_IO_time);
+
+ if (quick_mode)
+ init_filename_cache();
+
+ capture_util_state_before();
+
+ /*
+ * We pre-create the files that we need once and then we
+ * loop around N times doing IOs on the pre-created files.
+ *
+ * get_start_end() breaks up the total work here to make sure
+ * that all the files we need to pre-create fit into the
+ * available space in /data/local/tmp (hardcoded for now).
+ *
+ * If it won't fit, then we do several sweeps.
+ */
+ while ((num_files = get_start_end(&start_file))) {
+ struct timeval time_for_pass;
+
+ /* Create files once */
+ if (!summary_mode)
+ printf("Doing Pre-creation of Files\n");
+ if (quick_mode && !summary_mode)
+ printf("Skipping Pre-creation of read-only Files\n");
+ if (num_threads == 0 || num_threads > num_files)
+ num_threads = num_files;
+ (void)system("echo 3 > /proc/sys/vm/drop_caches");
+ init_work(start_file, num_files);
+ (void)gettimeofday(&time_for_pass,
+ (struct timezone *)NULL);
+ for (i = 0; i < num_threads; i++) {
+ if (ioshark_pthread_create(&(tid[i]),
+ create_files_thread)) {
+ fprintf(stderr,
+ "%s: Can't create creator thread %d\n",
+ progname, i);
+ exit(EXIT_FAILURE);
+ }
+ }
+ wait_for_threads(num_threads);
+ update_delta_time(&time_for_pass, &aggregate_file_create_time);
+ /* Do the IOs N times */
+ for (i = 0 ; i < num_iterations ; i++) {
+ (void)system("echo 3 > /proc/sys/vm/drop_caches");
+ if (!summary_mode) {
+ if (num_iterations > 1)
+ printf("Starting Test. Iteration %d...\n",
+ i);
+ else
+ printf("Starting Test...\n");
+ }
+ init_work(start_file, num_files);
+ (void)gettimeofday(&time_for_pass,
+ (struct timezone *)NULL);
+ for (c = 0; c < num_threads; c++) {
+ if (ioshark_pthread_create(&(tid[c]),
+ io_thread)) {
+ fprintf(stderr,
+ "%s: Can't create thread %d\n",
+ progname, c);
+ exit(EXIT_FAILURE);
+ }
+ }
+ wait_for_threads(num_threads);
+ update_delta_time(&time_for_pass,
+ &aggregate_IO_time);
+ }
+
+ /*
+ * We are done with the N iterations of IO.
+ * Destroy the files we pre-created.
+ */
+ init_work(start_file, num_files);
+ while ((state = get_work())) {
+ struct timeval start;
+
+ (void)gettimeofday(&start, (struct timezone *)NULL);
+ files_db_unlink_files(state->db_handle);
+ update_delta_time(&start, &aggregate_file_remove_time);
+ files_db_free_memory(state->db_handle);
+ }
+ }
+ if (!summary_mode) {
+ printf("Total Creation time = %ju.%ju (msecs.usecs)\n",
+ get_msecs(&aggregate_file_create_time),
+ get_usecs(&aggregate_file_create_time));
+ printf("Total Remove time = %ju.%ju (msecs.usecs)\n",
+ get_msecs(&aggregate_file_remove_time),
+ get_usecs(&aggregate_file_remove_time));
+ if (do_delay)
+ printf("Total delay time = %ju.%ju (msecs.usecs)\n",
+ get_msecs(&aggregate_delay_time),
+ get_usecs(&aggregate_delay_time));
+ printf("Total Test (IO) time = %ju.%ju (msecs.usecs)\n",
+ get_msecs(&aggregate_IO_time),
+ get_usecs(&aggregate_IO_time));
+ if (verbose)
+ print_bytes("Upfront File Creation bytes",
+ &aggr_create_rw_bytes);
+ print_bytes("Total Test (IO) bytes", &aggr_io_rw_bytes);
+ if (verbose)
+ print_op_stats(aggr_op_counts);
+ report_cpu_disk_util();
+ } else {
+ printf("%ju.%ju ",
+ get_msecs(&aggregate_file_create_time),
+ get_usecs(&aggregate_file_create_time));
+ printf("%ju.%ju ",
+ get_msecs(&aggregate_file_remove_time),
+ get_usecs(&aggregate_file_remove_time));
+ if (do_delay)
+ printf("%ju.%ju ",
+ get_msecs(&aggregate_delay_time),
+ get_usecs(&aggregate_delay_time));
+ printf("%ju.%ju ",
+ get_msecs(&aggregate_IO_time),
+ get_usecs(&aggregate_IO_time));
+ print_bytes(NULL, &aggr_io_rw_bytes);
+ report_cpu_disk_util();
+ printf("\n");
+ }
+ if (quick_mode)
+ free_filename_cache();
+}
diff --git a/ioshark/ioshark_bench.h b/ioshark/ioshark_bench.h
new file mode 100644
index 0000000..d0d0962
--- /dev/null
+++ b/ioshark/ioshark_bench.h
@@ -0,0 +1,154 @@
+/*
+ * 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.
+ */
+
+#ifdef IOSHARK_MAIN
+const char *IO_op[] = {
+ "LSEEK",
+ "LLSEEK",
+ "PREAD64",
+ "PWRITE64",
+ "READ",
+ "WRITE",
+ "MMAP",
+ "MMAP2",
+ "OPEN",
+ "FSYNC",
+ "FDATASYNC",
+ "CLOSE",
+ "MAPPED_PREAD",
+ "MAPPED_PWRITE",
+ "MAX_FILE_OP"
+};
+#endif
+
+#define MAX(A, B) ((A) > (B) ? (A) : (B))
+#define MIN(A, B) ((A) < (B) ? (A) : (B))
+
+#define MINBUFLEN (16*1024)
+
+#define FILE_DB_HASHSIZE 8192
+
+struct files_db_s {
+ char *filename;
+ int fileno;
+ size_t size;
+ int fd;
+ int readonly;
+ int debug_open_flags;
+ struct files_db_s *next;
+};
+
+struct files_db_handle {
+ struct files_db_s *files_db_buckets[FILE_DB_HASHSIZE];
+};
+
+struct IO_operation_s {
+ char *IO_op;
+};
+
+struct rw_bytes_s {
+ u_int64_t bytes_read;
+ u_int64_t bytes_written;
+};
+
+static inline void
+files_db_update_size(void *node, u_int64_t new_size)
+{
+ struct files_db_s *db_node = (struct files_db_s *)node;
+
+ if (db_node->size < new_size)
+ db_node->size = new_size;
+}
+
+static inline void
+files_db_update_filename(void *node, char *filename)
+{
+ ((struct files_db_s *)node)->filename = strdup(filename);
+}
+
+static inline int
+files_db_get_fileno(void *node)
+{
+ return (((struct files_db_s *)node)->fileno);
+}
+
+static inline int
+files_db_get_fd(void *node)
+{
+ return (((struct files_db_s *)node)->fd);
+}
+
+static inline char *
+files_db_get_filename(void *node)
+{
+ return (((struct files_db_s *)node)->filename);
+}
+
+static inline int
+files_db_readonly(void *node)
+{
+ return (((struct files_db_s *)node)->readonly);
+}
+
+static inline u_int64_t
+get_msecs(struct timeval *tv)
+{
+ return ((tv->tv_sec * 1000) + (tv->tv_usec / 1000));
+}
+
+static inline u_int64_t
+get_usecs(struct timeval *tv)
+{
+ return (tv->tv_usec % 1000);
+}
+
+static inline void
+update_delta_time(struct timeval *start,
+ struct timeval *destination)
+{
+ struct timeval res, finish;
+
+ (void)gettimeofday(&finish, (struct timezone *)NULL);
+ timersub(&finish, start, &res);
+ timeradd(destination, &res, &finish);
+ *destination = finish;
+}
+
+void *files_db_create_handle(void);
+void *files_db_lookup_byfileno(void *handle, int fileno);
+void *files_db_add_byfileno(void *handle, int fileno, int readonly);
+void files_db_update_fd(void *node, int fd);
+void files_db_unlink_files(void *db_handle);
+void files_db_close_files(void *handle);
+void files_db_close_fd(void *node);
+void files_db_free_memory(void *handle);
+void create_file(char *path, size_t size,
+ struct rw_bytes_s *rw_bytes);
+char *get_buf(char **buf, int *buflen, int len, int do_fill);
+void files_db_fsync_discard_files(void *handle);
+void print_op_stats(u_int64_t *op_counts);
+void print_bytes(char *desc, struct rw_bytes_s *rw_bytes);
+void ioshark_handle_mmap(void *db_node,
+ struct ioshark_file_operation *file_op,
+ char **bufp, int *buflen, u_int64_t *op_counts,
+ struct rw_bytes_s *rw_bytes);
+void capture_util_state_before(void);
+void report_cpu_disk_util(void);
+
+char *get_ro_filename(int ix);
+void init_filename_cache(void);
+void free_filename_cache(void);
+int is_readonly_mount(char *filename, size_t size);
diff --git a/ioshark/ioshark_bench_mmap.c b/ioshark/ioshark_bench_mmap.c
new file mode 100644
index 0000000..55d7060
--- /dev/null
+++ b/ioshark/ioshark_bench_mmap.c
@@ -0,0 +1,201 @@
+/*
+ * 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 <stdio.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+#include <sys/stat.h>
+#include <sys/errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <assert.h>
+#include "ioshark.h"
+#include "ioshark_bench.h"
+
+/*
+ * The purpose of this code is to convert mmap() calls into
+ * a mix of (semi)-random reads and writes.
+ * PROT_READ => 4KB/8KB/16KB random reads.
+ * PROT_WRITE => adds 4KB random writes.
+ */
+
+extern char *progname;
+
+#define IOSHARK_MAX_MMAP_IOLEN (16*1024)
+
+#define MMAP_ENTS 16
+
+struct mmap_io_ent_tab_s {
+ off_t offset;
+ size_t len;
+};
+
+struct mmap_io_ent_s {
+ int num_entries;
+ struct mmap_io_ent_tab_s table[MMAP_ENTS + 1];
+ size_t resid;
+};
+
+static void
+setup_mmap_io_state(struct mmap_io_ent_s *mio,
+ size_t total_len, off_t offset)
+{
+ int slice;
+
+ memset(mio, 0, sizeof(struct mmap_io_ent_s));
+ mio->resid = total_len;
+ slice = MAX(IOSHARK_MAX_MMAP_IOLEN,
+ total_len / MMAP_ENTS);
+ while (total_len > 0) {
+ assert(mio->num_entries < MMAP_ENTS + 1);
+ mio->table[mio->num_entries].offset = offset;
+ mio->table[mio->num_entries].len =
+ MIN((u_int64_t)total_len, (u_int64_t)slice);
+ total_len -= mio->table[mio->num_entries].len;
+ offset += mio->table[mio->num_entries].len;
+ mio->num_entries++;
+ }
+}
+
+static size_t
+mmap_getnext_off_len(struct mmap_io_ent_s *mio,
+ off_t *offset)
+{
+ int i;
+ int find_rand_index[MMAP_ENTS + 1];
+ int rand_index_len = 0;
+ size_t iolength;
+
+ if (mio->resid == 0)
+ return 0;
+ /* Pick a slot with residual length > 0 at random first */
+ for (i = 0 ; i < MMAP_ENTS + 1 ; i++) {
+ if (mio->table[i].len > 0)
+ find_rand_index[rand_index_len++] = i;
+ }
+ i = find_rand_index[rand() % rand_index_len];
+ /* Randomize iolength 4K-16K */
+ iolength = ((rand() % 4) + 1) * 4096;
+ iolength = MIN(mio->table[i].len, iolength);
+ *offset = mio->table[i].offset;
+ mio->table[i].offset += iolength;
+ mio->table[i].len -= iolength;
+ mio->resid -= iolength;
+ return iolength;
+}
+
+static void
+mmap_do_io(void *db_node, int prot, off_t offset, size_t len,
+ char **bufp, int *buflen, u_int64_t *op_counts,
+ struct rw_bytes_s *rw_bytes)
+{
+ char *p;
+ int ret;
+
+ if (!(prot & IOSHARK_PROT_WRITE)) {
+ /* Only preads */
+ p = get_buf(bufp, buflen, len, 0);
+ ret = pread(files_db_get_fd(db_node),
+ p, len, offset);
+ rw_bytes->bytes_read += len;
+ if (ret < 0) {
+ fprintf(stderr,
+ "%s: mapped pread(%s %zu %lu) error fd=%d %s\n",
+ progname, files_db_get_filename(db_node),
+ len, offset, files_db_get_fd(db_node),
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ op_counts[IOSHARK_MAPPED_PREAD]++;
+ } else {
+ /* 50-50 R/W */
+ if ((rand() % 2) == 1) {
+ p = get_buf(bufp, buflen, len, 1);
+ ret = pwrite(files_db_get_fd(db_node),
+ p, len, offset);
+ rw_bytes->bytes_written += len;
+ if (ret < 0) {
+#if 0
+ fprintf(stderr,
+ "%s: mapped pwrite failed, file unwriteable ? open_flags=%x\n",
+ progname,
+ fcntl(files_db_get_fd(db_node), F_GETFL));
+ exit(EXIT_FAILURE);
+#endif
+ } else
+ op_counts[IOSHARK_MAPPED_PWRITE]++;
+ } else {
+ p = get_buf(bufp, buflen, len, 0);
+ ret = pread(files_db_get_fd(db_node),
+ p, len, offset);
+ rw_bytes->bytes_read += len;
+ if (ret < 0) {
+ fprintf(stderr,
+ "%s: mapped pread(%s %zu %lu) error fd=%d %s\n",
+ progname, files_db_get_filename(db_node),
+ len,
+ offset, files_db_get_fd(db_node),
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ op_counts[IOSHARK_MAPPED_PREAD]++;
+ }
+ }
+}
+
+void
+ioshark_handle_mmap(void *db_node,
+ struct ioshark_file_operation *file_op,
+ char **bufp, int *buflen, u_int64_t *op_counts,
+ struct rw_bytes_s *rw_bytes)
+{
+ off_t offset = file_op->mmap_offset;
+ size_t len = file_op->mmap_len;
+ int prot = file_op->mmap_prot;
+ struct mmap_io_ent_s mio;
+ struct stat statbuf;
+
+ if (fstat(files_db_get_fd(db_node), &statbuf) < 0) {
+ fprintf(stderr,
+ "%s: fstat failure %s\n",
+ __func__, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ /*
+ * The size of the file better accomodate offset + len
+ * Else there is an issue with pre-creation
+ */
+ assert(offset + len <= statbuf.st_size);
+ if (len <= IOSHARK_MAX_MMAP_IOLEN) {
+ mmap_do_io(db_node, prot, offset, len,
+ bufp, buflen, op_counts,
+ rw_bytes);
+ return;
+ }
+ setup_mmap_io_state(&mio, len, offset);
+ assert(mio.num_entries > 0);
+ while ((len = mmap_getnext_off_len(&mio, &offset))) {
+ assert((offset + len) <=
+ (file_op->mmap_offset + file_op->mmap_len));
+ mmap_do_io(db_node, prot, offset, len, bufp, buflen,
+ op_counts, rw_bytes);
+ }
+}
diff --git a/ioshark/ioshark_bench_subr.c b/ioshark/ioshark_bench_subr.c
new file mode 100644
index 0000000..4280a5a
--- /dev/null
+++ b/ioshark/ioshark_bench_subr.c
@@ -0,0 +1,644 @@
+/*
+ * 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 <stdio.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/vfs.h>
+#include <sys/statvfs.h>
+#include <sys/mman.h>
+#include "ioshark.h"
+#include "ioshark_bench.h"
+
+extern char *progname;
+extern int verbose, summary_mode;
+
+void *
+files_db_create_handle(void)
+{
+ struct files_db_handle *h;
+ int i;
+
+ h = malloc(sizeof(struct files_db_handle));
+ for (i = 0 ; i < FILE_DB_HASHSIZE ; i++)
+ h->files_db_buckets[i] = NULL;
+ return h;
+}
+
+void *files_db_lookup_byfileno(void *handle, int fileno)
+{
+ u_int32_t hash;
+ struct files_db_handle *h = (struct files_db_handle *)handle;
+ struct files_db_s *db_node;
+
+ hash = fileno % FILE_DB_HASHSIZE;
+ db_node = h->files_db_buckets[hash];
+ while (db_node != NULL) {
+ if (db_node->fileno == fileno)
+ break;
+ db_node = db_node->next;
+ }
+ return db_node;
+}
+
+void *files_db_add_byfileno(void *handle, int fileno, int readonly)
+{
+ u_int32_t hash = fileno % FILE_DB_HASHSIZE;
+ struct files_db_handle *h = (struct files_db_handle *)handle;
+ struct files_db_s *db_node;
+
+ db_node = (struct files_db_s *)
+ files_db_lookup_byfileno(handle, fileno);
+ if (db_node == NULL) {
+ db_node = malloc(sizeof(struct files_db_s));
+ db_node->fileno = fileno;
+ db_node->filename = NULL;
+ db_node->readonly = readonly;
+ db_node->size = 0;
+ db_node->fd = -1;
+ db_node->next = h->files_db_buckets[hash];
+ h->files_db_buckets[hash] = db_node;
+ } else {
+ fprintf(stderr,
+ "%s: Node to be added already exists fileno = %d\n\n",
+ __func__, fileno);
+ exit(EXIT_FAILURE);
+ }
+ return db_node;
+}
+
+void
+files_db_fsync_discard_files(void *handle)
+{
+ struct files_db_handle *h = (struct files_db_handle *)handle;
+ struct files_db_s *db_node;
+ int i;
+
+ for (i = 0 ; i < FILE_DB_HASHSIZE ; i++) {
+ db_node = h->files_db_buckets[i];
+ while (db_node != NULL) {
+ int do_close = 0;
+
+ if (db_node->fd == -1) {
+ int fd;
+ int openflags;
+
+ /*n
+ * File was closed, let's open it so we can
+ * fsync and fadvise(DONTNEED) it.
+ */
+ do_close = 1;
+ if (files_db_readonly(db_node))
+ openflags = O_RDONLY;
+ else
+ openflags = O_RDWR;
+ fd = open(files_db_get_filename(db_node),
+ openflags);
+ if (fd < 0) {
+ fprintf(stderr,
+ "%s: open(%s %x) error %d\n",
+ progname, db_node->filename,
+ openflags,
+ errno);
+ exit(EXIT_FAILURE);
+ }
+ db_node->fd = fd;
+ }
+ if (!db_node->readonly && fsync(db_node->fd) < 0) {
+ fprintf(stderr, "%s: Cannot fsync %s\n",
+ __func__, db_node->filename);
+ exit(1);
+ }
+ if (posix_fadvise(db_node->fd, 0, 0,
+ POSIX_FADV_DONTNEED) < 0) {
+ fprintf(stderr,
+ "%s: Cannot fadvise(DONTNEED) %s\n",
+ __func__, db_node->filename);
+ exit(1);
+ }
+ if (do_close) {
+ close(db_node->fd);
+ db_node->fd = -1;
+ }
+ db_node = db_node->next;
+ }
+ }
+}
+
+void
+files_db_update_fd(void *node, int fd)
+{
+ struct files_db_s *db_node = (struct files_db_s *)node;
+
+ db_node->fd = fd;
+}
+
+void
+files_db_close_fd(void *node)
+{
+ struct files_db_s *db_node = (struct files_db_s *)node;
+
+ if (db_node->fd != -1)
+ close(db_node->fd);
+ db_node->fd = -1;
+}
+
+void
+files_db_close_files(void *handle)
+{
+ struct files_db_handle *h = (struct files_db_handle *)handle;
+ struct files_db_s *db_node;
+ int i;
+
+ for (i = 0 ; i < FILE_DB_HASHSIZE ; i++) {
+ db_node = h->files_db_buckets[i];
+ while (db_node != NULL) {
+ if ((db_node->fd != -1) && close(db_node->fd) < 0) {
+ fprintf(stderr, "%s: Cannot close %s\n",
+ __func__, db_node->filename);
+ exit(1);
+ }
+ db_node->fd = -1;
+ db_node = db_node->next;
+ }
+ }
+}
+
+void
+files_db_unlink_files(void *handle)
+{
+ struct files_db_handle *h = (struct files_db_handle *)handle;
+ struct files_db_s *db_node;
+ int i;
+
+ for (i = 0 ; i < FILE_DB_HASHSIZE ; i++) {
+ db_node = h->files_db_buckets[i];
+ while (db_node != NULL) {
+ if ((db_node->fd != -1) && close(db_node->fd) < 0) {
+ fprintf(stderr, "%s: Cannot close %s\n",
+ __func__, db_node->filename);
+ exit(1);
+ }
+ db_node->fd = -1;
+ if (is_readonly_mount(db_node->filename, db_node->size) == 0) {
+ if (unlink(db_node->filename) < 0) {
+ fprintf(stderr, "%s: Cannot unlink %s:%s\n",
+ __func__, db_node->filename,
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ }
+ db_node = db_node->next;
+ }
+ }
+}
+
+void
+files_db_free_memory(void *handle)
+{
+ struct files_db_handle *h = (struct files_db_handle *)handle;
+ struct files_db_s *db_node, *tmp;
+ int i;
+
+ for (i = 0 ; i < FILE_DB_HASHSIZE ; i++) {
+ db_node = h->files_db_buckets[i];
+ while (db_node != NULL) {
+ tmp = db_node;
+ db_node = db_node->next;
+ free(tmp->filename);
+ free(tmp);
+ }
+ }
+ free(h);
+}
+
+char *
+get_buf(char **buf, int *buflen, int len, int do_fill __attribute__((unused)))
+{
+ if (len == 0 && *buf == NULL) {
+ /*
+ * If we ever get a zero len
+ * request, start with MINBUFLEN
+ */
+ if (*buf == NULL)
+ len = MINBUFLEN / 2;
+ }
+ if (*buflen < len) {
+ *buflen = MAX(MINBUFLEN, len * 2);
+ if (*buf)
+ free(*buf);
+ *buf = malloc(*buflen);
+ if (do_fill) {
+ u_int32_t *s;
+ int count;
+
+ s = (u_int32_t *)*buf;
+ count = *buflen / sizeof(u_int32_t);
+ while (count > 0) {
+ *s++ = rand();
+ count--;
+ }
+ }
+ }
+ assert(*buf != NULL);
+ return *buf;
+}
+
+void
+create_file(char *path, size_t size, struct rw_bytes_s *rw_bytes)
+{
+ int fd, n;
+ char *buf = NULL;
+ int buflen = 0;
+
+ fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0644);
+ if (fd < 0) {
+ fprintf(stderr, "%s Cannot create file %s, error = %d\n",
+ progname, path, errno);
+ exit(EXIT_FAILURE);
+ }
+ while (size > 0) {
+ n = MIN(size, MINBUFLEN);
+ buf = get_buf(&buf, &buflen, n, 1);
+ if (write(fd, buf, n) < n) {
+ fprintf(stderr,
+ "%s Cannot write file %s, error = %d\n",
+ progname, path, errno);
+ exit(EXIT_FAILURE);
+ }
+ rw_bytes->bytes_written += n;
+ size -= n;
+ }
+ if (fsync(fd) < 0) {
+ fprintf(stderr, "%s Cannot fsync file %s, error = %d\n",
+ progname, path, errno);
+ exit(EXIT_FAILURE);
+ }
+ if (posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED) < 0) {
+ fprintf(stderr,
+ "%s Cannot fadvise(DONTNEED) file %s, error = %d\n",
+ progname, path, errno);
+ exit(EXIT_FAILURE);
+ }
+ close(fd);
+}
+
+void
+print_op_stats(u_int64_t *op_counts)
+{
+ int i;
+ extern char *IO_op[];
+
+ printf("IO Operation counts :\n");
+ for (i = IOSHARK_LSEEK ; i < IOSHARK_MAX_FILE_OP ; i++) {
+ printf("%s: %ju\n",
+ IO_op[i], op_counts[i]);
+ }
+}
+
+void
+print_bytes(char *desc, struct rw_bytes_s *rw_bytes)
+{
+ if (!summary_mode)
+ printf("%s: Reads = %dMB, Writes = %dMB\n",
+ desc,
+ (int)(rw_bytes->bytes_read / (1024 * 1024)),
+ (int)(rw_bytes->bytes_written / (1024 * 1024)));
+ else
+ printf("%d %d ",
+ (int)(rw_bytes->bytes_read / (1024 * 1024)),
+ (int)(rw_bytes->bytes_written / (1024 * 1024)));
+}
+
+struct cpu_disk_util_stats {
+ /* CPU util */
+ u_int64_t user_cpu_ticks;
+ u_int64_t nice_cpu_ticks;
+ u_int64_t system_cpu_ticks;
+ u_int64_t idle_cpu_ticks;
+ u_int64_t iowait_cpu_ticks;
+ u_int64_t hardirq_cpu_ticks;
+ u_int64_t softirq_cpu_ticks;
+ /* disk util */
+ unsigned long long uptime;
+ unsigned int tot_ticks;
+ unsigned long rd_ios;
+ unsigned long wr_ios;
+ unsigned long rd_sec;
+ unsigned long wr_sec;
+};
+
+static struct cpu_disk_util_stats before;
+static struct cpu_disk_util_stats after;
+
+#define BUFSIZE 8192
+
+static int hz;
+
+static void
+get_HZ(void)
+{
+ if ((hz = sysconf(_SC_CLK_TCK)) == -1)
+ exit(1);
+}
+
+#if 0
+static int num_cores;
+
+static void
+get_cores(void)
+{
+ if ((num_cores = sysconf(_SC_NPROCESSORS_ONLN)) == -1)
+ exit(1);
+}
+#endif
+
+static void
+get_blockdev_name(char *bdev)
+{
+ char dev_name[BUFSIZE];
+ FILE *cmd;
+
+ cmd = popen("getprop ro.product.name", "r");
+ if (cmd == NULL) {
+ fprintf(stderr, "%s: Cannot popen getprop\n",
+ progname);
+ exit(1);
+ }
+ if (fgets(dev_name, BUFSIZE, cmd) == NULL) {
+ fprintf(stderr,
+ "%s: Bad output from getprop ro.product.name\n",
+ progname);
+ exit(1);
+ }
+ pclose(cmd);
+ /* strncmp needed because of the trailing '\n' */
+ if (strncmp(dev_name, "bullhead", strlen("bullhead")) == 0 ||
+ strncmp(dev_name, "angler", strlen("angler")) == 0 ||
+ strncmp(dev_name, "shamu", strlen("shamu")) == 0) {
+ strcpy(bdev, "mmcblk0");
+ } else if (strncmp(dev_name, "marlin", strlen("marlin")) == 0 ||
+ strncmp(dev_name, "sailfish", strlen("sailfish")) == 0) {
+ strcpy(bdev, "sda");
+ } else {
+ fprintf(stderr,
+ "%s: Unknown device %s\n",
+ progname, dev_name);
+ exit(1);
+ }
+}
+
+static void
+read_disk_util_state(struct cpu_disk_util_stats *state)
+{
+ FILE *fp;
+ char line[BUFSIZE], dev_name[BUFSIZE];
+ unsigned int major, minor;
+ unsigned int ios_pgr;
+ unsigned int rq_ticks;
+ unsigned int wr_ticks;
+ unsigned long rd_ticks;
+ unsigned long rd_merges;
+ unsigned long wr_merges;
+ unsigned long up_sec, up_cent;
+ char blockdev_name[BUFSIZE];
+
+ /* Read and parse /proc/uptime */
+ fp = fopen("/proc/uptime", "r");
+ if (fgets(line, sizeof(line), fp) == NULL) {
+ fprintf(stderr, "%s: Cannot read /proc/uptime\n",
+ progname);
+ exit(1);
+ }
+ fclose(fp);
+ sscanf(line, "%lu.%lu", &up_sec, &up_cent);
+ state->uptime = (unsigned long long) up_sec * hz +
+ (unsigned long long) up_cent * hz / 100;
+
+ /* Read and parse /proc/diskstats */
+ get_blockdev_name(blockdev_name);
+ fp = fopen("/proc/diskstats", "r");
+ while (fgets(line, sizeof(line), fp)) {
+ sscanf(line,
+ "%u %u %s %lu %lu %lu %lu %lu %lu %lu %u %u %u %u",
+ &major, &minor, dev_name,
+ &state->rd_ios, &rd_merges, &state->rd_sec,
+ &rd_ticks, &state->wr_ios, &wr_merges,
+ &state->wr_sec, &wr_ticks,
+ &ios_pgr, &state->tot_ticks, &rq_ticks);
+ if (strcmp(dev_name, blockdev_name) == 0) {
+ /*
+ * tot_ticks is "number of milliseconds spent
+ * doing I/Os". Look at Documentation/iostats.txt.
+ * Or at genhd.c:diskstats_show(), which calls
+ * jiffies_to_msecs() on this field before printing
+ * it. Convert this to hz, so we can do all our math
+ * in ticks.
+ */
+ state->tot_ticks /= 1000; /* to seconds */
+ state->tot_ticks *= hz; /* to hz */
+ fclose(fp);
+ return;
+ }
+ }
+ fprintf(stderr, "%s: Did not find device sda in /proc/diskstats\n",
+ progname);
+ exit(1);
+}
+
+static void
+read_cpu_util_state(struct cpu_disk_util_stats *state)
+{
+ FILE *fp;
+ char line[BUFSIZE], cpu[BUFSIZE];
+
+ /* Read and parse /proc/stat */
+ fp = fopen("/proc/stat", "r");
+ if (fgets(line, sizeof(line), fp) == NULL) {
+ fprintf(stderr, "%s: Cannot read /proc/stat\n",
+ progname);
+ exit(1);
+ }
+ fclose(fp);
+ sscanf(line, "%s %ju %ju %ju %ju %ju %ju %ju",
+ cpu,
+ &state->user_cpu_ticks,
+ &state->nice_cpu_ticks,
+ &state->system_cpu_ticks,
+ &state->idle_cpu_ticks,
+ &state->iowait_cpu_ticks,
+ &state->hardirq_cpu_ticks,
+ &state->softirq_cpu_ticks);
+}
+
+void
+capture_util_state_before(void)
+{
+ get_HZ();
+ read_disk_util_state(&before);
+ read_cpu_util_state(&before);
+}
+
+void
+report_cpu_disk_util(void)
+{
+ double disk_util, cpu_util;
+ u_int64_t tot1, tot2, delta1, delta2;
+
+ read_disk_util_state(&after);
+ read_cpu_util_state(&after);
+ /* CPU Util */
+ tot2 = after.user_cpu_ticks + after.nice_cpu_ticks +
+ after.system_cpu_ticks + after.hardirq_cpu_ticks +
+ after.softirq_cpu_ticks;
+ tot1 = before.user_cpu_ticks + before.nice_cpu_ticks +
+ before.system_cpu_ticks + before.hardirq_cpu_ticks +
+ before.softirq_cpu_ticks;
+ delta1 = tot2 - tot1;
+ tot2 += after.iowait_cpu_ticks + after.idle_cpu_ticks;
+ tot1 += before.iowait_cpu_ticks + before.idle_cpu_ticks;
+ delta2 = tot2 - tot1;
+ cpu_util = delta1 * 100.0 / delta2;
+ if (!summary_mode)
+ printf("CPU util = %.2f%%\n", cpu_util);
+ else
+ printf("%.2f ", cpu_util);
+ /* Next compute system (incl irq/softirq) and user cpu util */
+ delta1 = (after.user_cpu_ticks + after.nice_cpu_ticks) -
+ (before.user_cpu_ticks + before.nice_cpu_ticks);
+ cpu_util = delta1 * 100.0 / delta2;
+ if (!summary_mode)
+ printf("User CPU util = %.2f%%\n", cpu_util);
+ else
+ printf("%.2f ", cpu_util);
+ delta1 = (after.system_cpu_ticks + after.hardirq_cpu_ticks +
+ after.softirq_cpu_ticks) -
+ (before.system_cpu_ticks + before.hardirq_cpu_ticks +
+ before.softirq_cpu_ticks);
+ cpu_util = delta1 * 100.0 / delta2;
+ if (!summary_mode)
+ printf("System CPU util = %.2f%%\n", cpu_util);
+ else
+ printf("%.2f ", cpu_util);
+ /* Disk Util */
+ disk_util = (after.tot_ticks - before.tot_ticks) * 100.0 /
+ (after.uptime - before.uptime);
+ if (verbose) {
+ printf("Reads : nr_ios %lu, MB read %lu\n",
+ (after.rd_ios - before.rd_ios),
+ (after.rd_sec - before.rd_sec) / 2048);
+ printf("Writes : nr_ios %lu, MB written %lu\n",
+ (after.wr_ios - before.wr_ios),
+ (after.wr_sec - before.wr_sec) / 2048);
+ }
+ if (!summary_mode)
+ printf("Disk util = %.2f%%\n", disk_util);
+ else
+ printf("%.2f", disk_util);
+}
+
+
+static struct ioshark_filename_struct *filename_cache;
+static int filename_cache_num_entries;
+
+char *
+get_ro_filename(int ix)
+{
+ if (ix >= filename_cache_num_entries)
+ return NULL;
+ return filename_cache[ix].path;
+}
+
+void
+init_filename_cache(void)
+{
+ int fd;
+ struct stat st;
+
+ fd = open("ioshark_filenames", O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "%s Can't open ioshark_filenames file\n",
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ if (fstat(fd, &st) < 0) {
+ fprintf(stderr, "%s Can't fstat ioshark_filenames file\n",
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ filename_cache_num_entries = st.st_size /
+ sizeof(struct ioshark_filename_struct);
+ filename_cache = mmap(NULL, st.st_size, PROT_READ,
+ MAP_SHARED | MAP_LOCKED | MAP_POPULATE,
+ fd, 0);
+ if (filename_cache == MAP_FAILED) {
+ fprintf(stderr, "%s Can't fstat ioshark_filenames file: %s\n",
+ progname, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ close(fd);
+}
+
+void
+free_filename_cache(void)
+{
+ size_t mmap_size;
+
+ mmap_size = filename_cache_num_entries *
+ sizeof(struct ioshark_filename_struct);
+ munmap(filename_cache, mmap_size);
+}
+
+/*
+ * Is the passed in filename a regular file ? (eg. not a directory).
+ * Second, is it in a read-only partition ?
+ */
+int
+is_readonly_mount(char *filename, size_t size)
+{
+ struct statfs statfsbuf;
+ struct stat statbuf;
+
+ if (stat(filename, &statbuf) < 0) {
+ /* File possibly deleted */
+ return 0;
+ }
+ if (!S_ISREG(statbuf.st_mode)) {
+ /* Is it a regular file ? */
+ return 0;
+ }
+ if ((size_t)statbuf.st_size < size) {
+ /* Size of existing file is smaller than we expect */
+ return 0;
+ }
+ if (statfs(filename, &statfsbuf) < 0) {
+ /* This shouldn't happen */
+ return 0;
+ }
+ if ((statfsbuf.f_flags & ST_RDONLY) == 0)
+ return 0;
+ else
+ return 1;
+}
diff --git a/ioshark/monkey-strace+fstrace.tgz b/ioshark/monkey-strace+fstrace.tgz
new file mode 100644
index 0000000..6f02301
--- /dev/null
+++ b/ioshark/monkey-strace+fstrace.tgz
Binary files differ
diff --git a/ioshark/monkeystracebig.tgz b/ioshark/monkeystracebig.tgz
new file mode 100644
index 0000000..e2ab7bc
--- /dev/null
+++ b/ioshark/monkeystracebig.tgz
Binary files differ
diff --git a/ioshark/monkeytracebig.tar.gz b/ioshark/monkeytracebig.tar.gz
new file mode 100644
index 0000000..2ff7047
--- /dev/null
+++ b/ioshark/monkeytracebig.tar.gz
Binary files differ
diff --git a/ioshark/wl.tar b/ioshark/wl.tar
new file mode 100644
index 0000000..a004ad1
--- /dev/null
+++ b/ioshark/wl.tar
Binary files differ
diff --git a/libpagemap/Android.bp b/libpagemap/Android.bp
index 976385c..e06caa6 100644
--- a/libpagemap/Android.bp
+++ b/libpagemap/Android.bp
@@ -15,6 +15,9 @@
cc_library {
name: "libpagemap",
vendor_available: true,
+ vndk: {
+ enabled: true,
+ },
srcs: [
"pm_kernel.c",
"pm_process.c",
diff --git a/librank/librank.c b/librank/librank.c
index a525f23..f017c9d 100644
--- a/librank/librank.c
+++ b/librank/librank.c
@@ -91,7 +91,7 @@
return libraries[i];
}
- if (libraries_count >= libraries_size) {
+ if (libraries_size && libraries_count >= libraries_size) {
libraries = realloc(libraries, 2 * libraries_size * sizeof(struct library_info *));
if (!libraries) {
fprintf(stderr, "Couldn't resize libraries array: %s\n", strerror(errno));
@@ -133,7 +133,7 @@
return library->mappings[i];
}
- if (library->mappings_count >= library->mappings_size) {
+ if (library->mappings_size && library->mappings_count >= library->mappings_size) {
library->mappings = realloc(library->mappings,
2 * library->mappings_size * sizeof(struct mapping_info*));
if (!library->mappings) {
@@ -419,7 +419,7 @@
fflush(stdout);
}
- return 0;
+ exit(0);
}
static void usage(char *myname) {
diff --git a/memcpy-perf/memcpy-perf.cpp b/memcpy-perf/memcpy-perf.cpp
index 20d060b..2dfd900 100644
--- a/memcpy-perf/memcpy-perf.cpp
+++ b/memcpy-perf/memcpy-perf.cpp
@@ -7,14 +7,20 @@
#include <memory>
#include <cmath>
#include <string>
+#include <thread>
+
+#define CACHE_HIT_SIZE 1 << 17
using namespace std;
-const size_t size_start = 64;
-const size_t size_end = 16 * (1ull << 20);
-const size_t samples = 2048;
+size_t size_start = 64;
+size_t size_end = 16 * (1ull << 20);
+size_t samples = 2048;
size_t size_per_test = 64 * (1ull << 20);
size_t tot_sum = 0;
+size_t delay = 0;
+float speed = 0;
+bool dummy = false;
void __attribute__((noinline)) memcpy_noinline(void *dst, void *src, size_t size);
void __attribute__((noinline)) memset_noinline(void *dst, int value, size_t size);
@@ -26,21 +32,64 @@
SumBench,
};
+static void usage(char* p) {
+ printf("Usage: %s <test> <options>\n"
+ "<test> is one of the following:\n"
+ " --memcpy\n"
+ " --memset\n"
+ " --sum\n"
+ "<options> are optional and apply to all tests:\n"
+ " --dummy\n"
+ " Simulates cpu-only load of a test. Guaranteed to use L2\n"
+ " instead. Not supported on --sum test.\n"
+ " --delay DELAY_DIVISOR\n"
+ " --start START_SIZE_MB\n"
+ " --end END_SIZE_MB (requires start, optional)\n"
+ " --samples NUM_SAMPLES\n"
+ , p);
+}
+
int main(int argc, char *argv[])
{
- BenchType type;
+ BenchType type = MemcpyBench;
if (argc <= 1) {
- cerr << "memcpy_perf [--memcpy|--memset|--sum]" << endl;
+ usage(argv[0]);
return 0;
}
- if (string(argv[1]) == string("--memcpy")) {
- type = MemcpyBench;
- } else if (string(argv[1]) == string("--memset")) {
- type = MemsetBench;
- } else if (string(argv[1]) == string("--sum")) {
- type = SumBench;
- } else {
- type = MemcpyBench;
+ for (int i = 1; i < argc; i++) {
+ if (string(argv[i]) == string("--memcpy")) {
+ type = MemcpyBench;
+ } else if (string(argv[i]) == string("--memset")) {
+ type = MemsetBench;
+ } else if (string(argv[i]) == string("--sum")) {
+ type = SumBench;
+ } else if (string(argv[i]) == string("--dummy")) {
+ dummy = true;
+ } else if (i + 1 < argc) {
+ if (string(argv[i]) == string("--delay")) {
+ delay = atoi(argv[++i]);
+ } else if (string(argv[i]) == string("--start")) {
+ size_start = atoi(argv[++i]) * (1ull << 20);
+ size_end = size_start;
+ } else if (string(argv[i]) == string("--end")) {
+ size_t end = atoi(argv[++i]) * (1ull << 20);
+ if (end > size_start && i > 3
+ && string(argv[i-3]) == string("--start")) {
+ size_end = end;
+ } else {
+ printf("Cannot specify --end without --start.\n");
+ return 0;
+ }
+ } else if (string(argv[i]) == string("--samples")) {
+ samples = atoi(argv[++i]);
+ } else {
+ printf("Unknown argument %s\n", argv[i]);
+ return 0;
+ }
+ } else {
+ printf("The %s option requires a single argument.\n", argv[i]);
+ return 0;
+ }
}
unique_ptr<uint8_t[]> src(new uint8_t[size_end]);
@@ -54,8 +103,10 @@
//cout << "src: " << (uintptr_t)src.get() << endl;
//cout << "dst: " << (uintptr_t)dst.get() << endl;
- for (double cur_pow = start_pow; cur_pow <= end_pow; cur_pow += pow_inc) {
- chrono::time_point<chrono::high_resolution_clock> copy_start, copy_end;
+ for (double cur_pow = start_pow; cur_pow <= end_pow && samples > 0;
+ cur_pow += pow_inc) {
+ chrono::time_point<chrono::high_resolution_clock>
+ copy_start, copy_end, pre_wait;
size_t cur_size = (size_t)pow(10.0, cur_pow);
size_t iter_per_size = size_per_test / cur_size;
@@ -65,9 +116,21 @@
case MemsetBench: {
memcpy_noinline(src.get(), dst.get(), cur_size);
memset_noinline(dst.get(), 0xdeadbeef, cur_size);
+ size_t hit_size = CACHE_HIT_SIZE;
copy_start = chrono::high_resolution_clock::now();
for (int i = 0; i < iter_per_size; i++) {
- memset_noinline(dst.get(), 0xdeadbeef, cur_size);
+ if (!dummy) {
+ memset_noinline(dst.get(), 0xdeadbeef, cur_size);
+ } else {
+ while (hit_size < cur_size) {
+ memset_noinline
+ (dst.get(), 0xdeadbeef, CACHE_HIT_SIZE);
+ hit_size += 1 << 17;
+ }
+ }
+ if (delay != 0)
+ this_thread::sleep_for(chrono
+ ::nanoseconds(size_per_test / delay));
}
copy_end = chrono::high_resolution_clock::now();
break;
@@ -75,9 +138,21 @@
case MemcpyBench: {
memcpy_noinline(dst.get(), src.get(), cur_size);
memcpy_noinline(src.get(), dst.get(), cur_size);
+ size_t hit_size = CACHE_HIT_SIZE;
copy_start = chrono::high_resolution_clock::now();
for (int i = 0; i < iter_per_size; i++) {
- memcpy_noinline(dst.get(), src.get(), cur_size);
+ if (!dummy) {
+ memcpy_noinline(dst.get(), src.get(), cur_size);
+ } else {
+ while (hit_size < cur_size) {
+ memcpy_noinline
+ (dst.get(), src.get(), CACHE_HIT_SIZE);
+ hit_size += CACHE_HIT_SIZE;
+ }
+ }
+ if (delay != 0)
+ this_thread::sleep_for(chrono
+ ::nanoseconds(size_per_test / delay));
}
copy_end = chrono::high_resolution_clock::now();
break;
@@ -88,6 +163,9 @@
copy_start = chrono::high_resolution_clock::now();
for (int i = 0; i < iter_per_size; i++) {
s += sum(src.get(), cur_size);
+ if (delay != 0)
+ this_thread::sleep_for(chrono
+ ::nanoseconds(size_per_test / delay));
}
copy_end = chrono::high_resolution_clock::now();
tot_sum += s;
@@ -95,11 +173,18 @@
}
}
+ samples--;
double ns_per_copy = chrono::duration_cast<chrono::nanoseconds>(copy_end - copy_start).count() / double(iter_per_size);
double gb_per_sec = ((double)cur_size / (1ull<<30)) / (ns_per_copy / 1.0E9);
if (type == MemcpyBench)
gb_per_sec *= 2.0;
- cout << "size: " << cur_size << ", perf: " << gb_per_sec << "GB/s, iter: " << iter_per_size << endl;
+ double percent_waiting = 0;
+ if (delay != 0) {
+ percent_waiting = (size_per_test / delay) / ns_per_copy * 100;
+ }
+ cout << "size: " << cur_size << ", perf: " << gb_per_sec
+ << "GB/s, iter: " << iter_per_size << ", \% time spent waiting: "
+ << percent_waiting << endl;
}
return 0;
}
diff --git a/perfprofd/Android.bp b/perfprofd/Android.bp
index f05f324..2c07ca1 100644
--- a/perfprofd/Android.bp
+++ b/perfprofd/Android.bp
@@ -63,7 +63,7 @@
"libcutils"
],
system_shared_libs: [
- "libc",
+ "libc", "libdl",
],
cppflags: perfprofd_cppflags,
diff --git a/preopt2cachename/preopt2cachename.cpp b/preopt2cachename/preopt2cachename.cpp
index dfdc63f..f9a12ff 100644
--- a/preopt2cachename/preopt2cachename.cpp
+++ b/preopt2cachename/preopt2cachename.cpp
@@ -24,37 +24,38 @@
#endif
static const char* kDalvikCacheDir = "/data/dalvik-cache/";
-static const char* kCacheSuffix = "@classes.dex";
+static const char* kOdexCacheSuffix = "@classes.dex";
+static const char* kVdexCacheSuffix = "@classes.vdex";
-// Returns the ISA extracted from the odex_file_location.
-// odex_file_location is formatted like /system/app/<app_name>/oat/<isa>/<app_name>.odex for all
-// functions. We return an empty string "" in error cases.
-static std::string ExtractISA(const std::string& odex_file_location) {
- std::vector<std::string> split_file_location = android::base::Split(odex_file_location, "/");
+// Returns the ISA extracted from the file_location.
+// file_location is formatted like /system/app/<app_name>/oat/<isa>/<app_name>.{odex,vdex}
+// for all functions. We return an empty string "" in error cases.
+static std::string ExtractISA(const std::string& file_location) {
+ std::vector<std::string> split_file_location = android::base::Split(file_location, "/");
if (split_file_location.size() <= 1) {
return "";
} else if (split_file_location.size() != 7) {
- LOG(WARNING) << "Unexpected length for odex-file-location. We expected 7 segments but found "
+ LOG(WARNING) << "Unexpected length for file-location. We expected 7 segments but found "
<< split_file_location.size();
}
return split_file_location[split_file_location.size() - 2];
}
-// Returns the apk name extracted from the odex_file_location.
-// odex_file_location is formatted like /system/app/<app_name>/oat/<isa>/<app_name>.odex. We return
-// the final <app_name> with the .odex replaced with .apk.
-static std::string ExtractAPKName(const std::string& odex_file_location) {
+// Returns the apk name extracted from the file_location.
+// file_location is formatted like /system/app/<app_name>/oat/<isa>/<app_name>.{odex,vdex}.
+// We return the final <app_name> with the .{odex,vdex} replaced with .apk.
+static std::string ExtractAPKName(const std::string& file_location) {
// Find and copy filename.
- size_t file_location_start = odex_file_location.rfind('/');
+ size_t file_location_start = file_location.rfind('/');
if (file_location_start == std::string::npos) {
return "";
}
- size_t ext_start = odex_file_location.rfind('.');
+ size_t ext_start = file_location.rfind('.');
if (ext_start == std::string::npos || ext_start < file_location_start) {
return "";
}
- std::string apk_name = odex_file_location.substr(file_location_start + 1,
- ext_start - file_location_start);
+ std::string apk_name = file_location.substr(file_location_start + 1,
+ ext_start - file_location_start);
// Replace extension with .apk.
apk_name += "apk";
@@ -62,18 +63,18 @@
}
// The cache file name is /data/dalvik-cache/<isa>/ prior to this function
-static bool OdexFilenameToCacheFile(const std::string& odex_file_location,
- /*in-out*/std::string& cache_file) {
- // Skip the first '/' in odex_file_location.
- size_t initial_position = odex_file_location[0] == '/' ? 1 : 0;
- size_t apk_position = odex_file_location.find("/oat", initial_position);
+static bool SystemBFilenameToCacheFile(const std::string& file_location,
+ /*in-out*/std::string& cache_file) {
+ // Skip the first '/' in file_location.
+ size_t initial_position = file_location[0] == '/' ? 1 : 0;
+ size_t apk_position = file_location.find("/oat", initial_position);
if (apk_position == std::string::npos) {
LOG(ERROR) << "Unable to find oat directory!";
return false;
}
size_t cache_file_position = cache_file.size();
- cache_file += odex_file_location.substr(initial_position, apk_position);
+ cache_file += file_location.substr(initial_position, apk_position);
// '/' -> '@' up to where the apk would be.
cache_file_position = cache_file.find('/', cache_file_position);
while (cache_file_position != std::string::npos) {
@@ -82,28 +83,33 @@
}
// Add <apk_name>.
- std::string apk_name = ExtractAPKName(odex_file_location);
+ std::string apk_name = ExtractAPKName(file_location);
if (apk_name.empty()) {
- LOG(ERROR) << "Unable to determine apk name from odex file name '" << odex_file_location << "'";
+ LOG(ERROR) << "Unable to determine apk name from file name '" << file_location << "'";
return false;
}
cache_file += apk_name;
- cache_file += kCacheSuffix;
+ if (file_location.size() >= 5 &&
+ file_location.substr(file_location.size() - 5) == std::string(".vdex")) {
+ cache_file += kVdexCacheSuffix;
+ } else {
+ cache_file += kOdexCacheSuffix;
+ }
return true;
}
-// Do the overall transformation from odex_file_location to output_file_location. Prior to this the
+// Do the overall transformation from file_location to output_file_location. Prior to this the
// output_file_location is empty.
-static bool OdexToCacheFile(std::string& odex_file_location,
- /*out*/std::string& output_file_location) {
- std::string isa = ExtractISA(odex_file_location);
+static bool SystemBFileToCacheFile(const std::string& file_location,
+ /*out*/std::string& output_file_location) {
+ std::string isa = ExtractISA(file_location);
if (isa.empty()) {
- LOG(ERROR) << "Unable to determine isa for odex file '" << odex_file_location << "', skipping";
+ LOG(ERROR) << "Unable to determine isa for file '" << file_location << "', skipping";
return false;
}
output_file_location += isa;
output_file_location += '/';
- return OdexFilenameToCacheFile(odex_file_location, output_file_location);
+ return SystemBFilenameToCacheFile(file_location, output_file_location);
}
// This program is used to determine where in the /data directory the runtime will search for an
@@ -115,9 +121,9 @@
LOG(ERROR) << "usage: preopt2cachename preopt-location";
return 2;
}
- std::string odex_file_location(argv[1]);
+ std::string file_location(argv[1]);
std::string output_file_location(kDalvikCacheDir);
- if (!OdexToCacheFile(odex_file_location, output_file_location)) {
+ if (!SystemBFileToCacheFile(file_location, output_file_location)) {
return 1;
} else {
std::cout << output_file_location;
diff --git a/procrank/procrank.cpp b/procrank/procrank.cpp
index 36a2043..b8ebd31 100644
--- a/procrank/procrank.cpp
+++ b/procrank/procrank.cpp
@@ -18,7 +18,6 @@
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
-#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
diff --git a/puncture_fs/puncture_fs.c b/puncture_fs/puncture_fs.c
index e9d08dc..dbb4efc 100644
--- a/puncture_fs/puncture_fs.c
+++ b/puncture_fs/puncture_fs.c
@@ -163,6 +163,10 @@
(int) (100.0 * starting_max / total_size));
hole_max = get_random_num(starting_max, ending_max);
+ do {
+ hole_max = get_random_num(starting_max, ending_max);
+ } while (hole_max == starting_max);
+
create_unique_file(stay_dir,
hole_max - starting_max,
file_id++,
diff --git a/simpleperf/Android.mk b/simpleperf/Android.mk
index b36c28c..665d348 100644
--- a/simpleperf/Android.mk
+++ b/simpleperf/Android.mk
@@ -492,4 +492,36 @@
LOCAL_MULTILIB := both
include $(BUILD_HOST_NATIVE_TEST)
+
+# simpleperf_script.zip (for release in ndk)
+# ============================================================
+SIMPLEPERF_SCRIPT_LIST := \
+ $(filter-out scripts/update.py,$(call all-named-files-under,*.py,scripts)) \
+ scripts/inferno.sh \
+ scripts/inferno.bat \
+ scripts/inferno/inferno.b64 \
+ scripts/inferno/jqueryui/LICENSE.txt \
+ $(call all-named-files-under,*.js,scripts) \
+ $(call all-named-files-under,*.css,scripts) \
+ $(call all-named-files-under,*,doc) \
+ $(call all-named-files-under,app-profiling.apk,demo) \
+ $(call all-named-files-under,*.so,demo) \
+ $(call all-cpp-files-under,demo) \
+ $(call all-java-files-under,demo) \
+ $(call all-named-files-under,*.kt,demo) \
+ testdata/perf_with_symbols.data \
+ testdata/perf_with_trace_offcpu.data
+
+SIMPLEPERF_SCRIPT_LIST := $(addprefix -f $(LOCAL_PATH)/,$(SIMPLEPERF_SCRIPT_LIST))
+
+SIMPLEPERF_SCRIPT_PATH := \
+ $(call intermediates-dir-for,PACKAGING,simplerperf_script,HOST)/simpleperf_script.zip
+
+$(SIMPLEPERF_SCRIPT_PATH) : $(SOONG_ZIP)
+ $(hide) $(SOONG_ZIP) -d -o $@ -C system/extras/simpleperf $(SIMPLEPERF_SCRIPT_LIST)
+
+sdk: $(SIMPLEPERF_SCRIPT_PATH)
+
+$(call dist-for-goals,sdk,$(SIMPLEPERF_SCRIPT_PATH))
+
include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/simpleperf/cmd_dumprecord.cpp b/simpleperf/cmd_dumprecord.cpp
index dcd7bf4..919b62a 100644
--- a/simpleperf/cmd_dumprecord.cpp
+++ b/simpleperf/cmd_dumprecord.cpp
@@ -92,7 +92,10 @@
return true;
}
-static const std::string GetFeatureName(int feature);
+static const std::string GetFeatureNameOrUnknown(int feature) {
+ std::string name = GetFeatureName(feature);
+ return name.empty() ? android::base::StringPrintf("unknown_feature(%d)", feature) : name;
+}
void DumpRecordCommand::DumpFileHeader() {
const FileHeader& header = record_file_reader_->FileHeader();
@@ -127,38 +130,10 @@
}
}
for (auto& feature : features) {
- printf("feature: %s\n", GetFeatureName(feature).c_str());
+ printf("feature: %s\n", GetFeatureNameOrUnknown(feature).c_str());
}
}
-static const std::string GetFeatureName(int feature) {
- static std::map<int, std::string> feature_name_map = {
- {FEAT_TRACING_DATA, "tracing_data"},
- {FEAT_BUILD_ID, "build_id"},
- {FEAT_HOSTNAME, "hostname"},
- {FEAT_OSRELEASE, "osrelease"},
- {FEAT_VERSION, "version"},
- {FEAT_ARCH, "arch"},
- {FEAT_NRCPUS, "nrcpus"},
- {FEAT_CPUDESC, "cpudesc"},
- {FEAT_CPUID, "cpuid"},
- {FEAT_TOTAL_MEM, "total_mem"},
- {FEAT_CMDLINE, "cmdline"},
- {FEAT_EVENT_DESC, "event_desc"},
- {FEAT_CPU_TOPOLOGY, "cpu_topology"},
- {FEAT_NUMA_TOPOLOGY, "numa_topology"},
- {FEAT_BRANCH_STACK, "branch_stack"},
- {FEAT_PMU_MAPPINGS, "pmu_mappings"},
- {FEAT_GROUP_DESC, "group_desc"},
- {FEAT_FILE, "file"},
- {FEAT_META_INFO, "meta_info"},
- };
- auto it = feature_name_map.find(feature);
- if (it != feature_name_map.end()) {
- return it->second;
- }
- return android::base::StringPrintf("unknown_feature(%d)", feature);
-}
void DumpRecordCommand::DumpAttrSection() {
std::vector<EventAttrWithId> attrs = record_file_reader_->AttrSection();
@@ -189,7 +164,7 @@
int feature = pair.first;
const auto& section = pair.second;
printf("feature section for %s: offset %" PRId64 ", size %" PRId64 "\n",
- GetFeatureName(feature).c_str(), section.offset, section.size);
+ GetFeatureNameOrUnknown(feature).c_str(), section.offset, section.size);
if (feature == FEAT_BUILD_ID) {
std::vector<BuildIdRecord> records = record_file_reader_->ReadBuildIdFeature();
for (auto& r : records) {
diff --git a/simpleperf/cmd_list.cpp b/simpleperf/cmd_list.cpp
index 7199814..3b22bde 100644
--- a/simpleperf/cmd_list.cpp
+++ b/simpleperf/cmd_list.cpp
@@ -165,6 +165,9 @@
if (IsDumpingRegsForTracepointEventsSupported()) {
printf("trace-offcpu\n");
}
+ if (IsSettingClockIdSupported()) {
+ printf("set-clockid\n");
+ }
}
void RegisterListCommand() {
diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp
index 487e219..d83b210 100644
--- a/simpleperf/cmd_record.cpp
+++ b/simpleperf/cmd_record.cpp
@@ -31,6 +31,9 @@
#include <android-base/parseint.h>
#include <android-base/strings.h>
#include <android-base/test_utils.h>
+#if defined(__ANDROID__)
+#include <android-base/properties.h>
+#endif
#include "command.h"
#include "dwarf_unwind.h"
@@ -59,6 +62,13 @@
{"ind_call", PERF_SAMPLE_BRANCH_IND_CALL},
};
+static std::unordered_map<std::string, int> clockid_map = {
+ {"realtime", CLOCK_REALTIME},
+ {"monotonic", CLOCK_MONOTONIC},
+ {"monotonic_raw", CLOCK_MONOTONIC_RAW},
+ {"boottime", CLOCK_BOOTTIME},
+};
+
// The max size of records dumped by kernel is 65535, and dump stack size
// should be a multiply of 8, so MAX_DUMP_STACK_SIZE is 65528.
constexpr uint32_t MAX_DUMP_STACK_SIZE = 65528;
@@ -108,7 +118,7 @@
"-f freq Set event sample frequency. It means recording at most [freq]\n"
" samples every second. For non-tracepoint events, the default\n"
" option is -f 4000. A -f/-c option affects all event types\n"
-" following it until meeting another -f/-c option. For example,"
+" following it until meeting another -f/-c option. For example,\n"
" for \"-f 1000 cpu-cycles -c 1 -e sched:sched_switch\", cpu-cycles\n"
" has sample freq 1000, sched:sched_switch event has sample period 1.\n"
"-c count Set event sample period. It means recording one sample when\n"
@@ -119,6 +129,9 @@
" frame as the method to parse call graph in stack.\n"
" Default is dwarf,65528.\n"
"-g Same as '--call-graph dwarf'.\n"
+"--clockid clock_id Generate timestamps of samples using selected clock.\n"
+" Possible values are: realtime, monotonic,\n"
+" monotonic_raw, boottime, perf. Default is perf.\n"
"--cpu cpu_item1,cpu_item2,...\n"
" Collect samples only on the selected cpus. cpu_item can be cpu\n"
" number like 1, or cpu range like 0-3.\n"
@@ -155,6 +168,8 @@
" will be unwound while recording by default. But it may lose\n"
" records as stacking unwinding can be time consuming. Use this\n"
" option to unwind the user's stack after recording.\n"
+"--exit-with-parent Stop recording when the process starting\n"
+" simpleperf dies.\n"
"--start_profiling_fd fd_no After starting profiling, write \"STARTED\" to\n"
" <fd_no>, then close <fd_no>.\n"
"--symfs <dir> Look for files with symbols relative to this directory.\n"
@@ -178,6 +193,7 @@
duration_in_sec_(0),
can_dump_kernel_symbols_(true),
dump_symbols_(true),
+ clockid_("perf"),
event_selection_set_(false),
mmap_page_range_(std::make_pair(1, DESIRED_PAGES_IN_MAPPED_BUFFER)),
record_filename_("perf.data"),
@@ -188,8 +204,6 @@
in_app_context_(false),
trace_offcpu_(false),
exclude_kernel_callchain_(false) {
- // Stop profiling if parent exits.
- prctl(PR_SET_PDEATHSIG, SIGHUP, 0, 0, 0);
// If we run `adb shell simpleperf record xxx` and stop profiling by ctrl-c, adb closes
// sockets connecting simpleperf. After that, simpleperf will receive SIGPIPE when writing
// to stdout/stderr, which is a problem when we use '--app' option. So ignore SIGPIPE to
@@ -234,6 +248,7 @@
double duration_in_sec_;
bool can_dump_kernel_symbols_;
bool dump_symbols_;
+ std::string clockid_;
std::vector<int> cpus_;
EventSelectionSet event_selection_set_;
@@ -322,8 +337,8 @@
} else if (!app_package_name_.empty()) {
// If app process is not created, wait for it. This allows simpleperf starts before
// app process. In this way, we can have a better support of app start-up time profiling.
- int pid = WaitForAppProcess(app_package_name_);
- event_selection_set_.AddMonitoredProcesses({pid});
+ std::set<pid_t> pids = WaitForAppProcesses(app_package_name_);
+ event_selection_set_.AddMonitoredProcesses(pids);
} else {
LOG(ERROR)
<< "No threads to monitor. Try `simpleperf help record` for help";
@@ -495,6 +510,21 @@
<< args[i];
return false;
}
+ } else if (args[i] == "--clockid") {
+ if (!NextArgumentOrError(args, &i)) {
+ return false;
+ }
+ if (args[i] != "perf") {
+ if (!IsSettingClockIdSupported()) {
+ LOG(ERROR) << "Setting clockid is not supported by the kernel.";
+ return false;
+ }
+ if (clockid_map.find(args[i]) == clockid_map.end()) {
+ LOG(ERROR) << "Invalid clockid: " << args[i];
+ return false;
+ }
+ }
+ clockid_ = args[i];
} else if (args[i] == "--cpu") {
if (!NextArgumentOrError(args, &i)) {
return false;
@@ -525,6 +555,8 @@
wait_setting_speed_event_groups_.push_back(group_id);
}
}
+ } else if (args[i] == "--exit-with-parent") {
+ prctl(PR_SET_PDEATHSIG, SIGHUP, 0, 0, 0);
} else if (args[i] == "-g") {
fp_callchain_sampling_ = false;
dwarf_callchain_sampling_ = true;
@@ -714,6 +746,9 @@
}
}
event_selection_set_.SetInherit(child_inherit_);
+ if (clockid_ != "perf") {
+ event_selection_set_.SetClockId(clockid_map[clockid_]);
+ }
return true;
}
@@ -1180,6 +1215,13 @@
// By storing event types information in perf.data, the readers of perf.data have the same
// understanding of event types, even if they are on another machine.
info_map["event_type_info"] = ScopedEventTypes::BuildString(event_selection_set_.GetEvents());
+#if defined(__ANDROID__)
+ info_map["product_props"] = android::base::StringPrintf("%s:%s:%s",
+ android::base::GetProperty("ro.product.manufacturer", "").c_str(),
+ android::base::GetProperty("ro.product.model", "").c_str(),
+ android::base::GetProperty("ro.product.name", "").c_str());
+#endif
+ info_map["clockid"] = clockid_;
return record_file_writer_->WriteMetaInfoFeature(info_map);
}
diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp
index 4873230..b0c1862 100644
--- a/simpleperf/cmd_record_test.cpp
+++ b/simpleperf/cmd_record_test.cpp
@@ -16,6 +16,8 @@
#include <gtest/gtest.h>
+#include <stdio.h>
+#include <stdlib.h>
#include <unistd.h>
#include <android-base/file.h>
@@ -191,7 +193,31 @@
TEST_IN_ROOT(ASSERT_TRUE(RunRecordCmd({"-a", "--call-graph", "fp"})));
}
+bool IsInNativeAbi() {
+ static int in_native_abi = -1;
+ if (in_native_abi == -1) {
+ FILE* fp = popen("uname -m", "re");
+ char buf[40];
+ memset(buf, '\0', sizeof(buf));
+ fgets(buf, sizeof(buf), fp);
+ pclose(fp);
+ std::string s = buf;
+ in_native_abi = 1;
+ if (GetBuildArch() == ARCH_X86_32 || GetBuildArch() == ARCH_X86_64) {
+ if (s.find("86") == std::string::npos) {
+ in_native_abi = 0;
+ }
+ } else if (GetBuildArch() == ARCH_ARM || GetBuildArch() == ARCH_ARM64) {
+ if (s.find("arm") == std::string::npos && s.find("aarch64") == std::string::npos) {
+ in_native_abi = 0;
+ }
+ }
+ }
+ return in_native_abi == 1;
+}
+
TEST(record_cmd, dwarf_callchain_sampling) {
+ OMIT_TEST_ON_NON_NATIVE_ABIS();
ASSERT_TRUE(IsDwarfCallChainSamplingSupported());
std::vector<std::unique_ptr<Workload>> workloads;
CreateProcesses(1, &workloads);
@@ -203,17 +229,20 @@
}
TEST(record_cmd, system_wide_dwarf_callchain_sampling) {
+ OMIT_TEST_ON_NON_NATIVE_ABIS();
ASSERT_TRUE(IsDwarfCallChainSamplingSupported());
TEST_IN_ROOT(RunRecordCmd({"-a", "--call-graph", "dwarf"}));
}
TEST(record_cmd, no_unwind_option) {
+ OMIT_TEST_ON_NON_NATIVE_ABIS();
ASSERT_TRUE(IsDwarfCallChainSamplingSupported());
ASSERT_TRUE(RunRecordCmd({"--call-graph", "dwarf", "--no-unwind"}));
ASSERT_FALSE(RunRecordCmd({"--no-unwind"}));
}
TEST(record_cmd, post_unwind_option) {
+ OMIT_TEST_ON_NON_NATIVE_ABIS();
ASSERT_TRUE(IsDwarfCallChainSamplingSupported());
std::vector<std::unique_ptr<Workload>> workloads;
CreateProcesses(1, &workloads);
@@ -332,6 +361,7 @@
ASSERT_TRUE(RunRecordCmd({"--no-dump-symbols"}, tmpfile.path));
CheckDsoSymbolRecords(tmpfile.path, false, &success);
ASSERT_TRUE(success);
+ OMIT_TEST_ON_NON_NATIVE_ABIS();
ASSERT_TRUE(IsDwarfCallChainSamplingSupported());
std::vector<std::unique_ptr<Workload>> workloads;
CreateProcesses(1, &workloads);
@@ -454,6 +484,9 @@
std::unordered_map<std::string, std::string> info_map;
ASSERT_TRUE(reader->ReadMetaInfoFeature(&info_map));
ASSERT_NE(info_map.find("simpleperf_version"), info_map.end());
+#if defined(__ANDROID__)
+ ASSERT_NE(info_map.find("product_props"), info_map.end());
+#endif
}
// See http://b/63135835.
@@ -468,6 +501,7 @@
TEST(record_cmd, dump_regs_for_tracepoint_events) {
TEST_REQUIRE_HOST_ROOT();
+ OMIT_TEST_ON_NON_NATIVE_ABIS();
// Check if the kernel can dump registers for tracepoint events.
// If not, probably a kernel patch below is missing:
// "5b09a094f2 arm64: perf: Fix callchain parse error with kernel tracepoint events"
@@ -477,6 +511,7 @@
TEST(record_cmd, trace_offcpu_option) {
// On linux host, we need root privilege to read tracepoint events.
TEST_REQUIRE_HOST_ROOT();
+ OMIT_TEST_ON_NON_NATIVE_ABIS();
TemporaryFile tmpfile;
ASSERT_TRUE(RunRecordCmd({"--trace-offcpu", "-f", "1000"}, tmpfile.path));
std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
@@ -486,3 +521,21 @@
ASSERT_EQ(info_map["trace_offcpu"], "true");
CheckEventType(tmpfile.path, "sched:sched_switch", 1u, 0u);
}
+
+TEST(record_cmd, exit_with_parent_option) {
+ ASSERT_TRUE(RunRecordCmd({"--exit-with-parent"}));
+}
+
+TEST(record_cmd, clockid_option) {
+ if (!IsSettingClockIdSupported()) {
+ ASSERT_FALSE(RunRecordCmd({"--clockid", "monotonic"}));
+ } else {
+ TemporaryFile tmpfile;
+ ASSERT_TRUE(RunRecordCmd({"--clockid", "monotonic"}, tmpfile.path));
+ std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
+ ASSERT_TRUE(reader);
+ std::unordered_map<std::string, std::string> info_map;
+ ASSERT_TRUE(reader->ReadMetaInfoFeature(&info_map));
+ ASSERT_EQ(info_map["clockid"], "monotonic");
+ }
+}
diff --git a/simpleperf/cmd_report_test.cpp b/simpleperf/cmd_report_test.cpp
index f274114..f6ab6f8 100644
--- a/simpleperf/cmd_report_test.cpp
+++ b/simpleperf/cmd_report_test.cpp
@@ -478,7 +478,7 @@
bool found = false;
for (auto& line : lines) {
if (line.find("SleepFunction") != std::string::npos) {
- ASSERT_NE(line.find("46.29%"), std::string::npos);
+ ASSERT_NE(line.find("38.77%"), std::string::npos);
found = true;
break;
}
@@ -494,6 +494,7 @@
}
TEST_F(ReportCommandTest, dwarf_callgraph) {
+ OMIT_TEST_ON_NON_NATIVE_ABIS();
ASSERT_TRUE(IsDwarfCallChainSamplingSupported());
std::vector<std::unique_ptr<Workload>> workloads;
CreateProcesses(1, &workloads);
@@ -523,6 +524,7 @@
TEST_F(ReportCommandTest, exclude_kernel_callchain) {
TEST_REQUIRE_HOST_ROOT();
+ OMIT_TEST_ON_NON_NATIVE_ABIS();
std::vector<std::unique_ptr<Workload>> workloads;
CreateProcesses(1, &workloads);
std::string pid = std::to_string(workloads[0]->GetPid());
diff --git a/simpleperf/cmd_stat.cpp b/simpleperf/cmd_stat.cpp
index a9d5036..ddd83f7 100644
--- a/simpleperf/cmd_stat.cpp
+++ b/simpleperf/cmd_stat.cpp
@@ -386,8 +386,8 @@
event_selection_set_.AddMonitoredProcesses({workload->GetPid()});
event_selection_set_.SetEnableOnExec(true);
} else if (!app_package_name_.empty()) {
- int pid = WaitForAppProcess(app_package_name_);
- event_selection_set_.AddMonitoredProcesses({pid});
+ std::set<pid_t> pids = WaitForAppProcesses(app_package_name_);
+ event_selection_set_.AddMonitoredProcesses(pids);
} else {
LOG(ERROR)
<< "No threads to monitor. Try `simpleperf help stat` for help\n";
diff --git a/simpleperf/cmd_stat_test.cpp b/simpleperf/cmd_stat_test.cpp
index 3cdb4eb..e8c722d 100644
--- a/simpleperf/cmd_stat_test.cpp
+++ b/simpleperf/cmd_stat_test.cpp
@@ -24,6 +24,7 @@
#include "command.h"
#include "environment.h"
+#include "event_selection_set.h"
#include "get_test_data.h"
#include "test_util.h"
@@ -175,3 +176,17 @@
while (tid == 0);
ASSERT_TRUE(StatCmd()->Run({"-t", std::to_string(tid), "--in-app"}));
}
+
+TEST(stat_cmd, sample_speed_should_be_zero) {
+ EventSelectionSet set(true);
+ ASSERT_TRUE(set.AddEventType("cpu-cycles"));
+ set.AddMonitoredProcesses({getpid()});
+ ASSERT_TRUE(set.OpenEventFiles({-1}));
+ std::vector<EventAttrWithId> attrs = set.GetEventAttrWithId();
+ ASSERT_GT(attrs.size(), 0u);
+ for (auto& attr : attrs) {
+ ASSERT_EQ(attr.attr->sample_period, 0u);
+ ASSERT_EQ(attr.attr->sample_freq, 0u);
+ ASSERT_EQ(attr.attr->freq, 0u);
+ }
+}
diff --git a/simpleperf/command.cpp b/simpleperf/command.cpp
index 034163f..0d071ab 100644
--- a/simpleperf/command.cpp
+++ b/simpleperf/command.cpp
@@ -98,8 +98,15 @@
CommandRegister command_register;
+static void StderrLogger(android::base::LogId, android::base::LogSeverity severity,
+ const char*, const char* file, unsigned int line, const char* message) {
+ static const char log_characters[] = "VDIWEFF";
+ char severity_char = log_characters[severity];
+ fprintf(stderr, "simpleperf %c %s:%u] %s\n", severity_char, file, line, message);
+}
+
bool RunSimpleperfCmd(int argc, char** argv) {
- android::base::InitLogging(argv, android::base::StderrLogger);
+ android::base::InitLogging(argv, StderrLogger);
std::vector<std::string> args;
android::base::LogSeverity log_severity = android::base::INFO;
diff --git a/simpleperf/cpu_hotplug_test.cpp b/simpleperf/cpu_hotplug_test.cpp
index 16e0e5c..8227761 100644
--- a/simpleperf/cpu_hotplug_test.cpp
+++ b/simpleperf/cpu_hotplug_test.cpp
@@ -19,7 +19,7 @@
#include <sys/stat.h>
#include <unistd.h>
#if defined(__BIONIC__)
-#include <sys/system_properties.h>
+#include <android-base/properties.h>
#endif
#include <atomic>
@@ -59,25 +59,22 @@
private:
bool IsMpdecisionRunning() {
- char value[PROP_VALUE_MAX];
- int len = __system_property_get("init.svc.mpdecision", value);
- if (len == 0 || (len > 0 && strstr(value, "stopped") != nullptr)) {
+ std::string value = android::base::GetProperty("init.svc.mpdecision", "");
+ if (value.empty() || value.find("stopped") != std::string::npos) {
return false;
}
return true;
}
void DisableMpdecision() {
- int ret = __system_property_set("ctl.stop", "mpdecision");
- CHECK_EQ(0, ret);
+ CHECK(android::base::SetProperty("ctl.stop", "mpdecision"));
// Need to wait until mpdecision is actually stopped.
std::this_thread::sleep_for(std::chrono::milliseconds(500));
CHECK(!IsMpdecisionRunning());
}
void EnableMpdecision() {
- int ret = __system_property_set("ctl.start", "mpdecision");
- CHECK_EQ(0, ret);
+ CHECK(android::base::SetProperty("ctl.start", "mpdecision"));
std::this_thread::sleep_for(std::chrono::milliseconds(500));
CHECK(IsMpdecisionRunning());
}
@@ -199,13 +196,24 @@
struct CpuToggleThreadArg {
int toggle_cpu;
std::atomic<bool> end_flag;
+ std::atomic<bool> cpu_hotplug_failed;
+
+ CpuToggleThreadArg(int cpu)
+ : toggle_cpu(cpu), end_flag(false), cpu_hotplug_failed(false) {
+ }
};
static void CpuToggleThread(CpuToggleThreadArg* arg) {
while (!arg->end_flag) {
- CHECK(SetCpuOnline(arg->toggle_cpu, true));
+ if (!SetCpuOnline(arg->toggle_cpu, true)) {
+ arg->cpu_hotplug_failed = true;
+ break;
+ }
std::this_thread::sleep_for(cpu_hotplug_interval);
- CHECK(SetCpuOnline(arg->toggle_cpu, false));
+ if (!SetCpuOnline(arg->toggle_cpu, false)) {
+ arg->cpu_hotplug_failed = true;
+ break;
+ }
std::this_thread::sleep_for(cpu_hotplug_interval);
}
}
@@ -223,9 +231,7 @@
if (!FindAHotpluggableCpu(&test_cpu)) {
return;
}
- CpuToggleThreadArg cpu_toggle_arg;
- cpu_toggle_arg.toggle_cpu = test_cpu;
- cpu_toggle_arg.end_flag = false;
+ CpuToggleThreadArg cpu_toggle_arg(test_cpu);
std::thread cpu_toggle_thread(CpuToggleThread, &cpu_toggle_arg);
std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles");
@@ -240,7 +246,7 @@
auto report_step = std::chrono::seconds(15);
size_t iterations = 0;
- while (cur_time < end_time) {
+ while (cur_time < end_time && !cpu_toggle_arg.cpu_hotplug_failed) {
if (cur_time + report_step < std::chrono::steady_clock::now()) {
// Report test time.
auto diff = std::chrono::duration_cast<std::chrono::seconds>(
@@ -261,6 +267,9 @@
GTEST_LOG_(INFO) << "Test offline while recording for " << iterations << " times.";
}
}
+ if (cpu_toggle_arg.cpu_hotplug_failed) {
+ GTEST_LOG_(INFO) << "Test ends because of cpu hotplug failure.";
+ }
cpu_toggle_arg.end_flag = true;
cpu_toggle_thread.join();
}
@@ -278,9 +287,7 @@
if (!FindAHotpluggableCpu(&test_cpu)) {
return;
}
- CpuToggleThreadArg cpu_toggle_arg;
- cpu_toggle_arg.toggle_cpu = test_cpu;
- cpu_toggle_arg.end_flag = false;
+ CpuToggleThreadArg cpu_toggle_arg(test_cpu);
std::thread cpu_toggle_thread(CpuToggleThread, &cpu_toggle_arg);
std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles");
@@ -295,7 +302,7 @@
auto report_step = std::chrono::seconds(15);
size_t iterations = 0;
- while (cur_time < end_time) {
+ while (cur_time < end_time && !cpu_toggle_arg.cpu_hotplug_failed) {
if (cur_time + report_step < std::chrono::steady_clock::now()) {
// Report test time.
auto diff = std::chrono::duration_cast<std::chrono::seconds>(
@@ -319,6 +326,9 @@
GTEST_LOG_(INFO) << "Test offline while ioctl(PERF_EVENT_IOC_ENABLE) for " << iterations << " times.";
}
}
+ if (cpu_toggle_arg.cpu_hotplug_failed) {
+ GTEST_LOG_(INFO) << "Test ends because of cpu hotplug failure.";
+ }
cpu_toggle_arg.end_flag = true;
cpu_toggle_thread.join();
}
@@ -350,9 +360,7 @@
if (!FindAHotpluggableCpu(&test_cpu)) {
return;
}
- CpuToggleThreadArg cpu_toggle_arg;
- cpu_toggle_arg.toggle_cpu = test_cpu;
- cpu_toggle_arg.end_flag = false;
+ CpuToggleThreadArg cpu_toggle_arg(test_cpu);
std::thread cpu_toggle_thread(CpuToggleThread, &cpu_toggle_arg);
// Start cpu spinner.
@@ -378,7 +386,7 @@
auto report_step = std::chrono::seconds(15);
size_t iterations = 0;
- while (cur_time < end_time) {
+ while (cur_time < end_time && !cpu_toggle_arg.cpu_hotplug_failed) {
if (cur_time + report_step < std::chrono::steady_clock::now()) {
auto diff = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::steady_clock::now() - start_time);
@@ -403,13 +411,17 @@
GTEST_LOG_(INFO) << "Test offline while user process profiling for " << iterations << " times.";
}
}
+ if (cpu_toggle_arg.cpu_hotplug_failed) {
+ GTEST_LOG_(INFO) << "Test ends because of cpu hotplug failure.";
+ }
cpu_toggle_arg.end_flag = true;
cpu_toggle_thread.join();
cpu_spin_arg.end_flag = true;
cpu_spin_thread.join();
// Check if the cpu-cycle event is still available on test_cpu.
- ASSERT_TRUE(SetCpuOnline(test_cpu, true));
- ASSERT_TRUE(EventFd::OpenEventFile(attr, -1, test_cpu, nullptr, true) != nullptr);
+ if (SetCpuOnline(test_cpu, true)) {
+ ASSERT_TRUE(EventFd::OpenEventFile(attr, -1, test_cpu, nullptr, true) != nullptr);
+ }
}
// http://b/19863147.
@@ -433,10 +445,14 @@
const size_t TEST_ITERATION_COUNT = 10u;
for (size_t i = 0; i < TEST_ITERATION_COUNT; ++i) {
int record_cpu = 0;
- ASSERT_TRUE(SetCpuOnline(test_cpu, true));
+ if (!SetCpuOnline(test_cpu, true)) {
+ break;
+ }
std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(attr, getpid(), record_cpu, nullptr);
ASSERT_TRUE(event_fd != nullptr);
- ASSERT_TRUE(SetCpuOnline(test_cpu, false));
+ if (!SetCpuOnline(test_cpu, false)) {
+ break;
+ }
event_fd = nullptr;
event_fd = EventFd::OpenEventFile(attr, getpid(), record_cpu, nullptr);
ASSERT_TRUE(event_fd != nullptr);
diff --git a/simpleperf/demo/README.md b/simpleperf/demo/README.md
index 2bba043..2c5f2b3 100644
--- a/simpleperf/demo/README.md
+++ b/simpleperf/demo/README.md
@@ -10,8 +10,8 @@
## Introduction
Simpleperf is a native profiler used on Android platform. It can be used to profile Android
-applications. It's document is at [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/README.md).
-Instructions of preparing your Android application for profiling are [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/README.md#Android-application-profiling).
+applications. It's document is at [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/README.md).
+Instructions of preparing your Android application for profiling are [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/README.md#Android-application-profiling).
This directory is to show examples of using simpleperf to profile Android applications. The
meaning of each directory is as below:
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/app-profiling.apk b/simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/app-profiling.apk
index 0254bc0..f297046 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/app-profiling.apk
+++ b/simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/app-profiling.apk
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml
index c611102..f42ec17 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml
+++ b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml
@@ -17,9 +17,12 @@
</intent-filter>
</activity>
<activity android:name=".SleepActivity"
- android:exported="true">
+ android:exported="true" />
+ <activity android:name=".MultiProcessActivity"
+ android:exported="true" />
- </activity>
+ <service android:name=".MultiProcessService"
+ android:process=":multiprocess_service" />
</application>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessActivity.java b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessActivity.java
new file mode 100644
index 0000000..de698ec
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessActivity.java
@@ -0,0 +1,78 @@
+package com.example.simpleperf.simpleperfexamplepurejava;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.util.Log;
+
+public class MultiProcessActivity extends AppCompatActivity {
+ public static final String TAG = "MultiProcessActivity";
+
+ Messenger mService = null;
+ boolean mBound;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_multi_process);
+
+ bindService(new Intent(this, MultiProcessService.class), mConnection,
+ Context.BIND_AUTO_CREATE);
+ createBusyThread();
+ }
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+ mService = new Messenger(iBinder);
+ mBound = true;
+ Message message = new Message();
+ message.what = MultiProcessService.MSG_START_BUSY_THREAD;
+ try {
+ mService.send(message);
+ } catch (RemoteException e) {
+ Log.d(TAG, e.toString());
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) {
+ mService = null;
+ mBound = false;
+ }
+ };
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ if (mBound) {
+ unbindService(mConnection);
+ mBound = false;
+ }
+ }
+
+ void createBusyThread() {
+ new Thread(new Runnable() {
+ volatile int i = 0;
+
+ @Override
+ public void run() {
+ while (true) {
+ i = callFunction(i);
+ }
+ }
+
+ private int callFunction(int a) {
+ return a+1;
+ }
+ }, "BusyThread").start();
+ }
+}
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessService.java b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessService.java
new file mode 100644
index 0000000..2fd4d57
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessService.java
@@ -0,0 +1,50 @@
+package com.example.simpleperf.simpleperfexamplepurejava;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+
+public class MultiProcessService extends Service {
+ public static final int MSG_START_BUSY_THREAD = 1;
+
+ public MultiProcessService() {
+ }
+
+ class IncomingHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START_BUSY_THREAD:
+ createBusyThread();
+ }
+ super.handleMessage(msg);
+ }
+ }
+
+ final Messenger mMessenger = new Messenger(new IncomingHandler());
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mMessenger.getBinder();
+ }
+
+ void createBusyThread() {
+ new Thread(new Runnable() {
+ volatile int i = 0;
+
+ @Override
+ public void run() {
+ while (true) {
+ i = callFunction(i);
+ }
+ }
+
+ private int callFunction(int a) {
+ return a+1;
+ }
+ }, "BusyService").start();
+ }
+}
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_main.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_main.xml
index 1aa4458..4a09b1a 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_main.xml
+++ b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_main.xml
@@ -9,7 +9,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="Hello World!"
+ android:text="MainActivity"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_multi_process.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_multi_process.xml
new file mode 100644
index 0000000..f97b72e
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_multi_process.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="com.example.simpleperf.simpleperfexamplepurejava.MultiProcessActivity">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="MultiProcessActivity"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+</android.support.constraint.ConstraintLayout>
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_sleep.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_sleep.xml
index e2274ca..f732f77 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_sleep.xml
+++ b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_sleep.xml
@@ -6,4 +6,13 @@
android:layout_height="match_parent"
tools:context="com.example.simpleperf.simpleperfexamplepurejava.SleepActivity">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="SleepActivity"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
</android.support.constraint.ConstraintLayout>
diff --git a/simpleperf/doc/README.md b/simpleperf/doc/README.md
index 078d56b..db58485 100644
--- a/simpleperf/doc/README.md
+++ b/simpleperf/doc/README.md
@@ -6,7 +6,7 @@
and above.
Simpleperf is part of the Android Open Source Project. The source code is [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/).
-The latest document is [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/README.md).
+The latest document is [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/README.md).
Bugs and feature requests can be submitted at http://github.com/android-ndk/ndk/issues.
@@ -28,6 +28,8 @@
- [Record and report call graph](#record-and-report-call-graph)
- [Visualize profiling data](#visualize-profiling-data)
- [Annotate source code](#annotate-source-code)
+ - [Trace offcpu time](#trace-offcpu-time)
+ - [Profile from launch of an application](#profile-from-launch-of-an-application)
- [Answers to common issues](#answers-to-common-issues)
- [Why we suggest profiling on android >= N devices](#why-we-suggest-profiling-on-android-n-devices)
@@ -93,7 +95,7 @@
`annotate.py` is used to annotate source files based on profiling data.
-`app_profiler.py` is used to profile Android applications.
+`app_profiler.py` is used to profile Android applications and native programs.
`binary_cache_builder.py` is used to pull libraries from Android devices.
@@ -103,6 +105,8 @@
`report_sample.py` is used to generate flamegraph.
+`run_simpleperf_on_device.py` is a simple wrapper to run simpleperf on device.
+
`simpleperf_report_lib.py` provides a python interface for parsing profiling data.
@@ -760,7 +764,7 @@
On Windows platform:
- $ ./inferno.bat -sc --symfs binary_cache
+ $ inferno.bat -sc --symfs binary_cache
Remove `--symfs binary_cache` if you selected not to collect binaries when
using `app_profiler.py`.
@@ -819,6 +823,73 @@
/* acc_p: 99.966552%, p: 83.628188% */ i = callFunction(i);
+### Trace offcpu time
+
+Simpleperf is a cpu profiler, it generates samples for a thread only when it is
+running on a cpu. However, sometimes we want to find out where time of a thread
+is spent, whether it is running on cpu, preempted by other threads, doing I/O
+work, or waiting for some events. To support this, we added the --trace-offcpu
+option in the simpleperf record command. When --trace-offcpu is used, simpleperf
+generates a sample when a running thread is scheduled out, so we know the
+callstack of a thread when it is scheduled out. And when reporting a perf.data
+generated with --trace-offcpu, we use timestamp to the next sample
+(instead of event counts from the previous sample) as the weight of current
+sample. As a result, we can get a callgraph based on timestamp, including both
+on cpu time and off cpu time.
+
+trace-offcpu is implemented using sched:sched_switch tracepoint event, which
+may not work well on old kernels. But it is guaranteed to be supported on
+devices after Android O MR1. We can check whether trace-offcpu is supported as
+below.
+
+ $ python run_simpleperf_on_device.py list --show-features
+ dwarf-based-call-graph
+ trace-offcpu
+
+If trace-offcpu is supported, it will be shown in the feature list.
+Then we can try it. Below is an example without using --trace-offcpu option.
+
+ $ python app_profiler.py -p com.example.simpleperf.simpleperfexamplepurejava \
+ -a .SleepActivity -r "-g -e cpu-cycles:u --duration 10"
+ $ ./inferno.sh -sc
+
+![flamegraph sample](./without_trace_offcpu.png)
+
+In the graph, all time is taken by RunFunction(), and sleep time is ignored.
+But if we add --trace-offcpu option, the graph is changed as below.
+
+ $ python app_profiler.py -p com.example.simpleperf.simpleperfexamplepurejava \
+ -a .SleepActivity -r "-g -e cpu-cycles:u --trace-offcpu --duration 10"
+ $ ./inferno.sh -sc
+
+![flamegraph sample](./trace_offcpu.png)
+
+As shown in the graph, half time is spent in RunFunction(), and half time is
+spent in SleepFunction(). It includes both on cpu time and off cpu time.
+
+### Profile from launch of an application
+
+Sometimes we want to profile the launch-time of an application. To support this,
+we added the --app option in the simpleperf record command. The --app option
+sets the package name of the Android application to profile. If the app is not
+already running, the simpleperf record command will poll for the app process in
+a loop with an interval of 1ms. So to profile from launch of an application,
+we can first start simpleperf record with --app, then start the app.
+Below is an example.
+
+ $ adb shell /data/local/tmp/simpleperf record -g \
+ --app com.example.simpleperf.simpleperfexamplepurejava --duration 1 \
+ -o /data/local/tmp/perf.data
+ # Start the app manually or using the `am` command.
+
+To make it convenient to use, app_profiler.py combines these in the
+--profile_from_launch option. Below is an example.
+
+ $ python app_profiler.py -p com.example.simpleperf.simpleperfexamplepurejava \
+ -a .MainActivity --arch arm64 -r "-g -e cpu-cycles:u --duration 1" \
+ --profile_from_launch
+
+
## Answers to common issues
### Why we suggest profiling on Android >= N devices?
diff --git a/simpleperf/doc/inferno.md b/simpleperf/doc/inferno.md
index 2f33e8d..bfe280a 100644
--- a/simpleperf/doc/inferno.md
+++ b/simpleperf/doc/inferno.md
@@ -52,7 +52,7 @@
Open a terminal and from `simpleperf/scripts` directory type:
```
./inferno.sh (on Linux/Mac)
-./inferno.bat (on Windows)
+inferno.bat (on Windows)
```
Inferno will collect data, process them and automatically open your web browser
diff --git a/simpleperf/doc/trace_offcpu.png b/simpleperf/doc/trace_offcpu.png
new file mode 100644
index 0000000..6af7f4b
--- /dev/null
+++ b/simpleperf/doc/trace_offcpu.png
Binary files differ
diff --git a/simpleperf/doc/without_trace_offcpu.png b/simpleperf/doc/without_trace_offcpu.png
new file mode 100644
index 0000000..e7a8380
--- /dev/null
+++ b/simpleperf/doc/without_trace_offcpu.png
Binary files differ
diff --git a/simpleperf/dso.cpp b/simpleperf/dso.cpp
index 4f202bc..9080640 100644
--- a/simpleperf/dso.cpp
+++ b/simpleperf/dso.cpp
@@ -153,7 +153,8 @@
min_vaddr_(std::numeric_limits<uint64_t>::max()),
is_loaded_(false),
dump_id_(UINT_MAX),
- symbol_dump_id_(0) {
+ symbol_dump_id_(0),
+ symbol_warning_loglevel_(android::base::WARNING) {
if (type_ == DSO_KERNEL) {
min_vaddr_ = 0;
}
@@ -287,6 +288,8 @@
// dumped_symbols, so later we can merge them with symbols read from file system.
dumped_symbols = std::move(symbols_);
symbols_.clear();
+ // Don't warn missing symbol table if we have dumped symbols in perf.data.
+ symbol_warning_loglevel_ = android::base::DEBUG;
}
bool result = false;
switch (type_) {
@@ -354,11 +357,10 @@
return true;
}
// Lacking symbol table isn't considered as an error but worth reporting.
- LOG(WARNING) << filename << " doesn't contain symbol table";
+ LOG(symbol_warning_loglevel_) << filename << " doesn't contain symbol table";
return true;
} else {
- LOG(WARNING) << "failed to read symbols from " << filename
- << ": " << result;
+ LOG(symbol_warning_loglevel_) << "failed to read symbols from " << filename << ": " << result;
return false;
}
}
@@ -380,7 +382,7 @@
}
}
if (all_zero) {
- LOG(WARNING)
+ LOG(symbol_warning_loglevel_)
<< "Symbol addresses in /proc/kallsyms on device are all zero. "
"`echo 0 >/proc/sys/kernel/kptr_restrict` if possible.";
symbols_.clear();
@@ -396,8 +398,8 @@
}
bool match = (build_id == real_build_id);
if (!match) {
- LOG(WARNING) << "failed to read symbols from /proc/kallsyms: Build id "
- << "mismatch";
+ LOG(symbol_warning_loglevel_) << "failed to read symbols from /proc/kallsyms: Build id "
+ << "mismatch";
return false;
}
}
@@ -417,8 +419,8 @@
}
}
if (all_zero) {
- LOG(WARNING) << "Symbol addresses in /proc/kallsyms are all zero. "
- "`echo 0 >/proc/sys/kernel/kptr_restrict` if possible.";
+ LOG(symbol_warning_loglevel_) << "Symbol addresses in /proc/kallsyms are all zero. "
+ "`echo 0 >/proc/sys/kernel/kptr_restrict` if possible.";
symbols_.clear();
return false;
}
diff --git a/simpleperf/dso.h b/simpleperf/dso.h
index f41b140..cb0e51d 100644
--- a/simpleperf/dso.h
+++ b/simpleperf/dso.h
@@ -22,6 +22,7 @@
#include <unordered_map>
#include <vector>
+#include <android-base/logging.h>
#include <android-base/test_utils.h>
#include "build_id.h"
@@ -182,6 +183,7 @@
uint32_t dump_id_;
// Used to assign dump_id for symbols in current dso.
uint32_t symbol_dump_id_;
+ android::base::LogSeverity symbol_warning_loglevel_;
};
const char* DsoTypeToString(DsoType dso_type);
diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp
index 79280d1..e238ffb 100644
--- a/simpleperf/environment.cpp
+++ b/simpleperf/environment.cpp
@@ -35,7 +35,7 @@
#include <procinfo/process.h>
#if defined(__ANDROID__)
-#include <sys/system_properties.h>
+#include <android-base/properties.h>
#endif
#include "event_type.h"
@@ -383,22 +383,22 @@
return true;
}
#if defined(__ANDROID__)
- const char* prop_name = "security.perf_harden";
- char prop_value[PROP_VALUE_MAX];
- if (__system_property_get(prop_name, prop_value) <= 0) {
+ const std::string prop_name = "security.perf_harden";
+ std::string prop_value = android::base::GetProperty(prop_name, "");
+ if (prop_value.empty()) {
// can't do anything if there is no such property.
return true;
}
- if (strcmp(prop_value, "0") == 0) {
+ if (prop_value == "0") {
return true;
}
// Try to enable perf_event_paranoid by setprop security.perf_harden=0.
- if (__system_property_set(prop_name, "0") == 0) {
+ if (android::base::SetProperty(prop_name, "0")) {
sleep(1);
if (can_read_paranoid && ReadPerfEventParanoid(&limit_level) && limit_level <= 1) {
return true;
}
- if (__system_property_get(prop_name, prop_value) > 0 && strcmp(prop_value, "0") == 0) {
+ if (android::base::GetProperty(prop_name, "") == "0") {
return true;
}
}
@@ -522,7 +522,23 @@
Dso::SetVdsoFile(std::move(tmpfile), sizeof(size_t) == sizeof(uint64_t));
}
-int WaitForAppProcess(const std::string& package_name) {
+static bool HasOpenedAppApkFile(int pid) {
+ std::string fd_path = "/proc/" + std::to_string(pid) + "/fd/";
+ std::vector<std::string> files = GetEntriesInDir(fd_path);
+ for (const auto& file : files) {
+ std::string real_path;
+ if (!android::base::Readlink(fd_path + file, &real_path)) {
+ continue;
+ }
+ if (real_path.find("app") != std::string::npos && real_path.find(".apk") != std::string::npos) {
+ return true;
+ }
+ }
+ return false;
+}
+
+std::set<pid_t> WaitForAppProcesses(const std::string& package_name) {
+ std::set<pid_t> result;
size_t loop_count = 0;
while (true) {
std::vector<pid_t> pids = GetAllProcesses();
@@ -532,13 +548,36 @@
// Maybe we don't have permission to read it.
continue;
}
- cmdline = android::base::Basename(cmdline);
- if (cmdline == package_name) {
- if (loop_count > 0u) {
- LOG(INFO) << "Got process " << pid << " for package " << package_name;
- }
- return pid;
+ std::string process_name = android::base::Basename(cmdline);
+ // The app may have multiple processes, with process name like
+ // com.google.android.googlequicksearchbox:search.
+ size_t split_pos = process_name.find(':');
+ if (split_pos != std::string::npos) {
+ process_name = process_name.substr(0, split_pos);
}
+ if (process_name != package_name) {
+ continue;
+ }
+ // If a debuggable app with wrap.sh runs on Android O, the app will be started with
+ // logwrapper as below:
+ // 1. Zygote forks a child process, rename it to package_name.
+ // 2. The child process execute sh, which starts a child process running
+ // /system/bin/logwrapper.
+ // 3. logwrapper starts a child process running sh, which interprets wrap.sh.
+ // 4. wrap.sh starts a child process running the app.
+ // The problem here is we want to profile the process started in step 4, but sometimes we
+ // run into the process started in step 1. To solve it, we can check if the process has
+ // opened an apk file in some app dirs.
+ if (!HasOpenedAppApkFile(pid)) {
+ continue;
+ }
+ if (loop_count > 0u) {
+ LOG(INFO) << "Got process " << pid << " for package " << package_name;
+ }
+ result.insert(pid);
+ }
+ if (!result.empty()) {
+ return result;
}
if (++loop_count == 1u) {
LOG(INFO) << "Waiting for process of app " << package_name;
diff --git a/simpleperf/environment.h b/simpleperf/environment.h
index 52d0a7c..0f12146 100644
--- a/simpleperf/environment.h
+++ b/simpleperf/environment.h
@@ -92,7 +92,7 @@
ArchType GetMachineArch();
void PrepareVdsoFile();
-int WaitForAppProcess(const std::string& package_name);
+std::set<pid_t> WaitForAppProcesses(const std::string& package_name);
bool RunInAppContext(const std::string& app_package_name, const std::string& cmd,
const std::vector<std::string>& args, size_t workload_args_size,
const std::string& output_filepath, bool need_tracepoint_events);
diff --git a/simpleperf/event_selection_set.cpp b/simpleperf/event_selection_set.cpp
index e0af586..ebd3477 100644
--- a/simpleperf/event_selection_set.cpp
+++ b/simpleperf/event_selection_set.cpp
@@ -103,6 +103,19 @@
return false;
}
+bool IsSettingClockIdSupported() {
+ const EventType* type = FindEventTypeByName("cpu-cycles");
+ if (type == nullptr) {
+ return false;
+ }
+ // Check if the kernel supports setting clockid, which was added in kernel 4.0. Just check with
+ // one clockid is enough. Because all needed clockids were supported before kernel 4.0.
+ perf_event_attr attr = CreateDefaultPerfEventAttr(*type);
+ attr.use_clockid = 1;
+ attr.clockid = CLOCK_MONOTONIC;
+ return IsEventAttrSupported(attr);
+}
+
bool EventSelectionSet::BuildAndCheckEventSelection(
const std::string& event_name, EventSelection* selection) {
std::unique_ptr<EventTypeAndModifier> event_type = ParseEventType(event_name);
@@ -128,13 +141,15 @@
selection->event_attr.exclude_host = event_type->exclude_host;
selection->event_attr.exclude_guest = event_type->exclude_guest;
selection->event_attr.precise_ip = event_type->precise_ip;
- if (event_type->event_type.type == PERF_TYPE_TRACEPOINT) {
- selection->event_attr.freq = 0;
- selection->event_attr.sample_period = DEFAULT_SAMPLE_PERIOD_FOR_TRACEPOINT_EVENT;
- } else {
- selection->event_attr.freq = 1;
- selection->event_attr.sample_freq =
- AdjustSampleFrequency(DEFAULT_SAMPLE_FREQ_FOR_NONTRACEPOINT_EVENT);
+ if (!for_stat_cmd_) {
+ if (event_type->event_type.type == PERF_TYPE_TRACEPOINT) {
+ selection->event_attr.freq = 0;
+ selection->event_attr.sample_period = DEFAULT_SAMPLE_PERIOD_FOR_TRACEPOINT_EVENT;
+ } else {
+ selection->event_attr.freq = 1;
+ selection->event_attr.sample_freq =
+ AdjustSampleFrequency(DEFAULT_SAMPLE_FREQ_FOR_NONTRACEPOINT_EVENT);
+ }
}
if (!IsEventAttrSupported(selection->event_attr)) {
LOG(ERROR) << "Event type '" << event_type->name
@@ -370,6 +385,15 @@
}
}
+void EventSelectionSet::SetClockId(int clock_id) {
+ for (auto& group : groups_) {
+ for (auto& selection : group) {
+ selection.event_attr.use_clockid = 1;
+ selection.event_attr.clockid = clock_id;
+ }
+ }
+}
+
bool EventSelectionSet::NeedKernelSymbol() const {
for (const auto& group : groups_) {
for (const auto& selection : group) {
@@ -402,7 +426,7 @@
EventFd* group_fd = nullptr;
for (auto& selection : group) {
std::unique_ptr<EventFd> event_fd =
- EventFd::OpenEventFile(selection.event_attr, tid, cpu, group_fd);
+ EventFd::OpenEventFile(selection.event_attr, tid, cpu, group_fd, false);
if (event_fd != nullptr) {
LOG(VERBOSE) << "OpenEventFile for " << event_fd->Name();
event_fds.push_back(std::move(event_fd));
@@ -462,24 +486,25 @@
}
} else {
for (const auto& pair : process_map) {
+ size_t success_count = 0;
+ std::string failed_event_type;
for (const auto& tid : pair.second) {
- size_t success_cpu_count = 0;
- std::string failed_event_type;
for (const auto& cpu : cpus) {
if (OpenEventFilesOnGroup(group, tid, cpu, &failed_event_type)) {
- success_cpu_count++;
+ success_count++;
}
}
- // As the online cpus can be enabled or disabled at runtime, we may not
- // open event file for all cpus successfully. But we should open at
- // least one cpu successfully.
- if (success_cpu_count == 0) {
- PLOG(ERROR) << "failed to open perf event file for event_type "
- << failed_event_type << " for "
- << (tid == -1 ? "all threads" : "thread " + std::to_string(tid))
- << " on all cpus";
- return false;
- }
+ }
+ // We can't guarantee to open perf event file successfully for each thread on each cpu.
+ // Because threads may exit between PrepareThreads() and OpenEventFilesOnGroup(), and
+ // cpus may be offlined between GetOnlineCpus() and OpenEventFilesOnGroup().
+ // So we only check that we can at least monitor one thread for each process.
+ if (success_count == 0) {
+ PLOG(ERROR) << "failed to open perf event file for event_type "
+ << failed_event_type << " for "
+ << (pair.first == -1 ? "all threads"
+ : "threads in process " + std::to_string(pair.first));
+ return false;
}
}
}
diff --git a/simpleperf/event_selection_set.h b/simpleperf/event_selection_set.h
index cc972be..7397c6f 100644
--- a/simpleperf/event_selection_set.h
+++ b/simpleperf/event_selection_set.h
@@ -101,6 +101,7 @@
void EnableFpCallChainSampling();
bool EnableDwarfCallChainSampling(uint32_t dump_stack_size);
void SetInherit(bool enable);
+ void SetClockId(int clock_id);
bool NeedKernelSymbol() const;
void AddMonitoredProcesses(const std::set<pid_t>& processes) {
@@ -201,5 +202,6 @@
bool IsBranchSamplingSupported();
bool IsDwarfCallChainSamplingSupported();
bool IsDumpingRegsForTracepointEventsSupported();
+bool IsSettingClockIdSupported();
#endif // SIMPLE_PERF_EVENT_SELECTION_SET_H_
diff --git a/simpleperf/gtest_main.cpp b/simpleperf/gtest_main.cpp
index 42519d5..599bb42 100644
--- a/simpleperf/gtest_main.cpp
+++ b/simpleperf/gtest_main.cpp
@@ -26,7 +26,7 @@
#include <ziparchive/zip_archive.h>
#if defined(__ANDROID__)
-#include <sys/system_properties.h>
+#include <android-base/properties.h>
#endif
#include "command.h"
@@ -106,26 +106,26 @@
class ScopedEnablingPerf {
public:
ScopedEnablingPerf() {
- memset(prop_value_, '\0', sizeof(prop_value_));
- __system_property_get("security.perf_harden", prop_value_);
+ prop_value_ = android::base::GetProperty("security.perf_harden", "");
SetProp("0");
}
~ScopedEnablingPerf() {
- if (strlen(prop_value_) != 0) {
+ if (!prop_value_.empty()) {
SetProp(prop_value_);
}
}
private:
- void SetProp(const char* value) {
- __system_property_set("security.perf_harden", value);
+ void SetProp(const std::string& value) {
+ android::base::SetProperty("security.perf_harden", value);
+
// Sleep one second to wait for security.perf_harden changing
// /proc/sys/kernel/perf_event_paranoid.
sleep(1);
}
- char prop_value_[PROP_VALUE_MAX];
+ std::string prop_value_;
};
class ScopedWorkloadExecutable {
diff --git a/simpleperf/record_file_format.h b/simpleperf/record_file_format.h
index f9ed6f3..1ddaf00 100644
--- a/simpleperf/record_file_format.h
+++ b/simpleperf/record_file_format.h
@@ -17,6 +17,8 @@
#ifndef SIMPLE_PERF_RECORD_FILE_FORMAT_H_
#define SIMPLE_PERF_RECORD_FILE_FORMAT_H_
+#include <string>
+
#include "perf_event.h"
/*
@@ -91,6 +93,9 @@
FEAT_MAX_NUM = 256,
};
+std::string GetFeatureName(int feature_id);
+int GetFeatureId(const std::string& feature_name);
+
struct SectionDesc {
uint64_t offset;
uint64_t size;
diff --git a/simpleperf/record_file_reader.cpp b/simpleperf/record_file_reader.cpp
index 67f35be..38a4b2d 100644
--- a/simpleperf/record_file_reader.cpp
+++ b/simpleperf/record_file_reader.cpp
@@ -29,6 +29,46 @@
using namespace PerfFileFormat;
+namespace PerfFileFormat {
+
+static const std::map<int, std::string> feature_name_map = {
+ {FEAT_TRACING_DATA, "tracing_data"},
+ {FEAT_BUILD_ID, "build_id"},
+ {FEAT_HOSTNAME, "hostname"},
+ {FEAT_OSRELEASE, "osrelease"},
+ {FEAT_VERSION, "version"},
+ {FEAT_ARCH, "arch"},
+ {FEAT_NRCPUS, "nrcpus"},
+ {FEAT_CPUDESC, "cpudesc"},
+ {FEAT_CPUID, "cpuid"},
+ {FEAT_TOTAL_MEM, "total_mem"},
+ {FEAT_CMDLINE, "cmdline"},
+ {FEAT_EVENT_DESC, "event_desc"},
+ {FEAT_CPU_TOPOLOGY, "cpu_topology"},
+ {FEAT_NUMA_TOPOLOGY, "numa_topology"},
+ {FEAT_BRANCH_STACK, "branch_stack"},
+ {FEAT_PMU_MAPPINGS, "pmu_mappings"},
+ {FEAT_GROUP_DESC, "group_desc"},
+ {FEAT_FILE, "file"},
+ {FEAT_META_INFO, "meta_info"},
+};
+
+std::string GetFeatureName(int feature_id) {
+ auto it = feature_name_map.find(feature_id);
+ return it == feature_name_map.end() ? "" : it->second;
+}
+
+int GetFeatureId(const std::string& feature_name) {
+ for (auto& pair : feature_name_map) {
+ if (pair.second == feature_name) {
+ return pair.first;
+ }
+ }
+ return -1;
+}
+
+} // namespace PerfFileFormat
+
std::unique_ptr<RecordFileReader> RecordFileReader::CreateInstance(const std::string& filename) {
std::string mode = std::string("rb") + CLOSE_ON_EXEC_MODE;
FILE* fp = fopen(filename.c_str(), mode.c_str());
@@ -203,6 +243,20 @@
}
if (record->type() == SIMPLE_PERF_RECORD_EVENT_ID) {
ProcessEventIdRecord(*static_cast<EventIdRecord*>(record.get()));
+ } else if (record->type() == PERF_RECORD_SAMPLE) {
+ SampleRecord* r = static_cast<SampleRecord*>(record.get());
+ // Although we have removed ip == 0 callchains when recording dwarf based callgraph,
+ // stack frame based callgraph can also generate ip == 0 callchains. Remove them here
+ // to avoid caller's effort.
+ if (r->sample_type & PERF_SAMPLE_CALLCHAIN) {
+ size_t i;
+ for (i = 0; i < r->callchain_data.ip_nr; ++i) {
+ if (r->callchain_data.ips[i] == 0) {
+ break;
+ }
+ }
+ r->callchain_data.ip_nr = i;
+ }
}
if (sorted) {
record_cache_->Push(std::move(record));
diff --git a/simpleperf/report_lib_interface.cpp b/simpleperf/report_lib_interface.cpp
index 79a2103..88bb4d7 100644
--- a/simpleperf/report_lib_interface.cpp
+++ b/simpleperf/report_lib_interface.cpp
@@ -72,9 +72,9 @@
CallChainEntry* entries;
};
-struct MetaInfoEntry {
- const char* key;
- const char* value;
+struct FeatureSection {
+ const char* data;
+ uint32_t data_size;
};
// Create a new instance,
@@ -96,7 +96,7 @@
CallChain* GetCallChainOfCurrentSample(ReportLib* report_lib) EXPORT;
const char* GetBuildIdForPath(ReportLib* report_lib, const char* path) EXPORT;
-MetaInfoEntry* GetNextMetaInfo(ReportLib* report_lib) EXPORT;
+FeatureSection* GetFeatureSection(ReportLib* report_lib, const char* feature_name) EXPORT;
}
struct EventAttrWithName {
@@ -120,7 +120,6 @@
current_thread_(nullptr),
update_flag_(0),
trace_offcpu_(false) {
- current_meta_info_.key = current_meta_info_.value = nullptr;
}
bool SetLogSeverity(const char* log_level);
@@ -142,7 +141,7 @@
CallChain* GetCallChainOfCurrentSample();
const char* GetBuildIdForPath(const char* path);
- MetaInfoEntry* GetNextMetaInfo();
+ FeatureSection* GetFeatureSection(const char* feature_name);
private:
Sample* GetCurrentSample();
@@ -164,12 +163,11 @@
std::string build_id_string_;
int update_flag_;
std::vector<EventAttrWithName> event_attrs_;
-
- std::unordered_map<std::string, std::string> meta_info_map_;
- MetaInfoEntry current_meta_info_;
std::unique_ptr<ScopedEventTypes> scoped_event_types_;
bool trace_offcpu_;
std::unordered_map<pid_t, std::unique_ptr<SampleRecord>> next_sample_cache_;
+ FeatureSection feature_section_;
+ std::vector<char> feature_section_data_;
};
bool ReportLib::SetLogSeverity(const char* log_level) {
@@ -200,16 +198,17 @@
return false;
}
record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
+ std::unordered_map<std::string, std::string> meta_info_map;
if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_META_INFO) &&
- !record_file_reader_->ReadMetaInfoFeature(&meta_info_map_)) {
+ !record_file_reader_->ReadMetaInfoFeature(&meta_info_map)) {
return false;
}
- auto it = meta_info_map_.find("event_type_info");
- if (it != meta_info_map_.end()) {
+ auto it = meta_info_map.find("event_type_info");
+ if (it != meta_info_map.end()) {
scoped_event_types_.reset(new ScopedEventTypes(it->second));
}
- it = meta_info_map_.find("trace_offcpu");
- if (it != meta_info_map_.end()) {
+ it = meta_info_map.find("trace_offcpu");
+ if (it != meta_info_map.end()) {
trace_offcpu_ = it->second == "true";
}
}
@@ -387,21 +386,17 @@
return build_id_string_.c_str();
}
-MetaInfoEntry* ReportLib::GetNextMetaInfo() {
+FeatureSection* ReportLib::GetFeatureSection(const char* feature_name) {
if (!OpenRecordFileIfNecessary()) {
return nullptr;
}
- auto it = meta_info_map_.begin();
- if (current_meta_info_.key != nullptr) {
- it = meta_info_map_.find(current_meta_info_.key);
- ++it;
- }
- if (it == meta_info_map_.end()) {
+ int feature = PerfFileFormat::GetFeatureId(feature_name);
+ if (feature == -1 || !record_file_reader_->ReadFeatureSection(feature, &feature_section_data_)) {
return nullptr;
}
- current_meta_info_.key = it->first.c_str();
- current_meta_info_.value = it->second.c_str();
- return ¤t_meta_info_;
+ feature_section_.data = feature_section_data_.data();
+ feature_section_.data_size = feature_section_data_.size();
+ return &feature_section_;
}
// Exported methods working with a client created instance
@@ -453,6 +448,6 @@
return report_lib->GetBuildIdForPath(path);
}
-MetaInfoEntry* GetNextMetaInfo(ReportLib* report_lib) {
- return report_lib->GetNextMetaInfo();
+FeatureSection* GetFeatureSection(ReportLib* report_lib, const char* feature_name) {
+ return report_lib->GetFeatureSection(feature_name);
}
diff --git a/simpleperf/scripts/Android.mk b/simpleperf/scripts/Android.mk
deleted file mode 100644
index e068bd4..0000000
--- a/simpleperf/scripts/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# Copyright (C) 2017 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.
-#
-LOCAL_PATH := $(call my-dir)
-
-SIMPLEPERF_SCRIPT_LIST := $(wildcard $(LOCAL_PATH)/*.py $(LOCAL_PATH)/*.config) \
- $(LOCAL_PATH)/../doc \
- $(LOCAL_PATH)/../demo \
- $(LOCAL_PATH)/../testdata/perf_with_symbols.data \
- $(LOCAL_PATH)/../testdata/perf_with_trace_offcpu.data
-
-SIMPLEPERF_SCRIPT_LIST := $(filter-out $(LOCAL_PATH)/update.py,$(SIMPLEPERF_SCRIPT_LIST))
-
-$(HOST_OUT_EXECUTABLES)/simpleperf_script.zip : $(SIMPLEPERF_SCRIPT_LIST)
- zip -r - $^ >$@
-
-SIMPLEPERF_SCRIPT_LIST :=
-
-sdk: $(HOST_OUT_EXECUTABLES)/simpleperf_script.zip
-
-$(call dist-for-goals,sdk,$(HOST_OUT_EXECUTABLES)/simpleperf_script.zip)
diff --git a/simpleperf/scripts/app_profiler.py b/simpleperf/scripts/app_profiler.py
index 0de735f..c089bbe 100644
--- a/simpleperf/scripts/app_profiler.py
+++ b/simpleperf/scripts/app_profiler.py
@@ -25,6 +25,7 @@
import copy
import os
import os.path
+import re
import shutil
import subprocess
import sys
@@ -53,12 +54,13 @@
self.app_program = self.config['app_package_name'] or self.config['native_program']
self.app_pid = None
self.has_symfs_on_device = False
+ self.record_subproc = None
def check_config(self, config):
config_names = ['app_package_name', 'native_program', 'cmd', 'native_lib_dir',
'apk_file_path', 'recompile_app', 'launch_activity', 'launch_inst_test',
- 'record_options', 'perf_data_path']
+ 'record_options', 'perf_data_path', 'profile_from_launch', 'app_arch']
for name in config_names:
if name not in config:
log_exit('config [%s] is missing' % name)
@@ -83,6 +85,13 @@
if not config['launch_activity'] and not config['launch_inst_test']:
# If recompile app, the app needs to be restarted to take effect.
config['launch_activity'] = '.MainActivity'
+ if config['profile_from_launch']:
+ if not config['app_package_name']:
+ log_exit('-p needs to be set to profile from launch.')
+ if not config['launch_activity']:
+ log_exit('-a needs to be set to profile from launch.')
+ if not config['app_arch']:
+ log_exit('--arch needs to be set to profile from launch.')
def profile(self):
@@ -101,27 +110,16 @@
self._recompile_app()
self._restart_app()
self._get_app_environment()
- self._download_simpleperf()
- self._download_native_libs()
+ if not self.config['profile_from_launch']:
+ self._download_simpleperf()
+ self._download_native_libs()
def _get_device_environment(self):
self.is_root_device = self.adb.switch_to_root()
-
- # Get android version.
- build_version = self.adb.get_property('ro.build.version.release')
- if build_version:
- if not build_version[0].isdigit():
- c = build_version[0].upper()
- if c < 'L':
- self.android_version = 0
- else:
- self.android_version = ord(c) - ord('L') + 5
- else:
- strs = build_version.split('.')
- if strs:
- self.android_version = int(strs[0])
-
+ self.android_version = self.adb.get_android_version()
+ if self.android_version < 7:
+ log_warning("app_profiler.py is not tested prior Android N, please switch to use cmdline interface.")
self.device_arch = self.adb.get_device_arch()
@@ -164,11 +162,14 @@
else:
self.config['launch_activity'] = '.MainActivity'
- pid = self._find_app_process()
- if pid is not None:
- self.run_in_app_dir(['kill', '-9', str(pid)])
+ self.adb.check_run(['shell', 'am', 'force-stop', self.config['app_package_name']])
+ while self._find_app_process():
time.sleep(1)
+ if self.config['profile_from_launch']:
+ self._download_simpleperf()
+ self.start_profiling()
+
if self.config['launch_activity']:
activity = self.config['app_package_name'] + '/' + self.config['launch_activity']
result = self.adb.run(['shell', 'am', 'start', '-n', activity])
@@ -182,8 +183,8 @@
log_exit("Can't start instrumentation test %s" % self.config['launch_inst_test'])
for i in range(10):
- pid = self._find_app_process()
- if pid is not None:
+ self.app_pid = self._find_app_process()
+ if self.app_pid is not None:
return
time.sleep(1)
log_info('Wait for the app process for %d seconds' % (i + 1))
@@ -191,20 +192,46 @@
def _find_app_process(self):
- # On Android >= N, pidof is available. Otherwise, we can use ps.
- if self.android_version >= 7:
+ if not self.config['app_package_name'] and self.android_version >= 7:
result, output = self.adb.run_and_return_output(['shell', 'pidof', self.app_program])
return int(output) if result else None
- result, output = self.adb.run_and_return_output(['shell', 'ps'], log_output=False)
+ ps_args = ['ps', '-e', '-o', 'PID,NAME'] if self.android_version >= 8 else ['ps']
+ result, output = self.adb.run_and_return_output(['shell'] + ps_args, log_output=False)
if not result:
return None
for line in output.split('\n'):
strs = line.split()
- if len(strs) > 2 and self.app_program in strs[-1]:
- return int(strs[1])
+ if len(strs) < 2:
+ continue
+ process_name = strs[-1]
+ if self.config['app_package_name']:
+ # This is to match process names in multiprocess apps.
+ process_name = process_name.split(':')[0]
+ if process_name == self.app_program:
+ pid = int(strs[0] if self.android_version >= 8 else strs[1])
+ # If a debuggable app with wrap.sh runs on Android O, the app will be started with
+ # logwrapper as below:
+ # 1. Zygote forks a child process, rename it to package_name.
+ # 2. The child process execute sh, which starts a child process running
+ # /system/bin/logwrapper.
+ # 3. logwrapper starts a child process running sh, which interprets wrap.sh.
+ # 4. wrap.sh starts a child process running the app.
+ # The problem here is we want to profile the process started in step 4, but
+ # sometimes we run into the process started in step 1. To solve it, we can check
+ # if the process has opened an apk file in some app dirs.
+ if self.android_version >= 8 and self.config['app_package_name'] and (
+ not self._has_opened_apk_file(pid)):
+ continue
+ return pid
return None
+ def _has_opened_apk_file(self, pid):
+ result, output = self.run_in_app_dir(['ls -l /proc/%d/fd' % pid],
+ check_result=False, log_output=False)
+ return result and re.search(r'app.*\.apk', output)
+
+
def _get_app_environment(self):
if not self.config['cmd']:
if self.app_pid is None:
@@ -252,7 +279,7 @@
searched_lib[item] = True
# Use '/' as path separator as item comes from android environment.
filename = item[item.rfind('/') + 1:]
- dirname = '/data/local/tmp' + item[:item.rfind('/')]
+ dirname = '/data/local/tmp/native_libs' + item[:item.rfind('/')]
path = filename_dict.get(filename)
if path is None:
continue
@@ -283,28 +310,40 @@
def start_and_wait_profiling(self):
- subproc = None
+ if self.record_subproc is None:
+ self.start_profiling()
+ self.wait_profiling()
+
+
+ def wait_profiling(self):
returncode = None
try:
- args = ['/data/local/tmp/simpleperf', 'record', self.config['record_options'],
- '-o', '/data/local/tmp/perf.data']
- if self.config['app_package_name']:
- args += ['--app', self.config['app_package_name']]
- elif self.config['native_program']:
- args += ['-p', str(self.app_pid)]
- elif self.config['cmd']:
- args.append(self.config['cmd'])
- if self.has_symfs_on_device:
- args += ['--symfs', '/data/local/tmp/native_libs']
- adb_args = [self.adb.adb_path, 'shell'] + args
- log_debug('run adb cmd: %s' % adb_args)
- subproc = subprocess.Popen(adb_args)
- returncode = subproc.wait()
+ returncode = self.record_subproc.wait()
except KeyboardInterrupt:
- if subproc:
- self.stop_profiling()
- returncode = 0
- log_debug('run adb cmd: %s [result %s]' % (adb_args, returncode == 0))
+ self.stop_profiling()
+ self.record_subproc = None
+ # Don't check return value of record_subproc. Because record_subproc also
+ # receives Ctrl-C, and always returns non-zero.
+ returncode = 0
+ log_debug('profiling result [%s]' % (returncode == 0))
+ if returncode != 0:
+ log_exit('Failed to record profiling data.')
+
+
+ def start_profiling(self):
+ args = ['/data/local/tmp/simpleperf', 'record', self.config['record_options'],
+ '-o', '/data/local/tmp/perf.data']
+ if self.config['app_package_name']:
+ args += ['--app', self.config['app_package_name']]
+ elif self.config['native_program']:
+ args += ['-p', str(self.app_pid)]
+ elif self.config['cmd']:
+ args.append(self.config['cmd'])
+ if self.has_symfs_on_device:
+ args += ['--symfs', '/data/local/tmp/native_libs']
+ adb_args = [self.adb.adb_path, 'shell'] + args
+ log_debug('run adb cmd: %s' % adb_args)
+ self.record_subproc = subprocess.Popen(adb_args)
def stop_profiling(self):
@@ -370,11 +409,11 @@
"""When profiling an Android app, we need the apk file to recompile the app on
Android version <= M.""")
parser.add_argument('-a', '--activity', help=
-"""When profiling an Android app, start an activity before profiling. It can be used to profile
-the startup time of an activity.""")
+"""When profiling an Android app, start an activity before profiling.
+It restarts the app if the app is already running.""")
parser.add_argument('-t', '--test', help=
"""When profiling an Android app, start an instrumentation test before profiling.
-It can be used to profile an instrumentation test.""")
+It restarts the app if the app is already running.""")
parser.add_argument('--arch', help=
"""Select which arch the app is running on, possible values are:
arm, arm64, x86, x86_64. If not set, the script will try to detect it.""")
@@ -385,6 +424,12 @@
parser.add_argument('-nb', '--skip_collect_binaries', action='store_true', help=
"""By default we collect binaries used in profiling data from device to
binary_cache directory. It can be used to annotate source code. This option skips it.""")
+ parser.add_argument('--profile_from_launch', action='store_true', help=
+"""Profile an activity from initial launch. It should be used with -p, -a, and --arch options.
+Normally we run in the following order: restart the app, detect the architecture of the app,
+download simpleperf and native libs with debug info on device, and start simpleperf record.
+But with --profile_from_launch option, we change the order as below: kill the app if it is
+already running, download simpleperf on device, start simpleperf record, and start the app.""")
parser.add_argument('--disable_adb_root', action='store_true', help=
"""Force adb to run in non root mode.""")
args = parser.parse_args()
@@ -403,6 +448,7 @@
config['record_options'] = args.record_options
config['perf_data_path'] = args.perf_data_path
config['collect_binaries'] = not args.skip_collect_binaries
+ config['profile_from_launch'] = args.profile_from_launch
config['disable_adb_root'] = args.disable_adb_root
profiler = AppProfiler(config)
diff --git a/simpleperf/scripts/bin/android/arm/simpleperf b/simpleperf/scripts/bin/android/arm/simpleperf
index 7fe6cd2..c4d0fce 100755
--- a/simpleperf/scripts/bin/android/arm/simpleperf
+++ b/simpleperf/scripts/bin/android/arm/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/android/arm64/simpleperf b/simpleperf/scripts/bin/android/arm64/simpleperf
index ef80a48..9519f68 100755
--- a/simpleperf/scripts/bin/android/arm64/simpleperf
+++ b/simpleperf/scripts/bin/android/arm64/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/android/x86/simpleperf b/simpleperf/scripts/bin/android/x86/simpleperf
index 4d881bd..b7c753a 100755
--- a/simpleperf/scripts/bin/android/x86/simpleperf
+++ b/simpleperf/scripts/bin/android/x86/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/android/x86_64/simpleperf b/simpleperf/scripts/bin/android/x86_64/simpleperf
index 4fc509e..069370b 100755
--- a/simpleperf/scripts/bin/android/x86_64/simpleperf
+++ b/simpleperf/scripts/bin/android/x86_64/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/darwin/x86/libsimpleperf_report.dylib b/simpleperf/scripts/bin/darwin/x86/libsimpleperf_report.dylib
index dc7492f..07ff964 100755
--- a/simpleperf/scripts/bin/darwin/x86/libsimpleperf_report.dylib
+++ b/simpleperf/scripts/bin/darwin/x86/libsimpleperf_report.dylib
Binary files differ
diff --git a/simpleperf/scripts/bin/darwin/x86/simpleperf b/simpleperf/scripts/bin/darwin/x86/simpleperf
index 2eba694..61fd1fe 100755
--- a/simpleperf/scripts/bin/darwin/x86/simpleperf
+++ b/simpleperf/scripts/bin/darwin/x86/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylib b/simpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylib
index 508a881..f70d7e7 100755
--- a/simpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylib
+++ b/simpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylib
Binary files differ
diff --git a/simpleperf/scripts/bin/darwin/x86_64/simpleperf b/simpleperf/scripts/bin/darwin/x86_64/simpleperf
index a6046ef..1af4ecf 100755
--- a/simpleperf/scripts/bin/darwin/x86_64/simpleperf
+++ b/simpleperf/scripts/bin/darwin/x86_64/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/linux/x86/libsimpleperf_report.so b/simpleperf/scripts/bin/linux/x86/libsimpleperf_report.so
index 5f98789..8d3ea66 100755
--- a/simpleperf/scripts/bin/linux/x86/libsimpleperf_report.so
+++ b/simpleperf/scripts/bin/linux/x86/libsimpleperf_report.so
Binary files differ
diff --git a/simpleperf/scripts/bin/linux/x86/simpleperf b/simpleperf/scripts/bin/linux/x86/simpleperf
index cbbbdc4..fc76b5a 100755
--- a/simpleperf/scripts/bin/linux/x86/simpleperf
+++ b/simpleperf/scripts/bin/linux/x86/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.so b/simpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.so
index a8ce067..9e57247 100755
--- a/simpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.so
+++ b/simpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.so
Binary files differ
diff --git a/simpleperf/scripts/bin/linux/x86_64/simpleperf b/simpleperf/scripts/bin/linux/x86_64/simpleperf
index 56b60a5..69b0cf9 100755
--- a/simpleperf/scripts/bin/linux/x86_64/simpleperf
+++ b/simpleperf/scripts/bin/linux/x86_64/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/windows/x86/libsimpleperf_report.dll b/simpleperf/scripts/bin/windows/x86/libsimpleperf_report.dll
index 3f56fa3..3a37be9 100755
--- a/simpleperf/scripts/bin/windows/x86/libsimpleperf_report.dll
+++ b/simpleperf/scripts/bin/windows/x86/libsimpleperf_report.dll
Binary files differ
diff --git a/simpleperf/scripts/bin/windows/x86/simpleperf.exe b/simpleperf/scripts/bin/windows/x86/simpleperf.exe
index 5de283c..cce47bf 100755
--- a/simpleperf/scripts/bin/windows/x86/simpleperf.exe
+++ b/simpleperf/scripts/bin/windows/x86/simpleperf.exe
Binary files differ
diff --git a/simpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dll b/simpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dll
index ee2e690..bbe6de6 100755
--- a/simpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dll
+++ b/simpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dll
Binary files differ
diff --git a/simpleperf/scripts/bin/windows/x86_64/simpleperf.exe b/simpleperf/scripts/bin/windows/x86_64/simpleperf.exe
index 5de283c..cce47bf 100755
--- a/simpleperf/scripts/bin/windows/x86_64/simpleperf.exe
+++ b/simpleperf/scripts/bin/windows/x86_64/simpleperf.exe
Binary files differ
diff --git a/simpleperf/scripts/inferno.bat b/simpleperf/scripts/inferno.bat
index 3021d50..5818f98 100644
--- a/simpleperf/scripts/inferno.bat
+++ b/simpleperf/scripts/inferno.bat
@@ -1 +1,2 @@
-python -m inferno.inferno %
+set PYTHONPATH=%PYTHONPATH%;%~dp0
+python -m inferno.inferno %*
diff --git a/simpleperf/scripts/inferno.sh b/simpleperf/scripts/inferno.sh
index 8ba5097..d30ee31 100755
--- a/simpleperf/scripts/inferno.sh
+++ b/simpleperf/scripts/inferno.sh
@@ -1,2 +1,4 @@
#!/bin/bash
+SCRIPTPATH=$(dirname "$0")
+export PYTHONPATH=$SCRIPTPATH:$PYTHONPATH
python -m inferno.inferno "$@"
diff --git a/simpleperf/scripts/inferno/adb.py b/simpleperf/scripts/inferno/adb.py
deleted file mode 100644
index be0a41e..0000000
--- a/simpleperf/scripts/inferno/adb.py
+++ /dev/null
@@ -1,88 +0,0 @@
-import subprocess
-import abc
-import os
-
-BIN_PATH = "bin/android/%s/simpleperf"
-
-class Abi:
- ARM = 1
- ARM_64 = 2
- X86 = 3
- X86_64 = 4
-
- def __init__(self):
- pass
-
-class Adb:
-
- def __init__(self):
- pass
-
-
- def delete_previous_data(self):
- err = subprocess.call(["adb", "shell", "rm", "-f", "/data/local/tmp/perf.data"])
-
-
- def get_process_pid(self, process_name):
- piof_output = subprocess.check_output(["adb", "shell", "pidof", process_name])
- try:
- process_id = int(piof_output)
- except ValueError:
- process_id = 0
- return process_id
-
-
- def pull_data(self):
- err = subprocess.call(["adb", "pull", "/data/local/tmp/perf.data", "."])
- return err
-
-
- @abc.abstractmethod
- def collect_data(self, simpleperf_command):
- raise NotImplementedError("%s.collect_data(str) is not implemented!" % self.__class__.__name__)
-
-
- def get_props(self):
- props = {}
- output = subprocess.check_output(["adb", "shell", "getprop"])
- lines = output.split("\n")
- for line in lines:
- tokens = line.split(": ")
- if len(tokens) < 2:
- continue
- key = tokens[0].replace("[", "").replace("]", "")
- value = tokens[1].replace("[", "").replace("]", "")
- props[key] = value
- return props
-
- def parse_abi(self, str):
- if str.find("arm64") != -1:
- return Abi.ARM_64
- if str.find("arm") != -1:
- return Abi.ARM
- if str.find("x86_64") != -1:
- return Abi.X86_64
- if str.find("x86") != -1:
- return Abi.X86
- return Abi.ARM_64
-
- def get_exec_path(self, abi):
- folder_name = "arm64"
- if abi == Abi.ARM:
- folder_name = "arm"
- if abi == Abi.X86:
- folder_name = "x86"
- if abi == Abi.X86_64:
- folder_name = "x86_64"
- return os.path.join(os.path.dirname(__file__), BIN_PATH % folder_name)
-
- def push_simpleperf_binary(self):
- # Detect the ABI of the device
- props = self.get_props()
- abi_raw = props["ro.product.cpu.abi"]
- abi = self.parse_abi(abi_raw)
- exec_path = self.get_exec_path(abi)
-
- # Push simpleperf to the device
- print "Pushing local '%s' to device." % exec_path
- subprocess.call(["adb", "push", exec_path, "/data/local/tmp/simpleperf"])
diff --git a/simpleperf/scripts/inferno/adb_non_root.py b/simpleperf/scripts/inferno/adb_non_root.py
deleted file mode 100644
index f187c28..0000000
--- a/simpleperf/scripts/inferno/adb_non_root.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from adb import Adb
-import subprocess
-import time
-
-class AdbNonRoot(Adb):
- # If adb cannot run as root, there is still a way to collect data but it is much more complicated.
- # 1. Identify the platform abi, use getprop: ro.product.cpu.abi
- # 2. Push the precompiled scripts/bin/android/[ABI]/simpleperf to device /data/local/tmp/simpleperf
- # 4. Use run-as to copy /data/local/tmp/simplerperf -> /apps/installation_path/simpleperf
- # 5. Use run-as to run: /apps/installation_path/simpleperf -p APP_PID -o /apps/installation_path/perf.data
- # 6. Use run-as fork+pipe trick to copy /apps/installation_path/perf.data to /data/local/tmp/perf.data
- def collect_data(self, process):
-
- if not process.args.skip_push_binary:
- self.push_simpleperf_binary()
-
- # Copy simpleperf to the data
- subprocess.check_output(["adb", "shell", "run-as %s" % process.name, "cp", "/data/local/tmp/simpleperf", "."])
-
- # Patch command to run with path to data folder where simpleperf was written.
- process.cmd = process.cmd.replace("/data/local/tmp/perf.data", "./perf.data")
-
- # Start collecting samples.
- process.cmd = ("run-as %s " % process.name) + process.cmd
- subprocess.call(["adb", "shell", process.cmd])
-
- # Wait sampling_duration+1.5 seconds.
- time.sleep(int(process.args.capture_duration) + 1)
-
- # Move data to a location where shell user can read it.
- subprocess.call(["adb", "shell", "run-as %s cat perf.data | tee /data/local/tmp/perf.data >/dev/null" % (process.name)])
-
- return True
diff --git a/simpleperf/scripts/inferno/adb_root.py b/simpleperf/scripts/inferno/adb_root.py
deleted file mode 100644
index 4958643..0000000
--- a/simpleperf/scripts/inferno/adb_root.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from adb import Adb
-import subprocess
-
-class AdbRoot(Adb):
- def collect_data(self, process):
- if not process.args.skip_push_binary:
- self.push_simpleperf_binary()
- subprocess.call(["adb", "shell", "cd /data/local/tmp; " + process.cmd])
- return True
\ No newline at end of file
diff --git a/simpleperf/scripts/inferno/data_types.py b/simpleperf/scripts/inferno/data_types.py
index 5341d23..4f07ba7 100644
--- a/simpleperf/scripts/inferno/data_types.py
+++ b/simpleperf/scripts/inferno/data_types.py
@@ -14,25 +14,31 @@
# limitations under the License.
#
+
class CallSite:
+
def __init__(self, ip, method, dso):
self.ip = ip
self.method = method
self.dso = dso
-
class Thread:
- def __init__(self, tid):
- self.tid = tid
- self.samples = []
- self.flamegraph = {}
- self.num_samples = 0
+ def __init__(self, tid, pid):
+ self.tid = tid
+ self.pid = pid
+ self.name = ""
+ self.samples = []
+ self.flamegraph = FlameGraphCallSite("root", "", 0)
+ self.num_samples = 0
+ self.event_count = 0
def add_callchain(self, callchain, symbol, sample):
- chain = []
+ self.name = sample.thread_comm
self.num_samples += 1
+ self.event_count += sample.period
+ chain = []
for j in range(callchain.nr):
entry = callchain.entries[callchain.nr - j - 1]
if entry.ip == 0:
@@ -40,75 +46,68 @@
chain.append(CallSite(entry.ip, entry.symbol.symbol_name, entry.symbol.dso_name))
chain.append(CallSite(sample.ip, symbol.symbol_name, symbol.dso_name))
- self.samples.append(chain)
-
-
- def collapse_flamegraph(self):
- flamegraph = FlameGraphCallSite("root", "")
- flamegraph.id = 0 # This is used for wasd navigation, 0 = not a valid target.
- self.flamegraph = flamegraph
- for sample in self.samples:
- flamegraph = self.flamegraph
- for callsite in sample:
- flamegraph = flamegraph.get_callsite(callsite.method, callsite.dso)
-
- # Populate root note.
- for node in self.flamegraph.callsites:
- self.flamegraph.num_samples += node.num_samples
+ self.flamegraph.add_callchain(chain, sample.period)
class Process:
+
def __init__(self, name, pid):
self.name = name
self.pid = pid
self.threads = {}
self.cmd = ""
self.props = {}
- self.args = None
self.num_samples = 0
- def get_thread(self, tid):
- if (tid not in self.threads.keys()):
- self.threads[tid] = Thread(tid)
+ def get_thread(self, tid, pid):
+ if tid not in self.threads.keys():
+ self.threads[tid] = Thread(tid, pid)
return self.threads[tid]
-CALLSITE_COUNTER = 0
-def get_callsite_id():
- global CALLSITE_COUNTER
- CALLSITE_COUNTER += 1
- toReturn = CALLSITE_COUNTER
- return toReturn
-
class FlameGraphCallSite:
- def __init__(self, method, dso):
- self.callsites = []
+ callsite_counter = 0
+ @classmethod
+ def _get_next_callsite_id(cls):
+ cls.callsite_counter += 1
+ return cls.callsite_counter
+
+ def __init__(self, method, dso, id):
+ self.children = []
self.method = method
self.dso = dso
- self.num_samples = 0
- self.offset = 0 # Offset allows position nodes in different branches.
- self.id = get_callsite_id()
+ self.event_count = 0
+ self.offset = 0 # Offset allows position nodes in different branches.
+ self.id = id
+ def weight(self):
+ return float(self.event_count)
- def get_callsite(self, name, dso):
- for c in self.callsites:
- if c.equivalent(name, dso):
- c.num_samples += 1
+ def add_callchain(self, chain, event_count):
+ self.event_count += event_count
+ current = self
+ for callsite in chain:
+ current = current._get_child(callsite)
+ current.event_count += event_count
+
+ def _get_child(self, callsite):
+ for c in self.children:
+ if c._equivalent(callsite.method, callsite.dso):
return c
- callsite = FlameGraphCallSite(name, dso)
- callsite.num_samples = 1
- self.callsites.append(callsite)
- return callsite
+ new_child = FlameGraphCallSite(callsite.method, callsite.dso, self._get_next_callsite_id())
+ self.children.append(new_child)
+ return new_child
- def equivalent(self, method, dso):
+ def _equivalent(self, method, dso):
return self.method == method and self.dso == dso
-
def get_max_depth(self):
- max = 0
- for c in self.callsites:
- depth = c.get_max_depth()
- if depth > max:
- max = depth
- return max +1
\ No newline at end of file
+ return max([c.get_max_depth() for c in self.children]) + 1 if self.children else 1
+
+ def generate_offset(self, start_offset):
+ self.offset = start_offset
+ child_offset = start_offset
+ for child in self.children:
+ child_offset = child.generate_offset(child_offset)
+ return self.offset + self.event_count
diff --git a/simpleperf/scripts/inferno/inferno.py b/simpleperf/scripts/inferno/inferno.py
index a6d950c..5060a35 100644
--- a/simpleperf/scripts/inferno/inferno.py
+++ b/simpleperf/scripts/inferno/inferno.py
@@ -29,89 +29,55 @@
"""
-from simpleperf_report_lib import ReportLib
import argparse
-from data_types import *
-from svg_renderer import *
import datetime
import os
import subprocess
+import sys
import webbrowser
-from adb_non_root import AdbNonRoot
-from adb_root import AdbRoot
-def create_process(adb_client, args):
- """ Retrieves target process pid and create a process contained.
+scripts_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+sys.path.append(scripts_path)
+from simpleperf_report_lib import ReportLib
+from utils import log_exit, log_info, AdbHelper
- :param args: Argument as parsed by argparse
- :return: Process objectk
- """
- process_id = adb_client.get_process_pid(args.process_name)
- process = Process(args.process_name, process_id)
- return process
+from data_types import *
+from svg_renderer import *
-def collect_data(adb_client, process):
- """ Start simpleperf on device and collect data. Pull perf.data into cwd.
-
- :param process: Process object
- :return: Populated Process object
- """
-
- if process.args.dwarf_unwinding:
- unwinding_parameter = "-g"
- print "Unwinding with dwarf."
+def collect_data(args):
+ app_profiler_args = [sys.executable, os.path.join(scripts_path, "app_profiler.py"), "-nb"]
+ if args.app:
+ app_profiler_args += ["-p", args.app]
+ elif args.native_program:
+ app_profiler_args += ["-np", args.native_program]
else:
- unwinding_parameter = "--call-graph fp"
- print "Unwinding with frame pointers."
-
- # Check whether sampling will be frequency based or event based.
- sampling_parameter = "-f %s" % process.args.sample_frequency
- if process.args.events:
- tokens = process.args.events.split(" ")
+ log_exit("Please set profiling target with -p or -np option.")
+ if args.skip_recompile:
+ app_profiler_args.append("-nc")
+ if args.disable_adb_root:
+ app_profiler_args.append("--disable_adb_root")
+ record_arg_str = ""
+ if args.dwarf_unwinding:
+ record_arg_str += "-g "
+ else:
+ record_arg_str += "--call-graph fp "
+ if args.events:
+ tokens = args.events.split()
if len(tokens) == 2:
num_events = tokens[0]
event_name = tokens[1]
- sampling_parameter = "-c %s -e '%s'" % (num_events, event_name)
+ record_arg_str += "-c %s -e %s " % (num_events, event_name)
else:
- print "Event format string not recognized. Expected \"requency event_name\"."
- print "Got : [" + ",".join(tokens) + "]"
- return False
- print "Using event sampling (%s)." % sampling_parameter
+ log_exit("Event format string of -e option cann't be recognized.")
+ log_info("Using event sampling (-c %s -e %s)." % (num_events, event_name))
else:
- print "Using frequency sampling (%s)." % sampling_parameter
-
- process.cmd = "./simpleperf record \
- -o /data/local/tmp/perf.data \
- %s \
- -p %s \
- --duration %s \
- %s" % (
- unwinding_parameter,
- process.pid,
- process.args.capture_duration,
- sampling_parameter)
-
- print("Process '%s' PID = %d" % (process.name, process.pid))
-
- if process.args.skip_collection:
- print("Skipping data collection, expecting perf.data in folder")
- return True
-
- print("Sampling for %s seconds..." % process.args.capture_duration)
-
-
- adb_client.delete_previous_data()
-
- success = adb_client.collect_data(process)
- if not success:
- return False
-
- err = adb_client.pull_data()
- if err:
- return False
-
- return True
+ record_arg_str += "-f %d " % args.sample_frequency
+ log_info("Using frequency sampling (-f %d)." % args.sample_frequency)
+ record_arg_str += "--duration %d " % args.capture_duration
+ app_profiler_args += ["-r", record_arg_str]
+ returncode = subprocess.call(app_profiler_args)
+ return returncode == 0
def parse_samples(process, args):
@@ -124,12 +90,19 @@
lib = ReportLib()
lib.ShowIpForUnknownSymbol()
- if symfs_dir is not None:
+ if symfs_dir:
lib.SetSymfs(symfs_dir)
- if record_file is not None:
+ if record_file:
lib.SetRecordFile(record_file)
- if kallsyms_file is not None:
+ if kallsyms_file:
lib.SetKallsymsFile(kallsyms_file)
+ process.cmd = lib.GetRecordCmd()
+ product_props = lib.MetaInfo().get("product_props")
+ if product_props:
+ tuple = product_props.split(':')
+ process.props['ro.product.manufacturer'] = tuple[0]
+ process.props['ro.product.model'] = tuple[1]
+ process.props['ro.product.name'] = tuple[2]
while True:
sample = lib.GetNextSample()
@@ -138,20 +111,16 @@
break
symbol = lib.GetSymbolOfCurrentSample()
callchain = lib.GetCallChainOfCurrentSample()
- process.get_thread(sample.tid).add_callchain(callchain, symbol, sample)
+ process.get_thread(sample.tid, sample.pid).add_callchain(callchain, symbol, sample)
process.num_samples += 1
- print("Parsed %s callchains." % process.num_samples)
+ if process.pid == 0:
+ main_threads = [thread for thread in process.threads.values() if thread.tid == thread.pid]
+ if main_threads:
+ process.name = main_threads[0].name
+ process.pid = main_threads[0].pid
-
-def collapse_callgraphs(process):
- """
- For each thread, collapse all callgraph into one flamegraph.
- :param process: Process object
- :return: None
- """
- for _, thread in process.threads.items():
- thread.collapse_flamegraph()
+ log_info("Parsed %s callchains." % process.num_samples)
def get_local_asset_content(local_path):
@@ -160,149 +129,174 @@
:param local_path: str, filename of local asset
:return: str, the content of local_path
"""
- f = open(os.path.join(os.path.dirname(__file__), local_path), 'r')
- content = f.read()
- f.close()
- return content
+ with open(os.path.join(os.path.dirname(__file__), local_path), 'r') as f:
+ return f.read()
-def output_report(process):
+def output_report(process, args):
"""
Generates a HTML report representing the result of simpleperf sampling as flamegraph
:param process: Process object
:return: str, absolute path to the file
"""
- f = open('report.html', 'w')
+ f = open(args.report_path, 'w')
filepath = os.path.realpath(f.name)
f.write("<html>")
+ f.write("<head>")
+ f.write("""<style type="text/css">""")
+ f.write(get_local_asset_content(os.path.join("jqueryui", "jquery-ui.min.css")))
+ f.write("</style>")
+ f.write("</head>")
f.write("<body style='font-family: Monospace;' onload='init()'>")
- f.write('<style type="text/css"> .s { stroke:black; stroke-width:0.5; cursor:pointer;} </style>')
+ f.write("""<style type="text/css"> .s { stroke:black; stroke-width:0.5; cursor:pointer;}
+ </style>""")
f.write('<style type="text/css"> .t:hover { cursor:pointer; } </style>')
f.write('<img height="180" alt = "Embedded Image" src ="data')
f.write(get_local_asset_content("inferno.b64"))
f.write('"/>')
- f.write("<div style='display:inline-block;'> \
- <font size='8'>\
- Inferno Flamegraph Report</font><br/><br/> \
- Process : %s (%d)<br/>\
- Date : %s<br/>\
- Threads : %d <br/>\
- Samples : %d</br>\
- Duration: %s seconds<br/>\
- Machine : %s (%s) by %s<br/>\
- Capture : %s<br/><br/></div>"
- % (
- process.name,process.pid,
- datetime.datetime.now().strftime("%Y-%m-%d (%A) %H:%M:%S"),
- len(process.threads),
- process.num_samples,
- process.args.capture_duration,
- process.props["ro.product.model"], process.props["ro.product.name"],
- process.props["ro.product.manufacturer"],
- process.cmd))
- f.write("<br/><br/><div>Navigate with WASD, zoom in with SPACE, zoom out with BACKSPACE.</div>")
- f.write(get_local_asset_content("script.js"))
+ process_entry = ("Process : %s (%d)<br/>" % (process.name, process.pid)) if process.pid else ""
+ # TODO: collect capture duration info from perf.data.
+ duration_entry = ("Duration: %s seconds<br/>" % args.capture_duration
+ ) if args.capture_duration else ""
+ f.write("""<div style='display:inline-block;'>
+ <font size='8'>
+ Inferno Flamegraph Report</font><br/><br/>
+ %s
+ Date : %s<br/>
+ Threads : %d <br/>
+ Samples : %d</br>
+ %s""" % (
+ process_entry,
+ datetime.datetime.now().strftime("%Y-%m-%d (%A) %H:%M:%S"),
+ len(process.threads),
+ process.num_samples,
+ duration_entry))
+ if 'ro.product.model' in process.props:
+ f.write(
+ "Machine : %s (%s) by %s<br/>" %
+ (process.props["ro.product.model"],
+ process.props["ro.product.name"],
+ process.props["ro.product.manufacturer"]))
+ if process.cmd:
+ f.write("Capture : %s<br/><br/>" % process.cmd)
+ f.write("</div>")
+ f.write("""<br/><br/>
+ <div>Navigate with WASD, zoom in with SPACE, zoom out with BACKSPACE.</div>""")
+ f.write("<script>")
+ f.write(get_local_asset_content(os.path.join("jqueryui", "jquery-3.2.1.min.js")))
+ f.write(get_local_asset_content(os.path.join("jqueryui", "jquery-ui.min.js")))
+ f.write("</script>")
+ f.write("<script>%s</script>" % get_local_asset_content("script.js"))
# Output tid == pid Thread first.
- main_thread = [x for _, x in process.threads.items() if x.tid == process.pid]
+ main_thread = [x for x in process.threads.values() if x.tid == process.pid]
for thread in main_thread:
- f.write("<br/><br/><b>Main Thread %d (%d samples):</b><br/>\n\n\n\n" % (thread.tid, thread.num_samples))
- renderSVG(thread.flamegraph, f, process.args.color, process.args.svg_width)
+ f.write("<br/><br/><b>Main Thread %d (%s) (%d samples):</b><br/>\n\n\n\n" % (
+ thread.tid, thread.name, thread.num_samples))
+ renderSVG(thread.flamegraph, f, args.color, args.svg_width)
- other_threads = [x for _, x in process.threads.items() if x.tid != process.pid]
+ other_threads = [x for x in process.threads.values() if x.tid != process.pid]
for thread in other_threads:
- f.write("<br/><br/><b>Thread %d (%d samples):</b><br/>\n\n\n\n" % (thread.tid, thread.num_samples))
- renderSVG(thread.flamegraph, f, process.args.color, process.args.svg_width)
+ f.write("<br/><br/><b>Thread %d (%s) (%d samples):</b><br/>\n\n\n\n" % (
+ thread.tid, thread.name, thread.num_samples))
+ renderSVG(thread.flamegraph, f, args.color, args.svg_width)
f.write("</body>")
f.write("</html>")
f.close()
return "file://" + filepath
-def generate_flamegraph_offsets(flamegraph):
- rover = flamegraph.offset
- for callsite in flamegraph.callsites:
- callsite.offset = rover
- rover += callsite.num_samples
- generate_flamegraph_offsets(callsite)
-
def generate_threads_offsets(process):
- for _, thread in process.threads.items():
- generate_flamegraph_offsets(thread.flamegraph)
+ for thread in process.threads.values():
+ thread.flamegraph.generate_offset(0)
-def collect_machine_info(adb_client, process):
- process.props = adb_client.get_props()
+def collect_machine_info(process):
+ adb = AdbHelper()
+ process.props = {}
+ process.props['ro.product.model'] = adb.get_property('ro.product.model')
+ process.props['ro.product.name'] = adb.get_property('ro.product.name')
+ process.props['ro.product.manufacturer'] = adb.get_property('ro.product.manufacturer')
-def setup_adb():
- err = subprocess.call(["adb", "root"])
- if err == 0:
- return AdbRoot()
- else:
- return AdbNonRoot()
-
def open_report_in_browser(report_path):
- # Try to open the report with Chrome
- browser_key = ""
- for key, value in webbrowser._browsers.items():
- if key.find("chrome") != -1:
- browser_key = key
- browser = webbrowser.get(browser_key)
- browser.open(report_path, new=0, autoraise=True)
-
+ try:
+ # Try to open the report with Chrome
+ browser_key = ""
+ for key, value in webbrowser._browsers.items():
+ if key.find("chrome") != -1:
+ browser_key = key
+ browser = webbrowser.get(browser_key)
+ browser.open(report_path, new=0, autoraise=True)
+ except:
+ # webbrowser.get() doesn't work well on darwin/windows.
+ webbrowser.open_new_tab(report_path)
def main():
parser = argparse.ArgumentParser(description='Report samples in perf.data.')
- parser.add_argument('--symfs', help='Set the path to find binaries with symbols and debug info.')
+ parser.add_argument('--symfs', help="""Set the path to find binaries with symbols and debug
+ info.""")
parser.add_argument('--kallsyms', help='Set the path to find kernel symbols.')
parser.add_argument('--record_file', default='perf.data', help='Default is perf.data.')
- parser.add_argument('-t', '--capture_duration', default=10, help='Capture duration in seconds.')
- parser.add_argument('-p', '--process_name', default='surfaceflinger', help='Default is surfaceflinger.')
+ parser.add_argument('-t', '--capture_duration', type=int, default=10,
+ help="""Capture duration in seconds.""")
+ parser.add_argument('-p', '--app', help="""Profile an Android app, given the package name.
+ Like -p com.example.android.myapp.""")
+ parser.add_argument('-np', '--native_program', default="surfaceflinger",
+ help="""Profile a native program. The program should be running on the
+ device. Like -np surfaceflinger.""")
parser.add_argument('-c', '--color', default='hot', choices=['hot', 'dso', 'legacy'],
- help='Color theme: hot=percentage of samples, dso=callsite DSO name, legacy=brendan style')
- parser.add_argument('-sc','--skip_collection', default=False, help='Skip data collection', action="store_true")
- parser.add_argument('-f', '--sample_frequency', default=6000, help='Sample frequency')
+ help="""Color theme: hot=percentage of samples, dso=callsite DSO name,
+ legacy=brendan style""")
+ parser.add_argument('-sc', '--skip_collection', default=False, help='Skip data collection',
+ action="store_true")
+ parser.add_argument('-nc', '--skip_recompile', action='store_true', help="""When profiling
+ an Android app, by default we recompile java bytecode to native
+ instructions to profile java code. It takes some time. You can skip it
+ if the code has been compiled or you don't need to profile java code.""")
+ parser.add_argument('-f', '--sample_frequency', type=int, default=6000, help='Sample frequency')
parser.add_argument('-w', '--svg_width', type=int, default=1124)
- parser.add_argument('-sb', '--skip_push_binary', help='Skip pushing simpleperf before profiling',
- default=False, action="store_true")
- parser.add_argument('-du', '--dwarf_unwinding', help='Perform unwinding using dwarf instead of fp.',
- default=False, action='store_true')
- parser.add_argument('-e', '--events',
- help='Sample based on event occurences instead of frequency. '
- 'Format expected is "event_counts event_name". e.g: "10000 cpu-cyles". A few examples of \
- nmames: cpu-cycles, cache-references, cache-misses, branch-instructions, branch-misses',
+ parser.add_argument(
+ '-du',
+ '--dwarf_unwinding',
+ help='Perform unwinding using dwarf instead of fp.',
+ default=False,
+ action='store_true')
+ parser.add_argument('-e', '--events', help="""Sample based on event occurences instead of
+ frequency. Format expected is "event_counts event_name".
+ e.g: "10000 cpu-cyles". A few examples of event_name: cpu-cycles,
+ cache-references, cache-misses, branch-instructions, branch-misses""",
default="")
+ parser.add_argument('--disable_adb_root', action='store_true', help="""Force adb to run in non
+ root mode.""")
+ parser.add_argument('-o', '--report_path', default='report.html', help="Set report path.")
args = parser.parse_args()
+ process = Process("", 0)
- # Since we may attempt to sample privileged process, let's try to be root.
- adb_client = setup_adb()
+ if not args.skip_collection:
+ process.name = args.app or args.native_program
+ log_info("Starting data collection stage for process '%s'." % process.name)
+ if not collect_data(args):
+ log_exit("Unable to collect data.")
+ result, output = AdbHelper().run_and_return_output(['shell', 'pidof', process.name])
+ if result:
+ try:
+ process.pid = int(output)
+ except:
+ process.pid = 0
+ collect_machine_info(process)
+ else:
+ args.capture_duration = 0
- # Create a process object
- process = create_process(adb_client, args)
- if process.pid == 0:
- print("Unable to retrive pid for process '%s'. Terminating." % process.name)
- return
- process.args = args
-
-
- print("Starting data collection stage for process '%s'." % args.process_name)
- success = collect_data(adb_client, process)
- if not success:
- print "Unable to collect data"
- return
-
- collect_machine_info(adb_client, process)
parse_samples(process, args)
- collapse_callgraphs(process)
generate_threads_offsets(process)
- report_path = output_report(process)
+ report_path = output_report(process, args)
open_report_in_browser(report_path)
- print "Report generated at '%s'." % report_path
+ log_info("Report generated at '%s'." % report_path)
if __name__ == "__main__":
- main()
\ No newline at end of file
+ main()
diff --git a/simpleperf/scripts/inferno/jqueryui/LICENSE.txt b/simpleperf/scripts/inferno/jqueryui/LICENSE.txt
new file mode 100644
index 0000000..4819e54
--- /dev/null
+++ b/simpleperf/scripts/inferno/jqueryui/LICENSE.txt
@@ -0,0 +1,43 @@
+Copyright jQuery Foundation and other contributors, https://jquery.org/
+
+This software consists of voluntary contributions made by many
+individuals. For exact contribution history, see the revision history
+available at https://github.com/jquery/jquery-ui
+
+The following license applies to all parts of this software except as
+documented below:
+
+====
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+====
+
+Copyright and related rights for sample code are waived via CC0. Sample
+code is defined as all source code contained within the demos directory.
+
+CC0: http://creativecommons.org/publicdomain/zero/1.0/
+
+====
+
+All files located in the node_modules and external directories are
+externally maintained libraries used by this software which have their
+own licenses; we recommend you read them, as their terms may differ from
+the terms above.
diff --git a/simpleperf/scripts/inferno/jqueryui/jquery-3.2.1.min.js b/simpleperf/scripts/inferno/jqueryui/jquery-3.2.1.min.js
new file mode 100644
index 0000000..644d35e
--- /dev/null
+++ b/simpleperf/scripts/inferno/jqueryui/jquery-3.2.1.min.js
@@ -0,0 +1,4 @@
+/*! jQuery v3.2.1 | (c) JS Foundation and other contributors | jquery.org/license */
+!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.2.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c<b?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:h,sort:c.sort,splice:c.splice},r.extend=r.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||r.isFunction(g)||(g={}),h===i&&(g=this,h--);h<i;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(r.isPlainObject(d)||(e=Array.isArray(d)))?(e?(e=!1,f=c&&Array.isArray(c)?c:[]):f=c&&r.isPlainObject(c)?c:{},g[b]=r.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},r.extend({expando:"jQuery"+(q+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===r.type(a)},isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=r.type(a);return("number"===b||"string"===b)&&!isNaN(a-parseFloat(a))},isPlainObject:function(a){var b,c;return!(!a||"[object Object]"!==k.call(a))&&(!(b=e(a))||(c=l.call(b,"constructor")&&b.constructor,"function"==typeof c&&m.call(c)===n))},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?j[k.call(a)]||"object":typeof a},globalEval:function(a){p(a)},camelCase:function(a){return a.replace(t,"ms-").replace(u,v)},each:function(a,b){var c,d=0;if(w(a)){for(c=a.length;d<c;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(s,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(w(Object(a))?r.merge(c,"string"==typeof a?[a]:a):h.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:i.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;d<c;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;f<g;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,f=0,h=[];if(w(a))for(d=a.length;f<d;f++)e=b(a[f],f,c),null!=e&&h.push(e);else for(f in a)e=b(a[f],f,c),null!=e&&h.push(e);return g.apply([],h)},guid:1,proxy:function(a,b){var c,d,e;if("string"==typeof b&&(c=a[b],b=a,a=c),r.isFunction(a))return d=f.call(arguments,2),e=function(){return a.apply(b||this,d.concat(f.call(arguments)))},e.guid=a.guid=a.guid||r.guid++,e},now:Date.now,support:o}),"function"==typeof Symbol&&(r.fn[Symbol.iterator]=c[Symbol.iterator]),r.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){j["[object "+b+"]"]=b.toLowerCase()});function w(a){var b=!!a&&"length"in a&&a.length,c=r.type(a);return"function"!==c&&!r.isWindow(a)&&("array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return c;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\0-\\xa0])+",M="\\["+K+"*("+L+")(?:"+K+"*([*^$|!~]?=)"+K+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+L+"))|)"+K+"*\\]",N=":("+L+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+M+")*)|.*)\\)|)",O=new RegExp(K+"+","g"),P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\r\\' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c<b;c+=2)a.push(c);return a}),odd:pa(function(a,b){for(var c=1;c<b;c+=2)a.push(c);return a}),lt:pa(function(a,b,c){for(var d=c<0?c+b:c;--d>=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=ma(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=na(b);function ra(){}ra.prototype=d.filters=d.pseudos,d.setFilters=new ra,g=ga.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){c&&!(e=Q.exec(h))||(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=R.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(P," ")}),h=h.slice(c.length));for(g in d.filter)!(e=V[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?ga.error(a):z(a,i).slice(0)};function sa(a){for(var b=0,c=a.length,d="";b<c;b++)d+=a[b].value;return d}function ta(a,b,c){var d=b.dir,e=b.next,f=e||d,g=c&&"parentNode"===f,h=x++;return b.first?function(b,c,e){while(b=b[d])if(1===b.nodeType||g)return a(b,c,e);return!1}:function(b,c,i){var j,k,l,m=[w,h];if(i){while(b=b[d])if((1===b.nodeType||g)&&a(b,c,i))return!0}else while(b=b[d])if(1===b.nodeType||g)if(l=b[u]||(b[u]={}),k=l[b.uniqueID]||(l[b.uniqueID]={}),e&&e===b.nodeName.toLowerCase())b=b[d]||b;else{if((j=k[f])&&j[0]===w&&j[1]===h)return m[2]=j[2];if(k[f]=m,m[2]=a(b,c,i))return!0}return!1}}function ua(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d<e;d++)ga(a,b[d],c);return c}function wa(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;h<i;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function xa(a,b,c,d,e,f){return d&&!d[u]&&(d=xa(d)),e&&!e[u]&&(e=xa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||va(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:wa(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=wa(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i<f;i++)if(c=d.relative[a[i].type])m=[ta(ua(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;e<f;e++)if(d.relative[a[e].type])break;return xa(i>1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i<e&&ya(a.slice(i,e)),e<f&&ya(a=a.slice(e)),e<f&&sa(a))}m.push(c)}return ua(m)}function za(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext;function B(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()}var C=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,D=/^.[^:#\[\.,]*$/;function E(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):D.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b<d;b++)if(r.contains(e[b],this))return!0}));for(c=this.pushStack([]),b=0;b<d;b++)r.find(a,e[b],c);return d>1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(E(this,a||[],!1))},not:function(a){return this.pushStack(E(this,a||[],!0))},is:function(a){return!!E(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var F,G=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,H=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||F,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:G.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),C.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};H.prototype=r.fn,F=r(d);var I=/^(?:parents|prev(?:Until|All))/,J={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a<c;a++)if(r.contains(this,b[a]))return!0})},closest:function(a,b){var c,d=0,e=this.length,f=[],g="string"!=typeof a&&r(a);if(!A.test(a))for(;d<e;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function K(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return K(a,"nextSibling")},prev:function(a){return K(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return B(a,"iframe")?a.contentDocument:(B(a,"template")&&(a=a.content||a),r.merge([],a.childNodes))}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(J[a]||r.uniqueSort(e),I.test(a)&&e.reverse()),this.pushStack(e)}});var L=/[^\x20\t\r\n\f]+/g;function M(a){var b={};return r.each(a.match(L)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?M(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=e||a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h<f.length)f[h].apply(c[0],c[1])===!1&&a.stopOnFalse&&(h=f.length,c=!1)}a.memory||(c=!1),b=!1,e&&(f=c?[]:"")},j={add:function(){return f&&(c&&!b&&(h=f.length-1,g.push(c)),function d(b){r.each(b,function(b,c){r.isFunction(c)?a.unique&&j.has(c)||f.push(c):c&&c.length&&"string"!==r.type(c)&&d(c)})}(arguments),c&&!b&&i()),this},remove:function(){return r.each(arguments,function(a,b){var c;while((c=r.inArray(b,f,c))>-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function N(a){return a}function O(a){throw a}function P(a,b,c,d){var e;try{a&&r.isFunction(e=a.promise)?e.call(a).done(b).fail(c):a&&r.isFunction(e=a.then)?e.call(a,b,c):b.apply(void 0,[a].slice(d))}catch(a){c.apply(void 0,[a])}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b<f)){if(a=d.apply(h,i),a===c.promise())throw new TypeError("Thenable self-resolution");j=a&&("object"==typeof a||"function"==typeof a)&&a.then,r.isFunction(j)?e?j.call(a,g(f,c,N,e),g(f,c,O,e)):(f++,j.call(a,g(f,c,N,e),g(f,c,O,e),g(f,c,N,c.notifyWith))):(d!==N&&(h=void 0,i=[a]),(e||c.resolveWith)(h,i))}},k=e?j:function(){try{j()}catch(a){r.Deferred.exceptionHook&&r.Deferred.exceptionHook(a,k.stackTrace),b+1>=f&&(d!==O&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:N,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:N)),c[2][3].add(g(0,a,r.isFunction(d)?d:O))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(P(a,g.done(h(c)).resolve,g.reject,!b),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)P(e[c],h(c),g.reject);return g.promise()}});var Q=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&Q.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var R=r.Deferred();r.fn.ready=function(a){return R.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||R.resolveWith(d,[r]))}}),r.ready.then=R.then;function S(){d.removeEventListener("DOMContentLoaded",S),
+a.removeEventListener("load",S),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",S),a.addEventListener("load",S));var T=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)T(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h<i;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},U=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function V(){this.expando=r.expando+V.uid++}V.uid=1,V.prototype={cache:function(a){var b=a[this.expando];return b||(b={},U(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[r.camelCase(b)]=c;else for(d in b)e[r.camelCase(d)]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][r.camelCase(b)]},access:function(a,b,c){return void 0===b||b&&"string"==typeof b&&void 0===c?this.get(a,b):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d=a[this.expando];if(void 0!==d){if(void 0!==b){Array.isArray(b)?b=b.map(r.camelCase):(b=r.camelCase(b),b=b in d?[b]:b.match(L)||[]),c=b.length;while(c--)delete d[b[c]]}(void 0===b||r.isEmptyObject(d))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!r.isEmptyObject(b)}};var W=new V,X=new V,Y=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Z=/[A-Z]/g;function $(a){return"true"===a||"false"!==a&&("null"===a?null:a===+a+""?+a:Y.test(a)?JSON.parse(a):a)}function _(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Z,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c=$(c)}catch(e){}X.set(a,b,c)}else c=void 0;return c}r.extend({hasData:function(a){return X.hasData(a)||W.hasData(a)},data:function(a,b,c){return X.access(a,b,c)},removeData:function(a,b){X.remove(a,b)},_data:function(a,b,c){return W.access(a,b,c)},_removeData:function(a,b){W.remove(a,b)}}),r.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=X.get(f),1===f.nodeType&&!W.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=r.camelCase(d.slice(5)),_(f,d,e[d])));W.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){X.set(this,a)}):T(this,function(b){var c;if(f&&void 0===b){if(c=X.get(f,a),void 0!==c)return c;if(c=_(f,a),void 0!==c)return c}else this.each(function(){X.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){X.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=W.get(a,b),c&&(!d||Array.isArray(c)?d=W.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return W.get(a,c)||W.access(a,c,{empty:r.Callbacks("once memory").add(function(){W.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?r.queue(this[0],a):void 0===b?this:this.each(function(){var c=r.queue(this,a,b);r._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&r.dequeue(this,a)})},dequeue:function(a){return this.each(function(){r.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=r.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=W.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var aa=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,ba=new RegExp("^(?:([+-])=|)("+aa+")([a-z%]*)$","i"),ca=["Top","Right","Bottom","Left"],da=function(a,b){return a=b||a,"none"===a.style.display||""===a.style.display&&r.contains(a.ownerDocument,a)&&"none"===r.css(a,"display")},ea=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};function fa(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return r.css(a,b,"")},i=h(),j=c&&c[3]||(r.cssNumber[b]?"":"px"),k=(r.cssNumber[b]||"px"!==j&&+i)&&ba.exec(r.css(a,b));if(k&&k[3]!==j){j=j||k[3],c=c||[],k=+i||1;do f=f||".5",k/=f,r.style(a,b,k+j);while(f!==(f=h()/i)&&1!==f&&--g)}return c&&(k=+k||+i||0,e=c[1]?k+(c[1]+1)*c[2]:+c[2],d&&(d.unit=j,d.start=k,d.end=e)),e}var ga={};function ha(a){var b,c=a.ownerDocument,d=a.nodeName,e=ga[d];return e?e:(b=c.body.appendChild(c.createElement(d)),e=r.css(b,"display"),b.parentNode.removeChild(b),"none"===e&&(e="block"),ga[d]=e,e)}function ia(a,b){for(var c,d,e=[],f=0,g=a.length;f<g;f++)d=a[f],d.style&&(c=d.style.display,b?("none"===c&&(e[f]=W.get(d,"display")||null,e[f]||(d.style.display="")),""===d.style.display&&da(d)&&(e[f]=ha(d))):"none"!==c&&(e[f]="none",W.set(d,"display",c)));for(f=0;f<g;f++)null!=e[f]&&(a[f].style.display=e[f]);return a}r.fn.extend({show:function(){return ia(this,!0)},hide:function(){return ia(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){da(this)?r(this).show():r(this).hide()})}});var ja=/^(?:checkbox|radio)$/i,ka=/<([a-z][^\/\0>\x20\t\r\n\f]+)/i,la=/^$|\/(?:java|ecma)script/i,ma={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ma.optgroup=ma.option,ma.tbody=ma.tfoot=ma.colgroup=ma.caption=ma.thead,ma.th=ma.td;function na(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&B(a,b)?r.merge([a],c):c}function oa(a,b){for(var c=0,d=a.length;c<d;c++)W.set(a[c],"globalEval",!b||W.get(b[c],"globalEval"))}var pa=/<|&#?\w+;/;function qa(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],n=0,o=a.length;n<o;n++)if(f=a[n],f||0===f)if("object"===r.type(f))r.merge(m,f.nodeType?[f]:f);else if(pa.test(f)){g=g||l.appendChild(b.createElement("div")),h=(ka.exec(f)||["",""])[1].toLowerCase(),i=ma[h]||ma._default,g.innerHTML=i[1]+r.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;r.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",n=0;while(f=m[n++])if(d&&r.inArray(f,d)>-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=na(l.appendChild(f),"script"),j&&oa(g),c){k=0;while(f=g[k++])la.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var ra=d.documentElement,sa=/^key/,ta=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ua=/^([^.]*)(?:\.(.+)|)/;function va(){return!0}function wa(){return!1}function xa(){try{return d.activeElement}catch(a){}}function ya(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ya(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=wa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(ra,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(L)||[""],j=b.length;while(j--)h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.hasData(a)&&W.get(a);if(q&&(i=q.events)){b=(b||"").match(L)||[""],j=b.length;while(j--)if(h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&W.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(W.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c<arguments.length;c++)i[c]=arguments[c];if(b.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,b)!==!1){h=r.event.handlers.call(this,b,j),c=0;while((f=h[c++])&&!b.isPropagationStopped()){b.currentTarget=f.elem,d=0;while((g=f.handlers[d++])&&!b.isImmediatePropagationStopped())b.rnamespace&&!b.rnamespace.test(g.namespace)||(b.handleObj=g,b.data=g.data,e=((r.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(b.result=e)===!1&&(b.preventDefault(),b.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,b),b.result}},handlers:function(a,b){var c,d,e,f,g,h=[],i=b.delegateCount,j=a.target;if(i&&j.nodeType&&!("click"===a.type&&a.button>=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c<i;c++)d=b[c],e=d.selector+" ",void 0===g[e]&&(g[e]=d.needsContext?r(e,this).index(j)>-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i<b.length&&h.push({elem:j,handlers:b.slice(i)}),h},addProp:function(a,b){Object.defineProperty(r.Event.prototype,a,{enumerable:!0,configurable:!0,get:r.isFunction(b)?function(){if(this.originalEvent)return b(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[a]},set:function(b){Object.defineProperty(this,a,{enumerable:!0,configurable:!0,writable:!0,value:b})}})},fix:function(a){return a[r.expando]?a:new r.Event(a)},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==xa()&&this.focus)return this.focus(),!1},delegateType:"focusin"},blur:{trigger:function(){if(this===xa()&&this.blur)return this.blur(),!1},delegateType:"focusout"},click:{trigger:function(){if("checkbox"===this.type&&this.click&&B(this,"input"))return this.click(),!1},_default:function(a){return B(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}}},r.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c)},r.Event=function(a,b){return this instanceof r.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?va:wa,this.target=a.target&&3===a.target.nodeType?a.target.parentNode:a.target,this.currentTarget=a.currentTarget,this.relatedTarget=a.relatedTarget):this.type=a,b&&r.extend(this,b),this.timeStamp=a&&a.timeStamp||r.now(),void(this[r.expando]=!0)):new r.Event(a,b)},r.Event.prototype={constructor:r.Event,isDefaultPrevented:wa,isPropagationStopped:wa,isImmediatePropagationStopped:wa,isSimulated:!1,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=va,a&&!this.isSimulated&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=va,a&&!this.isSimulated&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=va,a&&!this.isSimulated&&a.stopImmediatePropagation(),this.stopPropagation()}},r.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(a){var b=a.button;return null==a.which&&sa.test(a.type)?null!=a.charCode?a.charCode:a.keyCode:!a.which&&void 0!==b&&ta.test(a.type)?1&b?1:2&b?3:4&b?2:0:a.which}},r.event.addProp),r.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){r.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return e&&(e===d||r.contains(d,e))||(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),r.fn.extend({on:function(a,b,c,d){return ya(this,a,b,c,d)},one:function(a,b,c,d){return ya(this,a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,r(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return b!==!1&&"function"!=typeof b||(c=b,b=void 0),c===!1&&(c=wa),this.each(function(){r.event.remove(this,a,c,b)})}});var za=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,Aa=/<script|<style|<link/i,Ba=/checked\s*(?:[^=]|=\s*.checked.)/i,Ca=/^true\/(.*)/,Da=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function Ea(a,b){return B(a,"table")&&B(11!==b.nodeType?b:b.firstChild,"tr")?r(">tbody",a)[0]||a:a}function Fa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Ga(a){var b=Ca.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ha(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(W.hasData(a)&&(f=W.access(a),g=W.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c<d;c++)r.event.add(b,e,j[e][c])}X.hasData(a)&&(h=X.access(a),i=r.extend({},h),X.set(b,i))}}function Ia(a,b){var c=b.nodeName.toLowerCase();"input"===c&&ja.test(a.type)?b.checked=a.checked:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}function Ja(a,b,c,d){b=g.apply([],b);var e,f,h,i,j,k,l=0,m=a.length,n=m-1,q=b[0],s=r.isFunction(q);if(s||m>1&&"string"==typeof q&&!o.checkClone&&Ba.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ja(f,b,c,d)});if(m&&(e=qa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(na(e,"script"),Fa),i=h.length;l<m;l++)j=e,l!==n&&(j=r.clone(j,!0,!0),i&&r.merge(h,na(j,"script"))),c.call(a[l],j,l);if(i)for(k=h[h.length-1].ownerDocument,r.map(h,Ga),l=0;l<i;l++)j=h[l],la.test(j.type||"")&&!W.access(j,"globalEval")&&r.contains(k,j)&&(j.src?r._evalUrl&&r._evalUrl(j.src):p(j.textContent.replace(Da,""),k))}return a}function Ka(a,b,c){for(var d,e=b?r.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||r.cleanData(na(d)),d.parentNode&&(c&&r.contains(d.ownerDocument,d)&&oa(na(d,"script")),d.parentNode.removeChild(d));return a}r.extend({htmlPrefilter:function(a){return a.replace(za,"<$1></$2>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=na(h),f=na(a),d=0,e=f.length;d<e;d++)Ia(f[d],g[d]);if(b)if(c)for(f=f||na(a),g=g||na(h),d=0,e=f.length;d<e;d++)Ha(f[d],g[d]);else Ha(a,h);return g=na(h,"script"),g.length>0&&oa(g,!i&&na(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(U(c)){if(b=c[W.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[W.expando]=void 0}c[X.expando]&&(c[X.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ka(this,a,!0)},remove:function(a){return Ka(this,a)},text:function(a){return T(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.appendChild(a)}})},prepend:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(na(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return T(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!Aa.test(a)&&!ma[(ka.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c<d;c++)b=this[c]||{},1===b.nodeType&&(r.cleanData(na(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ja(this,arguments,function(b){var c=this.parentNode;r.inArray(this,a)<0&&(r.cleanData(na(this)),c&&c.replaceChild(b,this))},a)}}),r.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){r.fn[a]=function(a){for(var c,d=[],e=r(a),f=e.length-1,g=0;g<=f;g++)c=g===f?this:this.clone(!0),r(e[g])[b](c),h.apply(d,c.get());return this.pushStack(d)}});var La=/^margin/,Ma=new RegExp("^("+aa+")(?!px)[a-z%]+$","i"),Na=function(b){var c=b.ownerDocument.defaultView;return c&&c.opener||(c=a),c.getComputedStyle(b)};!function(){function b(){if(i){i.style.cssText="box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%",i.innerHTML="",ra.appendChild(h);var b=a.getComputedStyle(i);c="1%"!==b.top,g="2px"===b.marginLeft,e="4px"===b.width,i.style.marginRight="50%",f="4px"===b.marginRight,ra.removeChild(h),i=null}}var c,e,f,g,h=d.createElement("div"),i=d.createElement("div");i.style&&(i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",o.clearCloneStyle="content-box"===i.style.backgroundClip,h.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",h.appendChild(i),r.extend(o,{pixelPosition:function(){return b(),c},boxSizingReliable:function(){return b(),e},pixelMarginRight:function(){return b(),f},reliableMarginLeft:function(){return b(),g}}))}();function Oa(a,b,c){var d,e,f,g,h=a.style;return c=c||Na(a),c&&(g=c.getPropertyValue(b)||c[b],""!==g||r.contains(a.ownerDocument,a)||(g=r.style(a,b)),!o.pixelMarginRight()&&Ma.test(g)&&La.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+"":g}function Pa(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}var Qa=/^(none|table(?!-c[ea]).+)/,Ra=/^--/,Sa={position:"absolute",visibility:"hidden",display:"block"},Ta={letterSpacing:"0",fontWeight:"400"},Ua=["Webkit","Moz","ms"],Va=d.createElement("div").style;function Wa(a){if(a in Va)return a;var b=a[0].toUpperCase()+a.slice(1),c=Ua.length;while(c--)if(a=Ua[c]+b,a in Va)return a}function Xa(a){var b=r.cssProps[a];return b||(b=r.cssProps[a]=Wa(a)||a),b}function Ya(a,b,c){var d=ba.exec(b);return d?Math.max(0,d[2]-(c||0))+(d[3]||"px"):b}function Za(a,b,c,d,e){var f,g=0;for(f=c===(d?"border":"content")?4:"width"===b?1:0;f<4;f+=2)"margin"===c&&(g+=r.css(a,c+ca[f],!0,e)),d?("content"===c&&(g-=r.css(a,"padding"+ca[f],!0,e)),"margin"!==c&&(g-=r.css(a,"border"+ca[f]+"Width",!0,e))):(g+=r.css(a,"padding"+ca[f],!0,e),"padding"!==c&&(g+=r.css(a,"border"+ca[f]+"Width",!0,e)));return g}function $a(a,b,c){var d,e=Na(a),f=Oa(a,b,e),g="border-box"===r.css(a,"boxSizing",!1,e);return Ma.test(f)?f:(d=g&&(o.boxSizingReliable()||f===a.style[b]),"auto"===f&&(f=a["offset"+b[0].toUpperCase()+b.slice(1)]),f=parseFloat(f)||0,f+Za(a,b,c||(g?"border":"content"),d,e)+"px")}r.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Oa(a,"opacity");return""===c?"1":c}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=r.camelCase(b),i=Ra.test(b),j=a.style;return i||(b=Xa(h)),g=r.cssHooks[b]||r.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:j[b]:(f=typeof c,"string"===f&&(e=ba.exec(c))&&e[1]&&(c=fa(a,b,e),f="number"),null!=c&&c===c&&("number"===f&&(c+=e&&e[3]||(r.cssNumber[h]?"":"px")),o.clearCloneStyle||""!==c||0!==b.indexOf("background")||(j[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i?j.setProperty(b,c):j[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=r.camelCase(b),i=Ra.test(b);return i||(b=Xa(h)),g=r.cssHooks[b]||r.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=Oa(a,b,d)),"normal"===e&&b in Ta&&(e=Ta[b]),""===c||c?(f=parseFloat(e),c===!0||isFinite(f)?f||0:e):e}}),r.each(["height","width"],function(a,b){r.cssHooks[b]={get:function(a,c,d){if(c)return!Qa.test(r.css(a,"display"))||a.getClientRects().length&&a.getBoundingClientRect().width?$a(a,b,d):ea(a,Sa,function(){return $a(a,b,d)})},set:function(a,c,d){var e,f=d&&Na(a),g=d&&Za(a,b,d,"border-box"===r.css(a,"boxSizing",!1,f),f);return g&&(e=ba.exec(c))&&"px"!==(e[3]||"px")&&(a.style[b]=c,c=r.css(a,b)),Ya(a,c,g)}}}),r.cssHooks.marginLeft=Pa(o.reliableMarginLeft,function(a,b){if(b)return(parseFloat(Oa(a,"marginLeft"))||a.getBoundingClientRect().left-ea(a,{marginLeft:0},function(){return a.getBoundingClientRect().left}))+"px"}),r.each({margin:"",padding:"",border:"Width"},function(a,b){r.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];d<4;d++)e[a+ca[d]+b]=f[d]||f[d-2]||f[0];return e}},La.test(a)||(r.cssHooks[a+b].set=Ya)}),r.fn.extend({css:function(a,b){return T(this,function(a,b,c){var d,e,f={},g=0;if(Array.isArray(b)){for(d=Na(a),e=b.length;g<e;g++)f[b[g]]=r.css(a,b[g],!1,d);return f}return void 0!==c?r.style(a,b,c):r.css(a,b)},a,b,arguments.length>1)}});function _a(a,b,c,d,e){return new _a.prototype.init(a,b,c,d,e)}r.Tween=_a,_a.prototype={constructor:_a,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=_a.propHooks[this.prop];return a&&a.get?a.get(this):_a.propHooks._default.get(this)},run:function(a){var b,c=_a.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):_a.propHooks._default.set(this),this}},_a.prototype.init.prototype=_a.prototype,_a.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},_a.propHooks.scrollTop=_a.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=_a.prototype.init,r.fx.step={};var ab,bb,cb=/^(?:toggle|show|hide)$/,db=/queueHooks$/;function eb(){bb&&(d.hidden===!1&&a.requestAnimationFrame?a.requestAnimationFrame(eb):a.setTimeout(eb,r.fx.interval),r.fx.tick())}function fb(){return a.setTimeout(function(){ab=void 0}),ab=r.now()}function gb(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ca[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function hb(a,b,c){for(var d,e=(kb.tweeners[b]||[]).concat(kb.tweeners["*"]),f=0,g=e.length;f<g;f++)if(d=e[f].call(c,b,a))return d}function ib(a,b,c){var d,e,f,g,h,i,j,k,l="width"in b||"height"in b,m=this,n={},o=a.style,p=a.nodeType&&da(a),q=W.get(a,"fxshow");c.queue||(g=r._queueHooks(a,"fx"),null==g.unqueued&&(g.unqueued=0,h=g.empty.fire,g.empty.fire=function(){g.unqueued||h()}),g.unqueued++,m.always(function(){m.always(function(){g.unqueued--,r.queue(a,"fx").length||g.empty.fire()})}));for(d in b)if(e=b[d],cb.test(e)){if(delete b[d],f=f||"toggle"===e,e===(p?"hide":"show")){if("show"!==e||!q||void 0===q[d])continue;p=!0}n[d]=q&&q[d]||r.style(a,d)}if(i=!r.isEmptyObject(b),i||!r.isEmptyObject(n)){l&&1===a.nodeType&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=q&&q.display,null==j&&(j=W.get(a,"display")),k=r.css(a,"display"),"none"===k&&(j?k=j:(ia([a],!0),j=a.style.display||j,k=r.css(a,"display"),ia([a]))),("inline"===k||"inline-block"===k&&null!=j)&&"none"===r.css(a,"float")&&(i||(m.done(function(){o.display=j}),null==j&&(k=o.display,j="none"===k?"":k)),o.display="inline-block")),c.overflow&&(o.overflow="hidden",m.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]})),i=!1;for(d in n)i||(q?"hidden"in q&&(p=q.hidden):q=W.access(a,"fxshow",{display:j}),f&&(q.hidden=!p),p&&ia([a],!0),m.done(function(){p||ia([a]),W.remove(a,"fxshow");for(d in n)r.style(a,d,n[d])})),i=hb(p?q[d]:0,d,m),d in q||(q[d]=i.start,p&&(i.end=i.start,i.start=0))}}function jb(a,b){var c,d,e,f,g;for(c in a)if(d=r.camelCase(c),e=b[d],f=a[c],Array.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=r.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function kb(a,b,c){var d,e,f=0,g=kb.prefilters.length,h=r.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=ab||fb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;g<i;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),f<1&&i?c:(i||h.notifyWith(a,[j,1,0]),h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:r.extend({},b),opts:r.extend(!0,{specialEasing:{},easing:r.easing._default},c),originalProperties:b,originalOptions:c,startTime:ab||fb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=r.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;c<d;c++)j.tweens[c].run(1);return b?(h.notifyWith(a,[j,1,0]),h.resolveWith(a,[j,b])):h.rejectWith(a,[j,b]),this}}),k=j.props;for(jb(k,j.opts.specialEasing);f<g;f++)if(d=kb.prefilters[f].call(j,a,k,j.opts))return r.isFunction(d.stop)&&(r._queueHooks(j.elem,j.opts.queue).stop=r.proxy(d.stop,d)),d;return r.map(k,hb,j),r.isFunction(j.opts.start)&&j.opts.start.call(a,j),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always),r.fx.timer(r.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j}r.Animation=r.extend(kb,{tweeners:{"*":[function(a,b){var c=this.createTween(a,b);return fa(c.elem,a,ba.exec(b),c),c}]},tweener:function(a,b){r.isFunction(a)?(b=a,a=["*"]):a=a.match(L);for(var c,d=0,e=a.length;d<e;d++)c=a[d],kb.tweeners[c]=kb.tweeners[c]||[],kb.tweeners[c].unshift(b)},prefilters:[ib],prefilter:function(a,b){b?kb.prefilters.unshift(a):kb.prefilters.push(a)}}),r.speed=function(a,b,c){var d=a&&"object"==typeof a?r.extend({},a):{complete:c||!c&&b||r.isFunction(a)&&a,duration:a,easing:c&&b||b&&!r.isFunction(b)&&b};return r.fx.off?d.duration=0:"number"!=typeof d.duration&&(d.duration in r.fx.speeds?d.duration=r.fx.speeds[d.duration]:d.duration=r.fx.speeds._default),null!=d.queue&&d.queue!==!0||(d.queue="fx"),d.old=d.complete,d.complete=function(){r.isFunction(d.old)&&d.old.call(this),d.queue&&r.dequeue(this,d.queue)},d},r.fn.extend({fadeTo:function(a,b,c,d){return this.filter(da).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=r.isEmptyObject(a),f=r.speed(b,c,d),g=function(){var b=kb(this,r.extend({},a),f);(e||W.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=r.timers,g=W.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&db.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));!b&&c||r.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=W.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=r.timers,g=d?d.length:0;for(c.finish=!0,r.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;b<g;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),r.each(["toggle","show","hide"],function(a,b){var c=r.fn[b];r.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(gb(b,!0),a,d,e)}}),r.each({slideDown:gb("show"),slideUp:gb("hide"),slideToggle:gb("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){r.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),r.timers=[],r.fx.tick=function(){var a,b=0,c=r.timers;for(ab=r.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||r.fx.stop(),ab=void 0},r.fx.timer=function(a){r.timers.push(a),r.fx.start()},r.fx.interval=13,r.fx.start=function(){bb||(bb=!0,eb())},r.fx.stop=function(){bb=null},r.fx.speeds={slow:600,fast:200,_default:400},r.fn.delay=function(b,c){return b=r.fx?r.fx.speeds[b]||b:b,c=c||"fx",this.queue(c,function(c,d){var e=a.setTimeout(c,b);d.stop=function(){a.clearTimeout(e)}})},function(){var a=d.createElement("input"),b=d.createElement("select"),c=b.appendChild(d.createElement("option"));a.type="checkbox",o.checkOn=""!==a.value,o.optSelected=c.selected,a=d.createElement("input"),a.value="t",a.type="radio",o.radioValue="t"===a.value}();var lb,mb=r.expr.attrHandle;r.fn.extend({attr:function(a,b){return T(this,r.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?lb:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),
+null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&B(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(L);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),lb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=mb[b]||r.find.attr;mb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=mb[g],mb[g]=e,e=null!=c(a,b,d)?g:null,mb[g]=f),e}});var nb=/^(?:input|select|textarea|button)$/i,ob=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return T(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):nb.test(a.nodeName)||ob.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function pb(a){var b=a.match(L)||[];return b.join(" ")}function qb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,qb(this)))});if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,qb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,qb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(L)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=qb(this),b&&W.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":W.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+pb(qb(c))+" ").indexOf(b)>-1)return!0;return!1}});var rb=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":Array.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(rb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:pb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d<i;d++)if(c=e[d],(c.selected||d===f)&&!c.disabled&&(!c.parentNode.disabled||!B(c.parentNode,"optgroup"))){if(b=r(c).val(),g)return b;h.push(b)}return h},set:function(a,b){var c,d,e=a.options,f=r.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=r.inArray(r.valHooks.option.get(d),f)>-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(Array.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var sb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!sb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,sb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(W.get(h,"events")||{})[b.type]&&W.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&U(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!U(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=W.access(d,b);e||d.addEventListener(a,c,!0),W.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=W.access(d,b)-1;e?W.access(d,b,e):(d.removeEventListener(a,c,!0),W.remove(d,b))}}});var tb=a.location,ub=r.now(),vb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var wb=/\[\]$/,xb=/\r?\n/g,yb=/^(?:submit|button|image|reset|file)$/i,zb=/^(?:input|select|textarea|keygen)/i;function Ab(a,b,c,d){var e;if(Array.isArray(b))r.each(b,function(b,e){c||wb.test(a)?d(a,e):Ab(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)Ab(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(Array.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)Ab(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&zb.test(this.nodeName)&&!yb.test(a)&&(this.checked||!ja.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:Array.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(xb,"\r\n")}}):{name:b.name,value:c.replace(xb,"\r\n")}}).get()}});var Bb=/%20/g,Cb=/#.*$/,Db=/([?&])_=[^&]*/,Eb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Fb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Gb=/^(?:GET|HEAD)$/,Hb=/^\/\//,Ib={},Jb={},Kb="*/".concat("*"),Lb=d.createElement("a");Lb.href=tb.href;function Mb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(L)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Nb(a,b,c,d){var e={},f=a===Jb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Ob(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Pb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Qb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:tb.href,type:"GET",isLocal:Fb.test(tb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Kb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Ob(Ob(a,r.ajaxSettings),b):Ob(r.ajaxSettings,a)},ajaxPrefilter:Mb(Ib),ajaxTransport:Mb(Jb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Eb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||tb.href)+"").replace(Hb,tb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(L)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Lb.protocol+"//"+Lb.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Nb(Ib,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Gb.test(o.type),f=o.url.replace(Cb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(Bb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(vb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Db,"$1"),n=(vb.test(f)?"&":"?")+"_="+ub++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Kb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Nb(Jb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Pb(o,y,d)),v=Qb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Rb={0:200,1223:204},Sb=r.ajaxSettings.xhr();o.cors=!!Sb&&"withCredentials"in Sb,o.ajax=Sb=!!Sb,r.ajaxTransport(function(b){var c,d;if(o.cors||Sb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Rb[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r("<script>").prop({charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&f("error"===a.type?404:200,a.type)}),d.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Tb=[],Ub=/(=)\?(?=&|$)|\?\?/;r.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Tb.pop()||r.expando+"_"+ub++;return this[a]=!0,a}}),r.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Ub.test(b.url)?"url":"string"==typeof b.data&&0===(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ub.test(b.data)&&"data");if(h||"jsonp"===b.dataTypes[0])return e=b.jsonpCallback=r.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Ub,"$1"+e):b.jsonp!==!1&&(b.url+=(vb.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||r.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){void 0===f?r(a).removeProp(e):a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Tb.push(e)),g&&r.isFunction(f)&&f(g[0]),g=f=void 0}),"script"}),o.createHTMLDocument=function(){var a=d.implementation.createHTMLDocument("").body;return a.innerHTML="<form></form><form></form>",2===a.childNodes.length}(),r.parseHTML=function(a,b,c){if("string"!=typeof a)return[];"boolean"==typeof b&&(c=b,b=!1);var e,f,g;return b||(o.createHTMLDocument?(b=d.implementation.createHTMLDocument(""),e=b.createElement("base"),e.href=d.location.href,b.head.appendChild(e)):b=d),f=C.exec(a),g=!c&&[],f?[b.createElement(f[1])]:(f=qa([a],b,g),g&&g.length&&r(g).remove(),r.merge([],f.childNodes))},r.fn.load=function(a,b,c){var d,e,f,g=this,h=a.indexOf(" ");return h>-1&&(d=pb(a.slice(h)),a=a.slice(0,h)),r.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&r.ajax({url:a,type:e||"GET",dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?r("<div>").append(r.parseHTML(a)).find(d):a)}).always(c&&function(a,b){g.each(function(){c.apply(this,f||[a.responseText,b,a])})}),this},r.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){r.fn[b]=function(a){return this.on(b,a)}}),r.expr.pseudos.animated=function(a){return r.grep(r.timers,function(b){return a===b.elem}).length},r.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=r.css(a,"position"),l=r(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=r.css(a,"top"),i=r.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),r.isFunction(b)&&(b=b.call(a,c,r.extend({},h))),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},r.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){r.offset.setOffset(this,a,b)});var b,c,d,e,f=this[0];if(f)return f.getClientRects().length?(d=f.getBoundingClientRect(),b=f.ownerDocument,c=b.documentElement,e=b.defaultView,{top:d.top+e.pageYOffset-c.clientTop,left:d.left+e.pageXOffset-c.clientLeft}):{top:0,left:0}},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===r.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),B(a[0],"html")||(d=a.offset()),d={top:d.top+r.css(a[0],"borderTopWidth",!0),left:d.left+r.css(a[0],"borderLeftWidth",!0)}),{top:b.top-d.top-r.css(c,"marginTop",!0),left:b.left-d.left-r.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent;while(a&&"static"===r.css(a,"position"))a=a.offsetParent;return a||ra})}}),r.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c="pageYOffset"===b;r.fn[a]=function(d){return T(this,function(a,d,e){var f;return r.isWindow(a)?f=a:9===a.nodeType&&(f=a.defaultView),void 0===e?f?f[b]:a[d]:void(f?f.scrollTo(c?f.pageXOffset:e,c?e:f.pageYOffset):a[d]=e)},a,d,arguments.length)}}),r.each(["top","left"],function(a,b){r.cssHooks[b]=Pa(o.pixelPosition,function(a,c){if(c)return c=Oa(a,b),Ma.test(c)?r(a).position()[b]+"px":c})}),r.each({Height:"height",Width:"width"},function(a,b){r.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){r.fn[d]=function(e,f){var g=arguments.length&&(c||"boolean"!=typeof e),h=c||(e===!0||f===!0?"margin":"border");return T(this,function(b,c,e){var f;return r.isWindow(b)?0===d.indexOf("outer")?b["inner"+a]:b.document.documentElement["client"+a]:9===b.nodeType?(f=b.documentElement,Math.max(b.body["scroll"+a],f["scroll"+a],b.body["offset"+a],f["offset"+a],f["client"+a])):void 0===e?r.css(b,c,h):r.style(b,c,e,h)},b,g?e:void 0,g)}})}),r.fn.extend({bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}}),r.holdReady=function(a){a?r.readyWait++:r.ready(!0)},r.isArray=Array.isArray,r.parseJSON=JSON.parse,r.nodeName=B,"function"==typeof define&&define.amd&&define("jquery",[],function(){return r});var Vb=a.jQuery,Wb=a.$;return r.noConflict=function(b){return a.$===r&&(a.$=Wb),b&&a.jQuery===r&&(a.jQuery=Vb),r},b||(a.jQuery=a.$=r),r});
diff --git a/simpleperf/scripts/inferno/jqueryui/jquery-ui.min.css b/simpleperf/scripts/inferno/jqueryui/jquery-ui.min.css
new file mode 100644
index 0000000..22ca1d3
--- /dev/null
+++ b/simpleperf/scripts/inferno/jqueryui/jquery-ui.min.css
@@ -0,0 +1,7 @@
+/*! jQuery UI - v1.12.1 - 2017-08-18
+* http://jqueryui.com
+* Includes: core.css, resizable.css, theme.css
+* To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=base&cornerRadiusShadow=8px&offsetLeftShadow=0px&offsetTopShadow=0px&thicknessShadow=5px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=666666&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cc0000&fcError=5f3f3f&borderColorError=f1a899&bgTextureError=flat&bgColorError=fddfdf&iconColorHighlight=777620&fcHighlight=777620&borderColorHighlight=dad55e&bgTextureHighlight=flat&bgColorHighlight=fffa90&iconColorActive=ffffff&fcActive=ffffff&borderColorActive=003eff&bgTextureActive=flat&bgColorActive=007fff&iconColorHover=555555&fcHover=2b2b2b&borderColorHover=cccccc&bgTextureHover=flat&bgColorHover=ededed&iconColorDefault=777777&fcDefault=454545&borderColorDefault=c5c5c5&bgTextureDefault=flat&bgColorDefault=f6f6f6&iconColorContent=444444&fcContent=333333&borderColorContent=dddddd&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=444444&fcHeader=333333&borderColorHeader=dddddd&bgTextureHeader=flat&bgColorHeader=e9e9e9&cornerRadius=3px&fwDefault=normal&fsDefault=1em&ffDefault=Arial%2CHelvetica%2Csans-serif
+* Copyright jQuery Foundation and other contributors; Licensed MIT */
+
+.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important;pointer-events:none}.ui-icon{display:inline-block;vertical-align:middle;margin-top:-.25em;position:relative;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-icon-block{left:50%;margin-left:-8px;display:block}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-widget{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget.ui-widget-content{border:1px solid #c5c5c5}.ui-widget-content{border:1px solid #ddd;background:#fff;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #ddd;background:#e9e9e9;color:#333;font-weight:bold}.ui-widget-header a{color:#333}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default,.ui-button,html .ui-button.ui-state-disabled:hover,html .ui-button.ui-state-disabled:active{border:1px solid #c5c5c5;background:#f6f6f6;font-weight:normal;color:#454545}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited,a.ui-button,a:link.ui-button,a:visited.ui-button,.ui-button{color:#454545;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus,.ui-button:hover,.ui-button:focus{border:1px solid #ccc;background:#ededed;font-weight:normal;color:#2b2b2b}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited,a.ui-button:hover,a.ui-button:focus{color:#2b2b2b;text-decoration:none}.ui-visual-focus{box-shadow:0 0 3px 1px rgb(94,158,214)}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active,a.ui-button:active,.ui-button:active,.ui-button.ui-state-active:hover{border:1px solid #003eff;background:#007fff;font-weight:normal;color:#fff}.ui-icon-background,.ui-state-active .ui-icon-background{border:#003eff;background-color:#fff}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#fff;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #dad55e;background:#fffa90;color:#777620}.ui-state-checked{border:1px solid #dad55e;background:#fffa90}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#777620}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #f1a899;background:#fddfdf;color:#5f3f3f}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#5f3f3f}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#5f3f3f}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon,.ui-button:hover .ui-icon,.ui-button:focus .ui-icon{background-image:url("images/ui-icons_555555_256x240.png")}.ui-state-active .ui-icon,.ui-button:active .ui-icon{background-image:url("images/ui-icons_ffffff_256x240.png")}.ui-state-highlight .ui-icon,.ui-button .ui-state-highlight.ui-icon{background-image:url("images/ui-icons_777620_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_cc0000_256x240.png")}.ui-button .ui-icon{background-image:url("images/ui-icons_777777_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-caret-1-n{background-position:0 0}.ui-icon-caret-1-ne{background-position:-16px 0}.ui-icon-caret-1-e{background-position:-32px 0}.ui-icon-caret-1-se{background-position:-48px 0}.ui-icon-caret-1-s{background-position:-65px 0}.ui-icon-caret-1-sw{background-position:-80px 0}.ui-icon-caret-1-w{background-position:-96px 0}.ui-icon-caret-1-nw{background-position:-112px 0}.ui-icon-caret-2-n-s{background-position:-128px 0}.ui-icon-caret-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-65px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-65px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:1px -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:3px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:3px}.ui-widget-overlay{background:#aaa;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{-webkit-box-shadow:0 0 5px #666;box-shadow:0 0 5px #666}
\ No newline at end of file
diff --git a/simpleperf/scripts/inferno/jqueryui/jquery-ui.min.js b/simpleperf/scripts/inferno/jqueryui/jquery-ui.min.js
new file mode 100644
index 0000000..a26b9c0
--- /dev/null
+++ b/simpleperf/scripts/inferno/jqueryui/jquery-ui.min.js
@@ -0,0 +1,6 @@
+/*! jQuery UI - v1.12.1 - 2017-08-18
+* http://jqueryui.com
+* Includes: widget.js, disable-selection.js, widgets/resizable.js, widgets/mouse.js
+* Copyright jQuery Foundation and other contributors; Licensed MIT */
+
+(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)})(function(t){t.ui=t.ui||{},t.ui.version="1.12.1";var e=0,i=Array.prototype.slice;t.cleanData=function(e){return function(i){var s,n,o;for(o=0;null!=(n=i[o]);o++)try{s=t._data(n,"events"),s&&s.remove&&t(n).triggerHandler("remove")}catch(a){}e(i)}}(t.cleanData),t.widget=function(e,i,s){var n,o,a,r={},l=e.split(".")[0];e=e.split(".")[1];var h=l+"-"+e;return s||(s=i,i=t.Widget),t.isArray(s)&&(s=t.extend.apply(null,[{}].concat(s))),t.expr[":"][h.toLowerCase()]=function(e){return!!t.data(e,h)},t[l]=t[l]||{},n=t[l][e],o=t[l][e]=function(t,e){return this._createWidget?(arguments.length&&this._createWidget(t,e),void 0):new o(t,e)},t.extend(o,n,{version:s.version,_proto:t.extend({},s),_childConstructors:[]}),a=new i,a.options=t.widget.extend({},a.options),t.each(s,function(e,s){return t.isFunction(s)?(r[e]=function(){function t(){return i.prototype[e].apply(this,arguments)}function n(t){return i.prototype[e].apply(this,t)}return function(){var e,i=this._super,o=this._superApply;return this._super=t,this._superApply=n,e=s.apply(this,arguments),this._super=i,this._superApply=o,e}}(),void 0):(r[e]=s,void 0)}),o.prototype=t.widget.extend(a,{widgetEventPrefix:n?a.widgetEventPrefix||e:e},r,{constructor:o,namespace:l,widgetName:e,widgetFullName:h}),n?(t.each(n._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete n._childConstructors):i._childConstructors.push(o),t.widget.bridge(e,o),o},t.widget.extend=function(e){for(var s,n,o=i.call(arguments,1),a=0,r=o.length;r>a;a++)for(s in o[a])n=o[a][s],o[a].hasOwnProperty(s)&&void 0!==n&&(e[s]=t.isPlainObject(n)?t.isPlainObject(e[s])?t.widget.extend({},e[s],n):t.widget.extend({},n):n);return e},t.widget.bridge=function(e,s){var n=s.prototype.widgetFullName||e;t.fn[e]=function(o){var a="string"==typeof o,r=i.call(arguments,1),l=this;return a?this.length||"instance"!==o?this.each(function(){var i,s=t.data(this,n);return"instance"===o?(l=s,!1):s?t.isFunction(s[o])&&"_"!==o.charAt(0)?(i=s[o].apply(s,r),i!==s&&void 0!==i?(l=i&&i.jquery?l.pushStack(i.get()):i,!1):void 0):t.error("no such method '"+o+"' for "+e+" widget instance"):t.error("cannot call methods on "+e+" prior to initialization; "+"attempted to call method '"+o+"'")}):l=void 0:(r.length&&(o=t.widget.extend.apply(null,[o].concat(r))),this.each(function(){var e=t.data(this,n);e?(e.option(o||{}),e._init&&e._init()):t.data(this,n,new s(o,this))})),l}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{classes:{},disabled:!1,create:null},_createWidget:function(i,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=e++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),this.classesElementLookup={},s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),i),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){var e=this;this._destroy(),t.each(this.classesElementLookup,function(t,i){e._removeClass(i,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:t.noop,widget:function(){return this.element},option:function(e,i){var s,n,o,a=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(a={},s=e.split("."),e=s.shift(),s.length){for(n=a[e]=t.widget.extend({},this.options[e]),o=0;s.length-1>o;o++)n[s[o]]=n[s[o]]||{},n=n[s[o]];if(e=s.pop(),1===arguments.length)return void 0===n[e]?null:n[e];n[e]=i}else{if(1===arguments.length)return void 0===this.options[e]?null:this.options[e];a[e]=i}return this._setOptions(a),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(e){var i,s,n;for(i in e)n=this.classesElementLookup[i],e[i]!==this.options.classes[i]&&n&&n.length&&(s=t(n.get()),this._removeClass(n,i),s.addClass(this._classes({element:s,keys:i,classes:e,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(e){function i(i,o){var a,r;for(r=0;i.length>r;r++)a=n.classesElementLookup[i[r]]||t(),a=e.add?t(t.unique(a.get().concat(e.element.get()))):t(a.not(e.element).get()),n.classesElementLookup[i[r]]=a,s.push(i[r]),o&&e.classes[i[r]]&&s.push(e.classes[i[r]])}var s=[],n=this;return e=t.extend({element:this.element,classes:this.options.classes||{}},e),this._on(e.element,{remove:"_untrackClassesElement"}),e.keys&&i(e.keys.match(/\S+/g)||[],!0),e.extra&&i(e.extra.match(/\S+/g)||[]),s.join(" ")},_untrackClassesElement:function(e){var i=this;t.each(i.classesElementLookup,function(s,n){-1!==t.inArray(e.target,n)&&(i.classesElementLookup[s]=t(n.not(e.target).get()))})},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(t,e,i,s){s="boolean"==typeof s?s:i;var n="string"==typeof t||null===t,o={extra:n?e:i,keys:n?t:e,element:n?this.element:t,add:s};return o.element.toggleClass(this._classes(o),s),this},_on:function(e,i,s){var n,o=this;"boolean"!=typeof e&&(s=i,i=e,e=!1),s?(i=n=t(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),t.each(s,function(s,a){function r(){return e||o.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof a?o[a]:a).apply(o,arguments):void 0}"string"!=typeof a&&(r.guid=a.guid=a.guid||r.guid||t.guid++);var l=s.match(/^([\w:-]*)\s*(.*)$/),h=l[1]+o.eventNamespace,c=l[2];c?n.on(h,c,r):i.on(h,r)})},_off:function(e,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.off(i).off(i),this.bindings=t(this.bindings.not(e).get()),this.focusable=t(this.focusable.not(e).get()),this.hoverable=t(this.hoverable.not(e).get())},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){this._addClass(t(e.currentTarget),null,"ui-state-hover")},mouseleave:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){this._addClass(t(e.currentTarget),null,"ui-state-focus")},focusout:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}}),t.widget,t.fn.extend({disableSelection:function(){var t="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.on(t+".ui-disableSelection",function(t){t.preventDefault()})}}(),enableSelection:function(){return this.off(".ui-disableSelection")}}),t.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase());var s=!1;t(document).on("mouseup",function(){s=!1}),t.widget("ui.mouse",{version:"1.12.1",options:{cancel:"input, textarea, button, select, option",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.on("mousedown."+this.widgetName,function(t){return e._mouseDown(t)}).on("click."+this.widgetName,function(i){return!0===t.data(i.target,e.widgetName+".preventClickEvent")?(t.removeData(i.target,e.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):void 0}),this.started=!1},_mouseDestroy:function(){this.element.off("."+this.widgetName),this._mouseMoveDelegate&&this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(e){if(!s){this._mouseMoved=!1,this._mouseStarted&&this._mouseUp(e),this._mouseDownEvent=e;var i=this,n=1===e.which,o="string"==typeof this.options.cancel&&e.target.nodeName?t(e.target).closest(this.options.cancel).length:!1;return n&&!o&&this._mouseCapture(e)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){i.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(e)!==!1,!this._mouseStarted)?(e.preventDefault(),!0):(!0===t.data(e.target,this.widgetName+".preventClickEvent")&&t.removeData(e.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(t){return i._mouseMove(t)},this._mouseUpDelegate=function(t){return i._mouseUp(t)},this.document.on("mousemove."+this.widgetName,this._mouseMoveDelegate).on("mouseup."+this.widgetName,this._mouseUpDelegate),e.preventDefault(),s=!0,!0)):!0}},_mouseMove:function(e){if(this._mouseMoved){if(t.ui.ie&&(!document.documentMode||9>document.documentMode)&&!e.button)return this._mouseUp(e);if(!e.which)if(e.originalEvent.altKey||e.originalEvent.ctrlKey||e.originalEvent.metaKey||e.originalEvent.shiftKey)this.ignoreMissingWhich=!0;else if(!this.ignoreMissingWhich)return this._mouseUp(e)}return(e.which||e.button)&&(this._mouseMoved=!0),this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,e)!==!1,this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted)},_mouseUp:function(e){this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(e)),this._mouseDelayTimer&&(clearTimeout(this._mouseDelayTimer),delete this._mouseDelayTimer),this.ignoreMissingWhich=!1,s=!1,e.preventDefault()},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),t.ui.plugin={add:function(e,i,s){var n,o=t.ui[e].prototype;for(n in s)o.plugins[n]=o.plugins[n]||[],o.plugins[n].push([i,s[n]])},call:function(t,e,i,s){var n,o=t.plugins[e];if(o&&(s||t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType))for(n=0;o.length>n;n++)t.options[o[n][0]]&&o[n][1].apply(t.element,i)}},t.widget("ui.resizable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,classes:{"ui-resizable-se":"ui-icon ui-icon-gripsmall-diagonal-se"},containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_num:function(t){return parseFloat(t)||0},_isNumber:function(t){return!isNaN(parseFloat(t))},_hasScroll:function(e,i){if("hidden"===t(e).css("overflow"))return!1;var s=i&&"left"===i?"scrollLeft":"scrollTop",n=!1;return e[s]>0?!0:(e[s]=1,n=e[s]>0,e[s]=0,n)},_create:function(){var e,i=this.options,s=this;this._addClass("ui-resizable"),t.extend(this,{_aspectRatio:!!i.aspectRatio,aspectRatio:i.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:i.helper||i.ghost||i.animate?i.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/^(canvas|textarea|input|select|button|img)$/i)&&(this.element.wrap(t("<div class='ui-wrapper' style='overflow: hidden;'></div>").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.resizable("instance")),this.elementIsWrapper=!0,e={marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom"),marginLeft:this.originalElement.css("marginLeft")},this.element.css(e),this.originalElement.css("margin",0),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css(e),this._proportionallyResize()),this._setupHandles(),i.autoHide&&t(this.element).on("mouseenter",function(){i.disabled||(s._removeClass("ui-resizable-autohide"),s._handles.show())}).on("mouseleave",function(){i.disabled||s.resizing||(s._addClass("ui-resizable-autohide"),s._handles.hide())}),this._mouseInit()},_destroy:function(){this._mouseDestroy();var e,i=function(e){t(e).removeData("resizable").removeData("ui-resizable").off(".resizable").find(".ui-resizable-handle").remove()};return this.elementIsWrapper&&(i(this.element),e=this.element,this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")}).insertAfter(e),e.remove()),this.originalElement.css("resize",this.originalResizeStyle),i(this.originalElement),this},_setOption:function(t,e){switch(this._super(t,e),t){case"handles":this._removeHandles(),this._setupHandles();break;default:}},_setupHandles:function(){var e,i,s,n,o,a=this.options,r=this;if(this.handles=a.handles||(t(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this._handles=t(),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),s=this.handles.split(","),this.handles={},i=0;s.length>i;i++)e=t.trim(s[i]),n="ui-resizable-"+e,o=t("<div>"),this._addClass(o,"ui-resizable-handle "+n),o.css({zIndex:a.zIndex}),this.handles[e]=".ui-resizable-"+e,this.element.append(o);this._renderAxis=function(e){var i,s,n,o;e=e||this.element;for(i in this.handles)this.handles[i].constructor===String?this.handles[i]=this.element.children(this.handles[i]).first().show():(this.handles[i].jquery||this.handles[i].nodeType)&&(this.handles[i]=t(this.handles[i]),this._on(this.handles[i],{mousedown:r._mouseDown})),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/^(textarea|input|select|button)$/i)&&(s=t(this.handles[i],this.element),o=/sw|ne|nw|se|n|s/.test(i)?s.outerHeight():s.outerWidth(),n=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join(""),e.css(n,o),this._proportionallyResize()),this._handles=this._handles.add(this.handles[i])},this._renderAxis(this.element),this._handles=this._handles.add(this.element.find(".ui-resizable-handle")),this._handles.disableSelection(),this._handles.on("mouseover",function(){r.resizing||(this.className&&(o=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),r.axis=o&&o[1]?o[1]:"se")}),a.autoHide&&(this._handles.hide(),this._addClass("ui-resizable-autohide"))},_removeHandles:function(){this._handles.remove()},_mouseCapture:function(e){var i,s,n=!1;for(i in this.handles)s=t(this.handles[i])[0],(s===e.target||t.contains(s,e.target))&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(e){var i,s,n,o=this.options,a=this.element;return this.resizing=!0,this._renderProxy(),i=this._num(this.helper.css("left")),s=this._num(this.helper.css("top")),o.containment&&(i+=t(o.containment).scrollLeft()||0,s+=t(o.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:i,top:s},this.size=this._helper?{width:this.helper.width(),height:this.helper.height()}:{width:a.width(),height:a.height()},this.originalSize=this._helper?{width:a.outerWidth(),height:a.outerHeight()}:{width:a.width(),height:a.height()},this.sizeDiff={width:a.outerWidth()-a.width(),height:a.outerHeight()-a.height()},this.originalPosition={left:i,top:s},this.originalMousePosition={left:e.pageX,top:e.pageY},this.aspectRatio="number"==typeof o.aspectRatio?o.aspectRatio:this.originalSize.width/this.originalSize.height||1,n=t(".ui-resizable-"+this.axis).css("cursor"),t("body").css("cursor","auto"===n?this.axis+"-resize":n),this._addClass("ui-resizable-resizing"),this._propagate("start",e),!0},_mouseDrag:function(e){var i,s,n=this.originalMousePosition,o=this.axis,a=e.pageX-n.left||0,r=e.pageY-n.top||0,l=this._change[o];return this._updatePrevProperties(),l?(i=l.apply(this,[e,a,r]),this._updateVirtualBoundaries(e.shiftKey),(this._aspectRatio||e.shiftKey)&&(i=this._updateRatio(i,e)),i=this._respectSize(i,e),this._updateCache(i),this._propagate("resize",e),s=this._applyChanges(),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),t.isEmptyObject(s)||(this._updatePrevProperties(),this._trigger("resize",e,this.ui()),this._applyChanges()),!1):!1},_mouseStop:function(e){this.resizing=!1;var i,s,n,o,a,r,l,h=this.options,c=this;return this._helper&&(i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),n=s&&this._hasScroll(i[0],"left")?0:c.sizeDiff.height,o=s?0:c.sizeDiff.width,a={width:c.helper.width()-o,height:c.helper.height()-n},r=parseFloat(c.element.css("left"))+(c.position.left-c.originalPosition.left)||null,l=parseFloat(c.element.css("top"))+(c.position.top-c.originalPosition.top)||null,h.animate||this.element.css(t.extend(a,{top:l,left:r})),c.helper.height(c.size.height),c.helper.width(c.size.width),this._helper&&!h.animate&&this._proportionallyResize()),t("body").css("cursor","auto"),this._removeClass("ui-resizable-resizing"),this._propagate("stop",e),this._helper&&this.helper.remove(),!1},_updatePrevProperties:function(){this.prevPosition={top:this.position.top,left:this.position.left},this.prevSize={width:this.size.width,height:this.size.height}},_applyChanges:function(){var t={};return this.position.top!==this.prevPosition.top&&(t.top=this.position.top+"px"),this.position.left!==this.prevPosition.left&&(t.left=this.position.left+"px"),this.size.width!==this.prevSize.width&&(t.width=this.size.width+"px"),this.size.height!==this.prevSize.height&&(t.height=this.size.height+"px"),this.helper.css(t),t},_updateVirtualBoundaries:function(t){var e,i,s,n,o,a=this.options;o={minWidth:this._isNumber(a.minWidth)?a.minWidth:0,maxWidth:this._isNumber(a.maxWidth)?a.maxWidth:1/0,minHeight:this._isNumber(a.minHeight)?a.minHeight:0,maxHeight:this._isNumber(a.maxHeight)?a.maxHeight:1/0},(this._aspectRatio||t)&&(e=o.minHeight*this.aspectRatio,s=o.minWidth/this.aspectRatio,i=o.maxHeight*this.aspectRatio,n=o.maxWidth/this.aspectRatio,e>o.minWidth&&(o.minWidth=e),s>o.minHeight&&(o.minHeight=s),o.maxWidth>i&&(o.maxWidth=i),o.maxHeight>n&&(o.maxHeight=n)),this._vBoundaries=o},_updateCache:function(t){this.offset=this.helper.offset(),this._isNumber(t.left)&&(this.position.left=t.left),this._isNumber(t.top)&&(this.position.top=t.top),this._isNumber(t.height)&&(this.size.height=t.height),this._isNumber(t.width)&&(this.size.width=t.width)},_updateRatio:function(t){var e=this.position,i=this.size,s=this.axis;return this._isNumber(t.height)?t.width=t.height*this.aspectRatio:this._isNumber(t.width)&&(t.height=t.width/this.aspectRatio),"sw"===s&&(t.left=e.left+(i.width-t.width),t.top=null),"nw"===s&&(t.top=e.top+(i.height-t.height),t.left=e.left+(i.width-t.width)),t},_respectSize:function(t){var e=this._vBoundaries,i=this.axis,s=this._isNumber(t.width)&&e.maxWidth&&e.maxWidth<t.width,n=this._isNumber(t.height)&&e.maxHeight&&e.maxHeight<t.height,o=this._isNumber(t.width)&&e.minWidth&&e.minWidth>t.width,a=this._isNumber(t.height)&&e.minHeight&&e.minHeight>t.height,r=this.originalPosition.left+this.originalSize.width,l=this.originalPosition.top+this.originalSize.height,h=/sw|nw|w/.test(i),c=/nw|ne|n/.test(i);return o&&(t.width=e.minWidth),a&&(t.height=e.minHeight),s&&(t.width=e.maxWidth),n&&(t.height=e.maxHeight),o&&h&&(t.left=r-e.minWidth),s&&h&&(t.left=r-e.maxWidth),a&&c&&(t.top=l-e.minHeight),n&&c&&(t.top=l-e.maxHeight),t.width||t.height||t.left||!t.top?t.width||t.height||t.top||!t.left||(t.left=null):t.top=null,t},_getPaddingPlusBorderDimensions:function(t){for(var e=0,i=[],s=[t.css("borderTopWidth"),t.css("borderRightWidth"),t.css("borderBottomWidth"),t.css("borderLeftWidth")],n=[t.css("paddingTop"),t.css("paddingRight"),t.css("paddingBottom"),t.css("paddingLeft")];4>e;e++)i[e]=parseFloat(s[e])||0,i[e]+=parseFloat(n[e])||0;return{height:i[0]+i[2],width:i[1]+i[3]}},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var t,e=0,i=this.helper||this.element;this._proportionallyResizeElements.length>e;e++)t=this._proportionallyResizeElements[e],this.outerDimensions||(this.outerDimensions=this._getPaddingPlusBorderDimensions(t)),t.css({height:i.height()-this.outerDimensions.height||0,width:i.width()-this.outerDimensions.width||0})},_renderProxy:function(){var e=this.element,i=this.options;this.elementOffset=e.offset(),this._helper?(this.helper=this.helper||t("<div style='overflow:hidden;'></div>"),this._addClass(this.helper,this._helper),this.helper.css({width:this.element.outerWidth(),height:this.element.outerHeight(),position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++i.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(t,e){return{width:this.originalSize.width+e}},w:function(t,e){var i=this.originalSize,s=this.originalPosition;return{left:s.left+e,width:i.width-e}},n:function(t,e,i){var s=this.originalSize,n=this.originalPosition;return{top:n.top+i,height:s.height-i}},s:function(t,e,i){return{height:this.originalSize.height+i}},se:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},sw:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[e,i,s]))},ne:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},nw:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[e,i,s]))}},_propagate:function(e,i){t.ui.plugin.call(this,e,[i,this.ui()]),"resize"!==e&&this._trigger(e,i,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),t.ui.plugin.add("resizable","animate",{stop:function(e){var i=t(this).resizable("instance"),s=i.options,n=i._proportionallyResizeElements,o=n.length&&/textarea/i.test(n[0].nodeName),a=o&&i._hasScroll(n[0],"left")?0:i.sizeDiff.height,r=o?0:i.sizeDiff.width,l={width:i.size.width-r,height:i.size.height-a},h=parseFloat(i.element.css("left"))+(i.position.left-i.originalPosition.left)||null,c=parseFloat(i.element.css("top"))+(i.position.top-i.originalPosition.top)||null;i.element.animate(t.extend(l,c&&h?{top:c,left:h}:{}),{duration:s.animateDuration,easing:s.animateEasing,step:function(){var s={width:parseFloat(i.element.css("width")),height:parseFloat(i.element.css("height")),top:parseFloat(i.element.css("top")),left:parseFloat(i.element.css("left"))};n&&n.length&&t(n[0]).css({width:s.width,height:s.height}),i._updateCache(s),i._propagate("resize",e)}})}}),t.ui.plugin.add("resizable","containment",{start:function(){var e,i,s,n,o,a,r,l=t(this).resizable("instance"),h=l.options,c=l.element,u=h.containment,d=u instanceof t?u.get(0):/parent/.test(u)?c.parent().get(0):u;d&&(l.containerElement=t(d),/document/.test(u)||u===document?(l.containerOffset={left:0,top:0},l.containerPosition={left:0,top:0},l.parentData={element:t(document),left:0,top:0,width:t(document).width(),height:t(document).height()||document.body.parentNode.scrollHeight}):(e=t(d),i=[],t(["Top","Right","Left","Bottom"]).each(function(t,s){i[t]=l._num(e.css("padding"+s))}),l.containerOffset=e.offset(),l.containerPosition=e.position(),l.containerSize={height:e.innerHeight()-i[3],width:e.innerWidth()-i[1]},s=l.containerOffset,n=l.containerSize.height,o=l.containerSize.width,a=l._hasScroll(d,"left")?d.scrollWidth:o,r=l._hasScroll(d)?d.scrollHeight:n,l.parentData={element:d,left:s.left,top:s.top,width:a,height:r}))},resize:function(e){var i,s,n,o,a=t(this).resizable("instance"),r=a.options,l=a.containerOffset,h=a.position,c=a._aspectRatio||e.shiftKey,u={top:0,left:0},d=a.containerElement,p=!0;d[0]!==document&&/static/.test(d.css("position"))&&(u=l),h.left<(a._helper?l.left:0)&&(a.size.width=a.size.width+(a._helper?a.position.left-l.left:a.position.left-u.left),c&&(a.size.height=a.size.width/a.aspectRatio,p=!1),a.position.left=r.helper?l.left:0),h.top<(a._helper?l.top:0)&&(a.size.height=a.size.height+(a._helper?a.position.top-l.top:a.position.top),c&&(a.size.width=a.size.height*a.aspectRatio,p=!1),a.position.top=a._helper?l.top:0),n=a.containerElement.get(0)===a.element.parent().get(0),o=/relative|absolute/.test(a.containerElement.css("position")),n&&o?(a.offset.left=a.parentData.left+a.position.left,a.offset.top=a.parentData.top+a.position.top):(a.offset.left=a.element.offset().left,a.offset.top=a.element.offset().top),i=Math.abs(a.sizeDiff.width+(a._helper?a.offset.left-u.left:a.offset.left-l.left)),s=Math.abs(a.sizeDiff.height+(a._helper?a.offset.top-u.top:a.offset.top-l.top)),i+a.size.width>=a.parentData.width&&(a.size.width=a.parentData.width-i,c&&(a.size.height=a.size.width/a.aspectRatio,p=!1)),s+a.size.height>=a.parentData.height&&(a.size.height=a.parentData.height-s,c&&(a.size.width=a.size.height*a.aspectRatio,p=!1)),p||(a.position.left=a.prevPosition.left,a.position.top=a.prevPosition.top,a.size.width=a.prevSize.width,a.size.height=a.prevSize.height)},stop:function(){var e=t(this).resizable("instance"),i=e.options,s=e.containerOffset,n=e.containerPosition,o=e.containerElement,a=t(e.helper),r=a.offset(),l=a.outerWidth()-e.sizeDiff.width,h=a.outerHeight()-e.sizeDiff.height;e._helper&&!i.animate&&/relative/.test(o.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:l,height:h}),e._helper&&!i.animate&&/static/.test(o.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:l,height:h})}}),t.ui.plugin.add("resizable","alsoResize",{start:function(){var e=t(this).resizable("instance"),i=e.options;t(i.alsoResize).each(function(){var e=t(this);e.data("ui-resizable-alsoresize",{width:parseFloat(e.width()),height:parseFloat(e.height()),left:parseFloat(e.css("left")),top:parseFloat(e.css("top"))})})},resize:function(e,i){var s=t(this).resizable("instance"),n=s.options,o=s.originalSize,a=s.originalPosition,r={height:s.size.height-o.height||0,width:s.size.width-o.width||0,top:s.position.top-a.top||0,left:s.position.left-a.left||0};t(n.alsoResize).each(function(){var e=t(this),s=t(this).data("ui-resizable-alsoresize"),n={},o=e.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];t.each(o,function(t,e){var i=(s[e]||0)+(r[e]||0);i&&i>=0&&(n[e]=i||null)}),e.css(n)})},stop:function(){t(this).removeData("ui-resizable-alsoresize")}}),t.ui.plugin.add("resizable","ghost",{start:function(){var e=t(this).resizable("instance"),i=e.size;e.ghost=e.originalElement.clone(),e.ghost.css({opacity:.25,display:"block",position:"relative",height:i.height,width:i.width,margin:0,left:0,top:0}),e._addClass(e.ghost,"ui-resizable-ghost"),t.uiBackCompat!==!1&&"string"==typeof e.options.ghost&&e.ghost.addClass(this.options.ghost),e.ghost.appendTo(e.helper)},resize:function(){var e=t(this).resizable("instance");e.ghost&&e.ghost.css({position:"relative",height:e.size.height,width:e.size.width})},stop:function(){var e=t(this).resizable("instance");e.ghost&&e.helper&&e.helper.get(0).removeChild(e.ghost.get(0))}}),t.ui.plugin.add("resizable","grid",{resize:function(){var e,i=t(this).resizable("instance"),s=i.options,n=i.size,o=i.originalSize,a=i.originalPosition,r=i.axis,l="number"==typeof s.grid?[s.grid,s.grid]:s.grid,h=l[0]||1,c=l[1]||1,u=Math.round((n.width-o.width)/h)*h,d=Math.round((n.height-o.height)/c)*c,p=o.width+u,f=o.height+d,g=s.maxWidth&&p>s.maxWidth,m=s.maxHeight&&f>s.maxHeight,_=s.minWidth&&s.minWidth>p,v=s.minHeight&&s.minHeight>f;s.grid=l,_&&(p+=h),v&&(f+=c),g&&(p-=h),m&&(f-=c),/^(se|s|e)$/.test(r)?(i.size.width=p,i.size.height=f):/^(ne)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.top=a.top-d):/^(sw)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.left=a.left-u):((0>=f-c||0>=p-h)&&(e=i._getPaddingPlusBorderDimensions(this)),f-c>0?(i.size.height=f,i.position.top=a.top-d):(f=c-e.height,i.size.height=f,i.position.top=a.top+o.height-f),p-h>0?(i.size.width=p,i.position.left=a.left-u):(p=h-e.width,i.size.width=p,i.position.left=a.left+o.width-p))}}),t.ui.resizable});
\ No newline at end of file
diff --git a/simpleperf/scripts/inferno/script.js b/simpleperf/scripts/inferno/script.js
index 6a81d73..834504a 100644
--- a/simpleperf/scripts/inferno/script.js
+++ b/simpleperf/scripts/inferno/script.js
@@ -1,146 +1,142 @@
-<script type="text/ecmascript">
+'use strict';
+
function init() {
- var x = document.getElementsByTagName("svg")
- for (i = 0; i < x.length; i=i+1) {
+ let x = document.getElementsByTagName('svg');
+ for (let i = 0; i < x.length; i++) {
createZoomHistoryStack(x[i]);
}
}
// Create a stack add the root svg element in it.
function createZoomHistoryStack(svgElement) {
- stack = [];
- svgElement.zoomStack = stack;
- stack.push(svgElement.getElementById(svgElement.attributes["rootid"].value))
+ svgElement.zoomStack = [svgElement.getElementById(svgElement.attributes['rootid'].value)];
}
function dumpStack(svgElement) {
// Disable (enable for debugging)
- return
+ return;
stack = svgElement.zoomStack;
- for (i=0 ; i < stack.length; i++) {
- title = stack[i].getElementsByTagName("title")[0];
- console.log("[" +i+ "]-" + title.textContent)
+ for (i=0; i < stack.length; i++) {
+ let title = stack[i].getElementsByTagName('title')[0];
+ console.log('[' +i+ ']-' + title.textContent);
}
}
-function adjust_node_text_size(x) {
- title = x.getElementsByTagName("title")[0];
- text = x.getElementsByTagName("text")[0];
- rect = x.getElementsByTagName("rect")[0];
+function adjust_node_text_size(x, svgWidth) {
+ let title = x.getElementsByTagName('title')[0];
+ let text = x.getElementsByTagName('text')[0];
+ let rect = x.getElementsByTagName('rect')[0];
- width = parseFloat(rect.attributes["width"].value);
+ let width = parseFloat(rect.attributes['width'].value) * svgWidth * 0.01;
// Don't even bother trying to find a best fit. The area is too small.
- if (width < 25) {
- text.textContent = "";
+ if (width < 28) {
+ text.textContent = '';
return;
}
// Remove dso and #samples which are here only for mouseover purposes.
- methodName = title.textContent.substring(0, title.textContent.indexOf("|"));
+ let methodName = title.textContent.split(' | ')[0];
- var numCharacters;
- for (numCharacters=methodName.length; numCharacters>4; numCharacters--) {
+ let numCharacters;
+ for (numCharacters = methodName.length; numCharacters > 4; numCharacters--) {
// Avoid reflow by using hard-coded estimate instead of text.getSubStringLength(0, numCharacters)
// if (text.getSubStringLength(0, numCharacters) <= width) {
if (numCharacters * 7.5 <= width) {
- break ;
+ break;
}
}
if (numCharacters == methodName.length) {
text.textContent = methodName;
- return
+ return;
}
- text.textContent = methodName.substring(0, numCharacters-2) + "..";
+ text.textContent = methodName.substring(0, numCharacters-2) + '..';
}
function adjust_text_size(svgElement) {
- var x = svgElement.getElementsByTagName("g");
- var i;
- for (i=0 ; i < x.length ; i=i+1) {
- adjust_node_text_size(x[i])
+ let svgWidth = $(svgElement).parent().width();
+ let x = svgElement.getElementsByTagName('g');
+ for (let i = 0; i < x.length; i++) {
+ adjust_node_text_size(x[i], svgWidth);
}
}
function zoom(e) {
- svgElement = e.ownerSVGElement
- zoomStack = svgElement.zoomStack;
+ let svgElement = e.ownerSVGElement;
+ let zoomStack = svgElement.zoomStack;
zoomStack.push(e);
- displayFromElement(e)
+ displayFromElement(e);
select(e);
- dumpStack(e.ownerSVGElement);
+ dumpStack(svgElement);
// Show zoom out button.
- svgElement.getElementById("zoom_rect").style.display = "block";
- svgElement.getElementById("zoom_text").style.display = "block";
+ svgElement.getElementById('zoom_rect').style.display = 'block';
+ svgElement.getElementById('zoom_text').style.display = 'block';
}
function displayFromElement(e) {
- var clicked_rect = e.getElementsByTagName("rect")[0];
- var clicked_origin_x = clicked_rect.attributes["ox"].value;
- var clicked_origin_y = clicked_rect.attributes["oy"].value;
- var clicked_origin_width = clicked_rect.attributes["owidth"].value;
+ let clicked_rect = e.getElementsByTagName('rect')[0];
+ let clicked_origin_x = clicked_rect.attributes['ox'].value;
+ let clicked_origin_y = clicked_rect.attributes['oy'].value;
+ let clicked_origin_width = clicked_rect.attributes['owidth'].value;
- var svgBox = e.ownerSVGElement.getBoundingClientRect();
- var svgBoxHeight = svgBox.height
- var svgBoxWidth = svgBox.width
- var scaleFactor = svgBoxWidth/clicked_origin_width;
+ let svgBox = e.ownerSVGElement.getBoundingClientRect();
+ let svgBoxHeight = svgBox.height;
+ let svgBoxWidth = 100;
+ let scaleFactor = svgBoxWidth / clicked_origin_width;
- var callsites = e.ownerSVGElement.getElementsByTagName("g");
- var i;
- for (i = 0; i < callsites.length; i=i+1) {
- text = callsites[i].getElementsByTagName("text")[0];
- rect = callsites[i].getElementsByTagName("rect")[0];
+ let callsites = e.ownerSVGElement.getElementsByTagName('g');
+ for (let i = 0; i < callsites.length; i++) {
+ let text = callsites[i].getElementsByTagName('text')[0];
+ let rect = callsites[i].getElementsByTagName('rect')[0];
- rect_o_x = rect.attributes["ox"].value
- rect_o_y = parseFloat(rect.attributes["oy"].value)
+ let rect_o_x = parseFloat(rect.attributes['ox'].value);
+ let rect_o_y = parseFloat(rect.attributes['oy'].value);
// Avoid multiple forced reflow by hiding nodes.
if (rect_o_y > clicked_origin_y) {
- rect.style.display = "none"
- text.style.display = "none"
- continue;
- } else {
- rect.style.display = "block"
- text.style.display = "block"
+ rect.style.display = 'none';
+ text.style.display = 'none';
+ continue;
}
+ rect.style.display = 'block';
+ text.style.display = 'block';
- rect.attributes["x"].value = newrec_x = (rect_o_x - clicked_origin_x) * scaleFactor ;
- rect.attributes["y"].value = newrec_y = rect_o_y + (svgBoxHeight - clicked_origin_y - 17 -2);
+ let newrec_x = rect.attributes['x'].value = (rect_o_x - clicked_origin_x) * scaleFactor + "%";
+ let newrec_y = rect.attributes['y'].value = rect_o_y + (svgBoxHeight - clicked_origin_y
+ - 17 - 2);
- text.attributes["y"].value = newrec_y + 12;
- text.attributes["x"].value = newrec_x + 4;
+ text.attributes['y'].value = newrec_y + 12;
+ text.attributes['x'].value = newrec_x;
- rect.attributes["width"].value = rect.attributes["owidth"].value * scaleFactor;
+ rect.attributes['width'].value = (rect.attributes['owidth'].value * scaleFactor) + "%";
}
adjust_text_size(e.ownerSVGElement);
-
}
function unzoom(e) {
-
- var svgOwner = e.ownerSVGElement;
- stack = svgOwner.zoomStack;
+ let svgOwner = e.ownerSVGElement;
+ let stack = svgOwner.zoomStack;
// Unhighlight whatever was selected.
- if (selected != null)
- selected.classList.remove("s")
-
+ if (selected) {
+ selected.classList.remove('s');
+ }
// Stack management: Never remove the last element which is the flamegraph root.
if (stack.length > 1) {
- previouslySelected = stack.pop();
+ let previouslySelected = stack.pop();
select(previouslySelected);
}
- nextElement = stack[stack.length-1] // stack.peek()
+ let nextElement = stack[stack.length-1];
// Hide zoom out button.
- if (stack.length==1) {
- svgOwner.getElementById("zoom_rect").style.display = "none";
- svgOwner.getElementById("zoom_text").style.display = "none";
+ if (stack.length == 1) {
+ svgOwner.getElementById('zoom_rect').style.display = 'none';
+ svgOwner.getElementById('zoom_text').style.display = 'none';
}
displayFromElement(nextElement);
@@ -148,97 +144,105 @@
}
function search(e) {
- var term = prompt("Search for:", "");
+ let term = prompt('Search for:', '');
+ let svgOwner = e.ownerSVGElement;
+ let callsites = e.ownerSVGElement.getElementsByTagName('g');
- var svgOwner = e.ownerSVGElement
- var callsites = e.ownerSVGElement.getElementsByTagName("g");
-
- if (term == null || term == "") {
- for (i = 0; i < callsites.length; i=i+1) {
- rect = callsites[i].getElementsByTagName("rect")[0];
- rect.attributes["fill"].value = rect.attributes["ofill"].value;
+ if (!term) {
+ for (let i = 0; i < callsites.length; i++) {
+ let rect = callsites[i].getElementsByTagName('rect')[0];
+ rect.attributes['fill'].value = rect.attributes['ofill'].value;
}
return;
}
- for (i = 0; i < callsites.length; i=i+1) {
- title = callsites[i].getElementsByTagName("title")[0];
- rect = callsites[i].getElementsByTagName("rect")[0];
+ for (let i = 0; i < callsites.length; i++) {
+ let title = callsites[i].getElementsByTagName('title')[0];
+ let rect = callsites[i].getElementsByTagName('rect')[0];
if (title.textContent.indexOf(term) != -1) {
- rect.attributes["fill"].value = "rgb(230,100,230)";
+ rect.attributes['fill'].value = 'rgb(230,100,230)';
} else {
- rect.attributes["fill"].value = rect.attributes["ofill"].value;
+ rect.attributes['fill'].value = rect.attributes['ofill'].value;
}
}
}
-var selected;
+let selected;
document.onkeydown = function handle_keyboard_input(e) {
- if (selected == null)
+ if (!selected) {
return;
+ }
- title = selected.getElementsByTagName("title")[0];
- nav = selected.attributes["nav"].value.split(",")
- navigation_index = -1
+ let title = selected.getElementsByTagName('title')[0];
+ let nav = selected.attributes['nav'].value.split(',');
+ let navigation_index;
switch (e.keyCode) {
- //case 38: // ARROW UP
- case 87 : navigation_index = 0;break; //W
+ // case 38: // ARROW UP
+ case 87: navigation_index = 0; break; // W
- //case 32 : // ARROW LEFT
- case 65 : navigation_index = 1;break; //A
+ // case 32 : // ARROW LEFT
+ case 65: navigation_index = 1; break; // A
// case 43: // ARROW DOWN
- case 68 : navigation_index = 3;break; // S
+ case 68: navigation_index = 3; break; // S
// case 39: // ARROW RIGHT
- case 83 : navigation_index = 2;break; // D
+ case 83: navigation_index = 2; break; // D
- case 32 : zoom(selected); return false; break; // SPACE
+ case 32: zoom(selected); return false; // SPACE
case 8: // BACKSPACE
unzoom(selected); return false;
default: return true;
}
- if (nav[navigation_index] == "0")
+ if (nav[navigation_index] == '0') {
return false;
+ }
- target_element = selected.ownerSVGElement.getElementById(nav[navigation_index]);
- select(target_element)
+ let target_element = selected.ownerSVGElement.getElementById(nav[navigation_index]);
+ select(target_element);
return false;
-}
+};
function select(e) {
- if (selected != null)
- selected.classList.remove("s")
+ if (selected) {
+ selected.classList.remove('s');
+ }
selected = e;
- selected.classList.add("s")
+ selected.classList.add('s');
// Update info bar
- titleElement = selected.getElementsByTagName("title")[0];
- text = titleElement.textContent;
+ let titleElement = selected.getElementsByTagName('title')[0];
+ let text = titleElement.textContent;
// Parse title
- method_and_info = text.split(" | ");
- methodName = method_and_info[0];
- info = method_and_info[1]
+ let method_and_info = text.split(' | ');
+ let methodName = method_and_info[0];
+ let info = method_and_info[1];
// Parse info
// '/system/lib64/libhwbinder.so (4 samples: 0.28%)'
- var regexp = /(.*) \(.* ([0-9**\.[0-9]*%)\)/g;
- match = regexp.exec(info);
+ let regexp = /(.*) \(.* ([0-9**\.[0-9]*%)\)/g;
+ let match = regexp.exec(info);
if (match.length > 2) {
- percentage = match[2]
+ let percentage = match[2];
// Write percentage
- percentageTextElement = selected.ownerSVGElement.getElementById("percent_text")
- percentageTextElement.textContent = percentage
- //console.log("'" + percentage + "'")
+ let percentageTextElement = selected.ownerSVGElement.getElementById('percent_text');
+ percentageTextElement.textContent = percentage;
+ // console.log("'" + percentage + "'")
}
// Set fields
- barTextElement = selected.ownerSVGElement.getElementById("info_text")
- barTextElement.textContent = methodName
+ let barTextElement = selected.ownerSVGElement.getElementById('info_text');
+ barTextElement.textContent = methodName;
}
-
-</script>
\ No newline at end of file
+$(document).ready(function() {
+ $(".flamegraph_block").resizable({
+ handles: "e",
+ resize: function(event, ui) {
+ adjust_text_size(ui.element.find("svg")[0]);
+ }
+ });
+});
\ No newline at end of file
diff --git a/simpleperf/scripts/inferno/svg_renderer.py b/simpleperf/scripts/inferno/svg_renderer.py
index 00f2d75..3950572 100644
--- a/simpleperf/scripts/inferno/svg_renderer.py
+++ b/simpleperf/scripts/inferno/svg_renderer.py
@@ -22,36 +22,37 @@
def hash_to_float(string):
- return hash(string) / float(sys.maxint)
+ return hash(string) / float(sys.maxsize)
-def getLegacyColor(method) :
+
+def getLegacyColor(method):
r = 175 + int(50 * hash_to_float(reversed(method)))
g = 60 + int(180 * hash_to_float(method))
- b = 60 +int(55 * hash_to_float(reversed(method)))
- return (r,g,b)
+ b = 60 + int(55 * hash_to_float(reversed(method)))
+ return (r, g, b)
-def getDSOColor(method) :
+def getDSOColor(method):
r = 170 + int(80 * hash_to_float(reversed(method)))
- g = 180 +int(70 * hash_to_float((method)))
+ g = 180 + int(70 * hash_to_float((method)))
b = 170 + int(80 * hash_to_float(reversed(method)))
- return (r,g,b)
+ return (r, g, b)
-def getHeatColor(callsite, num_samples) :
- r = 245 + 10* (1- float(callsite.num_samples)/ num_samples)
- g = 110 + 105* (1-float(callsite.num_samples)/ num_samples)
+def getHeatColor(callsite, total_weight):
+ r = 245 + 10 * (1 - callsite.weight() / total_weight)
+ g = 110 + 105 * (1 - callsite.weight() / total_weight)
b = 100
- return (r,g,b)
+ return (r, g, b)
-def createSVGNode(callsite, depth, f, num_samples, height, color_scheme, nav):
- x = float(callsite.offset)/float(num_samples)*SVG_CANVAS_WIDTH
- y = height - (depth * SVG_NODE_HEIGHT) - SVG_NODE_HEIGHT
- width = float(callsite.num_samples) /float(num_samples) * SVG_CANVAS_WIDTH
+def createSVGNode(callsite, depth, f, total_weight, height, color_scheme, nav):
+ x = float(callsite.offset) / total_weight * 100
+ y = height - (depth + 1) * SVG_NODE_HEIGHT
+ width = callsite.weight() / total_weight * 100
method = callsite.method.replace(">", ">").replace("<", "<")
- if (width <= 0) :
+ if width <= 0:
return
if color_scheme == "dso":
@@ -59,111 +60,125 @@
elif color_scheme == "legacy":
r, g, b = getLegacyColor(method)
else:
- r, g, b = getHeatColor(callsite, num_samples)
+ r, g, b = getHeatColor(callsite, total_weight)
-
-
- r_border = (r - 50)
- if r_border < 0:
- r_border = 0
-
- g_border = (g - 50)
- if g_border < 0:
- g_border = 0
-
- b_border = (b - 50)
- if (b_border < 0):
- b_border = 0
+ r_border, g_border, b_border = [max(0, color - 50) for color in [r, g, b]]
f.write(
- '<g id=%d class="n" onclick="zoom(this);" onmouseenter="select(this);" nav="%s"> \n\
- <title>%s | %s (%d samples: %3.2f%%)</title>\n \
- <rect x="%f" y="%f" ox="%f" oy="%f" width="%f" owidth="%f" height="15.0" ofill="rgb(%d,%d,%d)" \
- fill="rgb(%d,%d,%d)" style="stroke:rgb(%d,%d,%d)"/>\n \
- <text x="%f" y="%f" font-size="%d" font-family="Monospace"></text>\n \
- </g>\n' % (callsite.id, ','.join(str(x) for x in nav),
- method, callsite.dso, callsite.num_samples, callsite.num_samples/float(num_samples) * 100,
- x, y, x, y, width , width, r, g, b, r, g, b, r_border, g_border, b_border,
- x+2, y+12, FONT_SIZE))
+ """<g id=%d class="n" onclick="zoom(this);" onmouseenter="select(this);" nav="%s">
+ <title>%s | %s (%.0f events: %3.2f%%)</title>
+ <rect x="%f%%" y="%f" ox="%f" oy="%f" width="%f%%" owidth="%f" height="15.0"
+ ofill="rgb(%d,%d,%d)" fill="rgb(%d,%d,%d)" style="stroke:rgb(%d,%d,%d)"/>
+ <text x="%f%%" y="%f" font-size="%d" font-family="Monospace"></text>
+ </g>""" %
+ (callsite.id,
+ ','.join(str(x) for x in nav),
+ method,
+ callsite.dso,
+ callsite.weight(),
+ callsite.weight() / total_weight * 100,
+ x,
+ y,
+ x,
+ y,
+ width,
+ width,
+ r,
+ g,
+ b,
+ r,
+ g,
+ b,
+ r_border,
+ g_border,
+ b_border,
+ x,
+ y + 12,
+ FONT_SIZE))
-def renderSVGNodes(flamegraph, depth, f, num_samples, height, color_scheme):
- for i, callsite in enumerate(flamegraph.callsites):
+def renderSVGNodes(flamegraph, depth, f, total_weight, height, color_scheme):
+ for i, child in enumerate(flamegraph.children):
# Prebuild navigation target for wasd
if i == 0:
left_index = 0
else:
- left_index = flamegraph.callsites[i-1].id
+ left_index = flamegraph.children[i - 1].id
- if i == len(flamegraph.callsites)-1:
+ if i == len(flamegraph.children) - 1:
right_index = 0
else:
- right_index = flamegraph.callsites[i+1].id
+ right_index = flamegraph.children[i + 1].id
-
- up_index = 0
- max_up = 0
- for upcallsite in callsite.callsites:
- if upcallsite.num_samples > max_up:
- max_up = upcallsite.num_samples
- up_index = upcallsite.id
+ up_index = max(child.children, key=lambda x: x.weight()).id if child.children else 0
# up, left, down, right
- nav = [up_index, left_index,flamegraph.id,right_index]
+ nav = [up_index, left_index, flamegraph.id, right_index]
- createSVGNode(callsite, depth, f, num_samples, height, color_scheme, nav)
+ createSVGNode(child, depth, f, total_weight, height, color_scheme, nav)
# Recurse down
- renderSVGNodes(callsite, depth+1, f, num_samples, height, color_scheme)
+ renderSVGNodes(child, depth + 1, f, total_weight, height, color_scheme)
+
def renderSearchNode(f):
f.write(
- '<rect id="search_rect" style="stroke:rgb(0,0,0);" onclick="search(this);" class="t" rx="10" ry="10" \
- x="%d" y="10" width="80" height="30" fill="rgb(255,255,255)""/> \
- <text id="search_text" class="t" x="%d" y="30" onclick="search(this);">Search</text>\n'
- % (SVG_CANVAS_WIDTH - 95, SVG_CANVAS_WIDTH - 80)
- )
+ """<rect id="search_rect" style="stroke:rgb(0,0,0);" onclick="search(this);" class="t"
+ rx="10" ry="10" x="%d" y="10" width="80" height="30" fill="rgb(255,255,255)""/>
+ <text id="search_text" class="t" x="%d" y="30" onclick="search(this);">Search</text>
+ """ % (SVG_CANVAS_WIDTH - 95, SVG_CANVAS_WIDTH - 80))
def renderUnzoomNode(f):
f.write(
- '<rect id="zoom_rect" style="display:none;stroke:rgb(0,0,0);" class="t" onclick="unzoom(this);" \
- rx="10" ry="10" x="10" y="10" width="80" height="30" fill="rgb(255,255,255)"/> \
- <text id="zoom_text" style="display:none;" class="t" x="19" y="30" \
- onclick="unzoom(this);">Zoom out</text>\n'
+ """<rect id="zoom_rect" style="display:none;stroke:rgb(0,0,0);" class="t"
+ onclick="unzoom(this);" rx="10" ry="10" x="10" y="10" width="80" height="30"
+ fill="rgb(255,255,255)"/>
+ <text id="zoom_text" style="display:none;" class="t" x="19" y="30"
+ onclick="unzoom(this);">Zoom out</text>"""
)
+
def renderInfoNode(f):
f.write(
- '<clipPath id="info_clip_path"> <rect id="info_rect" style="stroke:rgb(0,0,0);" \
- rx="10" ry="10" x="120" y="10" width="%d" height="30" fill="rgb(255,255,255)"/></clipPath> \
- <rect id="info_rect" style="stroke:rgb(0,0,0);" \
- rx="10" ry="10" x="120" y="10" width="%d" height="30" fill="rgb(255,255,255)"/> \
- <text clip-path="url(#info_clip_path)" id="info_text" x="128" y="30"></text>\n' % (SVG_CANVAS_WIDTH - 335, SVG_CANVAS_WIDTH - 325)
+ """<clipPath id="info_clip_path"> <rect id="info_rect" style="stroke:rgb(0,0,0);"
+ rx="10" ry="10" x="120" y="10" width="%d" height="30" fill="rgb(255,255,255)"/>
+ </clipPath>
+ <rect id="info_rect" style="stroke:rgb(0,0,0);"
+ rx="10" ry="10" x="120" y="10" width="%d" height="30" fill="rgb(255,255,255)"/>
+ <text clip-path="url(#info_clip_path)" id="info_text" x="128" y="30"></text>
+ """ % (SVG_CANVAS_WIDTH - 335, SVG_CANVAS_WIDTH - 325)
)
+
def renderPercentNode(f):
f.write(
- '<rect id="percent_rect" style="stroke:rgb(0,0,0);" \
- rx="10" ry="10" x="%d" y="10" width="82" height="30" fill="rgb(255,255,255)"/> \
- <text id="percent_text" text-anchor="end" x="%d" y="30">100.00%%</text>\n' % (SVG_CANVAS_WIDTH - (95 * 2),SVG_CANVAS_WIDTH - (125))
+ """<rect id="percent_rect" style="stroke:rgb(0,0,0);"
+ rx="10" ry="10" x="%d" y="10" width="82" height="30" fill="rgb(255,255,255)"/>
+ <text id="percent_text" text-anchor="end" x="%d" y="30">100.00%%</text>
+ """ % (SVG_CANVAS_WIDTH - (95 * 2), SVG_CANVAS_WIDTH - (125))
)
def renderSVG(flamegraph, f, color_scheme, width):
global SVG_CANVAS_WIDTH
SVG_CANVAS_WIDTH = width
- height = (flamegraph.get_max_depth() + 2 )* SVG_NODE_HEIGHT
- f.write('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" \
- width="%d" height="%d" style="border: 1px solid black;" \
- onload="adjust_text_size(this);" rootid="%d">\n' % (SVG_CANVAS_WIDTH, height, flamegraph.callsites[0].id))
- f.write('<defs > <linearGradient id="background_gradiant" y1="0" y2="1" x1="0" x2="0" > \
- <stop stop-color="#eeeeee" offset="5%" /> <stop stop-color="#efefb1" offset="90%" /> </linearGradient> </defs>')
- f.write('<rect x="0.0" y="0" width="%d" height="%d" fill="url(#background_gradiant)" />' % \
- (SVG_CANVAS_WIDTH, height))
- renderSVGNodes(flamegraph, 0, f, flamegraph.num_samples, height, color_scheme)
+ height = (flamegraph.get_max_depth() + 2) * SVG_NODE_HEIGHT
+ f.write("""<div class="flamegraph_block" style="width:%dpx; height:%dpx;">
+ """ % (SVG_CANVAS_WIDTH, height))
+ f.write("""<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
+ width="100%%" height="100%%" style="border: 1px solid black;"
+ onload="adjust_text_size(this);" rootid="%d">
+ """ % (flamegraph.children[0].id))
+ f.write("""<defs > <linearGradient id="background_gradiant" y1="0" y2="1" x1="0" x2="0" >
+ <stop stop-color="#eeeeee" offset="5%" /> <stop stop-color="#efefb1" offset="90%" />
+ </linearGradient> </defs>""")
+ f.write("""<rect x="0.0" y="0" width="100%" height="100%" fill="url(#background_gradiant)" />
+ """)
+ renderSVGNodes(flamegraph, 0, f, flamegraph.weight(), height, color_scheme)
renderSearchNode(f)
renderUnzoomNode(f)
renderInfoNode(f)
renderPercentNode(f)
- f.write("</svg><br/>\n\n")
\ No newline at end of file
+ f.write("</svg></div><br/>\n\n")
diff --git a/simpleperf/scripts/run_simpleperf_on_device.py b/simpleperf/scripts/run_simpleperf_on_device.py
new file mode 100644
index 0000000..37155bc
--- /dev/null
+++ b/simpleperf/scripts/run_simpleperf_on_device.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2017 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.
+#
+
+"""run_simpleperf_on_device.py:
+ It downloads simpleperf to /data/local/tmp on device, and run it with all given arguments.
+ It saves the time downloading simpleperf and using `adb shell` directly.
+"""
+import subprocess
+import sys
+from utils import *
+
+def main():
+ disable_debug_log()
+ adb = AdbHelper()
+ device_arch = adb.get_device_arch()
+ simpleperf_binary = get_target_binary_path(device_arch, 'simpleperf')
+ adb.check_run(['push', simpleperf_binary, '/data/local/tmp'])
+ adb.check_run(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf'])
+ shell_cmd = 'cd /data/local/tmp && ./simpleperf ' + ' '.join(sys.argv[1:])
+ sys.exit(subprocess.call([adb.adb_path, 'shell', shell_cmd]))
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/simpleperf/scripts/simpleperf_report_lib.py b/simpleperf/scripts/simpleperf_report_lib.py
index 67fdfa3..385df2f 100644
--- a/simpleperf/scripts/simpleperf_report_lib.py
+++ b/simpleperf/scripts/simpleperf_report_lib.py
@@ -83,9 +83,9 @@
('entries', ct.POINTER(CallChainEntryStructure))]
-class MetaInfoEntryStructure(ct.Structure):
- _fields_ = [('key', ct.c_char_p),
- ('value', ct.c_char_p)]
+class FeatureSectionStructure(ct.Structure):
+ _fields_ = [('data', ct.POINTER(ct.c_char)),
+ ('data_size', ct.c_uint32)]
# convert char_p to str for python3.
@@ -160,8 +160,8 @@
CallChainStructure)
self._GetBuildIdForPathFunc = self._lib.GetBuildIdForPath
self._GetBuildIdForPathFunc.restype = ct.c_char_p
- self._GetNextMetaInfoFunc = self._lib.GetNextMetaInfo
- self._GetNextMetaInfoFunc.restype = ct.POINTER(MetaInfoEntryStructure)
+ self._GetFeatureSection = self._lib.GetFeatureSection
+ self._GetFeatureSection.restype = ct.POINTER(FeatureSectionStructure)
self._instance = self._CreateReportLibFunc()
assert(not _is_null(self._instance))
@@ -241,15 +241,52 @@
assert(not _is_null(build_id))
return _char_pt_to_str(build_id)
+ def GetRecordCmd(self):
+ if hasattr(self, "record_cmd"):
+ return self.record_cmd
+ self.record_cmd = None
+ feature_data = self._GetFeatureSection(self.getInstance(), _char_pt("cmdline"))
+ if not _is_null(feature_data):
+ void_p = ct.cast(feature_data[0].data, ct.c_void_p)
+ data_size = feature_data[0].data_size
+ arg_count = ct.cast(void_p, ct.POINTER(ct.c_uint32)).contents.value
+ void_p.value += 4
+ args = []
+ for i in range(arg_count):
+ str_len = ct.cast(void_p, ct.POINTER(ct.c_uint32)).contents.value
+ void_p.value += 4
+ char_p = ct.cast(void_p, ct.POINTER(ct.c_char))
+ current_str = ""
+ for j in range(str_len):
+ c = bytes_to_str(char_p[j])
+ if c != '\0':
+ current_str += c
+ if ' ' in current_str:
+ current_str = '"' + current_str + '"'
+ args.append(current_str)
+ void_p.value += str_len
+ self.record_cmd = " ".join(args)
+ return self.record_cmd
+
+
def MetaInfo(self):
if self.meta_info is None:
self.meta_info = {}
- while True:
- entry = self._GetNextMetaInfoFunc(self.getInstance())
- if _is_null(entry): break
- key = _char_pt_to_str(entry[0].key)
- value = _char_pt_to_str(entry[0].value)
- self.meta_info[key] = value
+ feature_data = self._GetFeatureSection(self.getInstance(), _char_pt("meta_info"))
+ if not _is_null(feature_data):
+ str_list = []
+ data = feature_data[0].data
+ data_size = feature_data[0].data_size
+ current_str = ""
+ for i in range(data_size):
+ c = bytes_to_str(data[i])
+ if c != '\0':
+ current_str += c
+ else:
+ str_list.append(current_str)
+ current_str = ""
+ for i in range(0, len(str_list), 2):
+ self.meta_info[str_list[i]] = str_list[i + 1]
return self.meta_info
def getInstance(self):
diff --git a/simpleperf/scripts/test.py b/simpleperf/scripts/test.py
index a4f2ebc..e75a328 100644
--- a/simpleperf/scripts/test.py
+++ b/simpleperf/scripts/test.py
@@ -51,6 +51,8 @@
except:
has_google_protobuf = False
+inferno_script = os.path.join(get_script_dir(), "inferno.bat" if is_windows() else "./inferno.sh")
+
support_trace_offcpu = None
def is_trace_offcpu_supported():
@@ -66,7 +68,6 @@
support_trace_offcpu = 'trace-offcpu' in output
return support_trace_offcpu
-
def build_testdata():
""" Collect testdata from ../testdata and ../demo. """
from_testdata_path = os.path.join('..', 'testdata')
@@ -85,7 +86,27 @@
for demo in copy_demo_list:
shutil.copytree(os.path.join(from_demo_path, demo), os.path.join(testdata_path, demo))
-class TestExampleBase(unittest.TestCase):
+
+class TestBase(unittest.TestCase):
+ def run_cmd(self, args, return_output=False):
+ if args[0].endswith('.py'):
+ args = [sys.executable] + args
+ use_shell = args[0].endswith('.bat')
+ try:
+ if not return_output:
+ returncode = subprocess.call(args, shell=use_shell)
+ else:
+ subproc = subprocess.Popen(args, stdout=subprocess.PIPE, shell=use_shell)
+ (output_data, _) = subproc.communicate()
+ returncode = subproc.returncode
+ except:
+ returncode = None
+ self.assertEqual(returncode, 0, msg="failed to run cmd: %s" % args)
+ if return_output:
+ return output_data
+
+
+class TestExampleBase(TestBase):
@classmethod
def prepare(cls, example_name, package_name, activity_name, abi=None, adb_root=False):
cls.adb = AdbHelper(enable_switch_to_root=adb_root)
@@ -100,6 +121,9 @@
log_fatal("can't find app-profiling.apk under " + cls.example_path)
cls.package_name = package_name
cls.activity_name = activity_name
+ cls.abi = "arm64"
+ if abi and abi != "arm64" and abi.find("arm") != -1:
+ cls.abi = "arm"
args = ["install", "-r"]
if abi:
args += ["--abi", abi]
@@ -114,37 +138,25 @@
@classmethod
def tearDownClass(cls):
+ if hasattr(cls, 'test_result') and cls.test_result and not cls.test_result.wasSuccessful():
+ return
if hasattr(cls, 'package_name'):
cls.adb.check_run(["uninstall", cls.package_name])
-
- @classmethod
- def cleanupTestFiles(cls):
remove("binary_cache")
remove("annotated_files")
remove("perf.data")
remove("report.txt")
remove("pprof.profile")
- def run_cmd(self, args, return_output=False):
- args = [sys.executable] + args
- try:
- if not return_output:
- returncode = subprocess.call(args)
- else:
- subproc = subprocess.Popen(args, stdout=subprocess.PIPE)
- (output_data, _) = subproc.communicate()
- returncode = subproc.returncode
- except:
- returncode = None
- self.assertEqual(returncode, 0, msg="failed to run cmd: %s" % args)
- if return_output:
- return output_data
+ def run(self, result=None):
+ self.__class__.test_result = result
+ super(TestBase, self).run(result)
def run_app_profiler(self, record_arg = "-g --duration 3 -e cpu-cycles:u",
build_binary_cache=True, skip_compile=False, start_activity=True,
- native_lib_dir=None):
+ native_lib_dir=None, profile_from_launch=False, add_arch=False):
args = ["app_profiler.py", "--app", self.package_name, "--apk", self.apk_path,
- "-a", self.activity_name, "-r", record_arg, "-o", "perf.data"]
+ "-r", record_arg, "-o", "perf.data"]
if not build_binary_cache:
args.append("-nb")
if skip_compile or self.__class__.compiled:
@@ -153,6 +165,10 @@
args += ["-a", self.activity_name]
if native_lib_dir:
args += ["-lib", native_lib_dir]
+ if profile_from_launch:
+ args.append("--profile_from_launch")
+ if add_arch:
+ args += ["--arch", self.abi]
if not self.adb_root:
args.append("--disable_adb_root")
self.run_cmd(args)
@@ -209,6 +225,22 @@
fulfilled[i] = True
self.assertEqual(len(fulfilled), sum([int(x) for x in fulfilled]), fulfilled)
+ def check_inferno_report_html(self, check_entries, file="report.html"):
+ self.check_exist(file=file)
+ with open(file, 'r') as fh:
+ data = fh.read()
+ fulfilled = [False for x in check_entries]
+ for line in data.split('\n'):
+ # each entry is a (function_name, min_percentage) pair.
+ for i, entry in enumerate(check_entries):
+ if fulfilled[i] or line.find(entry[0]) == -1:
+ continue
+ m = re.search(r'(\d+\.\d+)%', line)
+ if m and float(m.group(1)) >= entry[1]:
+ fulfilled[i] = True
+ break
+ self.assertEqual(fulfilled, [True for x in check_entries])
+
def common_test_app_profiler(self):
self.run_cmd(["app_profiler.py", "-h"])
remove("binary_cache")
@@ -224,7 +256,6 @@
self.run_app_profiler(skip_compile=True)
self.run_app_profiler(start_activity=False)
-
def common_test_report(self):
self.run_cmd(["report.py", "-h"])
self.run_app_profiler(build_binary_cache=False)
@@ -247,7 +278,7 @@
self.run_cmd(["report_sample.py"])
output = self.run_cmd(["report_sample.py", "perf.data"], return_output=True)
self.check_strings_in_content(output, check_strings)
- self.run_app_profiler(record_arg="-g --duration 3 -e cpu-cycles:u, --no-dump-symbols")
+ self.run_app_profiler(record_arg="-g --duration 3 -e cpu-cycles:u --no-dump-symbols")
output = self.run_cmd(["report_sample.py", "--symfs", "binary_cache"], return_output=True)
self.check_strings_in_content(output, check_strings)
@@ -274,6 +305,18 @@
self.check_strings_in_content(output, check_strings_without_lines +
["has_line_numbers: False"])
+ def common_test_inferno(self):
+ self.run_cmd([inferno_script, "-h"])
+ remove("perf.data")
+ append_args = [] if self.adb_root else ["--disable_adb_root"]
+ self.run_cmd([inferno_script, "-p", self.package_name, "-t", "3"] + append_args)
+ self.check_exist(file="perf.data")
+ self.run_cmd([inferno_script, "-p", self.package_name, "-f", "1000", "-du", "-t", "1",
+ "-nc"] + append_args)
+ self.run_cmd([inferno_script, "-p", self.package_name, "-e", "100000 cpu-cycles",
+ "-t", "1", "-nc"] + append_args)
+ self.run_cmd([inferno_script, "-sc"])
+
class TestExamplePureJava(TestExampleBase):
@classmethod
@@ -285,11 +328,36 @@
def test_app_profiler(self):
self.common_test_app_profiler()
+ def test_app_profiler_profile_from_launch(self):
+ self.run_app_profiler(profile_from_launch=True, add_arch=True, build_binary_cache=False)
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+ self.check_strings_in_file("report.txt",
+ ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()",
+ "__start_thread"])
+
+ def test_app_profiler_multiprocesses(self):
+ self.adb.check_run(['shell', 'am', 'force-stop', self.package_name])
+ self.adb.check_run(['shell', 'am', 'start', '-n',
+ self.package_name + '/.MultiProcessActivity'])
+ # Wait until both MultiProcessActivity and MultiProcessService set up.
+ time.sleep(3)
+ self.run_app_profiler(skip_compile=True, start_activity=False)
+ self.run_cmd(["report.py", "-o", "report.txt"])
+ self.check_strings_in_file("report.txt", ["BusyService", "BusyThread"])
+
def test_app_profiler_with_ctrl_c(self):
if is_windows():
return
+ # `adb root` and `adb unroot` may consumes more time than 3 sec. So
+ # do it in advance to make sure ctrl-c happens when recording.
+ if self.adb_root:
+ self.adb.switch_to_root()
+ else:
+ self.adb._unroot()
args = [sys.executable, "app_profiler.py", "--app", self.package_name,
"-r", "--duration 10000", "-nc"]
+ if not self.adb_root:
+ args.append("--disable_adb_root")
subproc = subprocess.Popen(args)
time.sleep(3)
@@ -313,7 +381,7 @@
[("MainActivity.java", 80, 80),
("run", 80, 0),
("callFunction", 0, 0),
- ("line 24", 80, 0)])
+ ("line 23", 80, 0)])
def test_report_sample(self):
self.common_test_report_sample(
@@ -328,6 +396,28 @@
check_strings_without_lines=
["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()"])
+ def test_inferno(self):
+ self.common_test_inferno()
+ self.run_app_profiler()
+ self.run_cmd([inferno_script, "-sc"])
+ self.check_inferno_report_html(
+ [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()', 80)])
+ self.run_cmd([inferno_script, "-sc", "-o", "report2.html"])
+ self.check_inferno_report_html(
+ [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()', 80)],
+ "report2.html")
+ remove("report2.html")
+
+ def test_inferno_in_another_dir(self):
+ test_dir = 'inferno_testdir'
+ saved_dir = os.getcwd()
+ remove(test_dir)
+ os.mkdir(test_dir)
+ os.chdir(test_dir)
+ self.run_cmd([inferno_script])
+ os.chdir(saved_dir)
+ remove(test_dir)
+
class TestExamplePureJavaRoot(TestExampleBase):
@classmethod
@@ -368,6 +458,13 @@
("SleepFunction", 20, 0),
("line 24", 20, 0),
("line 32", 20, 0)])
+ self.run_cmd([inferno_script, "-sc"])
+ self.check_inferno_report_html(
+ [('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run() ', 80),
+ ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction()',
+ 20),
+ ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction(long)',
+ 20)])
class TestExampleWithNative(TestExampleBase):
@@ -382,6 +479,13 @@
remove("binary_cache")
self.run_app_profiler(native_lib_dir=self.example_path)
+ def test_app_profiler_profile_from_launch(self):
+ self.run_app_profiler(profile_from_launch=True, add_arch=True, build_binary_cache=False)
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+ self.check_strings_in_file("report.txt",
+ ["BusyLoopThread",
+ "__start_thread"])
+
def test_report(self):
self.common_test_report()
self.run_cmd(["report.py", "-g", "-o", "report.txt"])
@@ -411,6 +515,12 @@
check_strings_without_lines=
["BusyLoopThread"])
+ def test_inferno(self):
+ self.common_test_inferno()
+ self.run_app_profiler()
+ self.run_cmd([inferno_script, "-sc"])
+ self.check_inferno_report_html([('BusyLoopThread', 20)])
+
class TestExampleWithNativeRoot(TestExampleBase):
@classmethod
@@ -452,6 +562,10 @@
("SleepFunction", 20, 0),
("line 73", 20, 0),
("line 83", 20, 0)])
+ self.run_cmd([inferno_script, "-sc"])
+ self.check_inferno_report_html([('SleepThread', 80),
+ ('RunFunction', 20),
+ ('SleepFunction', 20)])
class TestExampleWithNativeJniCall(TestExampleBase):
@@ -480,6 +594,7 @@
("line 26", 20, 0),
("native-lib.cpp", 10, 0),
("line 40", 10, 0)])
+ self.run_cmd([inferno_script, "-sc"])
class TestExampleWithNativeForceArm(TestExampleWithNative):
@@ -520,6 +635,13 @@
def test_app_profiler(self):
self.common_test_app_profiler()
+ def test_app_profiler_profile_from_launch(self):
+ self.run_app_profiler(profile_from_launch=True, add_arch=True, build_binary_cache=False)
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+ self.check_strings_in_file("report.txt",
+ ["com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()",
+ "__start_thread"])
+
def test_report(self):
self.common_test_report()
self.run_cmd(["report.py", "-g", "-o", "report.txt"])
@@ -551,6 +673,14 @@
check_strings_without_lines=
["com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()"])
+ def test_inferno(self):
+ self.common_test_inferno()
+ self.run_app_profiler()
+ self.run_cmd([inferno_script, "-sc"])
+ self.check_inferno_report_html(
+ [('com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()',
+ 80)])
+
class TestExampleOfKotlinRoot(TestExampleBase):
@classmethod
@@ -591,6 +721,14 @@
("SleepFunction", 20, 0),
("line 24", 20, 0),
("line 32", 20, 0)])
+ self.run_cmd([inferno_script, "-sc"])
+ self.check_inferno_report_html(
+ [('void com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.run()',
+ 80),
+ ('long com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.RunFunction()',
+ 20),
+ ('long com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.SleepFunction(long)',
+ 20)])
class TestProfilingNativeProgram(TestExampleBase):
@@ -617,6 +755,16 @@
self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+class TestProfilingNativeProgram(TestExampleBase):
+ def test_smoke(self):
+ adb = AdbHelper()
+ if adb.switch_to_root():
+ self.run_cmd(["app_profiler.py", "-np", "surfaceflinger"])
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+ self.run_cmd([inferno_script, "-sc"])
+ self.run_cmd([inferno_script, "-np", "surfaceflinger"])
+
+
class TestReportLib(unittest.TestCase):
def setUp(self):
self.report_lib = ReportLib()
@@ -659,10 +807,11 @@
def test_meta_info(self):
self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data'))
meta_info = self.report_lib.MetaInfo()
- self.assertEqual(meta_info["simpleperf_version"], "1.65f91c7ed862")
+ self.assertTrue("simpleperf_version" in meta_info)
self.assertEqual(meta_info["system_wide_collection"], "false")
self.assertEqual(meta_info["trace_offcpu"], "true")
self.assertEqual(meta_info["event_type_info"], "cpu-cycles,0,0\nsched:sched_switch,2,47")
+ self.assertTrue("product_props" in meta_info)
def test_event_name_from_meta_info(self):
self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data'))
@@ -672,6 +821,11 @@
self.assertTrue('sched:sched_switch' in event_names)
self.assertTrue('cpu-cycles' in event_names)
+ def test_record_cmd(self):
+ self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data'))
+ self.assertEqual(self.report_lib.GetRecordCmd(),
+ "/data/local/tmp/simpleperf record --trace-offcpu --duration 2 -g ./simpleperf_runtest_run_and_sleep64")
+
def test_offcpu(self):
self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data'))
total_period = 0
@@ -689,14 +843,21 @@
sleep_function_period += sample.period
break
sleep_percentage = float(sleep_function_period) / total_period
- self.assertAlmostEqual(sleep_percentage, 0.4629, delta=0.0001)
+ self.assertGreater(sleep_percentage, 0.30)
+
+
+class TestRunSimpleperfOnDevice(TestBase):
+ def test_smoke(self):
+ self.run_cmd(['run_simpleperf_on_device.py', 'list', '--show-features'])
def main():
+ os.chdir(get_script_dir())
build_testdata()
- test_program = unittest.main(failfast=True, exit=False)
- if test_program.result.wasSuccessful():
- TestExampleBase.cleanupTestFiles()
+ if AdbHelper().get_android_version() < 7:
+ log_info("Skip tests on Android version < N.")
+ sys.exit(0)
+ unittest.main(failfast=True)
if __name__ == '__main__':
main()
\ No newline at end of file
diff --git a/simpleperf/scripts/utils.py b/simpleperf/scripts/utils.py
index 0570cfd..77f9f72 100644
--- a/simpleperf/scripts/utils.py
+++ b/simpleperf/scripts/utils.py
@@ -59,6 +59,9 @@
def log_exit(msg):
sys.exit(msg)
+def disable_debug_log():
+ logging.getLogger().setLevel(logging.WARN)
+
def str_to_bytes(str):
if not is_python3():
return str
@@ -262,6 +265,21 @@
log_fatal('unsupported architecture: %s' % output.strip())
+ def get_android_version(self):
+ build_version = self.get_property('ro.build.version.release')
+ android_version = 0
+ if build_version:
+ if not build_version[0].isdigit():
+ c = build_version[0].upper()
+ if c.isupper() and c >= 'L':
+ android_version = ord(c) - ord('L') + 5
+ else:
+ strs = build_version.split('.')
+ if strs:
+ android_version = int(strs[0])
+ return android_version
+
+
def flatten_arg_list(arg_list):
res = []
if arg_list:
diff --git a/simpleperf/test_util.h b/simpleperf/test_util.h
index bb061ba..f5fb590 100644
--- a/simpleperf/test_util.h
+++ b/simpleperf/test_util.h
@@ -46,3 +46,13 @@
#else
#define TEST_REQUIRE_HOST_ROOT() if (!IsRoot()) return
#endif
+
+bool IsInNativeAbi();
+// Used to skip tests not supposed to run on non-native ABIs.
+#define OMIT_TEST_ON_NON_NATIVE_ABIS() \
+ do { \
+ if (!IsInNativeAbi()) { \
+ GTEST_LOG_(INFO) << "Skip this test as it only runs on native ABIs."; \
+ return; \
+ } \
+ } while (0)
diff --git a/simpleperf/testdata/perf_with_trace_offcpu.data b/simpleperf/testdata/perf_with_trace_offcpu.data
index 6d0c5e0..7d81414 100644
--- a/simpleperf/testdata/perf_with_trace_offcpu.data
+++ b/simpleperf/testdata/perf_with_trace_offcpu.data
Binary files differ
diff --git a/su/su.cpp b/su/su.cpp
index ee1526e..f3e4ff0 100644
--- a/su/su.cpp
+++ b/su/su.cpp
@@ -19,7 +19,6 @@
#include <getopt.h>
#include <paths.h>
#include <pwd.h>
-#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
diff --git a/tests/bootloader/Android.mk b/tests/bootloader/Android.mk
new file mode 100644
index 0000000..ab311fd
--- /dev/null
+++ b/tests/bootloader/Android.mk
@@ -0,0 +1,25 @@
+LOCAL_PATH := $(call my-dir)
+
+# Build a module that has all of the python files as its LOCAL_PICKUP_FILES.
+# Since no action needs to be taken to compile the python source, just
+# use BUILD_PHONY_PACKAGE to give us a target to execute.
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := bootloader_unit_test
+LOCAL_MODULE_TAGS := tests
+
+bootloader_py_files := $(call find-subdir-files, *.py)
+
+bootloader_zip_prefix := $(TARGET_OUT_DATA)/py_bootloader
+bootloader_zip_path := $(bootloader_zip_prefix)/nativetest/py_bootloader
+
+GEN := $(addprefix $(bootloader_zip_path)/, $(bootloader_py_files))
+$(GEN) : PRIVATE_PATH := $(LOCAL_PATH)
+$(GEN) : PRIVATE_CUSTOM_TOOL = cp $< $@
+$(GEN) : $(bootloader_zip_path)/% : $(LOCAL_PATH)/%
+ $(transform-generated-source)
+
+LOCAL_PICKUP_FILES := $(bootloader_zip_prefix)/nativetest
+LOCAL_ADDITIONAL_DEPENDENCIES := $(GEN)
+
+include $(BUILD_PHONY_PACKAGE)
diff --git a/tests/icachetest/Android.mk b/tests/icachetest/Android.mk
index 132efd3..9874ffd 100644
--- a/tests/icachetest/Android.mk
+++ b/tests/icachetest/Android.mk
@@ -2,7 +2,7 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES:= icache_main.c icache.S icache2.S
+LOCAL_SRC_FILES:= icache_main.cpp Profiler.cpp icache.S
LOCAL_SHARED_LIBRARIES := libc
@@ -12,4 +12,6 @@
LOCAL_MODULE_TARGET_ARCH := arm
+LOCAL_CFLAGS += -Wall -Werror
+
include $(BUILD_EXECUTABLE)
diff --git a/tests/icachetest/Profiler.cpp b/tests/icachetest/Profiler.cpp
new file mode 100644
index 0000000..792cf43
--- /dev/null
+++ b/tests/icachetest/Profiler.cpp
@@ -0,0 +1,200 @@
+/*
+ * 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 "Profiler.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <iostream>
+
+#if defined(__linux__)
+
+#include <sys/syscall.h>
+
+#ifdef __ARM_ARCH
+ enum ARMv8PmuPerfTypes{
+ // Common micro-architecture events
+ ARMV8_PMUV3_PERFCTR_L1_ICACHE_REFILL = 0x01,
+ ARMV8_PMUV3_PERFCTR_L1_ICACHE_ACCESS = 0x14,
+ ARMV8_PMUV3_PERFCTR_L2_CACHE_ACCESS = 0x16,
+ ARMV8_PMUV3_PERFCTR_L2_CACHE_REFILL = 0x17,
+ ARMV8_PMUV3_PERFCTR_L2_CACHE_WB = 0x18,
+ };
+#endif
+
+static int perf_event_open(struct perf_event_attr* hw_event, pid_t pid,
+ int cpu, int group_fd, unsigned long flags) {
+ return syscall(__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags);
+}
+
+#endif // __linux__
+
+namespace utils {
+
+Profiler& Profiler::get() noexcept {
+ static Profiler sProfiler;
+ return sProfiler;
+}
+
+Profiler::Profiler() noexcept {
+ std::uninitialized_fill(mCountersFd.begin(), mCountersFd.end(), -1);
+ Profiler::resetEvents(EV_CPU_CYCLES | EV_L1D_RATES | EV_BPU_RATES);
+}
+
+Profiler::~Profiler() noexcept {
+ for (int fd : mCountersFd) {
+ if (fd >= 0) {
+ close(fd);
+ }
+ }
+}
+
+uint32_t Profiler::resetEvents(uint32_t eventMask) noexcept {
+ // close all counters
+ for (int& fd : mCountersFd) {
+ if (fd >= 0) {
+ close(fd);
+ fd = -1;
+ }
+ }
+ mEnabledEvents = 0;
+
+#if defined(__linux__)
+
+ struct perf_event_attr pe;
+ memset(&pe, 0, sizeof(struct perf_event_attr));
+ pe.type = PERF_TYPE_HARDWARE;
+ pe.size = sizeof(struct perf_event_attr);
+ pe.config = PERF_COUNT_HW_INSTRUCTIONS;
+ pe.disabled = 1;
+ pe.exclude_kernel = 1;
+ pe.exclude_hv = 1;
+ pe.read_format = PERF_FORMAT_GROUP |
+ PERF_FORMAT_ID |
+ PERF_FORMAT_TOTAL_TIME_ENABLED |
+ PERF_FORMAT_TOTAL_TIME_RUNNING;
+
+ uint8_t count = 0;
+ int fd = perf_event_open(&pe, 0, -1, -1, 0);
+ if (fd >= 0) {
+ const int groupFd = fd;
+ mIds[INSTRUCTIONS] = count++;
+ mCountersFd[INSTRUCTIONS] = fd;
+
+ pe.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID;
+
+ if (eventMask & EV_CPU_CYCLES) {
+ pe.type = PERF_TYPE_HARDWARE;
+ pe.config = PERF_COUNT_HW_CPU_CYCLES;
+ mCountersFd[CPU_CYCLES] = perf_event_open(&pe, 0, -1, groupFd, 0);
+ if (mCountersFd[CPU_CYCLES] > 0) {
+ mIds[CPU_CYCLES] = count++;
+ mEnabledEvents |= EV_CPU_CYCLES;
+ }
+ }
+
+ if (eventMask & EV_L1D_REFS) {
+ pe.type = PERF_TYPE_HARDWARE;
+ pe.config = PERF_COUNT_HW_CACHE_REFERENCES;
+ mCountersFd[DCACHE_REFS] = perf_event_open(&pe, 0, -1, groupFd, 0);
+ if (mCountersFd[DCACHE_REFS] > 0) {
+ mIds[DCACHE_REFS] = count++;
+ mEnabledEvents |= EV_L1D_REFS;
+ }
+ }
+
+ if (eventMask & EV_L1D_MISSES) {
+ pe.type = PERF_TYPE_HARDWARE;
+ pe.config = PERF_COUNT_HW_CACHE_MISSES;
+ mCountersFd[DCACHE_MISSES] = perf_event_open(&pe, 0, -1, groupFd, 0);
+ if (mCountersFd[DCACHE_MISSES] > 0) {
+ mIds[DCACHE_MISSES] = count++;
+ mEnabledEvents |= EV_L1D_MISSES;
+ }
+ }
+
+ if (eventMask & EV_BPU_REFS) {
+ pe.type = PERF_TYPE_HARDWARE;
+ pe.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS;
+ mCountersFd[BRANCHES] = perf_event_open(&pe, 0, -1, groupFd, 0);
+ if (mCountersFd[BRANCHES] > 0) {
+ mIds[BRANCHES] = count++;
+ mEnabledEvents |= EV_BPU_REFS;
+ }
+ }
+
+ if (eventMask & EV_BPU_MISSES) {
+ pe.type = PERF_TYPE_HARDWARE;
+ pe.config = PERF_COUNT_HW_BRANCH_MISSES;
+ mCountersFd[BRANCH_MISSES] = perf_event_open(&pe, 0, -1, groupFd, 0);
+ if (mCountersFd[BRANCH_MISSES] > 0) {
+ mIds[BRANCH_MISSES] = count++;
+ mEnabledEvents |= EV_BPU_MISSES;
+ }
+ }
+
+#ifdef __ARM_ARCH
+ if (eventMask & EV_L1I_REFS) {
+ pe.type = PERF_TYPE_RAW;
+ pe.config = ARMV8_PMUV3_PERFCTR_L1_ICACHE_ACCESS;
+ mCountersFd[ICACHE_REFS] = perf_event_open(&pe, 0, -1, groupFd, 0);
+ if (mCountersFd[ICACHE_REFS] > 0) {
+ mIds[ICACHE_REFS] = count++;
+ mEnabledEvents |= EV_L1I_REFS;
+ }
+ }
+
+ if (eventMask & EV_L1I_MISSES) {
+ pe.type = PERF_TYPE_RAW;
+ pe.config = ARMV8_PMUV3_PERFCTR_L1_ICACHE_REFILL;
+ mCountersFd[ICACHE_MISSES] = perf_event_open(&pe, 0, -1, groupFd, 0);
+ if (mCountersFd[ICACHE_MISSES] > 0) {
+ mIds[ICACHE_MISSES] = count++;
+ mEnabledEvents |= EV_L1I_MISSES;
+ }
+ }
+#else
+ if (eventMask & EV_L1I_REFS) {
+ pe.type = PERF_TYPE_HW_CACHE;
+ pe.config = PERF_COUNT_HW_CACHE_L1I |
+ (PERF_COUNT_HW_CACHE_OP_READ<<8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS<<16);
+ mCountersFd[ICACHE_REFS] = perf_event_open(&pe, 0, -1, groupFd, 0);
+ if (mCountersFd[ICACHE_REFS] > 0) {
+ mIds[ICACHE_REFS] = count++;
+ mEnabledEvents |= EV_L1I_REFS;
+ }
+ }
+
+ if (eventMask & EV_L1I_MISSES) {
+ pe.type = PERF_TYPE_HW_CACHE;
+ pe.config = PERF_COUNT_HW_CACHE_L1I |
+ (PERF_COUNT_HW_CACHE_OP_READ<<8) | (PERF_COUNT_HW_CACHE_RESULT_MISS<<16);
+ mCountersFd[ICACHE_MISSES] = perf_event_open(&pe, 0, -1, groupFd, 0);
+ if (mCountersFd[ICACHE_MISSES] > 0) {
+ mIds[ICACHE_MISSES] = count++;
+ mEnabledEvents |= EV_L1I_MISSES;
+ }
+ }
+#endif
+ }
+#endif // __linux__
+ return mEnabledEvents;
+}
+
+} // namespace utils
diff --git a/tests/icachetest/Profiler.h b/tests/icachetest/Profiler.h
new file mode 100644
index 0000000..a36cab3
--- /dev/null
+++ b/tests/icachetest/Profiler.h
@@ -0,0 +1,229 @@
+/*
+ * 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 TNT_UTILS_PROFILER_H
+#define TNT_UTILS_PROFILER_H
+
+#include <assert.h>
+#include <stdint.h>
+
+#include <array>
+#include <chrono>
+
+#if defined(__linux__)
+# include <unistd.h>
+# include <sys/ioctl.h>
+# include <linux/perf_event.h>
+#endif
+
+namespace utils {
+
+class Profiler {
+ enum {
+ INSTRUCTIONS = 0, // must be zero
+ CPU_CYCLES = 1,
+ DCACHE_REFS = 2,
+ DCACHE_MISSES = 3,
+ BRANCHES = 4,
+ BRANCH_MISSES = 5,
+ ICACHE_REFS = 6,
+ ICACHE_MISSES = 7,
+
+ // Must be last one
+ EVENT_COUNT
+ };
+
+public:
+
+ enum {
+ EV_CPU_CYCLES = 1 << CPU_CYCLES,
+ EV_L1D_REFS = 1 << DCACHE_REFS,
+ EV_L1D_MISSES = 1 << DCACHE_MISSES,
+ EV_BPU_REFS = 1 << BRANCHES,
+ EV_BPU_MISSES = 1 << BRANCH_MISSES,
+ EV_L1I_REFS = 1 << ICACHE_REFS,
+ EV_L1I_MISSES = 1 << ICACHE_MISSES,
+ // helpers
+ EV_L1D_RATES = EV_L1D_REFS | EV_L1D_MISSES,
+ EV_L1I_RATES = EV_L1I_REFS | EV_L1I_MISSES,
+ EV_BPU_RATES = EV_BPU_REFS | EV_BPU_MISSES,
+ };
+
+ static Profiler& get() noexcept;
+
+
+ Profiler(const Profiler& rhs) = delete;
+ Profiler(Profiler&& rhs) = delete;
+ Profiler& operator=(const Profiler& rhs) = delete;
+ Profiler& operator=(Profiler&& rhs) = delete;
+
+ // selects which events are enabled.
+ // By Default: EV_CPU_CYCLES | EV_L1D_RATES | EV_BPU_RATES
+ uint32_t resetEvents(uint32_t eventMask) noexcept;
+
+ uint32_t getEnabledEvents() const noexcept { return mEnabledEvents; }
+
+ // could return false if performance counters are not supported/enabled
+ bool isValid() const { return mCountersFd[0] >= 0; }
+
+ class Counters {
+ friend class Profiler;
+ uint64_t nr;
+ uint64_t time_enabled;
+ uint64_t time_running;
+ struct {
+ uint64_t value;
+ uint64_t id;
+ } counters[Profiler::EVENT_COUNT];
+
+ friend Counters operator-(Counters lhs, const Counters& rhs) noexcept {
+ lhs.nr -= rhs.nr;
+ lhs.time_enabled -= rhs.time_enabled;
+ lhs.time_running -= rhs.time_running;
+ for (size_t i=0 ; i<EVENT_COUNT ; ++i) {
+ lhs.counters[i].value -= rhs.counters[i].value;
+ }
+ return lhs;
+ }
+
+ public:
+ uint64_t getInstructions() const { return counters[INSTRUCTIONS].value; }
+ uint64_t getCpuCycles() const { return counters[CPU_CYCLES].value; }
+ uint64_t getL1DReferences() const { return counters[DCACHE_REFS].value; }
+ uint64_t getL1DMisses() const { return counters[DCACHE_MISSES].value; }
+ uint64_t getL1IReferences() const { return counters[ICACHE_REFS].value; }
+ uint64_t getL1IMisses() const { return counters[ICACHE_MISSES].value; }
+ uint64_t getBranchInstructions() const { return counters[BRANCHES].value; }
+ uint64_t getBranchMisses() const { return counters[BRANCH_MISSES].value; }
+
+ std::chrono::duration<uint64_t, std::nano> getWallTime() const {
+ return std::chrono::duration<uint64_t, std::nano>(time_enabled);
+ }
+
+ std::chrono::duration<uint64_t, std::nano> getRunningTime() const {
+ return std::chrono::duration<uint64_t, std::nano>(time_running);
+ }
+
+ double getIPC() const noexcept {
+ uint64_t cpuCycles = getCpuCycles();
+ uint64_t instructions = getInstructions();
+ return double(instructions) / double(cpuCycles);
+ }
+
+ double getCPI() const noexcept {
+ uint64_t cpuCycles = getCpuCycles();
+ uint64_t instructions = getInstructions();
+ return double(cpuCycles) / double(instructions);
+ }
+
+ double getL1DMissRate() const noexcept {
+ uint64_t cacheReferences = getL1DReferences();
+ uint64_t cacheMisses = getL1DMisses();
+ return double(cacheMisses) / double(cacheReferences);
+ }
+
+ double getL1DHitRate() const noexcept {
+ return 1.0 - getL1DMissRate();
+ }
+
+ double getL1IMissRate() const noexcept {
+ uint64_t cacheReferences = getL1IReferences();
+ uint64_t cacheMisses = getL1IMisses();
+ return double(cacheMisses) / double(cacheReferences);
+ }
+
+ double getL1IHitRate() const noexcept {
+ return 1.0 - getL1IMissRate();
+ }
+
+ double getBranchMissRate() const noexcept {
+ uint64_t branchReferences = getBranchInstructions();
+ uint64_t branchMisses = getBranchMisses();
+ return double(branchMisses) / double(branchReferences);
+ }
+
+ double getBranchHitRate() const noexcept {
+ return 1.0 - getBranchMissRate();
+ }
+
+ double getMPKI(uint64_t misses) const noexcept {
+ return (misses * 1000.0) / getInstructions();
+ }
+
+ };
+
+#if defined(__linux__)
+
+ void reset() noexcept {
+ int fd = mCountersFd[0];
+ ioctl(fd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP);
+ }
+
+ void start() noexcept {
+ int fd = mCountersFd[0];
+ ioctl(fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP);
+ }
+
+ void stop() noexcept {
+ int fd = mCountersFd[0];
+ ioctl(fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP);
+ }
+
+ void readCounters(Counters* outCounters) noexcept {
+ Counters counters;
+ ssize_t n = read(mCountersFd[0], &counters, sizeof(Counters));
+ memset(outCounters, 0, sizeof(Counters));
+ if (n > 0) {
+ outCounters->nr = counters.nr;
+ outCounters->time_enabled = counters.time_enabled;
+ outCounters->time_running = counters.time_running;
+ for (size_t i=0 ; i<size_t(EVENT_COUNT) ; i++) {
+ if (mCountersFd[i] >= 0) {
+ outCounters->counters[i] = counters.counters[mIds[i]];
+ }
+ }
+ }
+ }
+
+#else // !__linux__
+
+ void reset() noexcept { }
+ void start() noexcept { }
+ void stop() noexcept { }
+ void readCounters(Counters* counters) noexcept { }
+
+#endif // __linux__
+
+ bool hasBranchRates() const noexcept {
+ return (mCountersFd[BRANCHES] >= 0) && (mCountersFd[BRANCH_MISSES] >= 0);
+ }
+
+ bool hasICacheRates() const noexcept {
+ return (mCountersFd[ICACHE_REFS] >= 0) && (mCountersFd[ICACHE_MISSES] >= 0);
+ }
+
+private:
+ Profiler() noexcept;
+ ~Profiler() noexcept;
+
+ std::array<uint8_t, EVENT_COUNT> mIds;
+ std::array<int, EVENT_COUNT> mCountersFd;
+ uint32_t mEnabledEvents = 0;
+};
+
+} // namespace utils
+
+#endif // TNT_UTILS_PROFILER_H
diff --git a/tests/icachetest/icache.S b/tests/icachetest/icache.S
index fbe8fa7..e82895d 100644
--- a/tests/icachetest/icache.S
+++ b/tests/icachetest/icache.S
@@ -19,6 +19,14 @@
mov r0, r0 ; \
mov r0, r0 ; \
mov r0, r0 ; \
+ mov r0, r0 ; \
+ mov r0, r0 ; \
+ mov r0, r0 ; \
+ mov r0, r0 ; \
+ mov r0, r0 ; \
+ mov r0, r0 ; \
+ mov r0, r0 ; \
+ mov r0, r0 ; \
beq end_loop ; \
mov r0, r0 ; \
@@ -37,6 +45,14 @@
mov r0, r0
mov r0, r0
mov r0, r0
+ mov r0, r0
+ mov r0, r0
+ mov r0, r0
+ mov r0, r0
+ mov r0, r0
+ mov r0, r0
+ mov r0, r0
+ mov r0, r0
end_loop:
subs r0, r0, r1
diff --git a/tests/icachetest/icache2.S b/tests/icachetest/icache2.S
deleted file mode 100644
index 2a204ce..0000000
--- a/tests/icachetest/icache2.S
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * icache.s
- *
- *
- * Copyright 2005 The Android Open Source Project
- *
- */
-
- .text
- .align
-
- .global icache_test2
- .type icache_test2, %function
-
-#define LOOP \
- mov r0, r0 ; \
- mov r0, r0 ; \
- mov r0, r0 ; \
- mov r0, r0 ; \
- mov r0, r0 ; \
- mov r0, r0 ; \
- mov r0, r0 ; \
- mov r0, r0 ;
-
-
- /*
- * r0 = loop_count
- * r1 = step
- * r2 = mask
- */
-
-icache_test2:
-end_loop:
-
- /* each loop iteration is one cache line
- repeat this block 2048 times... */
-
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
- LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP
-
- subs r0, r0, #1
- bgt end_loop
- bx lr
-
-
diff --git a/tests/icachetest/icache_main.c b/tests/icachetest/icache_main.c
deleted file mode 100644
index 93f36d4..0000000
--- a/tests/icachetest/icache_main.c
+++ /dev/null
@@ -1,34 +0,0 @@
-#include <stdio.h>
-#include <sys/time.h>
-
-extern void icache_test(long count, long step);
-extern void icache_test2(long count);
-
-int main()
-{
- printf("[bytes]\t[us]\n");
-
- struct timeval now, tm;
- long long t;
- long MBs;
- long i;
- long step = 32;
- for (i=0 ; step<=2048 ; i++, step+=32)
- {
- long value;
- gettimeofday(&now, 0);
- icache_test(0x800000L, step);
- gettimeofday(&tm, 0);
- t = (tm.tv_sec*1000000LL+tm.tv_usec) - (now.tv_sec*1000000LL+now.tv_usec);
- printf("%6ld\t%lld\n", step*32, t);
- }
-
- gettimeofday(&now, 0);
- icache_test2(0x800000L / 2048);
- gettimeofday(&tm, 0);
- t = (tm.tv_sec*1000000LL+tm.tv_usec) - (now.tv_sec*1000000LL+now.tv_usec);
- MBs = (8388608LL*32*1000000) / (t * (1024*1024));
- printf("\n%6lld us\t%ld MB/s\n", t, MBs);
-
- return 0;
-}
diff --git a/tests/icachetest/icache_main.cpp b/tests/icachetest/icache_main.cpp
new file mode 100644
index 0000000..d5aeda0
--- /dev/null
+++ b/tests/icachetest/icache_main.cpp
@@ -0,0 +1,124 @@
+#include <stdio.h>
+#include <sys/time.h>
+#include <getopt.h>
+
+#include <thread>
+#include <iostream>
+#include <iomanip>
+
+#include <sched.h>
+
+#include "Profiler.h"
+
+extern "C" void icache_test(long count, long step);
+
+static constexpr size_t MAX_CODE_SIZE = 128*1024;
+static constexpr size_t CACHE_LINE_SIZE = 64;
+static constexpr size_t MAX_ITERATIONS_COUNT = MAX_CODE_SIZE / CACHE_LINE_SIZE;
+static constexpr size_t REPETITIONS = 0x800000L;
+
+
+using namespace utils;
+
+static cpu_set_t g_cpu_set;
+
+static void printUsage(char* name) {
+ std::string exec_name(name);
+ std::string usage(
+ "ICACHE is a command-line tool for testing the L1 instruction cache performance.\n"
+ "(Make sure security.perf_harden is set to 0)\n\n"
+ "Usages:\n"
+ " ICACHE [options]\n"
+ "\n"
+ "Options:\n"
+ " --help, -h\n"
+ " print this message\n\n"
+ " --affinity=N, -a N\n"
+ " Specify which CPU the test should run on.\n\n"
+ );
+ const std::string from("ICACHE");
+ for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) {
+ usage.replace(pos, from.length(), exec_name);
+ }
+ printf("%s", usage.c_str());
+}
+
+static int handleCommandLineArgments(int argc, char* argv[]) {
+ static constexpr const char* OPTSTR = "ha:";
+ static const struct option OPTIONS[] = {
+ { "help", no_argument, 0, 'h' },
+ { "affinity", required_argument, 0, 'a' },
+ { 0, 0, 0, 0 } // termination of the option list
+ };
+ int opt;
+ int option_index = 0;
+ while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &option_index)) >= 0) {
+ std::string arg(optarg ? optarg : "");
+ switch (opt) {
+ default:
+ case 'h':
+ printUsage(argv[0]);
+ exit(0);
+ break;
+ case 'a':
+ size_t cpu = std::stoi(arg);
+ if (cpu < std::thread::hardware_concurrency()) {
+ CPU_SET(cpu, &g_cpu_set);
+ } else {
+ std::cerr << "N must be < " << std::thread::hardware_concurrency() << std::endl;
+ exit(0);
+ }
+ break;
+ }
+ }
+ return optind;
+}
+
+int main(int argc, char* argv[]) {
+ CPU_ZERO(&g_cpu_set);
+
+ [[maybe_unused]] int option_index = handleCommandLineArgments(argc, argv);
+ [[maybe_unused]] int num_args = argc - option_index;
+
+ if (CPU_COUNT(&g_cpu_set)) {
+ sched_setaffinity(gettid(), sizeof(g_cpu_set), &g_cpu_set);
+ }
+
+ Profiler& profiler = Profiler::get();
+ profiler.resetEvents(Profiler::EV_CPU_CYCLES | Profiler::EV_L1I_RATES);
+
+ if (!profiler.isValid()) {
+ fprintf(stderr, "performance counters not enabled. try \"setprop security.perf_harden 0\"\n");
+ exit(0);
+ }
+
+ size_t const stepInBytes = 1024; // 1 KiB steps
+ size_t const step = stepInBytes / CACHE_LINE_SIZE;
+
+ std::cout << std::fixed << std::setprecision(2);
+
+ printf("[KiB]\t[cyc]\t[refs]\t[MPKI]\t[ns]\n");
+
+ Profiler::Counters counters;
+
+ for (size_t i=step ; i <= MAX_ITERATIONS_COUNT ; i += step) {
+ profiler.reset();
+
+ auto now = std::chrono::steady_clock::now();
+ profiler.start();
+ icache_test(REPETITIONS, i);
+ profiler.stop();
+ auto duration = std::chrono::steady_clock::now() - now;
+
+ profiler.readCounters(&counters);
+
+ std::cout << ((i*CACHE_LINE_SIZE)/1024) << "\t"
+ << counters.getCpuCycles()/double(REPETITIONS) << "\t"
+ << counters.getL1IReferences()/double(REPETITIONS) << "\t"
+ << counters.getMPKI(counters.getL1IMisses()) << "\t"
+ << duration.count()/double(REPETITIONS) << "\t"
+ << std::endl;
+ }
+
+ return 0;
+}
diff --git a/tests/kernel.config/Android.mk b/tests/kernel.config/Android.mk
index af68c05..0c87107 100644
--- a/tests/kernel.config/Android.mk
+++ b/tests/kernel.config/Android.mk
@@ -15,7 +15,6 @@
# Required Tests
cts_src_files := \
- aslr_test.cpp \
logger_test.cpp \
multicast_test.cpp \
nfs_test.cpp \
@@ -23,8 +22,10 @@
sysvipc_test.cpp \
# Required plus Recommended Tests
+# TODO: move aslr_test.cpp back to cts_src_files b/36888825
test_src_files := \
$(cts_src_files) \
+ aslr_test.cpp \
aslr_rec_test.cpp \
mmc_max_speed_test.cpp \
diff --git a/tests/kernel.config/AndroidTest.xml b/tests/kernel.config/AndroidTest.xml
index 4fe3192..df7aa49 100644
--- a/tests/kernel.config/AndroidTest.xml
+++ b/tests/kernel.config/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Kernel Config test cases">
+ <option name="config-descriptor:metadata" key="component" value="systems" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
<option name="push" value="CtsKernelConfigTestCases->/data/local/tmp/CtsKernelConfigTestCases" />
@@ -22,5 +23,8 @@
<test class="com.android.tradefed.testtype.GTest" >
<option name="native-test-device-path" value="/data/local/tmp" />
<option name="module-name" value="CtsKernelConfigTestCases" />
+ <!-- Make sure there is some data in the pstore -->
+ <option name="before-test-cmd" value="echo HELLOWORLD >/dev/pmsg0" />
+ <option name="reboot-before-test" value="true" />
</test>
</configuration>
diff --git a/tests/sdcard/sdcard_perf_test.cpp b/tests/sdcard/sdcard_perf_test.cpp
index c93c52b..7efa650 100644
--- a/tests/sdcard/sdcard_perf_test.cpp
+++ b/tests/sdcard/sdcard_perf_test.cpp
@@ -132,7 +132,7 @@
" -s --size: Size in kbytes of the data.\n"
" -S --chunk-size: Size of a chunk. Default to size ie 1 chunk.\n"
" Data will be written/read using that chunk size.\n"
- " -D --depth: Depth of directory tree to create for traversal.\n",
+ " -D --depth: Depth of directory tree to create for traversal.\n"
" -i --iterations: Number of time a process should carry its task.\n"
" -p --procnb: Number of processes to use.\n"
" -d --dump: Print the raw timing on stdout.\n"
diff --git a/tests/workloads/pwrsummary.sh b/tests/workloads/pwrsummary.sh
index 3d3aeb8..c527b54 100755
--- a/tests/workloads/pwrsummary.sh
+++ b/tests/workloads/pwrsummary.sh
@@ -99,7 +99,7 @@
# 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
+ # stdbuf -o0 adb shell /data/local/tmp/hwuimacro shadowgrid2 400 | stdbuf -o0 ./hwuitestfilter.sh | tee t.csv
sed -e 's/ns//' -e 's/[\(\)%]/ /g' | awk '
BEGIN { startTime=0; lastTime=0; }
/^Stats since:/ {
diff --git a/tests/workloads/pwrtest.sh b/tests/workloads/pwrtest.sh
index 39f7b11..fd5d825 100755
--- a/tests/workloads/pwrtest.sh
+++ b/tests/workloads/pwrtest.sh
@@ -94,16 +94,16 @@
case $DEVICE in
(shamu|hammerhead)
- HWUITEST=hwuitest
+ HWUIMACRO=hwuimacro
onSwipe="700 1847 700 400 50"
;;
(*)
- HWUITEST=hwuitest64
+ HWUIMACRO=hwuimacro64
onSwipe="500 1200 500 550 150"
;;
esac
-scripts="defs.sh systemapps.sh recentfling.sh youtube.sh chromefling.sh $HWUITEST"
+scripts="defs.sh systemapps.sh recentfling.sh youtube.sh chromefling.sh"
if ! $MONSOON >/dev/null 2>&1; then
echo $MONSOON must be in your PATH >&2
@@ -253,6 +253,7 @@
echo Copying $scripts to device $devdir...
copy_files
+adb shell ln -s /data/benchmarktest/hwuimacro/$HWUIMACRO $devdir/$HWUIMACRO
tests=""
# measure background power
@@ -332,9 +333,9 @@
if [ $shadowgrid2Time -gt 0 ]; then
airplane_mode on
echo $(date) Test 4 : shadowgrid2 for $shadowgrid2Time minutes
- start_job "./$HWUITEST shadowgrid2 100000"
+ start_job "./$HWUIMACRO --onscreen shadowgrid2 100000"
run_test shadowgrid2 $shadowgrid2Time
- cleanup_job shadowgrid2 $HWUITEST
+ cleanup_job shadowgrid2 $HWUIMACRO
airplane_mode off
date
tests="$tests shadowgrid2"
diff --git a/verity/build_verity_tree.cpp b/verity/build_verity_tree.cpp
index c50e449..69c761d 100644
--- a/verity/build_verity_tree.cpp
+++ b/verity/build_verity_tree.cpp
@@ -10,11 +10,11 @@
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
-#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
+#include <vector>
#include <android-base/file.h>
@@ -126,7 +126,7 @@
{
char *data_filename;
char *verity_filename;
- unsigned char *salt = NULL;
+ std::vector<unsigned char> salt;
size_t salt_size = 0;
bool sparse = false;
size_t block_size = 4096;
@@ -150,12 +150,8 @@
switch (c) {
case 'a':
- salt_size = strlen(optarg);
- salt = new unsigned char[salt_size]();
- if (salt == NULL) {
- FATAL("failed to allocate memory for salt\n");
- }
- memcpy(salt, optarg, salt_size);
+ salt.clear();
+ salt.insert(salt.end(), optarg, &optarg[strlen(optarg)]);
break;
case 'A': {
BIGNUM *bn = NULL;
@@ -163,11 +159,8 @@
FATAL("failed to convert salt from hex\n");
}
salt_size = BN_num_bytes(bn);
- salt = new unsigned char[salt_size]();
- if (salt == NULL) {
- FATAL("failed to allocate memory for salt\n");
- }
- if((size_t)BN_bn2bin(bn, salt) != salt_size) {
+ salt.resize(salt_size);
+ if((size_t)BN_bn2bin(bn, salt.data()) != salt_size) {
FATAL("failed to convert salt to bytes\n");
}
}
@@ -214,19 +207,16 @@
size_t hash_size = EVP_MD_size(md);
assert(hash_size * 2 < block_size);
- if (!salt || !salt_size) {
+ if (salt.data() || !salt_size) {
salt_size = hash_size;
- salt = new unsigned char[salt_size];
- if (salt == NULL) {
- FATAL("failed to allocate memory for salt\n");
- }
+ salt.resize(salt_size);
int random_fd = open("/dev/urandom", O_RDONLY);
if (random_fd < 0) {
FATAL("failed to open /dev/urandom\n");
}
- ssize_t ret = read(random_fd, salt, salt_size);
+ ssize_t ret = read(random_fd, salt.data(), salt_size);
if (ret != (ssize_t)salt_size) {
FATAL("failed to read %zu bytes from /dev/urandom: %zd %d\n", salt_size, ret, errno);
}
@@ -310,14 +300,14 @@
unsigned char zero_block_hash[hash_size];
unsigned char zero_block[block_size];
memset(zero_block, 0, block_size);
- hash_block(md, zero_block, block_size, salt, salt_size, zero_block_hash, NULL);
+ hash_block(md, zero_block, block_size, salt.data(), salt_size, zero_block_hash, NULL);
unsigned char root_hash[hash_size];
verity_tree_levels[levels] = root_hash;
struct sparse_hash_ctx ctx;
ctx.hashes = verity_tree_levels[0];
- ctx.salt = salt;
+ ctx.salt = salt.data();
ctx.salt_size = salt_size;
ctx.hash_size = hash_size;
ctx.block_size = block_size;
@@ -334,7 +324,7 @@
hash_blocks(md,
verity_tree_levels[i], verity_tree_level_blocks[i] * block_size,
verity_tree_levels[i + 1], &out_size,
- salt, salt_size, block_size);
+ salt.data(), salt_size, block_size);
if (i < levels - 1) {
assert(div_round_up(out_size, block_size) == verity_tree_level_blocks[i + 1]);
} else {
@@ -347,7 +337,7 @@
}
printf(" ");
for (size_t i = 0; i < salt_size; i++) {
- printf("%02x", salt[i]);
+ printf("%02x", salt.data()[i]);
}
printf("\n");
@@ -363,5 +353,4 @@
delete[] verity_tree_levels;
delete[] verity_tree_level_blocks;
delete[] verity_tree;
- delete[] salt;
}
diff --git a/verity/fec/image.cpp b/verity/fec/image.cpp
index 509b102..861ca60 100644
--- a/verity/fec/image.cpp
+++ b/verity/fec/image.cpp
@@ -28,7 +28,6 @@
#include <getopt.h>
#include <openssl/sha.h>
#include <pthread.h>
-#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
diff --git a/verity/fec/main.cpp b/verity/fec/main.cpp
index 93f1ec2..6063a66 100644
--- a/verity/fec/main.cpp
+++ b/verity/fec/main.cpp
@@ -25,7 +25,6 @@
#include <getopt.h>
#include <fcntl.h>
#include <pthread.h>
-#include <stdbool.h>
#include <stdlib.h>
#include <string.h>