/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ioshark.h" #include "ioshark_bench.h" #define _BSD_SOURCE #include 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): %m\n", progname, db_node->filename, openflags); 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: %m\n", __func__, db_node->filename); 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: %m\n", progname, path); 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: %m\n", progname, path); free(buf); exit(EXIT_FAILURE); } rw_bytes->bytes_written += n; size -= n; } free(buf); if (fsync(fd) < 0) { fprintf(stderr, "%s Cannot fsync file %s: %m\n", progname, path); exit(EXIT_FAILURE); } if ((errno = posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED)) != 0) { fprintf(stderr, "%s Cannot fadvise(DONTNEED) file %s: %m\n", progname, path); 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 extern char *blockdev_name; 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 || strncmp(dev_name, "aosp_gobo", strlen("aosp_gobo")) == 0 || strncmp(dev_name, "full_k37_y33_gms", strlen("full_k37_y33_gms")) == 0 || strncmp(dev_name, "fugu", strlen("fugu")) == 0) { strcpy(bdev, "mmcblk0"); } else if (strncmp(dev_name, "marlin", strlen("marlin")) == 0 || strncmp(dev_name, "sailfish", strlen("sailfish")) == 0 || strncmp(dev_name, "taimen", strlen("taimen")) == 0 || strncmp(dev_name, "walleye", strlen("walleye")) == 0) { strcpy(bdev, "sda"); } else if (blockdev_name != NULL) { strcpy(bdev, blockdev_name); } else { fprintf(stderr, "%s: Unknown device %s, please specify block device name with -b\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: %m\n", progname); 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; } int ioshark_read_header(FILE *fp, struct ioshark_header *header) { if (fread(header, sizeof(struct ioshark_header), 1, fp) != 1) return -1; header->version = be64toh(header->version); header->num_files = be64toh(header->num_files); header->num_io_operations = be64toh(header->num_io_operations); return 1; } int ioshark_read_file_state(FILE *fp, struct ioshark_file_state *state) { if (fread(state, sizeof(struct ioshark_file_state), 1, fp) != 1) return -1; state->fileno = be64toh(state->fileno); state->size = be64toh(state->size); state->global_filename_ix = be64toh(state->global_filename_ix); return 1; } int ioshark_read_file_op(FILE *fp, struct ioshark_file_operation *file_op) { if (fread(file_op, sizeof(struct ioshark_file_operation), 1, fp) != 1) return -1; file_op->delta_us = be64toh(file_op->delta_us); file_op->op_union.enum_size = be32toh(file_op->op_union.enum_size); file_op->fileno = be64toh(file_op->fileno); switch (file_op->ioshark_io_op) { case IOSHARK_LSEEK: case IOSHARK_LLSEEK: file_op->lseek_offset = be64toh(file_op->lseek_offset); file_op->lseek_action = be32toh(file_op->lseek_action); break; case IOSHARK_PREAD64: case IOSHARK_PWRITE64: file_op->prw_offset = be64toh(file_op->prw_offset); file_op->prw_len = be64toh(file_op->prw_len); break; case IOSHARK_READ: case IOSHARK_WRITE: file_op->rw_len = be64toh(file_op->rw_len); break; case IOSHARK_MMAP: case IOSHARK_MMAP2: file_op->mmap_offset = be64toh(file_op->mmap_offset); file_op->mmap_len = be64toh(file_op->mmap_len); file_op->mmap_prot = be32toh(file_op->mmap_prot); break; case IOSHARK_OPEN: file_op->open_flags = be32toh(file_op->open_flags); file_op->open_mode = be32toh(file_op->open_mode); break; case IOSHARK_FSYNC: case IOSHARK_FDATASYNC: break; case IOSHARK_CLOSE: break; default: fprintf(stderr, "%s: unknown FILE_OP %d\n", progname, file_op->ioshark_io_op); exit(EXIT_FAILURE); break; } return 1; }