├── .gitignore ├── Makefile ├── README.md └── src ├── Makefile ├── main.c ├── ppc_viewer.c ├── ppc_viewer.h ├── util.c └── util.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | *.swo 34 | *.swm 35 | *.swn 36 | tags 37 | *.swp 38 | ppc_viewer 39 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | cd src && make 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ppc_viewer 2 | 3 | > process pagecache viewer 4 | 5 | ### 1) ppc_viewer 是干什么的? 6 | 7 | ``` 8 | 1. 查看进程打开了哪些文件 9 | 10 | 2. 查看进程 pagecache 的使用情况, 同时可以看单个文件的 pagecahce. 11 | ``` 12 | 13 | [Github代码](https://github.com/git-hulk/ppc_viewer) 14 | 15 | ### 2) 怎么用 16 | 17 | ``` 18 | $ git clone https://github.com/git-hulk/ppc_viewer.git 19 | $ cd ppc_viewer/src 20 | $ make 21 | $ ./ppc_viewer -p pid 22 | ``` 23 | 24 | #### 2.1) 支持选项 25 | 26 | ``` 27 | -p pid 必选,选择查看的pid 28 | 29 | -t directory 和pid 必须有一个,检查目录的pagecache使用情况。 30 | 31 | -d 可选, 详细模式,会打印当前进程打开的文件, 对应的pagecache使用情况 32 | 33 | -l 可选, 查看当前进程打开的文件 34 | 35 | -o [output file path]可选, 结果输出到文件 36 | 37 | -e [loglevel] 可选, 1:DEBUG, 2:INFO 3:WARN 4:ERROR 38 | 39 | -f [log file path]可选,日志文件路径 40 | 41 | -i [interval] 可选,查询间隔 42 | 43 | -c [count] 可选, 查询次数 44 | 45 | -h 可选, 打印帮助 46 | ``` 47 | 48 | #### 2.2) 简单打印输出 49 | 50 | ![image](http://hulkdev-hulkimgs.stor.sinaapp.com/imgs/fip_viewer.jpeg) 51 | 52 | 53 | ### 3) TODO 54 | 55 | 56 | > 1. 后台运行支持 57 | > 2. 支持正则过滤查看文件 58 | > 3. 长期的 bug 修复 59 | 60 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | .Phony: all clean 2 | 3 | PROG_NAME=ppc_viewer 4 | CC=gcc 5 | CFLAGS=-Wall -g -O2 6 | 7 | OBJS=ppc_viewer.o util.o main.o 8 | all: $(OBJS) 9 | $(CC) $(CFLAGS) -o $(PROG_NAME) $(OBJS) 10 | 11 | main.o: main.c ppc_viewer.h util.h 12 | ppc_viewer.o: ppc_viewer.c ppc_viewer.h util.h 13 | util.o: util.c ppc_viewer.h util.h 14 | 15 | clean: 16 | rm -rf $(PROG_NAME) *.o 17 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "ppc_viewer.h" 6 | 7 | extern struct option opt; 8 | static void usage() { 9 | fprintf(stderr, "\n============== PPC_VIEWER USAGE ==============\n"); 10 | fprintf(stderr, "PPC_VIEWER is process file/pagecache finder.\n"); 11 | fprintf(stderr, "You must and can only specify one way to run PPC_VIEWER, pid or directory\n"); 12 | fprintf(stderr, " -h show usage.\n"); 13 | fprintf(stderr, " -p pid.\n"); 14 | fprintf(stderr, " -t directory.\n"); 15 | fprintf(stderr, " -i interval.\n"); 16 | fprintf(stderr, " -c count.\n"); 17 | fprintf(stderr, " -d detail mode.\n"); 18 | fprintf(stderr, " -l just list files.\n"); 19 | fprintf(stderr, " -o result output file path.\n"); 20 | fprintf(stderr, " -f log file path\n"); 21 | fprintf(stderr, " -e loglevel, 1:DEBUG, 2:INFO 3:WARN 4:ERROR\n"); 22 | fprintf(stderr, "============== PPC_VIEWER USAGE ==============\n"); 23 | exit(1); 24 | } 25 | 26 | int main(int argc, char **argv) { 27 | char ch; 28 | int i; 29 | int is_usage = 0; 30 | int pid = 0; 31 | const int INF = 0x3fffffff; 32 | 33 | while((ch = getopt(argc, argv, "p:r:t:dlhi:c:f:e:o:")) != -1) { 34 | switch(ch) { 35 | case 'p': pid = atoi(optarg); break; 36 | case 't': opt.path = strdup(optarg); break; 37 | case 'i': opt.interval = atoi(optarg); break; 38 | case 'c': opt.count = atoi(optarg); break; 39 | case 'h': is_usage = 1; break; 40 | case 'd': opt.is_detail = 1; break; 41 | case 'l': opt.just_list_file = 1; break; 42 | case 'r': opt.regular = strdup(optarg); break; 43 | case 'f': opt.log_file = strdup(optarg); break; 44 | case 'e': opt.log_level = atoi(optarg); break; 45 | case 'o': opt.output_file = strdup(optarg); break; 46 | } 47 | } 48 | if(is_usage || ( !pid && opt.path == NULL )) { 49 | usage(); 50 | } 51 | else if ( pid && opt.path != NULL) { 52 | usage(); 53 | } 54 | 55 | if(opt.count <= 0 && opt.interval <= 0) { 56 | opt.count = 1; 57 | } else if(opt.count <= 0 && opt.interval > 0) { 58 | opt.count = INF; 59 | } else if(opt.count > 0 && opt.interval <= 0) { 60 | opt.interval = 1; 61 | } 62 | 63 | if(opt.log_level <= 0 ) { 64 | opt.log_level = INFO; 65 | } 66 | 67 | for (i=0; i 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include "ppc_viewer.h" 32 | 33 | static struct global_stats g_stats; 34 | struct option opt; 35 | 36 | static void print_summary() { 37 | char tp_buf[128]; 38 | char mp_buf[128]; 39 | double mem_ratio = 0; 40 | FILE *fp = stderr; 41 | 42 | bytes_to_human(tp_buf,g_stats.total_pages*4096); 43 | bytes_to_human(mp_buf,g_stats.mem_pages*4096); 44 | if(g_stats.total_pages > 0) { 45 | mem_ratio = (1.0*g_stats.mem_pages)/g_stats.total_pages*100; 46 | } 47 | 48 | if (opt.output_file) { 49 | fp = fopen(opt.output_file,"a"); 50 | } else { 51 | fprintf(fp, C_GREEN); 52 | } 53 | fprintf(fp, "========================== SUMMARY ==========================\n"); 54 | fprintf(fp, 55 | "total_size: %s\n" \ 56 | "mem_page_size: %s\n" \ 57 | "total_pages: %"PRIu64"\n" \ 58 | "mem_pages: %"PRIu64"\n" \ 59 | "ratio: %.3f%%\n" \ 60 | "total_files: %"PRIu64"\n" \ 61 | "skip_files: %"PRIu64"\n" \ 62 | "", 63 | tp_buf, 64 | mp_buf, 65 | g_stats.total_pages, 66 | g_stats.mem_pages, 67 | mem_ratio, 68 | g_stats.total_files, 69 | g_stats.skip_files 70 | ); 71 | fprintf(fp, "========================== SUMMARY ==========================\n"); 72 | if(opt.output_file) { 73 | fclose(fp); 74 | } else { 75 | fprintf(fp, C_NONE); 76 | } 77 | } 78 | 79 | static void print_file(char *fname) { 80 | char *delete_str = " (deleted)"; 81 | int f_len = strlen(fname); 82 | int d_len = strlen(delete_str); 83 | FILE *fp; 84 | 85 | // 文件被删除的fd 86 | fp = (opt.output_file == NULL) ? stdout : fopen(opt.output_file,"a"); 87 | if(strncmp(fname+f_len-d_len, delete_str, d_len) == 0) { 88 | fprintf(fp, "%s\n", fname); 89 | goto close_fp; 90 | } 91 | 92 | struct stat sb; 93 | if(stat(fname, &sb) != 0) { 94 | logger(WARN, "can't stat file :%s", fname); 95 | goto close_fp; 96 | } 97 | if(!S_ISREG(sb.st_mode)) { 98 | goto close_fp; 99 | } 100 | 101 | char buf[128]; 102 | bytes_to_human(buf, sb.st_size); 103 | fprintf(fp, "filename: %s\tfilesize:%s\n", fname, buf); 104 | 105 | close_fp: 106 | if(opt.output_file) { 107 | fclose(fp); 108 | } 109 | } 110 | 111 | static uint64_t get_file_size(char *fname) { 112 | struct stat sb; 113 | if(stat(fname, &sb) != 0) { 114 | if ( '/' == fname[0]) { 115 | g_stats.skip_files++; 116 | logger(WARN, "can't stat file %s", fname); 117 | } 118 | return 0; 119 | } 120 | if(!S_ISREG(sb.st_mode)) { 121 | g_stats.skip_files++; 122 | logger(DEBUG, "not regular file %s", fname); 123 | return 0; 124 | } 125 | if(sb.st_size <= 0) { 126 | g_stats.skip_files++; 127 | logger(INFO, "empty file %s, skip..", fname); 128 | return 0; 129 | } 130 | return sb.st_size; 131 | } 132 | 133 | static void touch(char *fname) { 134 | if(!check_file(fname)) return; 135 | 136 | uint64_t file_size; 137 | if((file_size = get_file_size(fname)) == 0) { 138 | return; 139 | } 140 | 141 | int fd, npages; 142 | char *mmap_addr; 143 | fd = open(fname, O_RDONLY, 0); 144 | if (fd == -1) { 145 | logger(INFO, "open file %s error, skip..", fname); 146 | return; 147 | } 148 | mmap_addr = mmap(NULL, file_size, PROT_READ, MAP_SHARED, fd, 0); 149 | if (mmap_addr == MAP_FAILED || !PAGE_ALIGNED(mmap_addr)) { 150 | if(mmap_addr == MAP_FAILED) { 151 | logger(INFO, "mmap file %s failed, skip..", fname); 152 | } else { 153 | logger(INFO, "mmap address is not page aligned."); 154 | } 155 | g_stats.skip_files++; 156 | close(fd); 157 | return; 158 | } 159 | 160 | npages = PAGE_NUM(file_size); 161 | unsigned char mincore_vec[npages]; 162 | mincore(mmap_addr, file_size, mincore_vec); 163 | int i, pages_in_mem = 0; 164 | for(i = 0; i < npages; i++) { 165 | if(PAGE_IN_MEM(mincore_vec[i])) { 166 | pages_in_mem++; 167 | } 168 | } 169 | 170 | if(opt.is_detail) { 171 | FILE *fp; 172 | fp = (opt.output_file == NULL) ? stdout : fopen(opt.output_file,"a"); 173 | fprintf(fp, "%s\t page_in_mem:%d\ttotal_pages:%d\n", fname, pages_in_mem, npages); 174 | if(opt.output_file) { 175 | fclose(fp); 176 | } 177 | } 178 | 179 | g_stats.total_files++; 180 | g_stats.total_pages += npages; 181 | g_stats.mem_pages += pages_in_mem; 182 | 183 | if(munmap(mmap_addr, file_size) != 0) { 184 | logger(INFO, "file %s munmap failed.", fname); 185 | } 186 | close(fd); 187 | } 188 | 189 | void handle_link(char *path){ 190 | char fname[2048]; 191 | int fname_size; 192 | memset(fname, '\0', sizeof(fname)); 193 | fname_size = readlink(path, fname, sizeof(fname)); 194 | if(fname_size == -1) { 195 | logger(WARN, "readlink error, file: %s", path); 196 | return; 197 | } 198 | if(fname_size == sizeof(fname)) { 199 | logger(INFO, "may be filename is too long"); 200 | return; 201 | } 202 | if(!check_file(fname)) { 203 | return; 204 | } 205 | 206 | if(opt.just_list_file) { 207 | print_file(fname); 208 | } 209 | else { 210 | touch(fname); 211 | } 212 | } 213 | 214 | // traverse /proc/{pid}/fd/* to get regular file list. 215 | void traverse_porcess(int pid) { 216 | // reset stats 217 | memset(&g_stats, '\0', sizeof(struct global_stats)); 218 | 219 | char buf[128]; 220 | snprintf(buf, sizeof(buf), "/proc/%d/fd/", pid); 221 | 222 | if(access(buf, F_OK) != 0) { 223 | logger(ERROR, "maybe process %d is not exists.", pid); 224 | } 225 | if(access(buf, R_OK) != 0) { 226 | logger(ERROR, "access pid %d permission denied.", pid); 227 | } 228 | 229 | DIR *proc_dir = opendir(buf); 230 | if(!proc_dir) { 231 | logger(ERROR, "open %s for read failed.", buf); 232 | } 233 | 234 | struct dirent *de; 235 | while((de = readdir(proc_dir)) != NULL) { 236 | if(strcmp(de->d_name, ".") == 0 || 237 | strcmp(de->d_name, "..") == 0) { 238 | continue; 239 | } 240 | // NOTE: file in /proc/{pid}/fd/* should be link. 241 | char dirent_path[2048]; 242 | if(DT_LNK == de->d_type) { 243 | memset(dirent_path, '\0', sizeof(dirent_path)); 244 | snprintf(dirent_path, sizeof(dirent_path), "/proc/%d/fd/%s", pid, de->d_name); 245 | handle_link(dirent_path); 246 | } 247 | } 248 | 249 | if(!opt.just_list_file) { 250 | print_summary(); 251 | } 252 | closedir(proc_dir); 253 | } 254 | 255 | void traverse_path(char *path, int flag) { 256 | 257 | if (flag){ 258 | memset(&g_stats, '\0', sizeof(struct global_stats)); 259 | if(access(path, F_OK) != 0) { 260 | logger(ERROR, "maybe this file or directory is not exists: %s .", path ); 261 | } 262 | if(access(path, R_OK) != 0) { 263 | logger(ERROR, "access this file or directory permission denied :%s .", path); 264 | } 265 | } 266 | 267 | struct stat sb; 268 | if (lstat(path, &sb) != 0){ 269 | logger(WARN, "can't stat this file or directory: %s .", path); 270 | } 271 | if (S_ISLNK(sb.st_mode)){ 272 | handle_link(path); 273 | } 274 | else if(S_ISREG(sb.st_mode)){ 275 | if(opt.just_list_file) { 276 | print_file(path); 277 | } 278 | else { 279 | touch(path); 280 | } 281 | } 282 | else if (S_ISDIR(sb.st_mode)){ 283 | 284 | DIR *proc_dir = opendir(path); 285 | if(!proc_dir) { 286 | logger(ERROR, "open %s for read failed.", path); 287 | } 288 | 289 | struct dirent *de; 290 | while((de = readdir(proc_dir)) != NULL) { 291 | if(strcmp(de->d_name, ".") == 0 || 292 | strcmp(de->d_name, "..") == 0) { 293 | continue; 294 | } 295 | char dirent_path[2048]; 296 | memset(dirent_path, '\0', sizeof(dirent_path)); 297 | snprintf(dirent_path, sizeof(dirent_path), "%s/%s", path, de->d_name); 298 | traverse_path(dirent_path, 0); 299 | } 300 | closedir(proc_dir); 301 | } 302 | 303 | if(flag && !opt.just_list_file) { 304 | print_summary(); 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /src/ppc_viewer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * *************************************************************** 3 | * ppc_view.h 4 | * author by @git-hulk at 2015-07-18 5 | * Copyright (C) 2015 Inc. 6 | * ************************************************************* 7 | 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | 21 | #ifndef _PPC_VIEWER_H_ 22 | #define _PPC_VIEWER_H_ 23 | 24 | #include 25 | #include "util.h" 26 | struct option { 27 | int is_detail; 28 | int just_list_file; 29 | int interval; 30 | int count; 31 | enum LEVEL log_level; 32 | char *path; 33 | char *log_file; 34 | char *regular; 35 | char *output_file; 36 | }; 37 | 38 | struct global_stats { 39 | uint64_t total_pages; 40 | uint64_t mem_pages; 41 | uint64_t total_files; 42 | uint64_t skip_files; 43 | }; 44 | 45 | void traverse_porcess(int pid); 46 | void traverse_path(char *path, int flag); 47 | #endif 48 | 49 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * *************************************************************** 3 | * util.c utility function for ppc_viewer 4 | * author by @git-hulk at 2015-07-18 5 | * Copyright (C) 2015 Inc. 6 | * ************************************************************* 7 | 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "ppc_viewer.h" 27 | #include "util.h" 28 | 29 | struct option opt; 30 | 31 | void logger(enum LEVEL loglevel,char *fmt, ...) 32 | { 33 | FILE *fp; 34 | va_list ap; 35 | time_t now; 36 | char buf[4096]; 37 | char t_buf[64]; 38 | char *msg = NULL; 39 | const char *color = ""; 40 | 41 | if(loglevel < opt.log_level) { 42 | return; 43 | } 44 | 45 | va_start(ap, fmt); 46 | vsnprintf(buf, sizeof(buf), fmt, ap); 47 | va_end(ap); 48 | switch(loglevel) { 49 | case DEBUG: msg = "DEBUG"; break; 50 | case INFO: msg = "INFO"; color = C_YELLOW ; break; 51 | case WARN: msg = "WARN"; color = C_PURPLE; break; 52 | case ERROR: msg = "ERROR"; color = C_RED; break; 53 | } 54 | 55 | now = time(NULL); 56 | strftime(t_buf,64,"%Y-%m-%d %H:%M:%S",localtime(&now)); 57 | fp = (opt.log_file == NULL) ? stdout : fopen(opt.log_file,"a"); 58 | if(opt.log_file) { 59 | fprintf(fp, "[%s] [%s] %s\n", t_buf, msg, buf); 60 | fclose(fp); 61 | } else { 62 | fprintf(fp, "%s[%s] [%s] %s"C_NONE"\n", color, t_buf, msg, buf); 63 | } 64 | 65 | if(loglevel >= ERROR) { 66 | exit(1); 67 | } 68 | } 69 | 70 | int check_file(char *fname) { 71 | if (NULL == opt.path ) { 72 | if(fname[0] != '/') { 73 | return 0; 74 | } 75 | } 76 | if(opt.regular && strncmp(opt.regular, fname, strlen(opt.regular)) != 0) { 77 | return 0; 78 | } 79 | return 1; 80 | } 81 | 82 | void bytes_to_human(char *s, unsigned long long n) { 83 | double d; 84 | if (n < 1024) { 85 | sprintf(s,"%lluB",n); 86 | return; 87 | } else if (n < (1024*1024)) { 88 | d = (double)n/(1024); 89 | sprintf(s,"%.2fK",d); 90 | } else if (n < (1024LL*1024*1024)) { 91 | d = (double)n/(1024*1024); 92 | sprintf(s,"%.2fM",d); 93 | } else if (n < (1024LL*1024*1024*1024)) { 94 | d = (double)n/(1024LL*1024*1024); 95 | sprintf(s,"%.2fG",d); 96 | } else { 97 | d = (double)n/(1024LL*1024*1024*1024); 98 | sprintf(s,"%.2fT",d); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * *************************************************************** 3 | * util.h 4 | * author by @git-hulk at 2015-07-18 5 | * Copyright (C) 2015 Inc. 6 | * ************************************************************* 7 | 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | 21 | #ifndef _UTIL_H_ 22 | #define _UTIL_H_ 23 | 24 | #define C_RED "\033[31m" 25 | #define C_GREEN "\033[32m" 26 | #define C_YELLOW "\033[33m" 27 | #define C_PURPLE "\033[35m" 28 | #define C_NONE "\033[0m" 29 | 30 | #define PAGE_IN_MEM(c) ((c) & 0x1) 31 | #define PAGE_ALIGNED(addr) ((((long)addr) & (PAGE_SIZE - 1))== 0) 32 | #define PAGE_SIZE sysconf(_SC_PAGESIZE) 33 | #define PAGE_NUM(filesize) (((filesize)+PAGE_SIZE-1)/PAGE_SIZE) 34 | 35 | enum LEVEL { 36 | DEBUG = 1, 37 | INFO, 38 | WARN, 39 | ERROR 40 | }; 41 | 42 | void logger(enum LEVEL loglevel,char *fmt, ...); 43 | int check_file(char *fname); 44 | void bytes_to_human(char *s, unsigned long long n); 45 | #endif 46 | --------------------------------------------------------------------------------