diff options
author | Mohan Srinivasan <srmohan@google.com> | 2016-12-21 22:18:13 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2016-12-21 22:18:13 +0000 |
commit | 0d69af9d9a24059151eb8ee09a343bd4155b7e12 (patch) | |
tree | 0b1e42246bdb67c127ba075f62d33055269139a3 /ioshark | |
parent | 325bc98318284a3039ff91e79fb6dbac5ed49a0d (diff) | |
parent | 99c8f98f2a9c76fabb19cdbc5b495978cfc479b3 (diff) | |
download | extras-0d69af9d9a24059151eb8ee09a343bd4155b7e12.tar.gz |
Merge "ioshark: A Repeatable Application Workload Based Storage Benchmark."
Diffstat (limited to 'ioshark')
-rw-r--r-- | ioshark/Android.mk | 30 | ||||
-rw-r--r-- | ioshark/README | 26 | ||||
-rwxr-xr-x | ioshark/collect-straces.sh | 94 | ||||
-rw-r--r-- | ioshark/compile_ioshark.c | 578 | ||||
-rw-r--r-- | ioshark/compile_ioshark.h | 78 | ||||
-rw-r--r-- | ioshark/compile_ioshark_subr.c | 102 | ||||
-rw-r--r-- | ioshark/ioshark.h | 108 | ||||
-rw-r--r-- | ioshark/ioshark_bench.c | 745 | ||||
-rw-r--r-- | ioshark/ioshark_bench.h | 151 | ||||
-rw-r--r-- | ioshark/ioshark_bench_mmap.c | 213 | ||||
-rw-r--r-- | ioshark/ioshark_bench_subr.c | 324 |
11 files changed, 2449 insertions, 0 deletions
diff --git a/ioshark/Android.mk b/ioshark/Android.mk new file mode 100644 index 00000000..a2e4816d --- /dev/null +++ b/ioshark/Android.mk @@ -0,0 +1,30 @@ +# 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_SRC_FILES := ioshark_bench.c ioshark_bench_subr.c ioshark_bench_mmap.c +LOCAL_CFLAGS := -g -O2 -Wall -Werror +LOCAL_MODULE := ioshark_bench +include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := compile_ioshark.c compile_ioshark_subr.c +LOCAL_CFLAGS := -g -O2 -Wall -Werror -D_GNU_SOURCE +LOCAL_MODULE := compile_ioshark +include $(BUILD_HOST_EXECUTABLE) + + + diff --git a/ioshark/README b/ioshark/README new file mode 100644 index 00000000..3afbe76e --- /dev/null +++ b/ioshark/README @@ -0,0 +1,26 @@ +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 compiler that takes straces 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.
\ No newline at end of file diff --git a/ioshark/collect-straces.sh b/ioshark/collect-straces.sh new file mode 100755 index 00000000..4577e736 --- /dev/null +++ b/ioshark/collect-straces.sh @@ -0,0 +1,94 @@ +/* + * 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 + mv foo bar + if [ -s bar ] + then + echo parsing $i + compile_ioshark bar $i.wl + rm -f bar + else + rm -f $i bar + fi + fi + done +} + +# main() starts here + +adb root && adb wait-for-device + +adb shell 'ps -ef' | 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 +tar zcf wl.tgz *.wl + + diff --git a/ioshark/compile_ioshark.c b/ioshark/compile_ioshark.c new file mode 100644 index 00000000..609d2bc2 --- /dev/null +++ b/ioshark/compile_ioshark.c @@ -0,0 +1,578 @@ +/* + * 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 <linux/fs.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 } +}; + +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); +} + +u_int64_t +get_delta_ts(char *buf, struct timeval *start_tv) +{ + struct timeval op_tv, tv_res; + + sscanf(buf, "%lu.%lu", &op_tv.tv_sec, &op_tv.tv_usec); + timersub(&op_tv, start_tv, &tv_res); + return (tv_res.tv_usec + (tv_res.tv_sec * 1000000)); +} + +void +get_pathname(char *buf, char *pathname, enum file_op file_op) +{ + char *s, *s2, save; + + 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, "%lu", 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, "%lx", 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, "%lu", 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, "%lu", 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, "%lu", len); + sscanf(s_offset + 2, "%lu", offset); +} + +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)); +} + +void +get_start_time(struct timeval *tv) +{ + FILE *fp; + + tv->tv_usec = 0; + fp = fopen("trace.begin", "r"); + if (fp == NULL) { + fprintf(stderr, "%s Can't open trace.begin\n", + __func__); + exit(EXIT_FAILURE); + } + if (fscanf(fp, "%lu", &tv->tv_sec) != 1) { + fprintf(stderr, "%s Can't read seconds from trace.begin\n", + __func__); + exit(EXIT_FAILURE); + } +} + +/* + * 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 start_time; + + 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); + } + get_start_time(&start_time); + 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, &start_time); + get_syscall(s, syscall); + disk_file_op->file_op = map_syscall(syscall); + 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, + &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, &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, + &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; + 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); + } +} diff --git a/ioshark/compile_ioshark.h b/ioshark/compile_ioshark.h new file mode 100644 index 00000000..13095bf6 --- /dev/null +++ b/ioshark/compile_ioshark.h @@ -0,0 +1,78 @@ +/* + * 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; +}; + +/* 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); + + + diff --git a/ioshark/compile_ioshark_subr.c b/ioshark/compile_ioshark_subr.c new file mode 100644 index 00000000..e2b6b891 --- /dev/null +++ b/ioshark/compile_ioshark_subr.c @@ -0,0 +1,102 @@ +/* + * 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; + +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; + 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->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; +} + + + diff --git a/ioshark/ioshark.h b/ioshark/ioshark.h new file mode 100644 index 00000000..f0a715f7 --- /dev/null +++ b/ioshark/ioshark.h @@ -0,0 +1,108 @@ +/* + * 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; +}; + +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; +}; diff --git a/ioshark/ioshark_bench.c b/ioshark/ioshark_bench.c new file mode 100644 index 00000000..9bbfcae1 --- /dev/null +++ b/ioshark/ioshark_bench.c @@ -0,0 +1,745 @@ +/* + * 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 <linux/fs.h> +#include <fcntl.h> +#include <string.h> +#include <assert.h> +#include <pthread.h> +#include <sys/statfs.h> +#include "ioshark.h" +#define IOSHARK_MAIN +#include "ioshark_bench.h" + +#define UNUSED_PARAM(X) ((void)X) + +char *progname; + +#define MAX_INPUT_FILES 1024 +#define MAX_THREADS 1024 + +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; + +#if 0 +static long gettid() +{ + return syscall(__NR_gettid); +} +#endif + +void usage() +{ + fprintf(stderr, "%s [-d preserve_delays] [-n num_iterations] [-t num_threads] <list of parsed input files>\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 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; + +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) +{ + struct timeval file_create_time; + int i; + struct ioshark_file_state file_state; + char path[512]; + void *db_node; + struct rw_bytes_s rw_bytes; + + timerclear(&file_create_time); + 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); + } + sprintf(path, "file.%d.%d", + (int)(state - thread_state), + file_state.fileno); + create_file(path, file_state.size, + &file_create_time, &rw_bytes); + db_node = files_db_add_byfileno(state->db_handle, + file_state.fileno); + files_db_update_size(db_node, file_state.size); + files_db_update_filename(db_node, path); + } + update_byte_counts(&aggr_create_rw_bytes, &rw_bytes); + update_time(&aggregate_file_create_time, &file_create_time); +} + +static void +do_one_io(void *db_node, + struct ioshark_file_operation *file_op, + struct timeval *io_time, u_int64_t *op_counts, + struct rw_bytes_s *rw_bytes, + char **bufp, int *buflen) +{ + struct timeval start; + + 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: + (void)gettimeofday(&start, (struct timezone *)NULL); + ret = lseek(files_db_get_fd(db_node), + file_op->lseek_offset, + file_op->lseek_action); + update_delta_time(&start, io_time); + 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); + (void)gettimeofday(&start, + (struct timezone *)NULL); + ret = pread(files_db_get_fd(db_node), p, + file_op->prw_len, file_op->prw_offset); + update_delta_time(&start, io_time); + rw_bytes->bytes_read += file_op->prw_len; + if (ret < 0) { + fprintf(stderr, + "%s: pread(%s %lu %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); + (void)gettimeofday(&start, + (struct timezone *)NULL); + ret = pwrite(files_db_get_fd(db_node), p, + file_op->prw_len, file_op->prw_offset); + update_delta_time(&start, io_time); + rw_bytes->bytes_written += file_op->prw_len; + if (ret < 0) { + fprintf(stderr, + "%s: pwrite(%s %lu %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); + (void)gettimeofday(&start, + (struct timezone *)NULL); + ret = read(files_db_get_fd(db_node), p, + file_op->rw_len); + update_delta_time(&start, io_time); + rw_bytes->bytes_read += file_op->rw_len; + if (ret < 0) { + fprintf(stderr, + "%s: read(%s %lu) 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); + (void)gettimeofday(&start, + (struct timezone *)NULL); + ret = write(files_db_get_fd(db_node), p, + file_op->rw_len); + update_delta_time(&start, io_time); + rw_bytes->bytes_written += file_op->rw_len; + if (ret < 0) { + fprintf(stderr, + "%s: write(%s %lu) 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, io_time); + break; + case IOSHARK_OPEN: + if (file_op->open_flags & O_CREAT) { + (void)gettimeofday(&start, + (struct timezone *)NULL); + 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) { + update_delta_time(&start, io_time); + 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 + update_delta_time(&start, io_time); + } else { + (void)gettimeofday(&start, + (struct timezone *)NULL); + fd = open(files_db_get_filename(db_node), + file_op->open_flags); + update_delta_time(&start, io_time); + 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) { + (void)gettimeofday(&start, + (struct timezone *)NULL); + ret = fsync(files_db_get_fd(db_node)); + update_delta_time(&start, io_time); + if (ret < 0) { + fprintf(stderr, + "%s: fsync(%s) error %d\n", + progname, + files_db_get_filename(db_node), + errno); + exit(EXIT_FAILURE); + } + } else { + (void)gettimeofday(&start, + (struct timezone *)NULL); + ret = fdatasync(files_db_get_fd(db_node)); + update_delta_time(&start, io_time); + 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: + (void)gettimeofday(&start, + (struct timezone *)NULL); + ret = close(files_db_get_fd(db_node)); + update_delta_time(&start, io_time); + 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; + u_int64_t prev_delay = 0; + int fd; + int i; + char *buf = NULL; + int buflen = 0; + struct timeval total_io_time; + struct timeval total_delay_time; + struct timeval start; + 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_io_time); + 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) { + (void)gettimeofday(&start, (struct timezone *)NULL); + usleep(file_op.delta_us - prev_delay); + update_delta_time(&start, &total_delay_time); + prev_delay = file_op.delta_us; + } + 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) { + /* + * 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. + */ + fd = open(files_db_get_filename(db_node), O_RDWR); + if (fd < 0) { + fprintf(stderr, "%s: open(%s O_RDWR) error %d\n", + progname, files_db_get_filename(db_node), + errno); + exit(EXIT_FAILURE); + } + files_db_update_fd(db_node, fd); + } + do_one_io(db_node, &file_op, &total_io_time, + op_counts, &rw_bytes, &buf, &buflen); + } + (void)gettimeofday(&start, (struct timezone *)NULL); + files_db_fsync_discard_files(state->db_handle); + files_db_close_files(state->db_handle); + update_delta_time(&start, &total_io_time); + update_time(&aggregate_IO_time, &total_io_time); + 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) +{ + struct thread_state_s *state; + + UNUSED_PARAM(unused); + 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) +{ + struct thread_state_s *state; + + UNUSED_PARAM(unused); + 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; + + 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 (file_state.size > free_fs_bytes) { + fclose(fp); + printf("Reducing number of input files from %d down to %d\n", + num_input_files, + i - fssize_clamp_next_index); + goto out; + } + free_fs_bytes -= file_state.size; + } + fclose(fp); + } +out: + *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; + } +} + +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:t:")) != EOF) { + switch (c) { + case 'd': + do_delay = 1; + break; + case 'n': + num_iterations = atoi(optarg); + break; + case 't': + num_threads = atoi(optarg); + break; + default: + usage(); + } + } + + if (num_threads > MAX_THREADS) + usage(); + + if (optind == argc) + usage(); + + 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); + continue; + } + 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); + } + 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); + + /* + * 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))) { + /* Create files once */ + printf("Doing Pre-creation of 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); + 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); + + /* Do the IOs N times */ + for (i = 0 ; i < num_iterations ; i++) { + (void)system("echo 3 > /proc/sys/vm/drop_caches"); + printf("Starting Test. Iteration %d\n", i); + init_work(start_file, num_files); + 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); + } + /* + * 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); + } + } + printf("Total Creation time = %lu.%lu (msecs.usecs)\n", + get_msecs(&aggregate_file_create_time), + get_usecs(&aggregate_file_create_time)); + printf("Total Remove time = %lu.%lu (msecs.usecs)\n", + get_msecs(&aggregate_file_remove_time), + get_usecs(&aggregate_file_remove_time)); + if (do_delay) + printf("Total delay time = %lu.%lu (msecs.usecs)\n", + get_msecs(&aggregate_delay_time), + get_usecs(&aggregate_delay_time)); + printf("Total IO time = %lu.%lu (msecs.usecs)\n", + get_msecs(&aggregate_IO_time), + get_usecs(&aggregate_IO_time)); + print_bytes("Upfront File Creation bytes", + &aggr_create_rw_bytes); + print_bytes("IO bytes", &aggr_io_rw_bytes); + print_op_stats(aggr_op_counts); +} diff --git a/ioshark/ioshark_bench.h b/ioshark/ioshark_bench.h new file mode 100644 index 00000000..6ce9322f --- /dev/null +++ b/ioshark/ioshark_bench.h @@ -0,0 +1,151 @@ +/* + * 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 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 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); +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 timeval *total_time, + 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, struct timeval *io_time); + + + + + + + + + + diff --git a/ioshark/ioshark_bench_mmap.c b/ioshark/ioshark_bench_mmap.c new file mode 100644 index 00000000..7ee7c41b --- /dev/null +++ b/ioshark/ioshark_bench_mmap.c @@ -0,0 +1,213 @@ +/* + * 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 <linux/fs.h> +#include <fcntl.h> +#include <string.h> +#include <assert.h> +#include <pthread.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, struct timeval *io_time) +{ + struct timeval start; + char *p; + int ret; + + if (!(prot & IOSHARK_PROT_WRITE)) { + /* Only preads */ + p = get_buf(bufp, buflen, len, 0); + (void)gettimeofday(&start, + (struct timezone *)NULL); + ret = pread(files_db_get_fd(db_node), + p, len, offset); + update_delta_time(&start, io_time); + rw_bytes->bytes_read += len; + if (ret < 0) { + fprintf(stderr, + "%s: mapped pread(%s %lu %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); + (void)gettimeofday(&start, + (struct timezone *)NULL); + ret = pwrite(files_db_get_fd(db_node), + p, len, offset); + update_delta_time(&start, io_time); + 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); + (void)gettimeofday(&start, + (struct timezone *)NULL); + ret = pread(files_db_get_fd(db_node), + p, len, offset); + update_delta_time(&start, io_time); + rw_bytes->bytes_read += len; + if (ret < 0) { + fprintf(stderr, + "%s: mapped pread(%s %lu %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, struct timeval *io_time) +{ + 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, io_time); + 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, io_time); + } +} diff --git a/ioshark/ioshark_bench_subr.c b/ioshark/ioshark_bench_subr.c new file mode 100644 index 00000000..ff273cce --- /dev/null +++ b/ioshark/ioshark_bench_subr.c @@ -0,0 +1,324 @@ +/* + * 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 <linux/fs.h> +#include <fcntl.h> +#include <string.h> +#include <assert.h> +#include "ioshark.h" +#include "ioshark_bench.h" + +extern char *progname; + +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) +{ + 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->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; + + /* + * File was closed, let's open it so we can + * fsync and fadvise(DONTNEED) it. + */ + do_close = 1; + fd = open(files_db_get_filename(db_node), + O_RDWR); + if (fd < 0) { + fprintf(stderr, + "%s: open(%s O_RDWR) error %d\n", + progname, db_node->filename, + errno); + exit(EXIT_FAILURE); + } + db_node->fd = fd; + } + if (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 (unlink(db_node->filename) < 0) { + fprintf(stderr, "%s: Cannot unlink %s:%s\n", + __func__, db_node->filename, strerror(errno)); + exit(1); + } + 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) +{ + 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 timeval *total_time, + struct rw_bytes_s *rw_bytes) +{ + int fd, n; + char *buf = NULL; + int buflen = 0; + struct timeval start; + + (void)gettimeofday(&start, (struct timezone *)NULL); + fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0644); + update_delta_time(&start, total_time); + 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); + (void)gettimeofday(&start, (struct timezone *)NULL); + 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; + update_delta_time(&start, total_time); + size -= n; + } + (void)gettimeofday(&start, (struct timezone *)NULL); + 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); + update_delta_time(&start, total_time); +} + +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: %lu\n", + IO_op[i], op_counts[i]); + } +} + +void +print_bytes(char *desc, struct rw_bytes_s *rw_bytes) +{ + printf("%s: Reads = %dMB, Writes = %dMB\n", + desc, + (int)(rw_bytes->bytes_read / (1024 * 1024)), + (int)(rw_bytes->bytes_written / (1024 * 1024))); +} + |